diff --git a/.pi/agents/developer.md b/.pi/agents/developer.md index c86272f..14e6379 100644 --- a/.pi/agents/developer.md +++ b/.pi/agents/developer.md @@ -2,7 +2,7 @@ name: developer description: Implements assigned tasks. model: openrouter/stepfun/step-3.5-flash:free -tools: read,edit,write,bash,grep,find,ls +tools: read,edit,write,bash,grep,find,ls,report_task_result --- You are a developer. Implement the task assigned to you, and only it. @@ -12,7 +12,8 @@ When done, call the `report_task_result` tool with: - status: "done" - summary: Brief summary of what was implemented - filesChanged: Array of file paths that were created or modified -- notes: Any additional notes (optional) +- evidence: Array of concrete command, test, inspection, or manual evidence +- issues: Array of `{severity, description, reproduction?}` objects Example: @@ -21,6 +22,7 @@ report_task_result({ status: "done", summary: "Created config module with validation", filesChanged: ["app/config.py"], - notes: "Uses pydantic for validation" + evidence: [{kind: "test", description: "The config tests pass", outcome: "pass"}], + issues: [] }) ``` diff --git a/.pi/agents/pm.md b/.pi/agents/pm.md index 9d7f743..21542dd 100644 --- a/.pi/agents/pm.md +++ b/.pi/agents/pm.md @@ -2,19 +2,19 @@ name: pm description: Project manager who plans and delegates tasks in waves. model: openrouter/stepfun/step-3.5-flash:free -tools: read,grep,find,ls +tools: read,grep,find,ls,generate_wave --- You are a technical PM. Your goal is to implement user's project. Split the project into waves, each wave consisting of atomic technical tasks. All tasks within one wave will be completed at the same time in parallel. Only generate one wave at a time. Once the wave is finished, you will have an opportunity to create the next one. -Each task should be testable and should have clear verification requirements. +Each task should be testable and should have clear, nonempty verification requirements. Consider integration and tests too. When ready to spawn a wave, call the `generate_wave` tool with: -- wave: { goal: "...", tasks: [{id, title, description, requirements, assignee}, ...] } +- wave: { goal: "...", tasks: [{id, title, description, requirements, assignee: "developer"}, ...] } - done: true (if project complete) or false "tasks" should include: @@ -25,6 +25,10 @@ When ready to spawn a wave, call the `generate_wave` tool with: - requirements: Verification requirements for the verifier (developer won't see this) - assignee: "developer" (only assign to developers, verifiers auto-assign) +Task IDs must be unique safe identifiers (letters, numbers, `_`, or `-`, up to 64 characters). +Call `generate_wave` at most once per turn. Do not report completion with a wave, and do not +report a wave without calling the tool. + Example for new wave: ``` diff --git a/.pi/agents/verifier.md b/.pi/agents/verifier.md index ae22b30..3e89149 100644 --- a/.pi/agents/verifier.md +++ b/.pi/agents/verifier.md @@ -2,21 +2,27 @@ name: verifier description: Reviews developer work and validates requirements. model: openrouter/stepfun/step-3.5-flash:free -tools: read,grep,find,ls,bash +tools: read,grep,find,ls,bash,report_task_result --- -You are a verifier. Your job is only to review and QA the task assigned to you. +You are a verifier. Your job is only to review and QA the task assigned to you. You are +structurally read-only: do not edit or write files. Verify every requirement and use adversarial +checks where appropriate. When done, call the `report_task_result` tool with: -- status: "pass" or "fail" -- issues: Array of issue descriptions (empty array if pass) +- status: "pass", "fail", or "partial" +- summary: Concise verification conclusion +- evidence: Concrete evidence; `pass` requires at least one item with outcome `pass` +- issues: Array of `{severity, description, reproduction?}` objects Example for passing: ``` report_task_result({ status: "pass", + summary: "All requirements are satisfied.", + evidence: [{kind: "test", description: "The verification suite passes", outcome: "pass"}], issues: [] }) ``` @@ -26,6 +32,8 @@ Example for failing: ``` report_task_result({ status: "fail", - issues: ["File missing: app/config.py", "Validation not implemented"] + summary: "The implementation is incomplete.", + evidence: [{kind: "inspection", description: "The required file is missing", outcome: "fail"}], + issues: [{severity: "blocking", description: "File missing: app/config.py"}] }) ``` diff --git a/.pi/extensions/workflow-orchestrator/agents.ts b/.pi/extensions/workflow-orchestrator/agents.ts index 3130c69..40dbcc6 100644 --- a/.pi/extensions/workflow-orchestrator/agents.ts +++ b/.pi/extensions/workflow-orchestrator/agents.ts @@ -1,6 +1,28 @@ import * as fs from "node:fs"; +import * as os from "node:os"; import * as path from "node:path"; -import { getAgentDir, parseFrontmatter } from "@mariozechner/pi-coding-agent"; + +function getAgentDir(): string { + return process.env.PI_CODING_AGENT_DIR || path.join(os.homedir(), ".pi", "agent"); +} + +function parseFrontmatter>( + content: string, +): { frontmatter: T; body: string } { + const normalized = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n"); + if (!normalized.startsWith("---")) return { frontmatter: {} as T, body: normalized }; + const end = normalized.indexOf("\n---", 3); + if (end < 0) return { frontmatter: {} as T, body: normalized }; + const frontmatter: Record = {}; + for (const line of normalized.slice(4, end).split("\n")) { + const separator = line.indexOf(":"); + if (separator < 0) continue; + const key = line.slice(0, separator).trim(); + const value = line.slice(separator + 1).trim(); + if (key) frontmatter[key] = value; + } + return { frontmatter: frontmatter as T, body: normalized.slice(end + 4).trim() }; +} export type AgentSource = "user" | "project"; diff --git a/.pi/extensions/workflow-orchestrator/commands.ts b/.pi/extensions/workflow-orchestrator/commands.ts new file mode 100644 index 0000000..30373b0 --- /dev/null +++ b/.pi/extensions/workflow-orchestrator/commands.ts @@ -0,0 +1,127 @@ +import { DEFAULT_WORKFLOW_NAME } from "./setup.js"; +import { normalizeGoal } from "./utils.js"; + +export const WORKFLOW_COMMANDS = new Set([ + "start", + "resume", + "status", + "stop", + "stop-task", + "message", + "expand", + "collapse", + "help", +]); + +export interface ParsedWorkflowStart { + workflowName: string; + goal?: string; + model?: string; +} + +/** Tokenize command arguments while preserving quoted phrases as one token. */ +export function tokenizeWorkflowArgs(args: string): string[] { + const tokens: string[] = []; + let token = ""; + let quote: '"' | "'" | undefined; + let escaped = false; + + const pushToken = () => { + if (token) tokens.push(token); + token = ""; + }; + + for (const char of args) { + if (escaped) { + token += char; + escaped = false; + continue; + } + + if (char === "\\" && quote !== "'") { + escaped = true; + continue; + } + + if (quote) { + if (char === quote) quote = undefined; + else token += char; + continue; + } + + if (char === '"' || (char === "'" && token.length === 0)) { + quote = char; + continue; + } + + if (/\s/.test(char)) pushToken(); + else token += char; + } + + if (escaped) token += "\\"; + pushToken(); + return tokens; +} + +export function isValidWorkflowName(name: string): boolean { + return /^[a-zA-Z0-9_-]+$/.test(name) && name.length > 0 && name.length <= 50; +} + +export function extractModelFlag(tokens: string[]): { tokens: string[]; model?: string } { + const stripped: string[] = []; + let model: string | undefined; + + for (let i = 0; i < tokens.length; i++) { + const token = tokens[i]; + if (token === "--model" && i + 1 < tokens.length) { + model = normalizeGoal(tokens[++i]); + continue; + } + if (token === "--model") continue; + stripped.push(token); + } + + return { tokens: stripped, model }; +} + +export function parseWorkflowStartArgs(tokens: string[]): ParsedWorkflowStart | null { + const { tokens: rest, model } = extractModelFlag(tokens.slice(1)); + if (rest.length === 0) return null; + + if (rest.length === 1) { + const token = rest[0]; + if (isValidWorkflowName(token)) { + return { workflowName: token, goal: undefined, model }; + } + return { + workflowName: DEFAULT_WORKFLOW_NAME, + goal: normalizeGoal(token), + model, + }; + } + + if (!isValidWorkflowName(rest[0])) { + return { + workflowName: DEFAULT_WORKFLOW_NAME, + goal: normalizeGoal(rest.join(" ")), + model, + }; + } + + return { + workflowName: rest[0], + goal: normalizeGoal(rest.slice(1).join(" ")), + model, + }; +} + +export function parseWorkflowShorthandGoal(tokens: string[]): { + goal?: string; + model?: string; +} { + const { tokens: stripped, model } = extractModelFlag(tokens); + return { + goal: normalizeGoal(stripped.join(" ")), + model, + }; +} diff --git a/.pi/extensions/workflow-orchestrator/config.ts b/.pi/extensions/workflow-orchestrator/config.ts index 598b494..5712f78 100644 --- a/.pi/extensions/workflow-orchestrator/config.ts +++ b/.pi/extensions/workflow-orchestrator/config.ts @@ -1,22 +1,27 @@ import * as fs from "node:fs"; import * as path from "node:path"; -import { Type, type Static } from "@sinclair/typebox"; -import { Value } from "@sinclair/typebox/value"; +import { Type, type Static } from "typebox"; +import { Check, Errors } from "typebox/value"; +import { + IdentifierSchema, + SemanticStageIdSchema, + WaveSchema, + validateGenerateWave, +} from "./contracts.js"; const TransitionSchema = Type.Object({ when: Type.Object({ - field: Type.String(), - equals: Type.String(), + field: Type.String({ minLength: 1 }), + equals: Type.String({ minLength: 1 }), }), - next: Type.String(), + next: Type.Union([IdentifierSchema, Type.Literal("complete")]), }); const StageSchema = Type.Object({ - id: Type.String(), - agent: Type.String(), - inputTemplate: Type.String(), - outputSchema: Type.Record(Type.String(), Type.String()), - transitions: Type.Optional(Type.Array(TransitionSchema)), + id: SemanticStageIdSchema, + agent: Type.String({ minLength: 1 }), + inputTemplate: Type.String({ minLength: 1 }), + transitions: Type.Optional(Type.Array(TransitionSchema, { maxItems: 20 })), }); const TaskFlowMemorySchema = Type.Object({ @@ -31,57 +36,35 @@ const TaskFlowMemorySchema = Type.Object({ ), }); -const TaskSchema = Type.Object({ - id: Type.String(), - title: Type.String(), - description: Type.String(), - requirements: Type.Optional(Type.String()), - assignee: Type.Optional(Type.String()), -}); - -const WaveSchema = Type.Object({ - goal: Type.String(), - tasks: Type.Array(TaskSchema), -}); - const WaveSourceSchema = Type.Object({ type: Type.Union([Type.Literal("pm"), Type.Literal("static")]), - staticWaves: Type.Optional(Type.Array(WaveSchema)), + staticWaves: Type.Optional(Type.Array(WaveSchema, { maxItems: 100 })), }); const AllowedExtensionsByAgentSchema = Type.Record(Type.String(), Type.Array(Type.String())); - -const AgentsSchema = Type.Record(Type.String(), Type.String()); - -const AgentRetrySchema = Type.Object({ - maxAttempts: Type.Optional(Type.Number()), - initialDelayMs: Type.Optional(Type.Number()), - maxDelayMs: Type.Optional(Type.Number()), - backoffMultiplier: Type.Optional(Type.Number()), - jitterMs: Type.Optional(Type.Number()), -}); +const AgentsSchema = Type.Record(IdentifierSchema, Type.String({ minLength: 1 })); const WorkflowSchema = Type.Object({ - name: Type.String(), - goal: Type.String(), - maxWaves: Type.Optional(Type.Number()), - maxTaskRetries: Type.Optional(Type.Number()), - maxPmRetries: Type.Optional(Type.Number()), - parallelism: Type.Optional(Type.Number()), - agentRetry: Type.Optional(AgentRetrySchema), + name: IdentifierSchema, + goal: Type.String({ minLength: 1, maxLength: 4000 }), + piCommand: Type.Optional(Type.String({ minLength: 1, maxLength: 1000 })), + maxWaves: Type.Optional(Type.Integer({ minimum: 1 })), + maxTaskRetries: Type.Optional(Type.Integer({ minimum: 0 })), + maxPmRetries: Type.Optional(Type.Integer({ minimum: 1 })), + parallelism: Type.Optional(Type.Integer({ minimum: 1 })), allowedExtensions: Type.Optional(Type.Array(Type.String())), allowedExtensionsByAgent: Type.Optional(AllowedExtensionsByAgentSchema), agents: AgentsSchema, waveSource: WaveSourceSchema, taskFlow: Type.Object({ - stages: Type.Array(StageSchema), + stages: Type.Array(StageSchema, { minItems: 2, maxItems: 2 }), memory: Type.Optional(TaskFlowMemorySchema), }), }); export type WorkflowConfig = Static; export type WorkflowStage = Static; -export type WorkflowTask = Static; +export type WorkflowTask = Static["tasks"][number]; export type WorkflowWave = Static; export interface LoadedWorkflow { @@ -89,28 +72,72 @@ export interface LoadedWorkflow { path: string; } -function sanitizeWorkflowName(name: string): string { - if (!name || typeof name !== "string") { - throw new Error("Workflow name is required"); +function assertPositiveInteger(name: string, value: number, allowZero = false): void { + const minimum = allowZero ? 0 : 1; + if (!Number.isInteger(value) || value < minimum) { + throw new Error(`${name} must be a ${allowZero ? "non-negative" : "positive"} integer`); + } +} + +function validateCrossFields(config: WorkflowConfig): void { + const requiredRoles = ["pm", "developer", "verifier"] as const; + for (const role of requiredRoles) { + const agentName = config.agents[role]; + if (typeof agentName !== "string" || !agentName.trim()) { + throw new Error(`agents.${role} must resolve to a non-empty agent name`); + } } - // Only allow alphanumeric, dash, underscore - if (!/^[a-zA-Z0-9_-]+$/.test(name)) { + const stageIds = new Set(config.taskFlow.stages.map((stage) => stage.id)); + if (!stageIds.has("develop") || !stageIds.has("verify") || stageIds.size !== 2) { throw new Error( - `Invalid workflow name: ${name}. Only alphanumeric, dash, and underscore allowed.`, + 'taskFlow.stages must contain exactly the semantic stages "develop" and "verify"', ); } - if (name.length > 50) { - throw new Error("Workflow name too long (max 50 characters)"); + const configuredAgentNames = new Set(Object.values(config.agents)); + for (const stage of config.taskFlow.stages) { + if (!configuredAgentNames.has(stage.agent)) { + throw new Error(`Stage ${stage.id} references unknown agent: ${stage.agent}`); + } + for (const transition of stage.transitions ?? []) { + if ( + transition.next !== "complete" && + !stageIds.has(transition.next as "develop" | "verify") + ) { + throw new Error(`Stage ${stage.id} transition targets unknown stage: ${transition.next}`); + } + } } - return name; + if (config.waveSource.type === "static") { + for (const [index, wave] of (config.waveSource.staticWaves ?? []).entries()) { + try { + validateGenerateWave({ done: false, wave }); + } catch (error) { + throw new Error(`waveSource.staticWaves[${index}] is invalid: ${(error as Error).message}`); + } + } + } + + assertPositiveInteger("maxWaves", config.maxWaves!); + assertPositiveInteger("maxTaskRetries", config.maxTaskRetries!, true); + assertPositiveInteger("maxPmRetries", config.maxPmRetries!); + assertPositiveInteger("parallelism", config.parallelism!); } export function loadWorkflowConfig(cwd: string, name: string): LoadedWorkflow { - const safeName = sanitizeWorkflowName(name); - const workflowPath = path.join(cwd, ".pi", "workflows", `${safeName}.workflow.json`); + if (typeof name !== "string" || !name.trim()) { + throw new Error("Workflow name is required"); + } + if (name.length > 64) { + throw new Error("Workflow name too long (max 64 characters)"); + } + if (!Check(IdentifierSchema, name)) { + throw new Error(`Invalid workflow name: ${name}. Expected 1-64 safe identifier characters.`); + } + + const workflowPath = path.join(cwd, ".pi", "workflows", `${name}.workflow.json`); if (!fs.existsSync(workflowPath)) { throw new Error(`Workflow not found: ${workflowPath}`); } @@ -123,25 +150,25 @@ export function loadWorkflowConfig(cwd: string, name: string): LoadedWorkflow { throw new Error(`Invalid JSON in workflow file: ${workflowPath}`); } - if (!Value.Check(WorkflowSchema, parsed)) { - const errors = [...Value.Errors(WorkflowSchema, parsed)].map( - (err) => `${err.path} ${err.message}`, + if (parsed && typeof parsed === "object" && "agentRetry" in parsed) { + throw new Error( + "Unsupported workflow configuration: agentRetry was removed. Configure Pi retry.enabled, retry.maxRetries, and retry.baseDelayMs in Pi settings instead.", + ); + } + + if (!Check(WorkflowSchema, parsed)) { + const errors = [...Errors(WorkflowSchema, parsed)].map( + (error) => `${"path" in error && error.path ? error.path : "value"} ${error.message}`, ); throw new Error(`Workflow schema validation failed:\n${errors.join("\n")}`); } const config = parsed as WorkflowConfig; - - // Apply defaults + config.piCommand = config.piCommand ?? "pi"; config.maxWaves = config.maxWaves ?? 10; config.maxTaskRetries = config.maxTaskRetries ?? 2; + config.maxPmRetries = config.maxPmRetries ?? 3; config.parallelism = config.parallelism ?? 1; - config.agentRetry = config.agentRetry ?? {}; - config.agentRetry.maxAttempts = config.agentRetry.maxAttempts ?? 5; - config.agentRetry.initialDelayMs = config.agentRetry.initialDelayMs ?? 5000; - config.agentRetry.maxDelayMs = config.agentRetry.maxDelayMs ?? 120000; - config.agentRetry.backoffMultiplier = config.agentRetry.backoffMultiplier ?? 2; - config.agentRetry.jitterMs = config.agentRetry.jitterMs ?? 1000; config.taskFlow.memory = config.taskFlow.memory ?? {}; config.taskFlow.memory.keepDeveloperMemory = config.taskFlow.memory.keepDeveloperMemory ?? true; config.taskFlow.memory.keepVerifierMemoryOnDeveloperFailure = @@ -149,24 +176,6 @@ export function loadWorkflowConfig(cwd: string, name: string): LoadedWorkflow { config.taskFlow.memory.verifierSelfFailureMemory = config.taskFlow.memory.verifierSelfFailureMemory ?? "keep"; - if (config.parallelism < 1) { - throw new Error("parallelism must be at least 1"); - } - if (config.agentRetry.maxAttempts < 1) { - throw new Error("agentRetry.maxAttempts must be at least 1"); - } - if (config.agentRetry.initialDelayMs < 0) { - throw new Error("agentRetry.initialDelayMs must be at least 0"); - } - if (config.agentRetry.maxDelayMs < 0) { - throw new Error("agentRetry.maxDelayMs must be at least 0"); - } - if (config.agentRetry.backoffMultiplier < 1) { - throw new Error("agentRetry.backoffMultiplier must be at least 1"); - } - if (config.agentRetry.jitterMs < 0) { - throw new Error("agentRetry.jitterMs must be at least 0"); - } - + validateCrossFields(config); return { config, path: workflowPath }; } diff --git a/.pi/extensions/workflow-orchestrator/contracts.ts b/.pi/extensions/workflow-orchestrator/contracts.ts new file mode 100644 index 0000000..1b0fc7a --- /dev/null +++ b/.pi/extensions/workflow-orchestrator/contracts.ts @@ -0,0 +1,295 @@ +import { Type, type Static, type TSchema } from "typebox"; +import { Check, Errors } from "typebox/value"; + +export const IdentifierSchema = Type.String({ + pattern: "^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$", +}); + +export const EvidenceSchema = Type.Object({ + kind: Type.Union([ + Type.Literal("command"), + Type.Literal("test"), + Type.Literal("inspection"), + Type.Literal("manual"), + ]), + description: Type.String({ minLength: 1, maxLength: 2000 }), + command: Type.Optional(Type.String({ maxLength: 2000 })), + outcome: Type.Union([Type.Literal("pass"), Type.Literal("fail"), Type.Literal("blocked")]), +}); + +export const IssueSchema = Type.Object({ + severity: Type.Union([Type.Literal("blocking"), Type.Literal("non_blocking")]), + description: Type.String({ minLength: 1, maxLength: 4000 }), + reproduction: Type.Optional(Type.String({ maxLength: 4000 })), +}); + +export const DeveloperReportSchema = Type.Object({ + status: Type.Union([Type.Literal("done"), Type.Literal("partial")]), + summary: Type.String({ minLength: 1, maxLength: 4000 }), + filesChanged: Type.Array(Type.String({ minLength: 1, maxLength: 1000 }), { + maxItems: 500, + }), + evidence: Type.Array(EvidenceSchema, { maxItems: 100 }), + issues: Type.Array(IssueSchema, { maxItems: 100 }), +}); + +export const VerifierReportSchema = Type.Object({ + status: Type.Union([Type.Literal("pass"), Type.Literal("fail"), Type.Literal("partial")]), + summary: Type.String({ minLength: 1, maxLength: 4000 }), + evidence: Type.Array(EvidenceSchema, { maxItems: 100 }), + issues: Type.Array(IssueSchema, { maxItems: 100 }), +}); + +export const WaveTaskSchema = Type.Object({ + id: IdentifierSchema, + title: Type.String({ minLength: 1, maxLength: 1000 }), + description: Type.String({ minLength: 1, maxLength: 4000 }), + requirements: Type.String({ minLength: 1, maxLength: 4000 }), + assignee: Type.Optional(Type.Literal("developer")), +}); + +export const WaveSchema = Type.Object({ + goal: Type.String({ minLength: 1, maxLength: 4000 }), + tasks: Type.Array(WaveTaskSchema, { maxItems: 100 }), +}); + +export const GenerateWaveSchema = Type.Object({ + done: Type.Boolean(), + wave: Type.Optional(WaveSchema), +}); + +export const SemanticStageIdSchema = Type.Union([Type.Literal("develop"), Type.Literal("verify")]); + +export type Identifier = Static; +export type Evidence = Static; +export type Issue = Static; +export type DeveloperReport = Static; +export type VerifierReport = Static; +export type WaveTask = Static; +export type WorkflowWave = Static; +export type GenerateWaveParams = Static; +export type SemanticStageId = Static; + +export interface StageResultEnvelope { + runId: string; + waveIndex: number; + taskId: string; + stageId: SemanticStageId; + role: "developer" | "verifier"; + report: TReport; + toolCallId: string; + startedAt: number; + completedAt: number; +} + +export type DeveloperResultEnvelope = StageResultEnvelope; +export type VerifierResultEnvelope = StageResultEnvelope; +export type StageOutput = DeveloperResultEnvelope | VerifierResultEnvelope; + +const StageEnvelopeBaseSchema = Type.Object({ + runId: Type.String({ minLength: 1, maxLength: 128 }), + waveIndex: Type.Integer({ minimum: 0 }), + taskId: IdentifierSchema, + toolCallId: Type.String({ minLength: 1, maxLength: 512 }), + startedAt: Type.Integer({ minimum: 0 }), + completedAt: Type.Integer({ minimum: 0 }), +}); + +export const DeveloperResultEnvelopeSchema = Type.Intersect([ + StageEnvelopeBaseSchema, + Type.Object({ + stageId: Type.Literal("develop"), + role: Type.Literal("developer"), + report: DeveloperReportSchema, + }), +]); + +export const VerifierResultEnvelopeSchema = Type.Intersect([ + StageEnvelopeBaseSchema, + Type.Object({ + stageId: Type.Literal("verify"), + role: Type.Literal("verifier"), + report: VerifierReportSchema, + }), +]); + +export const StageOutputSchema = Type.Union([ + DeveloperResultEnvelopeSchema, + VerifierResultEnvelopeSchema, +]); + +export interface PriorWaveSummaryTask { + id: string; + title: string; + status: "pending" | "in_progress" | "stopping" | "verified" | "failed" | "stopped"; + retries: number; + developerSummary?: string; + filesChanged: string[]; + verifierSummary?: string; + evidence: Evidence[]; + issues: Issue[]; +} + +export interface PriorWaveSummary { + waveIndex: number; + goal: string; + outcome: "verified" | "failed" | "partial" | "stopped"; + tasks: PriorWaveSummaryTask[]; +} + +export const PriorWaveSummaryTaskSchema = Type.Object({ + id: IdentifierSchema, + title: Type.String({ minLength: 1, maxLength: 1000 }), + status: Type.Union([ + Type.Literal("pending"), + Type.Literal("in_progress"), + Type.Literal("stopping"), + Type.Literal("verified"), + Type.Literal("failed"), + Type.Literal("stopped"), + ]), + retries: Type.Integer({ minimum: 0 }), + developerSummary: Type.Optional(Type.String({ maxLength: 4000 })), + filesChanged: Type.Array(Type.String({ minLength: 1, maxLength: 1000 }), { maxItems: 500 }), + verifierSummary: Type.Optional(Type.String({ maxLength: 4000 })), + evidence: Type.Array(EvidenceSchema, { maxItems: 100 }), + issues: Type.Array(IssueSchema, { maxItems: 100 }), +}); + +export const PriorWaveSummarySchema = Type.Object({ + waveIndex: Type.Integer({ minimum: 0 }), + goal: Type.String({ maxLength: 4000 }), + outcome: Type.Union([ + Type.Literal("verified"), + Type.Literal("failed"), + Type.Literal("partial"), + Type.Literal("stopped"), + ]), + tasks: Type.Array(PriorWaveSummaryTaskSchema, { maxItems: 100 }), +}); + +export function validationError(label: string, schema: TSchema, value: unknown): Error { + const errors = [...Errors(schema, value)].map((error) => { + const location = "path" in error && error.path ? error.path : "value"; + return `${location} ${error.message}`; + }); + return new Error(`${label} validation failed${errors.length ? `: ${errors.join("; ")}` : ""}`); +} + +export function validateSchema( + schema: TSchemaType, + value: unknown, + label: string, +): Static { + if (!Check(schema, value)) throw validationError(label, schema, value); + return value as Static; +} + +function hasBlockingIssue(issues: Issue[]): boolean { + return issues.some((issue) => issue.severity === "blocking"); +} + +function hasEvidenceOutcome(evidence: Evidence[], outcome: Evidence["outcome"]): boolean { + return evidence.some((item) => item.outcome === outcome); +} + +function validateRelativeChangedFiles(report: DeveloperReport): void { + const seen = new Set(); + for (const declaredPath of report.filesChanged) { + const normalized = declaredPath.replaceAll("\\", "/"); + if ( + normalized.startsWith("/") || + /^[A-Za-z]:\//.test(normalized) || + normalized.split("/").includes("..") || + normalized.split("/").some((part) => part.length === 0) + ) { + throw new Error( + `Developer report validation failed: filesChanged path must be relative and safe: ${declaredPath}`, + ); + } + const canonical = normalized + .split("/") + .filter((part) => part !== ".") + .join("/"); + if (!canonical || seen.has(canonical)) { + throw new Error( + `Developer report validation failed: filesChanged contains duplicate or empty path: ${declaredPath}`, + ); + } + seen.add(canonical); + } +} + +export function validateDeveloperReport(value: unknown): DeveloperReport { + const report = validateSchema(DeveloperReportSchema, value, "Developer report"); + validateRelativeChangedFiles(report); + if (report.status === "partial" && !hasBlockingIssue(report.issues)) { + throw new Error("Developer report validation failed: partial requires a blocking issue"); + } + if (report.status === "done" && report.issues.some((issue) => issue.severity === "blocking")) { + throw new Error("Developer report validation failed: done cannot contain a blocking issue"); + } + return report; +} + +export function validateVerifierReport(value: unknown): VerifierReport { + const report = validateSchema(VerifierReportSchema, value, "Verifier report"); + if (report.status === "pass") { + if (!hasEvidenceOutcome(report.evidence, "pass")) { + throw new Error("Verifier report validation failed: pass requires passing evidence"); + } + if (hasBlockingIssue(report.issues)) { + throw new Error("Verifier report validation failed: pass cannot contain a blocking issue"); + } + if (hasEvidenceOutcome(report.evidence, "fail")) { + throw new Error("Verifier report validation failed: pass cannot contain failed evidence"); + } + } + if (report.status === "fail" && !hasBlockingIssue(report.issues)) { + throw new Error("Verifier report validation failed: fail requires a blocking issue"); + } + if (report.status === "partial") { + if (!hasEvidenceOutcome(report.evidence, "blocked")) { + throw new Error("Verifier report validation failed: partial requires blocked evidence"); + } + if (!hasBlockingIssue(report.issues)) { + throw new Error("Verifier report validation failed: partial requires a blocking issue"); + } + } + return report; +} + +export function validateGenerateWave(value: unknown): GenerateWaveParams { + const params = validateSchema(GenerateWaveSchema, value, "generate_wave parameters"); + if (params.done && params.wave) { + throw new Error("generate_wave validation failed: done=true cannot include a wave"); + } + if (!params.done && !params.wave) { + throw new Error("generate_wave validation failed: done=false requires a wave"); + } + if (!params.wave) return params; + if (params.wave.tasks.length === 0) { + throw new Error("generate_wave validation failed: done=false requires a nonempty wave"); + } + + const ids = new Set(); + for (const task of params.wave.tasks) { + if (ids.has(task.id)) { + throw new Error(`generate_wave validation failed: duplicate task id: ${task.id}`); + } + ids.add(task.id); + if (task.assignee && task.assignee !== "developer") { + throw new Error( + `generate_wave validation failed: task ${task.id} must be assigned to developer`, + ); + } + } + return params; +} + +export function validateReportForStage( + stageId: SemanticStageId, + value: unknown, +): DeveloperReport | VerifierReport { + return stageId === "develop" ? validateDeveloperReport(value) : validateVerifierReport(value); +} diff --git a/.pi/extensions/workflow-orchestrator/engine.ts b/.pi/extensions/workflow-orchestrator/engine.ts index be415eb..ba7542e 100644 --- a/.pi/extensions/workflow-orchestrator/engine.ts +++ b/.pi/extensions/workflow-orchestrator/engine.ts @@ -102,7 +102,11 @@ export async function runTaskFlow( const firstStageId = input.startStageId ?? input.stages[0]?.id; let matchedTransition = false; if (stage.transitions && stage.transitions.length > 0) { - const fieldTarget = (output as any)?.output ?? output; + const fieldTarget = + (output as any)?.output?.report ?? + (output as any)?.report ?? + (output as any)?.output ?? + output; for (const transition of stage.transitions) { const fieldValue = getField(fieldTarget as any, transition.when.field); if (String(fieldValue) === transition.when.equals) { diff --git a/.pi/extensions/workflow-orchestrator/index.ts b/.pi/extensions/workflow-orchestrator/index.ts index fe1db4e..ef028e6 100644 --- a/.pi/extensions/workflow-orchestrator/index.ts +++ b/.pi/extensions/workflow-orchestrator/index.ts @@ -1,13 +1,16 @@ import * as fs from "node:fs"; import * as path from "node:path"; +import { execFile as nodeExecFile } from "node:child_process"; +import { promisify } from "node:util"; import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext, -} from "@mariozechner/pi-coding-agent"; -import { Type } from "@sinclair/typebox"; -import { Text } from "@mariozechner/pi-tui"; +} from "@earendil-works/pi-coding-agent"; +import { Type } from "typebox"; +import { Text } from "@earendil-works/pi-tui"; import { discoverAgents, findAgentByName } from "./agents.js"; +import { parseWorkflowStartArgs, tokenizeWorkflowArgs, WORKFLOW_COMMANDS } from "./commands.js"; import { loadWorkflowConfig, type WorkflowConfig, @@ -18,12 +21,35 @@ import { import { runTaskFlow } from "./engine.js"; import { setPmWidgetStatus, setTaskListExpanded, updateStatus } from "./render.js"; import { RpcAgent } from "./runner.js"; -import { appendState, restoreState, type TaskState, type WorkflowState } from "./state.js"; -import { extractJson, normalizeGoal } from "./utils.js"; +import { preflightPiExecutable, selectStructuredToolResult, type RpcRunResult } from "./runner.js"; +import { + appendState, + isWorkflowActive, + restoreState, + type TaskState, + type WorkflowState, + type WorkflowStatus, +} from "./state.js"; +import { + validateDeveloperReport, + validateGenerateWave, + validateVerifierReport, + type DeveloperReport, + type Issue, + type PriorWaveSummary, + type StageOutput, + type SemanticStageId, + type VerifierReport, +} from "./contracts.js"; +import { materializeProjectDefaults } from "./setup.js"; +import { normalizeGoal } from "./utils.js"; + +const execFile = promisify(nodeExecFile); interface WorkflowRunHandle { abortController: AbortController; promise: Promise; + stopRequested: boolean; } const PM_MESSAGE_TYPE = "workflow-pm"; @@ -31,7 +57,9 @@ const PM_MESSAGE_TYPE = "workflow-pm"; interface TaskRunner { key: string; agent: RpcAgent; - stageId: string; + stageId: SemanticStageId; + lifecycle: "idle" | "running" | "aborting" | "stopped" | "disposed"; + activePrompt?: Promise; } let currentRun: WorkflowRunHandle | null = null; @@ -45,7 +73,13 @@ const clarificationWaiters = new Map void>>(); function setState(pi: ExtensionAPI, ctx: ExtensionContext, state: WorkflowState, persist = true) { const previousClarificationToken = currentState?.clarificationToken; - const nextState = { ...state, updatedAt: Date.now() }; + const status = state.status ?? (state.active ? "running" : "completed"); + const nextState: WorkflowState = { + ...state, + status, + active: isWorkflowActive(status), + updatedAt: Date.now(), + }; currentState = nextState; if (persist) appendState(pi, nextState); updateStatus(ctx, nextState); @@ -53,12 +87,20 @@ function setState(pi: ExtensionAPI, ctx: ExtensionContext, state: WorkflowState, previousClarificationToken && (previousClarificationToken !== nextState.clarificationToken || !nextState.waitingForClarification || - nextState.active === false) + !nextState.active) ) { signalClarificationResolved(previousClarificationToken); } } +function markActiveTasksStopped(tasks: TaskState[]): TaskState[] { + return tasks.map((task) => + task.status === "in_progress" || task.status === "stopping" + ? { ...task, status: "stopped", lastNote: "stopped" } + : task, + ); +} + function startStatusTicker(ctx: ExtensionContext) { if (!ctx.hasUI) return; if (statusInterval) clearInterval(statusInterval); @@ -93,6 +135,7 @@ function renderTemplate(template: string, data: Record): string } const MAX_TICKER_CHARS = 160; +const MAX_STATE_TEXT_CHARS = 4000; function truncateTicker(text: string): string { if (text.length <= MAX_TICKER_CHARS) return text; @@ -100,6 +143,11 @@ function truncateTicker(text: string): string { return `…${text.slice(-sliceLength)}`; } +function truncateStateText(text: string): string { + if (text.length <= MAX_STATE_TEXT_CHARS) return text; + return `${text.slice(0, MAX_STATE_TEXT_CHARS - 19)}… [truncated]`; +} + function lastSentence(text: string): string { const normalized = text.replace(/\s+/g, " ").trim(); const match = normalized.match(/[^.!?]*[.!?](?=\s|$)/g); @@ -174,19 +222,124 @@ function resetTransientWorkflowState(): void { clearClarificationWaiters(); } -function buildWaveSummary(state: WorkflowState): string { - const lines = (state.tasks ?? []).map((task) => { - const status = - task.status === "verified" ? "verified" : task.status === "failed" ? "failed" : task.status; - const note = task.lastNote ? ` — ${task.lastNote}` : ""; - const issues = task.issues?.length ? ` issues: ${task.issues.join("; ")}` : ""; - const devOutput = task.stageOutputs?.["develop"]; - const filesChanged = Array.isArray(devOutput?.filesChanged) - ? ` files: ${devOutput.filesChanged.join(", ")}` - : ""; - return `${task.id}: ${task.title} (${status})${note}${issues}${filesChanged}`; +function issueFromText(description: string): Issue { + return { severity: "blocking", description: description.slice(0, 4000) }; +} + +async function compareDeclaredFiles( + cwd: string, + declaredFiles: string[], +): Promise { + try { + const [{ stdout: diffStdout }, { stdout: untrackedStdout }] = await Promise.all([ + execFile("git", ["diff", "--name-only"], { cwd, shell: false }), + execFile("git", ["ls-files", "--others", "--exclude-standard"], { cwd, shell: false }), + ]); + const actual = new Set( + `${String(diffStdout)}\n${String(untrackedStdout)}` + .split(/\r?\n/) + .map((file) => file.trim().replaceAll("\\", "/")) + .filter(Boolean), + ); + const declared = new Set(declaredFiles.map((file) => file.replaceAll("\\", "/"))); + const missing = [...declared].filter((file) => !actual.has(file)); + const undeclared = [...actual].filter((file) => !declared.has(file)); + if (missing.length === 0 && undeclared.length === 0) return undefined; + return [ + "Declared filesChanged does not match git diff --name-only.", + missing.length > 0 ? `Missing from diff: ${missing.join(", ")}` : "", + undeclared.length > 0 ? `Undeclared diff files: ${undeclared.join(", ")}` : "", + ] + .filter(Boolean) + .join(" ") + .slice(0, 4000); + } catch { + // A non-Git working directory, unavailable Git executable, or failed diff is not + // itself a task failure; the verifier still receives the developer's evidence. + return undefined; + } +} + +function buildWaveSummary(state: WorkflowState): PriorWaveSummary { + const tasks = (state.tasks ?? []).map((task) => { + const developer = task.stageOutputs?.develop?.report; + const verifier = task.stageOutputs?.verify?.report; + const developerReport = developer && "filesChanged" in developer ? developer : undefined; + const verifierReport = verifier && "evidence" in verifier ? verifier : undefined; + return { + id: task.id, + title: task.title, + status: task.status, + retries: task.retries, + developerSummary: developerReport?.summary, + filesChanged: developerReport?.filesChanged ?? [], + verifierSummary: verifierReport?.summary, + evidence: [...(developerReport?.evidence ?? []), ...(verifierReport?.evidence ?? [])].slice( + 0, + 20, + ), + issues: [ + ...(developerReport?.issues ?? []), + ...(verifierReport?.issues ?? []), + ...(task.issues ?? []).map(issueFromText), + ].slice(0, 20), + }; }); - return lines.join("\n"); + const outcome = tasks.some((task) => task.status === "stopped") + ? "stopped" + : tasks.some((task) => task.status === "failed") + ? "failed" + : tasks.every((task) => task.status === "verified") + ? "verified" + : "partial"; + return { + waveIndex: state.waveIndex, + goal: state.wave?.goal ?? "", + outcome, + tasks, + }; +} + +const MAX_SUMMARY_CHARS = 2000; + +function boundedSummaryText(value: string | undefined): string | undefined { + if (value === undefined || value.length <= MAX_SUMMARY_CHARS) return value; + return `${value.slice(0, MAX_SUMMARY_CHARS - 19)}… [truncated]`; +} + +function serializePriorWaveSummary(summary: PriorWaveSummary): string { + const tasks = summary.tasks.slice(0, 100).map((task) => { + const evidence = task.evidence.slice(0, 20).map((item) => ({ + ...item, + description: boundedSummaryText(item.description), + command: boundedSummaryText(item.command), + })); + const issues = task.issues.slice(0, 20).map((issue) => ({ + ...issue, + description: boundedSummaryText(issue.description), + reproduction: boundedSummaryText(issue.reproduction), + })); + const filesChanged = task.filesChanged.slice(0, 100); + if (task.filesChanged.length > filesChanged.length) { + filesChanged.push(`[${task.filesChanged.length - filesChanged.length} files truncated]`); + } + return { + id: task.id, + title: task.title, + status: task.status, + retries: task.retries, + developerSummary: boundedSummaryText(task.developerSummary), + filesChanged, + verifierSummary: boundedSummaryText(task.verifierSummary), + evidence, + issues, + }; + }); + const omitted = + summary.tasks.length > tasks.length + ? `\n[${summary.tasks.length - tasks.length} tasks truncated]` + : ""; + return `${JSON.stringify({ ...summary, tasks }, null, 2)}${omitted}`; } function summarizeWave(wave: WorkflowWave): string { @@ -195,11 +348,13 @@ function summarizeWave(wave: WorkflowWave): string { } function buildPmChatPrompt(state: WorkflowState, message: string): string { - const summary = state.tasks.length > 0 ? buildWaveSummary(state) : "No tasks yet."; + const summary = state.previousSummary + ? serializePriorWaveSummary(state.previousSummary) + : "No previous wave summary."; return [ `Project goal: ${state.goal}`, `Current wave: ${state.waveIndex + 1}`, - `Wave summary:\n${summary}`, + `Previous wave summary:\n${summary}`, "User message:", message, "Respond conversationally. Do NOT output JSON.", @@ -244,7 +399,7 @@ async function pauseForClarification( ctx: ExtensionCommandContext, signal: AbortSignal, waveIndex: number, - previousSummary: string, + previousSummary: PriorWaveSummary | undefined, ): Promise { if (!currentState) throw new Error("No workflow state"); @@ -258,6 +413,7 @@ async function pauseForClarification( updatedAt: Date.now(), previousSummary, waveSummaries: currentState.waveSummaries ?? [], + status: "waiting_for_clarification", active: true, waitingForClarification: true, clarificationToken, @@ -292,25 +448,33 @@ function buildTaskState(task: WorkflowTask): TaskState { }; } -function ensureSessionFile(state: WorkflowState, task: TaskState, stageId: string): string { - const workflowDir = path.join(".pi", "workflows", "sessions", state.runId); +function ensureSessionFile( + ctx: ExtensionContext, + state: WorkflowState, + task: TaskState, + stageId: SemanticStageId, +): string { + const workflowDir = path.resolve(ctx.cwd, ".pi", "workflows", "sessions", state.runId); fs.mkdirSync(workflowDir, { recursive: true }); if (!task.sessionFiles) task.sessionFiles = {}; - if (!task.sessionFiles[stageId]) { - task.sessionFiles[stageId] = path.join(workflowDir, `${task.id}-${stageId}.jsonl`); + const sessionPath = path.resolve( + task.sessionFiles[stageId] ?? path.resolve(workflowDir, `${task.id}-${stageId}.jsonl`), + ); + if (sessionPath === workflowDir || !sessionPath.startsWith(`${workflowDir}${path.sep}`)) { + throw new Error(`Task session path escaped run directory: ${sessionPath}`); } - return task.sessionFiles[stageId]!; + task.sessionFiles[stageId] = sessionPath; + return sessionPath; } -function slugify(value: string): string { - return value.replace(/[^\w.-]+/g, "_"); -} - -function ensurePmSessionFile(state: WorkflowState): string { - const workflowDir = path.join(".pi", "workflows", "sessions"); +function ensurePmSessionFile(ctx: ExtensionContext, state: WorkflowState): string { + const workflowDir = path.resolve(ctx.cwd, ".pi", "workflows", "sessions", state.runId); fs.mkdirSync(workflowDir, { recursive: true }); - const name = slugify(state.workflowName || "default"); - return path.join(workflowDir, `pm-${name}.jsonl`); + const sessionPath = path.resolve(workflowDir, "pm.jsonl"); + if (!sessionPath.startsWith(`${workflowDir}${path.sep}`)) { + throw new Error(`PM session path escaped run directory: ${sessionPath}`); + } + return sessionPath; } function resolveAllowedExtensions( @@ -334,7 +498,7 @@ function resolveAllowedExtensions( return state?.allowedExtensions ?? config.allowedExtensions; } -function getRunnerKey(taskId: string, stageId: string): string { +function getRunnerKey(taskId: string, stageId: SemanticStageId): string { return `${taskId}:${stageId}`; } @@ -358,45 +522,60 @@ function getTaskRunner( const agent = findAgentByName(agents, agentName); if (!agent) throw new Error(`Agent not found: ${agentName}`); - const sessionFile = ensureSessionFile(currentState, task, stage.id); + const sessionFile = ensureSessionFile(ctx, currentState, task, stage.id); const runner = new RpcAgent({ cwd: ctx.cwd, sessionFile, systemPrompt: agent.systemPrompt, - model: agent.model, + model: currentState.model ?? agent.model, tools: agent.tools, allowedExtensions: resolveAllowedExtensions(agentName, config, currentState), - retry: config.agentRetry, + piCommand: config.piCommand, }); - const taskRunner: TaskRunner = { key, agent: runner, stageId: stage.id }; + const taskRunner: TaskRunner = { key, agent: runner, stageId: stage.id, lifecycle: "idle" }; taskRunners.set(key, taskRunner); return taskRunner; } -function stopTask(task: TaskState) { - if (!task.stageId) return; +async function stopTask(pi: ExtensionAPI, ctx: ExtensionContext, task: TaskState): Promise { + if (!task.stageId) { + task.status = "stopped"; + task.lastNote = "stopped"; + return; + } const key = getRunnerKey(task.id, task.stageId); const runner = taskRunners.get(key); - runner?.agent.abort(); + task.status = "stopping"; + task.lastNote = "stopping"; + if (runner) runner.lifecycle = "aborting"; + if (currentState) setState(pi, ctx, { ...currentState, tasks: [...currentState.tasks] }); + if (runner) { + await runner.agent.abort(); + await runner.activePrompt?.catch(() => {}); + runner.lifecycle = "stopped"; + } task.status = "stopped"; task.lastNote = "stopped"; } -function resetStageMemory(task: TaskState, stageId: string) { +function resetStageMemory(ctx: ExtensionContext, task: TaskState, stageId: SemanticStageId) { if (!currentState) return; const key = getRunnerKey(task.id, stageId); const runner = taskRunners.get(key); runner?.agent.dispose(); taskRunners.delete(key); - const workflowDir = path.join(".pi", "workflows", "sessions", currentState.runId); + const workflowDir = path.resolve(ctx.cwd, ".pi", "workflows", "sessions", currentState.runId); fs.mkdirSync(workflowDir, { recursive: true }); if (!task.sessionResetCounts) task.sessionResetCounts = {}; const nextReset = (task.sessionResetCounts[stageId] ?? 0) + 1; task.sessionResetCounts[stageId] = nextReset; if (!task.sessionFiles) task.sessionFiles = {}; - task.sessionFiles[stageId] = path.join(workflowDir, `${task.id}-${stageId}-r${nextReset}.jsonl`); + task.sessionFiles[stageId] = path.resolve( + workflowDir, + `${task.id}-${stageId}-r${nextReset}.jsonl`, + ); } async function messageTask( @@ -415,6 +594,7 @@ async function messageTask( } taskLocks.add(task.id); + let retainLock = false; try { const stage = findStageById(config.taskFlow.stages, task.stageId); if (!stage) throw new Error(`Stage not found: ${task.stageId}`); @@ -423,37 +603,45 @@ async function messageTask( const runner = taskRunners.get(key); if (runner) { if (!currentState) throw new Error("No workflow state"); - task.lastNote = "running"; - task.status = "in_progress"; - setState(pi, ctx, { ...currentState, tasks: [...currentState.tasks] }); if (runner.agent.isRunning()) { - runner.agent.sendSteer(message); + task.lastNote = "steering"; + try { + await runner.agent.sendSteer(message); + } catch (error) { + task.lastNote = `steer failed: ${error instanceof Error ? error.message : String(error)}`; + } + setState(pi, ctx, { ...currentState, tasks: [...currentState.tasks] }); return; } - const taskPrompt = `${message}`; - void runner.agent.runPrompt(taskPrompt).catch(() => { - // handled by processTask when re-run - }); - return; + } + + if (task.status === "in_progress" || task.status === "stopping") { + throw new Error(`Task ${task.id} is still running; wait for its prompt to settle`); } if (!currentState?.wave) throw new Error("No active wave"); - task.resumeMessage = message; + task.resumeMessage = truncateStateText(message); task.lastNote = "running"; - task.status = "in_progress"; + task.status = "pending"; setState(pi, ctx, { ...currentState, tasks: [...currentState.tasks] }); - void processTask( + const resumePromise = processTask( pi, ctx, config, task, currentState.wave, agents, - new AbortController().signal, + currentRun?.abortController.signal ?? new AbortController().signal, task.stageId, ); + retainLock = true; + void resumePromise.then( + () => taskLocks.delete(task.id), + () => taskLocks.delete(task.id), + ); + return; } finally { - taskLocks.delete(task.id); + if (!retainLock) taskLocks.delete(task.id); } } @@ -475,16 +663,16 @@ async function processTask( wave: WorkflowWave, agents: ReturnType["agents"], signal: AbortSignal, - startStageId?: string, + startStageId?: SemanticStageId, ): Promise { const stages = config.taskFlow.stages; - await runTaskFlow({ + await runTaskFlow({ task, stages, maxRetries: config.maxTaskRetries ?? 2, startStageId: startStageId, - isStopped: (t) => t.status === "stopped" || signal.aborted, + isStopped: (t) => t.status === "stopped" || t.status === "stopping" || signal.aborted, onStageStart: (stage, t) => { if (!currentState) return; // Guard against workflow stop during execution const workflowStage = stage as WorkflowStage; @@ -518,94 +706,85 @@ async function processTask( } const runner = getTaskRunner(ctx, config, t, workflowStage, workflowStage.agent, agents); - const outputText = await runner.agent.runPrompt(taskPrompt, { + const startedAt = Date.now(); + runner.lifecycle = "running"; + const activePrompt = runner.agent.runPrompt(taskPrompt, { + signal, onUpdate: (update) => { if (!currentState) return; if (update.type === "text_delta") { appendOutput(t, update.delta, "delta"); setState(pi, ctx, { ...currentState, tasks: [...currentState.tasks] }, false); - return; - } - if (update.type === "tool_start") { + } else if (update.type === "tool_start") { appendOutput(t, `tool ${update.toolName}`, "line"); setState(pi, ctx, { ...currentState, tasks: [...currentState.tasks] }, false); } }, }); - - // Get tool calls from the runner - prefer structured tool output over JSON parsing - const toolCalls = runner.agent.getLastToolCalls(); - let output: any = null; - - // Look for report_task_result or generate_wave tool calls - const reportCall = toolCalls.find((tc) => tc.name === "report_task_result"); - const waveCall = toolCalls.find((tc) => tc.name === "generate_wave"); - - if (reportCall) { - // Use the tool arguments directly as output - output = reportCall.arguments as Record; - } else if (waveCall) { - output = waveCall.arguments as Record; - } else { - // Fallback to JSON parsing for backward compatibility - try { - output = extractJson(outputText); - } catch { - // If no tool was called and JSON parsing fails, return null output - output = null; + runner.activePrompt = activePrompt; + try { + const rpcResult = await activePrompt; + const selected = selectStructuredToolResult(rpcResult, "report_task_result"); + const semanticStageId = workflowStage.id as SemanticStageId; + const report = + semanticStageId === "develop" + ? validateDeveloperReport(selected.params) + : validateVerifierReport(selected.params); + if (semanticStageId === "develop") { + const mismatch = await compareDeclaredFiles( + ctx.cwd, + (report as DeveloperReport).filesChanged, + ); + if (mismatch) { + t.issues = [...(t.issues ?? []), truncateStateText(mismatch)].slice(-100); + } } + const envelope: StageOutput = + semanticStageId === "develop" + ? { + runId: currentState?.runId ?? "unknown", + waveIndex: currentState?.waveIndex ?? 0, + taskId: t.id, + stageId: "develop", + role: "developer", + report: report as DeveloperReport, + toolCallId: selected.execution.toolCallId, + startedAt, + completedAt: selected.execution.endedAt ?? Date.now(), + } + : { + runId: currentState?.runId ?? "unknown", + waveIndex: currentState?.waveIndex ?? 0, + taskId: t.id, + stageId: "verify", + role: "verifier", + report: report as VerifierReport, + toolCallId: selected.execution.toolCallId, + startedAt, + completedAt: selected.execution.endedAt ?? Date.now(), + }; + return { output: envelope, outputText: rpcResult.outputText }; + } finally { + runner.activePrompt = undefined; + runner.lifecycle = "idle"; } - - return { output, outputText, toolCalls }; }, applyOutput: (t, stageId, result) => { if (!currentState) return; // Guard against workflow stop during execution const output = result.output; - - // Initialize stageOutputs if needed + const semanticStageId = stageId as SemanticStageId; if (!t.stageOutputs) t.stageOutputs = {}; - - // Fallback: if output is null but we have text, use the text as summary - if (output === null && result.outputText) { - const textSummary = result.outputText.split("\n").slice(0, 3).join(" ").trim(); - if (stageId === "develop") { - t.stageOutputs[stageId] = { summary: textSummary, filesChanged: [] }; - } - t.lastNote = textSummary.slice(0, 80); - } else { - t.stageOutputs[stageId] = output; - - if (typeof output?.status === "string") { - t.lastNote = String(output.status); - } else if (typeof output?.summary === "string") { - t.lastNote = output.summary.slice(0, 80); - } else { - t.lastNote = "completed"; - } - } - - const tickerSource = - typeof output?.summary === "string" - ? output.summary - : (result.outputText.split("\n")[0] ?? ""); - t.lastOutput = truncateTicker(tickerSource.trim()); + t.stageOutputs[semanticStageId] = output; + t.lastNote = output.report.status; + t.lastOutput = truncateTicker(output.report.summary.trim()); t.lastActivityAt = Date.now(); - if (stageId === "develop") { - const summary = - typeof output?.summary === "string" - ? output.summary - : output === null - ? (result.outputText.split("\n")[0] ?? "completed") - : JSON.stringify(output); - sendAgentSummary(pi, t, stageId, summary); + if (semanticStageId === "develop") { + sendAgentSummary(pi, t, semanticStageId, output.report.summary); } - if (stageId === "verify") { - const status = output?.status ? String(output.status) : "unknown"; - const issues = Array.isArray(output?.issues) ? output.issues.join("; ") : ""; - const summary = issues ? `${status}\nissues: ${issues}` : status; - sendAgentSummary(pi, t, stageId, summary); + if (semanticStageId === "verify") { + sendAgentSummary(pi, t, semanticStageId, output.report.summary); } const key = t.stageId ? getRunnerKey(t.id, t.stageId) : undefined; @@ -620,11 +799,12 @@ async function processTask( applyVerifyFailure: (t, stageId, result, errorMessage, reason = "verification_failed") => { if (!currentState) return; // Guard against workflow stop during execution const output = result?.output; - const issues = output?.issues ?? (errorMessage ? [errorMessage] : []); - if (!t.stageOutputs) t.stageOutputs = {}; - t.stageOutputs[stageId] = { status: "fail", issues }; - t.issues = Array.isArray(issues) ? issues.map(String) : [String(issues)]; - t.lastNote = errorMessage ? `error: ${errorMessage}` : "fail"; + const report = output?.report as VerifierReport | undefined; + const issues = + report?.issues.map((issue) => issue.description) ?? + (errorMessage ? [errorMessage] : ["Verifier did not return a valid report"]); + t.issues = issues.map(truncateStateText); + t.lastNote = errorMessage ? truncateStateText(`error: ${errorMessage}`) : "fail"; if (errorMessage) t.lastOutput = truncateTicker(errorMessage); const keepDeveloperMemory = config.taskFlow.memory?.keepDeveloperMemory ?? true; @@ -633,41 +813,41 @@ async function processTask( const verifierSelfFailureMemory = config.taskFlow.memory?.verifierSelfFailureMemory ?? "keep"; if ((reason === "verification_failed" || reason === "error") && !keepDeveloperMemory) { - resetStageMemory(t, "develop"); + resetStageMemory(ctx, t, "develop"); } if (reason === "verification_failed" && !keepVerifierMemoryOnDeveloperFailure) { - resetStageMemory(t, "verify"); + resetStageMemory(ctx, t, "verify"); } if (reason === "malformed_output") { if ( verifierSelfFailureMemory === "reset" || verifierSelfFailureMemory === "reset_on_malformed_output" ) { - resetStageMemory(t, "verify"); + resetStageMemory(ctx, t, "verify"); } } else if (reason === "error" && verifierSelfFailureMemory === "reset") { - resetStageMemory(t, "verify"); + resetStageMemory(ctx, t, "verify"); } setState(pi, ctx, { ...currentState, tasks: [...currentState.tasks] }); }, applyGenericFailure: (t, errorMessage) => { if (!currentState) return; // Guard against workflow stop during execution - t.issues = [errorMessage]; - t.lastNote = `error: ${errorMessage}`; + t.issues = [truncateStateText(errorMessage)]; + t.lastNote = truncateStateText(`error: ${errorMessage}`); t.lastOutput = truncateTicker(errorMessage); setState(pi, ctx, { ...currentState, tasks: [...currentState.tasks] }); }, markVerified: (t, stageId) => { if (!currentState) return; // Guard against workflow stop during execution t.status = "verified"; - t.stageId = stageId; + t.stageId = stageId as SemanticStageId; setState(pi, ctx, { ...currentState, tasks: [...currentState.tasks] }); }, markFailed: (t, stageId) => { if (!currentState) return; // Guard against workflow stop during execution t.status = "failed"; - t.stageId = stageId; + t.stageId = stageId as SemanticStageId; setState(pi, ctx, { ...currentState, tasks: [...currentState.tasks] }); }, getField: getByPath, @@ -734,15 +914,15 @@ function getPmRunner( const pmAgent = findAgentByName(agents, config.agents.pm); if (!pmAgent) throw new Error(`PM agent not found: ${config.agents.pm}`); - const sessionFile = ensurePmSessionFile(currentState); + const sessionFile = ensurePmSessionFile(ctx, currentState); pmRunner = new RpcAgent({ cwd: ctx.cwd, sessionFile, systemPrompt: pmAgent.systemPrompt, - model: pmAgent.model, + model: currentState.model ?? pmAgent.model, tools: pmAgent.tools, allowedExtensions: resolveAllowedExtensions(pmAgent.name, config, currentState), - retry: config.agentRetry, + piCommand: config.piCommand, }); return pmRunner; } @@ -754,7 +934,7 @@ async function runPmAgent( ctx: ExtensionContext, signal: AbortSignal, prompt: string, -): Promise { +): Promise { if (pmBusy) throw new Error("PM is already running"); pmBusy = true; setPmStatus(ctx, "PM: responding..."); @@ -777,13 +957,13 @@ async function generateWaveFromPm( agents: ReturnType["agents"], ctx: ExtensionCommandContext, signal: AbortSignal, - previousSummary: string, + previousSummary: PriorWaveSummary | undefined, errorMessage?: string, ): Promise<{ done: boolean; wave?: WorkflowWave; clarification?: string }> { const promptParts = [`Project goal: ${config.goal}`]; if (previousSummary) { - promptParts.push(`Previous wave summary:\n${previousSummary}`); + promptParts.push(`Previous wave summary:\n${serializePriorWaveSummary(previousSummary)}`); } if (errorMessage) { @@ -796,47 +976,40 @@ async function generateWaveFromPm( ); const prompt = promptParts.join("\n\n"); - const outputText = await runPmAgent(pi, config, agents, ctx, signal, prompt); - - // Get tool calls from PM runner - prefer structured tool output - const runner = getPmRunner(ctx, config, agents); - const toolCalls = runner.getLastToolCalls(); - const waveCall = toolCalls.find((tc) => tc.name === "generate_wave"); - - let output: any; - if (waveCall) { - output = waveCall.arguments as Record; - } else { - // Try JSON parsing for backward compatibility - try { - output = extractJson(outputText || ""); - } catch { - output = null; + const runResult = await runPmAgent(pi, config, agents, ctx, signal, prompt); + const expectedExecutions = runResult.executions.filter( + (execution) => execution.name === "generate_wave", + ); + if ( + expectedExecutions.length > 0 && + expectedExecutions.every((execution) => execution.isError === true) + ) { + throw new Error("generate_wave tool execution failed"); + } + + let selected: ReturnType; + try { + selected = selectStructuredToolResult(runResult, "generate_wave"); + } catch (error) { + if (expectedExecutions.length === 0 && runResult.outputText.trim()) { + sendPmMessage(pi, runResult.outputText); + return { done: false, clarification: runResult.outputText }; } + throw error; } + const output = validateGenerateWave(selected.params); - if (output?.done === true) { + if (output.done === true) { sendPmMessage(pi, "PM reports: all work is complete."); return { done: true }; } - if (output?.wave) { - const wave = output.wave as WorkflowWave; - // Validate wave structure before using it - if (!wave.tasks || !Array.isArray(wave.tasks)) { - throw new Error('generate_wave returned invalid wave - "tasks" is missing or not an array'); - } + if (output.wave) { + const wave = output.wave; sendPmMessage(pi, summarizeWave(wave)); return { done: false, wave }; } - - // PM didn't call tool or return JSON - treat as clarification request - if (outputText && outputText.trim()) { - sendPmMessage(pi, outputText); - return { done: false, clarification: outputText }; - } - - throw new Error("PM output missing wave"); + throw new Error("PM generate_wave result did not contain a wave or done=true"); } async function resolveWaveForIndex( @@ -845,7 +1018,7 @@ async function resolveWaveForIndex( config: WorkflowConfig, agents: ReturnType["agents"], signal: AbortSignal, - previousSummary: string, + previousSummary: PriorWaveSummary | undefined, waveIndex: number, currentWave?: WorkflowWave, currentTasks?: TaskState[], @@ -911,14 +1084,16 @@ async function resumeWorkflow(pi: ExtensionAPI, ctx: ExtensionCommandContext): P const { config } = loadWorkflowConfig(ctx.cwd, currentState.workflowName); const { agents } = discoverAgents(ctx.cwd); const effectiveConfig: WorkflowConfig = { ...config, goal: currentState.goal }; + await preflightPiExecutable(effectiveConfig.piCommand, ctx.cwd); const abortController = new AbortController(); const runPromise = (async () => { try { - setState(pi, ctx, { ...currentState!, active: true }); + setState(pi, ctx, { ...currentState!, status: "running", active: true }); sendWorkflowNotice(pi, "Workflow resumed."); - let previousSummary = currentState?.previousSummary ?? ""; + let previousSummary = currentState?.previousSummary; + let pmReportedDone = false; for ( let waveIndex = currentState!.waveIndex; @@ -942,7 +1117,10 @@ async function resumeWorkflow(pi: ExtensionAPI, ctx: ExtensionCommandContext): P hasExistingWave ? currentState!.wave : undefined, hasExistingWave ? currentState!.tasks : undefined, ); - if (resolved.done) break; + if (resolved.done) { + pmReportedDone = true; + break; + } if (resolved.clarification) { await pauseForClarification(pi, ctx, abortController.signal, waveIndex, previousSummary); waveIndex -= 1; @@ -960,6 +1138,7 @@ async function resumeWorkflow(pi: ExtensionAPI, ctx: ExtensionCommandContext): P updatedAt: Date.now(), previousSummary, waveSummaries: currentState?.waveSummaries ?? [], + status: "running", active: true, }; setState(pi, ctx, updatedState); @@ -986,26 +1165,45 @@ async function resumeWorkflow(pi: ExtensionAPI, ctx: ExtensionCommandContext): P } } + const tasks = currentState?.tasks ?? []; + const allVerified = tasks.length === 0 || tasks.every((task) => task.status === "verified"); + const status: WorkflowStatus = pmReportedDone + ? allVerified + ? "completed" + : "partial" + : "exhausted"; const finalState: WorkflowState = { ...currentState!, + status, active: false, waitingForClarification: false, clarificationToken: undefined, updatedAt: Date.now(), }; setState(pi, ctx, finalState); - sendWorkflowNotice(pi, "Workflow completed."); - if (ctx.hasUI) ctx.ui.notify("Workflow completed", "info"); + const notice = + status === "completed" + ? "Workflow completed." + : status === "exhausted" + ? "Workflow exhausted its wave limit before completion." + : "Workflow is partial: PM finished with unverified tasks."; + sendWorkflowNotice(pi, notice); + if (ctx.hasUI) ctx.ui.notify(notice, status === "completed" ? "info" : "warning"); } catch (error: any) { const message = error?.message || "Workflow failed"; - sendWorkflowNotice(pi, `Workflow error: ${message}`); - if (ctx.hasUI) ctx.ui.notify(message, "error"); + const stopped = abortController.signal.aborted; + const status: WorkflowStatus = stopped ? "stopped" : "failed"; + sendWorkflowNotice(pi, stopped ? "Workflow stopped." : `Workflow error: ${message}`); + if (ctx.hasUI) + ctx.ui.notify(stopped ? "Workflow stopped" : message, stopped ? "info" : "error"); if (currentState) { setState(pi, ctx, { ...currentState, + status, active: false, waitingForClarification: false, clarificationToken: undefined, + tasks: stopped ? markActiveTasksStopped(currentState.tasks) : currentState.tasks, updatedAt: Date.now(), }); } @@ -1015,7 +1213,7 @@ async function resumeWorkflow(pi: ExtensionAPI, ctx: ExtensionCommandContext): P } })(); - currentRun = { abortController, promise: runPromise }; + currentRun = { abortController, promise: runPromise, stopRequested: false }; } async function startWorkflow( @@ -1023,6 +1221,7 @@ async function startWorkflow( ctx: ExtensionCommandContext, workflowName: string, goalOverride?: string, + modelOverride?: string, ): Promise { if (currentRun) { if (ctx.hasUI) ctx.ui.notify("Workflow already running", "warning"); @@ -1031,10 +1230,14 @@ async function startWorkflow( const { config } = loadWorkflowConfig(ctx.cwd, workflowName); const { agents } = discoverAgents(ctx.cwd); + if (goalOverride && goalOverride.length > MAX_STATE_TEXT_CHARS) { + throw new Error(`Workflow goal is too long (max ${MAX_STATE_TEXT_CHARS} characters)`); + } const effectiveConfig: WorkflowConfig = { ...config, goal: goalOverride ?? config.goal, }; + await preflightPiExecutable(effectiveConfig.piCommand, ctx.cwd); const abortController = new AbortController(); const runPromise = (async () => { @@ -1043,19 +1246,22 @@ async function startWorkflow( runId: `${Date.now()}-${Math.random().toString(16).slice(2)}`, workflowName: effectiveConfig.name, goal: effectiveConfig.goal, + model: modelOverride, + status: "running", active: true, waveIndex: 0, tasks: [], updatedAt: Date.now(), allowedExtensions: effectiveConfig.allowedExtensions, allowedExtensionsByAgent: effectiveConfig.allowedExtensionsByAgent, - previousSummary: "", + previousSummary: undefined, waveSummaries: [], }; setState(pi, ctx, initialState); sendWorkflowNotice(pi, `Workflow started: ${effectiveConfig.goal}`); - let previousSummary = initialState.previousSummary ?? ""; + let previousSummary = initialState.previousSummary; + let pmReportedDone = false; for (let waveIndex = 0; waveIndex < (effectiveConfig.maxWaves ?? 10); waveIndex++) { if (abortController.signal.aborted) throw new Error("Workflow aborted"); @@ -1068,7 +1274,10 @@ async function startWorkflow( previousSummary, waveIndex, ); - if (resolved.done) break; + if (resolved.done) { + pmReportedDone = true; + break; + } if (resolved.clarification) { await pauseForClarification(pi, ctx, abortController.signal, waveIndex, previousSummary); waveIndex -= 1; @@ -1101,26 +1310,45 @@ async function startWorkflow( } } + const tasks = currentState?.tasks ?? []; + const allVerified = tasks.length === 0 || tasks.every((task) => task.status === "verified"); + const status: WorkflowStatus = pmReportedDone + ? allVerified + ? "completed" + : "partial" + : "exhausted"; const finalState: WorkflowState = { ...currentState!, + status, active: false, waitingForClarification: false, clarificationToken: undefined, updatedAt: Date.now(), }; setState(pi, ctx, finalState); - sendWorkflowNotice(pi, "Workflow completed."); - if (ctx.hasUI) ctx.ui.notify("Workflow completed", "info"); + const notice = + status === "completed" + ? "Workflow completed." + : status === "exhausted" + ? "Workflow exhausted its wave limit before completion." + : "Workflow is partial: PM finished with unverified tasks."; + sendWorkflowNotice(pi, notice); + if (ctx.hasUI) ctx.ui.notify(notice, status === "completed" ? "info" : "warning"); } catch (error: any) { const message = error?.message || "Workflow failed"; - sendWorkflowNotice(pi, `Workflow error: ${message}`); - if (ctx.hasUI) ctx.ui.notify(message, "error"); + const stopped = abortController.signal.aborted; + const status: WorkflowStatus = stopped ? "stopped" : "failed"; + sendWorkflowNotice(pi, stopped ? "Workflow stopped." : `Workflow error: ${message}`); + if (ctx.hasUI) + ctx.ui.notify(stopped ? "Workflow stopped" : message, stopped ? "info" : "error"); if (currentState) { setState(pi, ctx, { ...currentState, + status, active: false, waitingForClarification: false, clarificationToken: undefined, + tasks: stopped ? markActiveTasksStopped(currentState.tasks) : currentState.tasks, updatedAt: Date.now(), }); } @@ -1130,7 +1358,7 @@ async function startWorkflow( } })(); - currentRun = { abortController, promise: runPromise }; + currentRun = { abortController, promise: runPromise, stopRequested: false }; } async function stopWorkflow(pi: ExtensionAPI, ctx: ExtensionCommandContext): Promise { @@ -1138,33 +1366,37 @@ async function stopWorkflow(pi: ExtensionAPI, ctx: ExtensionCommandContext): Pro if (ctx.hasUI) ctx.ui.notify("No active workflow", "warning"); return; } - const runPromise = currentRun.promise; - currentRun.abortController.abort(); - currentRun = null; - for (const runner of taskRunners.values()) { - runner.agent.abort(); - runner.agent.dispose(); - } - taskRunners.clear(); - disposePmRunner(); + const handle = currentRun; + const runPromise = handle.promise; + handle.stopRequested = true; + handle.abortController.abort(); if (currentState) { - currentState.active = false; + currentState.status = "stopping"; + currentState.active = true; currentState.waitingForClarification = false; currentState.clarificationToken = undefined; - setState(pi, ctx, currentState); + setState(pi, ctx, { ...currentState }); } - resetTransientWorkflowState(); - sendWorkflowNotice(pi, "Workflow stopped."); - if (ctx.hasUI) ctx.ui.notify("Workflow stopped", "info"); + await Promise.all( + [...taskRunners.values()].map(async (runner) => { + await runner.activePrompt?.catch(() => {}); + runner.agent.dispose(); + }), + ); await runPromise.catch(() => {}); + taskRunners.clear(); + disposePmRunner(); + resetTransientWorkflowState(); } export default function (pi: ExtensionAPI) { pi.on("session_start", (_event, ctx) => { + materializeProjectDefaults(ctx.cwd); currentState = restoreState(ctx); if (currentState?.active) { setState(pi, ctx, { ...currentState, + status: "stopped", active: false, waitingForClarification: false, clarificationToken: undefined, @@ -1182,8 +1414,10 @@ export default function (pi: ExtensionAPI) { pi.on("session_shutdown", async (_event, ctx) => { stopStatusTicker(); if (currentRun) { - currentRun.abortController.abort(); - currentRun = null; + const handle = currentRun; + handle.stopRequested = true; + handle.abortController.abort(); + await handle.promise.catch(() => {}); } for (const runner of taskRunners.values()) { runner.agent.abort(); @@ -1192,11 +1426,12 @@ export default function (pi: ExtensionAPI) { taskRunners.clear(); disposePmRunner(); if (currentState) { + currentState.status = "stopped"; currentState.active = false; currentState.waitingForClarification = false; currentState.clarificationToken = undefined; currentState.tasks = (currentState.tasks ?? []).map((task) => { - if (task.status === "in_progress") { + if (task.status === "in_progress" || task.status === "stopping") { return { ...task, status: "stopped", lastNote: "stopped" }; } return task; @@ -1230,12 +1465,13 @@ export default function (pi: ExtensionAPI) { new AbortController().signal, prompt, ); - sendPmMessage(pi, outputText); + sendPmMessage(pi, outputText.outputText); // Clear the clarification flag - user has responded if (currentState.waitingForClarification) { setState(pi, ctx, { ...currentState, + status: "running", waitingForClarification: false, clarificationToken: undefined, }); @@ -1251,10 +1487,9 @@ export default function (pi: ExtensionAPI) { pi.registerCommand("workflow", { description: "Manage workflow orchestrator", handler: async (args, ctx) => { - const tokens = (args || "").split(/\s+/).filter(Boolean); + const tokens = tokenizeWorkflowArgs(args || ""); const command = tokens[0]; const name = tokens[1]; - const goalText = normalizeGoal(tokens.slice(2).join(" ")); if (!command || command === "help") { sendWorkflowNotice( @@ -1262,6 +1497,7 @@ export default function (pi: ExtensionAPI) { [ "Workflow commands:", " /workflow start [goal]", + ' /workflow "goal" [--model ]', " /workflow resume", " /workflow status", " /workflow stop", @@ -1276,8 +1512,9 @@ export default function (pi: ExtensionAPI) { return; } - if (command === "start") { - if (!name) { + if (command === "start" || !WORKFLOW_COMMANDS.has(command)) { + const parsed = parseWorkflowStartArgs(command === "start" ? tokens : ["start", ...tokens]); + if (!parsed) { ctx.ui?.notify("Usage: /workflow start [goal]", "warning"); return; } @@ -1285,12 +1522,22 @@ export default function (pi: ExtensionAPI) { ctx.ui?.notify("Existing workflow state found. Use /workflow resume.", "warning"); return; } - void startWorkflow(pi, ctx, name, goalText); + void startWorkflow(pi, ctx, parsed.workflowName, parsed.goal, parsed.model).catch( + (error: unknown) => { + const message = error instanceof Error ? error.message : String(error); + sendWorkflowNotice(pi, `Workflow could not start: ${message}`); + if (ctx.hasUI) ctx.ui.notify(message, "error"); + }, + ); return; } if (command === "resume") { - void resumeWorkflow(pi, ctx); + void resumeWorkflow(pi, ctx).catch((error: unknown) => { + const message = error instanceof Error ? error.message : String(error); + sendWorkflowNotice(pi, `Workflow could not resume: ${message}`); + if (ctx.hasUI) ctx.ui.notify(message, "error"); + }); return; } @@ -1322,7 +1569,7 @@ export default function (pi: ExtensionAPI) { ctx.ui?.notify(`Task not found: ${name}`, "warning"); return; } - stopTask(task); + await stopTask(pi, ctx, task); setState(pi, ctx, { ...currentState, tasks: [...currentState.tasks] }); return; } @@ -1348,7 +1595,12 @@ export default function (pi: ExtensionAPI) { } const { config } = loadWorkflowConfig(ctx.cwd, currentState.workflowName); const { agents } = discoverAgents(ctx.cwd); - void messageTask(pi, ctx, config, task, message, agents); + try { + await messageTask(pi, ctx, config, task, message, agents); + } catch (error) { + const detail = error instanceof Error ? error.message : String(error); + if (ctx.hasUI) ctx.ui.notify(detail, "warning"); + } return; } @@ -1420,7 +1672,7 @@ export default function (pi: ExtensionAPI) { const task = findTask(params.id); if (!task) return { content: [{ type: "text", text: `Task not found: ${params.id}` }], details: {} }; - stopTask(task); + await stopTask(pi, ctx, task); setState(pi, ctx, { ...currentState, tasks: [...currentState.tasks] }); return { content: [{ type: "text", text: `Stopped task ${params.id}` }], details: {} }; }, diff --git a/.pi/extensions/workflow-orchestrator/models.ts b/.pi/extensions/workflow-orchestrator/models.ts new file mode 100644 index 0000000..e22e556 --- /dev/null +++ b/.pi/extensions/workflow-orchestrator/models.ts @@ -0,0 +1,8 @@ +/** First non-empty model string wins. */ +export function pickModel(...candidates: Array): string | undefined { + for (const candidate of candidates) { + const trimmed = candidate?.trim(); + if (trimmed) return trimmed; + } + return undefined; +} diff --git a/.pi/extensions/workflow-orchestrator/render.ts b/.pi/extensions/workflow-orchestrator/render.ts index 92c3b3e..f83cf3e 100644 --- a/.pi/extensions/workflow-orchestrator/render.ts +++ b/.pi/extensions/workflow-orchestrator/render.ts @@ -1,4 +1,4 @@ -import type { ExtensionContext } from "@mariozechner/pi-coding-agent"; +import type { ExtensionContext } from "@earendil-works/pi-coding-agent"; import type { WorkflowState } from "./state.js"; let pmWidgetStatus: string | undefined; @@ -41,10 +41,17 @@ export function updateStatus(ctx: ExtensionContext, state?: WorkflowState): void const verified = state.tasks.filter((task) => task.status === "verified").length; const failed = state.tasks.filter((task) => task.status === "failed").length; - const status = `Wave ${state.waveIndex + 1}: ${verified}/${total} verified${failed ? `, ${failed} failed` : ""}`; + const status = `${state.status}: Wave ${state.waveIndex + 1}: ${verified}/${total} verified${failed ? `, ${failed} failed` : ""}`; ctx.ui.setStatus("workflow", status); - const order = { in_progress: 0, pending: 1, stopped: 2, verified: 3, failed: 4 } as const; + const order = { + in_progress: 0, + stopping: 1, + pending: 2, + stopped: 3, + verified: 4, + failed: 5, + } as const; const sortedTasks = [...state.tasks].sort((a, b) => { const aOrder = order[a.status] ?? 9; const bOrder = order[b.status] ?? 9; @@ -71,7 +78,7 @@ export function updateStatus(ctx: ExtensionContext, state?: WorkflowState): void ? "✓" : task.status === "failed" ? "✗" - : task.status === "stopped" + : task.status === "stopped" || task.status === "stopping" ? "⏸" : task.status === "in_progress" ? spinner @@ -90,7 +97,7 @@ export function updateStatus(ctx: ExtensionContext, state?: WorkflowState): void ? "error" : task.status === "in_progress" ? "warning" - : task.status === "stopped" + : task.status === "stopped" || task.status === "stopping" ? "muted" : "text"; const taskLine = `${header}: ${title}`; diff --git a/.pi/extensions/workflow-orchestrator/runner.ts b/.pi/extensions/workflow-orchestrator/runner.ts index 9b43e42..c312f7c 100644 --- a/.pi/extensions/workflow-orchestrator/runner.ts +++ b/.pi/extensions/workflow-orchestrator/runner.ts @@ -1,38 +1,68 @@ -import { spawn, type ChildProcessWithoutNullStreams } from "node:child_process"; +import { + execFile as nodeExecFile, + spawn, + type ChildProcessWithoutNullStreams, +} from "node:child_process"; import * as fs from "node:fs"; import * as os from "node:os"; import * as path from "node:path"; -import type { Message } from "@mariozechner/pi-ai"; +import { StringDecoder } from "node:string_decoder"; +import { promisify } from "node:util"; + +const execFile = promisify(nodeExecFile); + +export const SUPPORTED_PI_MIN_VERSION = "0.80.10"; +export const SUPPORTED_PI_MAX_MAJOR_MINOR = "0.81.0"; +export const DEFAULT_RPC_TIMEOUTS = { + startupTimeoutMs: 10_000, + commandTimeoutMs: 10_000, + runTimeoutMs: 30 * 60_000, + abortGraceMs: 5_000, + killGraceMs: 2_000, + maxRecordBytes: 4 * 1024 * 1024, + maxStderrBytes: 64 * 1024, +} as const; export type AgentRunUpdate = | { type: "text_delta"; delta: string } - | { type: "tool_start"; toolName: string; args: any } - | { type: "tool_update"; toolName: string; partialResult: any } - | { type: "tool_end"; toolName: string; isError: boolean }; + | { type: "tool_start"; toolCallId: string; toolName: string; args: unknown } + | { type: "tool_update"; toolCallId: string; toolName: string; partialResult: unknown } + | { type: "tool_end"; toolCallId: string; toolName: string; isError: boolean } + | { type: "retry"; attempt: number; maxAttempts: number; delayMs: number; errorMessage: string }; -export interface AgentRunInput { +export interface RpcToolExecution { + toolCallId: string; name: string; - task: string; - cwd: string; - systemPrompt: string; - model?: string; - tools?: string[]; - signal?: AbortSignal; - onUpdate?: (update: AgentRunUpdate) => void; - allowedExtensions?: string[]; + attemptedArgs: unknown; + startedAt: number; + endedAt?: number; + isError?: boolean; + result?: unknown; } export interface ToolCallCapture { name: string; arguments: Record; + toolCallId?: string; + isError?: boolean; } -export interface AgentRunResult { +export interface RpcRunResult { outputText: string; - messages: Message[]; + executions: RpcToolExecution[]; + successfulToolExecutions: RpcToolExecution[]; + failedToolExecutions: RpcToolExecution[]; stderr: string; - exitCode: number; - toolCalls: ToolCallCapture[]; + lifecycleEvents: string[]; + usage?: unknown; + metrics?: RpcRunMetrics; +} + +export interface RpcRunMetrics { + turns: number; + retries: number; + successfulToolExecutions: number; + failedToolExecutions: number; } export interface RpcAgentOptions { @@ -42,7 +72,14 @@ export interface RpcAgentOptions { model?: string; tools?: string[]; allowedExtensions?: string[]; - retry?: Partial; + piCommand?: string; + startupTimeoutMs?: number; + commandTimeoutMs?: number; + runTimeoutMs?: number; + abortGraceMs?: number; + killGraceMs?: number; + maxRecordBytes?: number; + maxStderrBytes?: number; } export interface RpcRunOptions { @@ -50,131 +87,89 @@ export interface RpcRunOptions { signal?: AbortSignal; } -export interface AgentRetryOptions { - maxAttempts: number; - initialDelayMs: number; - maxDelayMs: number; - backoffMultiplier: number; - jitterMs: number; +interface PendingCommand { + id: string; + command: string; + resolve: (data: unknown) => void; + reject: (error: Error) => void; + timeout: ReturnType; } interface RpcRunState { - resolve: (value: string) => void; + resolve: (value: RpcRunResult) => void; reject: (error: Error) => void; - lastAssistantText: string; - toolCalls: ToolCallCapture[]; + promptAccepted: boolean; + lifecycle: "prompt_pending" | "running" | "settling"; + outputText: string; + executions: Map; + lifecycleEvents: string[]; + promptAcceptedPromise: Promise; + resolvePromptAccepted: () => void; + rejectPromptAccepted: (error: Error) => void; + assistantError?: string; + retrySucceeded: boolean; + retryCount: number; + turnCount: number; + usage?: unknown; + abortRequested: boolean; + timeout?: ReturnType; + abortTimeout?: ReturnType; + signal?: AbortSignal; + onAbort?: () => void; onUpdate?: (update: AgentRunUpdate) => void; - aborted?: boolean; + settled: boolean; } -export const DEFAULT_AGENT_RETRY_OPTIONS: AgentRetryOptions = { - maxAttempts: 5, - initialDelayMs: 5000, - maxDelayMs: 120000, - backoffMultiplier: 2, - jitterMs: 1000, -}; - -const RETRYABLE_ERROR_PATTERNS = [ - /(?:^|\D)429(?:\D|$)/i, - /rate.?limit/i, - /too many requests/i, - /retry.?after/i, - /temporarily unavailable/i, - /service unavailable/i, - /overloaded/i, - /timeout/i, - /timed out/i, - /econnreset/i, - /etimedout/i, - /(?:^|\D)502(?:\D|$)/i, - /(?:^|\D)503(?:\D|$)/i, - /(?:^|\D)504(?:\D|$)/i, -]; - -export function normalizeAgentRetryOptions(retry?: Partial): AgentRetryOptions { - const merged = { ...DEFAULT_AGENT_RETRY_OPTIONS, ...retry }; - return { - maxAttempts: Math.max(1, Math.floor(merged.maxAttempts)), - initialDelayMs: Math.max(0, merged.initialDelayMs), - maxDelayMs: Math.max(0, merged.maxDelayMs), - backoffMultiplier: Math.max(1, merged.backoffMultiplier), - jitterMs: Math.max(0, merged.jitterMs), - }; +export interface PiVersionInfo { + command: string; + version: string; } -function errorText(error: unknown): string { +function errorMessage(error: unknown): string { if (error instanceof Error) return error.message; if (typeof error === "string") return error; try { - return JSON.stringify(error) ?? ""; + return JSON.stringify(error) ?? String(error); } catch { - return ""; + return String(error); } } -export function isRetryableAgentError(error: unknown): boolean { - const text = errorText(error); - if (!text) return false; - return RETRYABLE_ERROR_PATTERNS.some((pattern) => pattern.test(text)); -} - -function parseRetryAfterMs(error: unknown): number | undefined { - const text = errorText(error); - const retryAfterMatch = - text.match(/retry-after["':\s]+(\d+(?:\.\d+)?)/i) ?? - text.match(/retry_after["':\s]+(\d+(?:\.\d+)?)/i) ?? - text.match(/try again in\s+(\d+(?:\.\d+)?)\s*(ms|s|sec|seconds|m|min|minutes)?/i); - - if (!retryAfterMatch) return undefined; - - const value = Number(retryAfterMatch[1]); - if (!Number.isFinite(value) || value < 0) return undefined; - - const unit = retryAfterMatch[2]?.toLowerCase(); - if (unit === "ms") return value; - if (unit === "m" || unit === "min" || unit === "minutes") return value * 60_000; - return value * 1000; +export function parsePiVersion(output: string): string | undefined { + return output.match(/\b(\d+)\.(\d+)\.(\d+)(?:[-+][0-9A-Za-z.-]+)?\b/)?.[0]; } -export function getAgentRetryDelayMs( - failedAttempt: number, - retry: AgentRetryOptions, - error?: unknown, - random: () => number = Math.random, -): number { - const retryAfterMs = parseRetryAfterMs(error); - if (retryAfterMs !== undefined) return Math.min(retryAfterMs, retry.maxDelayMs); - - const exponent = Math.max(0, failedAttempt - 1); - const exponentialDelay = retry.initialDelayMs * retry.backoffMultiplier ** exponent; - const cappedDelay = Math.min(exponentialDelay, retry.maxDelayMs); - const jitter = retry.jitterMs > 0 ? Math.floor(random() * retry.jitterMs) : 0; - return cappedDelay + jitter; +function isSupportedVersion(version: string): boolean { + const match = version.match(/^(\d+)\.(\d+)\.(\d+)/); + if (!match) return false; + const major = Number(match[1]); + const minor = Number(match[2]); + const patch = Number(match[3]); + return major === 0 && minor === 80 && patch >= 10; } -function waitForRetry(ms: number, signal?: AbortSignal): Promise { - if (ms <= 0) return Promise.resolve(); - if (signal?.aborted) return Promise.reject(new Error("Aborted")); - - return new Promise((resolve, reject) => { - const timeout = setTimeout(() => { - cleanup(); - resolve(); - }, ms); - - const onAbort = () => { - cleanup(); - reject(new Error("Aborted")); - }; - - const cleanup = () => { - clearTimeout(timeout); - signal?.removeEventListener("abort", onAbort); - }; +export async function preflightPiExecutable( + command = "pi", + cwd = process.cwd(), +): Promise { + let stdout = ""; + let stderr = ""; + try { + const result = await execFile(command, ["--version"], { cwd, shell: false }); + stdout = String(result.stdout ?? ""); + stderr = String(result.stderr ?? ""); + } catch (error) { + const detail = errorMessage(error); + throw new Error(`Pi executable preflight failed for ${command}: ${detail}`); + } - signal?.addEventListener("abort", onAbort, { once: true }); - }); + const version = parsePiVersion(`${stdout}\n${stderr}`); + if (!version || !isSupportedVersion(version)) { + throw new Error( + `Unsupported Pi executable for ${command}: reported ${version ?? "no numeric version"}; supported range is >=${SUPPORTED_PI_MIN_VERSION} <${SUPPORTED_PI_MAX_MAJOR_MINOR}`, + ); + } + return { command, version }; } function writePromptToTempFile( @@ -188,179 +183,290 @@ function writePromptToTempFile( return { dir: tmpDir, filePath }; } -function getFinalOutput(messages: Message[]): string { - for (let i = messages.length - 1; i >= 0; i--) { - const msg = messages[i]; - if (msg.role === "assistant") { - for (const part of msg.content) { - if (part.type === "text") return part.text; - } - } - } - return ""; +function getTextFromMessage(message: unknown): string { + if (!message || typeof message !== "object") return ""; + const candidate = message as { role?: string; content?: unknown }; + if (candidate.role !== "assistant" || !Array.isArray(candidate.content)) return ""; + return candidate.content + .filter((part): part is { type: "text"; text: string } => { + return Boolean( + part && typeof part === "object" && (part as { type?: string }).type === "text", + ); + }) + .map((part) => part.text) + .join(""); } -export async function runAgent(input: AgentRunInput): Promise { - const args: string[] = [ - "--mode", - "json", - "-p", - "--no-session", - "--no-extensions", - "--no-skills", - "--no-prompt-templates", - ]; - if (input.allowedExtensions) { - for (const ext of input.allowedExtensions) { - args.push("-e", ext); - } +function deepEqual(left: unknown, right: unknown): boolean { + if (Object.is(left, right)) return true; + if (typeof left !== typeof right || left === null || right === null) return false; + if (Array.isArray(left) || Array.isArray(right)) { + if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) return false; + return left.every((item, index) => deepEqual(item, right[index])); } - if (input.model) args.push("--model", input.model); - if (input.tools && input.tools.length > 0) args.push("--tools", input.tools.join(",")); + if (typeof left !== "object" || typeof right !== "object") return false; + const leftRecord = left as Record; + const rightRecord = right as Record; + const leftKeys = Object.keys(leftRecord).sort(); + const rightKeys = Object.keys(rightRecord).sort(); + return ( + leftKeys.length === rightKeys.length && + leftKeys.every( + (key, index) => key === rightKeys[index] && deepEqual(leftRecord[key], rightRecord[key]), + ) + ); +} - let tmpPromptDir: string | null = null; - let tmpPromptPath: string | null = null; +export interface SelectedStructuredToolResult { + execution: RpcToolExecution; + params: unknown; +} - if (input.systemPrompt.trim()) { - const tmp = writePromptToTempFile(input.name, input.systemPrompt); - tmpPromptDir = tmp.dir; - tmpPromptPath = tmp.filePath; - args.push("--append-system-prompt", tmpPromptPath); +export function selectStructuredToolResult( + result: Pick, + expectedToolName: string, +): SelectedStructuredToolResult { + const candidates = result.executions.filter( + (execution) => + execution.name === expectedToolName && + execution.endedAt !== undefined && + execution.isError === false, + ); + if (candidates.length === 0) { + throw new Error(`No successful terminal ${expectedToolName} execution was recorded`); + } + if (candidates.length > 1) { + throw new Error( + `Ambiguous ${expectedToolName} result: ${candidates.length} successful executions`, + ); } - args.push(input.task); + const execution = candidates[0]; + const details = execution.result; + const detailsParams = + details && typeof details === "object" && "details" in details + ? (details as { details?: { params?: unknown } }).details?.params + : undefined; + if (detailsParams !== undefined && !deepEqual(detailsParams, execution.attemptedArgs)) { + throw new Error( + `${expectedToolName} execution arguments differ from its terminal result details`, + ); + } + return { execution, params: detailsParams ?? execution.attemptedArgs }; +} - const messages: Message[] = []; - const toolCalls: ToolCallCapture[] = []; - let stderr = ""; - let assistantError = ""; +export class PiRpcProcess { + private proc: ChildProcessWithoutNullStreams | null = null; + private stdoutBuffer = ""; + private stdoutDecoder = new StringDecoder("utf8"); + private stderrDecoder = new StringDecoder("utf8"); + private stderr = ""; + private pending = new Map(); + private currentRun: RpcRunState | null = null; + private lastRunResult: RpcRunResult | null = null; + private lastToolCalls: ToolCallCapture[] = []; + private commandCounter = 0; + private ready = false; + private disposed = false; + private closing = false; + private startupPromise: Promise | null = null; + private abortPromise: Promise | null = null; + private tmpPromptDir: string | null = null; + private tmpPromptPath: string | null = null; + private options: Required< + Pick< + RpcAgentOptions, + | "piCommand" + | "startupTimeoutMs" + | "commandTimeoutMs" + | "runTimeoutMs" + | "abortGraceMs" + | "killGraceMs" + | "maxRecordBytes" + | "maxStderrBytes" + > + > & + RpcAgentOptions; - const exitCode = await new Promise((resolve) => { - const proc = spawn("pi", args, { - cwd: input.cwd, - shell: false, - stdio: ["ignore", "pipe", "pipe"], - }); + constructor(options: RpcAgentOptions) { + this.options = { + ...options, + piCommand: options.piCommand ?? "pi", + startupTimeoutMs: options.startupTimeoutMs ?? DEFAULT_RPC_TIMEOUTS.startupTimeoutMs, + commandTimeoutMs: options.commandTimeoutMs ?? DEFAULT_RPC_TIMEOUTS.commandTimeoutMs, + runTimeoutMs: options.runTimeoutMs ?? DEFAULT_RPC_TIMEOUTS.runTimeoutMs, + abortGraceMs: options.abortGraceMs ?? DEFAULT_RPC_TIMEOUTS.abortGraceMs, + killGraceMs: options.killGraceMs ?? DEFAULT_RPC_TIMEOUTS.killGraceMs, + maxRecordBytes: options.maxRecordBytes ?? DEFAULT_RPC_TIMEOUTS.maxRecordBytes, + maxStderrBytes: options.maxStderrBytes ?? DEFAULT_RPC_TIMEOUTS.maxStderrBytes, + }; + } - let buffer = ""; + getLastToolCalls(): ToolCallCapture[] { + return this.lastToolCalls.map((call) => ({ ...call })); + } - const processLine = (line: string) => { - if (!line.trim()) return; - let event: any; - try { - event = JSON.parse(line); - } catch { - return; - } + getLastRunResult(): RpcRunResult | null { + return this.lastRunResult; + } - if (event.type === "message_update" && event.assistantMessageEvent?.type === "text_delta") { - input.onUpdate?.({ type: "text_delta", delta: event.assistantMessageEvent.delta ?? "" }); - } + getStderr(): string { + return this.stderr; + } - if (event.type === "tool_execution_start") { - // Capture tool call arguments for structured output - toolCalls.push({ name: event.toolName, arguments: event.args ?? {} }); - input.onUpdate?.({ type: "tool_start", toolName: event.toolName, args: event.args }); - } + isRunning(): boolean { + return this.currentRun !== null; + } - if (event.type === "tool_execution_update") { - input.onUpdate?.({ - type: "tool_update", - toolName: event.toolName, - partialResult: event.partialResult, - }); - } + async start(): Promise { + if (this.ready && this.proc) return; + if (this.startupPromise) return this.startupPromise; + if (this.disposed) throw new Error("RPC process has been disposed"); + + this.startupPromise = (async () => { + this.spawnProcess(); + await this.sendCommand({ type: "get_state" }, "get_state", this.options.startupTimeoutMs); + this.ready = true; + this.cleanupPrompt(); + })(); + try { + await this.startupPromise; + } catch (error) { + this.startupPromise = null; + this.terminateProcess(); + throw error; + } + this.startupPromise = null; + } - if (event.type === "tool_execution_end") { - input.onUpdate?.({ type: "tool_end", toolName: event.toolName, isError: event.isError }); - } + async runPrompt(message: string, options?: RpcRunOptions): Promise { + await this.start(); + if (this.currentRun) throw new Error("Agent already running"); + if (!this.proc || !this.ready) throw new Error("RPC process is not ready"); + if (options?.signal?.aborted) throw new Error("Aborted"); + + const result = new Promise((resolve, reject) => { + let resolvePromptAccepted!: () => void; + let rejectPromptAccepted!: (error: Error) => void; + const promptAcceptedPromise = new Promise((resolveAccepted, rejectAccepted) => { + resolvePromptAccepted = resolveAccepted; + rejectPromptAccepted = rejectAccepted; + }); + void promptAcceptedPromise.catch(() => {}); + const run: RpcRunState = { + resolve, + reject, + promptAccepted: false, + lifecycle: "prompt_pending", + outputText: "", + executions: new Map(), + lifecycleEvents: [], + promptAcceptedPromise, + resolvePromptAccepted, + rejectPromptAccepted, + retrySucceeded: false, + retryCount: 0, + turnCount: 0, + abortRequested: false, + signal: options?.signal, + onUpdate: options?.onUpdate, + settled: false, + }; + this.currentRun = run; - if (event.type === "message_end" && event.message) { - const msg = event.message as Message; - messages.push(msg); - if (event.message.stopReason === "error" && event.message.errorMessage) { - assistantError = event.message.errorMessage; - } - } - if (event.type === "tool_result_end" && event.message) { - const msg = event.message as Message; - messages.push(msg); + const finishAbort = () => { + if (!this.currentRun || this.currentRun !== run) return; + run.abortRequested = true; + void this.abort(); + }; + if (options?.signal) { + run.onAbort = finishAbort; + options.signal.addEventListener("abort", finishAbort, { once: true }); } - }; - - proc.stdout.on("data", (data) => { - buffer += data.toString(); - const lines = buffer.split("\n"); - buffer = lines.pop() || ""; - for (const line of lines) processLine(line); + run.timeout = setTimeout(() => { + this.failRun( + new Error( + `RPC run timed out: session=${this.options.sessionFile}, lastEvent=${run.lifecycleEvents.at(-1) ?? "none"}, stderr=${this.boundedStderr()}`, + ), + true, + ); + }, this.options.runTimeoutMs); + + void this.sendCommand( + { type: "prompt", message }, + "prompt", + this.options.commandTimeoutMs, + ).then( + () => { + if (this.currentRun !== run || run.settled) return; + if (!run.promptAccepted) { + run.promptAccepted = true; + run.lifecycle = "running"; + run.lifecycleEvents.push("prompt_accepted"); + run.resolvePromptAccepted(); + } + }, + (error: Error) => this.failRun(error), + ); }); - proc.stderr.on("data", (data) => { - stderr += data.toString(); - }); - - proc.on("close", (code) => { - if (buffer.trim()) processLine(buffer); - resolve(code ?? 0); - }); - - proc.on("error", () => resolve(1)); + return result; + } - if (input.signal) { - const killProc = () => { - proc.kill("SIGTERM"); - setTimeout(() => { - if (!proc.killed) proc.kill("SIGKILL"); - }, 5000); - }; - if (input.signal.aborted) killProc(); - else input.signal.addEventListener("abort", killProc, { once: true }); + async sendSteer(message: string): Promise { + const run = this.currentRun; + if (!run) { + throw new Error("Cannot steer an RPC process without an accepted prompt"); } - }); - - const outputText = getFinalOutput(messages); + if (!run.promptAccepted) await run.promptAcceptedPromise; + if (this.currentRun !== run || !run.promptAccepted) { + throw new Error("Cannot steer an RPC process without an accepted prompt"); + } + await this.sendCommand({ type: "steer", message }, "steer"); + } - if (tmpPromptPath) + async abort(): Promise { + if (this.abortPromise) return this.abortPromise; + this.abortPromise = this.abortProcess(); try { - fs.unlinkSync(tmpPromptPath); - } catch { - /* ignore */ + await this.abortPromise; + } finally { + this.abortPromise = null; } - if (tmpPromptDir) + } + + private async abortProcess(): Promise { + if (!this.proc) return; + if (this.currentRun) this.currentRun.abortRequested = true; try { - fs.rmdirSync(tmpPromptDir); - } catch { - /* ignore */ + await this.sendCommand({ type: "abort" }, "abort", this.options.abortGraceMs); + } catch (error) { + if (this.currentRun) + this.currentRun.lifecycleEvents.push(`abort_error:${errorMessage(error)}`); + this.terminateProcess(); + return; } - return { outputText, messages, stderr: assistantError || stderr, exitCode, toolCalls }; -} - -export class RpcAgent { - private proc: ChildProcessWithoutNullStreams | null = null; - private buffer = ""; - private stderr = ""; - private currentRun: RpcRunState | null = null; - private lastToolCalls: ToolCallCapture[] = []; - private options: RpcAgentOptions; - private retry: AgentRetryOptions; - private tmpPromptDir: string | null = null; - private tmpPromptPath: string | null = null; - - constructor(options: RpcAgentOptions) { - this.options = options; - this.retry = normalizeAgentRetryOptions(options.retry); + if (this.currentRun) { + const run = this.currentRun; + if (run.abortTimeout) clearTimeout(run.abortTimeout); + run.abortTimeout = setTimeout(() => { + if (this.currentRun === run && !run.settled) this.terminateProcess(); + }, this.options.abortGraceMs); + } } - getLastToolCalls(): ToolCallCapture[] { - return this.lastToolCalls; + dispose(): void { + if (this.disposed) return; + this.disposed = true; + this.closing = true; + this.failRun(new Error("RPC process disposed")); + this.rejectPending(new Error("RPC process disposed")); + this.terminateProcess(); + this.cleanupPrompt(); } - start(): void { - if (this.proc) return; - this.buffer = ""; - this.stderr = ""; - + private spawnProcess(): void { const args: string[] = [ "--mode", "rpc", @@ -370,16 +476,9 @@ export class RpcAgent { "--no-skills", "--no-prompt-templates", ]; - - if (this.options.allowedExtensions) { - for (const ext of this.options.allowedExtensions) { - args.push("-e", ext); - } - } - + for (const ext of this.options.allowedExtensions ?? []) args.push("-e", ext); if (this.options.model) args.push("--model", this.options.model); - if (this.options.tools && this.options.tools.length > 0) - args.push("--tools", this.options.tools.join(",")); + if (this.options.tools?.length) args.push("--tools", this.options.tools.join(",")); if (this.options.systemPrompt.trim()) { const tmp = writePromptToTempFile("agent", this.options.systemPrompt); @@ -388,225 +487,519 @@ export class RpcAgent { args.push("--append-system-prompt", tmp.filePath); } - this.proc = spawn("pi", args, { - cwd: this.options.cwd, - shell: false, - stdio: ["pipe", "pipe", "pipe"], - }); - - this.proc.stdout.on("data", (data) => this.onData(data.toString())); - this.proc.stderr.on("data", (data) => (this.stderr += data.toString())); - this.proc.on("close", () => this.handleClose()); - } + let proc: ChildProcessWithoutNullStreams; + try { + proc = spawn(this.options.piCommand, args, { + cwd: this.options.cwd, + shell: false, + stdio: ["pipe", "pipe", "pipe"], + }); + } catch (error) { + this.cleanupPrompt(); + throw new Error( + `Failed to spawn Pi executable ${this.options.piCommand}: ${errorMessage(error)}`, + ); + } + this.proc = proc; + this.ready = false; + this.closing = false; + this.stdoutBuffer = ""; + this.stdoutDecoder = new StringDecoder("utf8"); + this.stderrDecoder = new StringDecoder("utf8"); + this.stderr = ""; - isRunning(): boolean { - return Boolean(this.currentRun); + proc.stdout.on("data", (chunk: Buffer | string) => this.onStdout(chunk)); + proc.stderr.on("data", (chunk: Buffer | string) => this.onStderr(chunk)); + proc.stdin.on("error", (error) => + this.handleProcessError(new Error(`Pi stdin error: ${error.message}`)), + ); + proc.stdout.on("error", (error) => + this.handleProcessError(new Error(`Pi stdout error: ${error.message}`)), + ); + proc.stderr.on("error", (error) => + this.handleProcessError(new Error(`Pi stderr error: ${error.message}`)), + ); + proc.on("error", (error) => + this.handleProcessError(new Error(`Pi process error: ${error.message}`)), + ); + proc.on("exit", (code, signal) => { + if (this.currentRun) + this.currentRun.lifecycleEvents.push(`exit:${code ?? "null"}:${signal ?? "null"}`); + }); + proc.on("close", (code, signal) => this.handleClose(code, signal)); } - async runPrompt(message: string, options?: RpcRunOptions): Promise { - let failedAttempts = 0; - - while (true) { + private sendCommand( + payload: Record, + command: string, + timeoutMs = this.options.commandTimeoutMs, + ): Promise { + if (!this.proc || this.closing) return Promise.reject(new Error("RPC process is not running")); + const id = `piorch-${++this.commandCounter}`; + const body = { id, ...payload }; + return new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + this.pending.delete(id); + const error = new Error(`RPC command timed out: ${command} (${id})`); + reject(error); + if (command === "prompt" || command === "get_state") this.failRun(error, true); + }, timeoutMs); + this.pending.set(id, { id, command, resolve, reject, timeout }); try { - return await this.runPromptOnce(message, options); + this.proc?.stdin.write(`${JSON.stringify(body)}\n`); } catch (error) { - failedAttempts += 1; - if ( - options?.signal?.aborted || - failedAttempts >= this.retry.maxAttempts || - !isRetryableAgentError(error) - ) { - throw error; - } - - this.dispose(); - const delayMs = getAgentRetryDelayMs(failedAttempts, this.retry, error); - await waitForRetry(delayMs, options?.signal); - } - } - } - - private async runPromptOnce(message: string, options?: RpcRunOptions): Promise { - this.start(); - if (this.currentRun) throw new Error("Agent already running"); - - return new Promise((resolve, reject) => { - let cleanup = () => {}; - const finishResolve = (value: string) => { - cleanup(); - resolve(value); - }; - const finishReject = (error: Error) => { - cleanup(); - reject(error); - }; - - this.currentRun = { - resolve: finishResolve, - reject: finishReject, - lastAssistantText: "", - toolCalls: [], - onUpdate: options?.onUpdate, - }; - - if (options?.signal) { - const onAbort = () => { - const run = this.currentRun; - if (!run) return; - run.aborted = true; - this.currentRun = null; - this.abort(); - finishReject(new Error("Aborted")); - }; - cleanup = () => options.signal?.removeEventListener("abort", onAbort); - if (options.signal.aborted) { - onAbort(); - return; - } - options.signal.addEventListener("abort", onAbort, { once: true }); + clearTimeout(timeout); + this.pending.delete(id); + reject(new Error(`Failed to write RPC command ${command}: ${errorMessage(error)}`)); } - - this.send({ type: "prompt", message }); }); } - sendSteer(message: string): void { - this.start(); - const command = this.currentRun - ? { type: "prompt", message, streamingBehavior: "steer" } - : { type: "prompt", message }; - this.send(command); - } - - abort(): void { - if (!this.proc) return; - this.send({ type: "abort" }); - } - - dispose(): void { - if (!this.proc) return; - this.proc.kill("SIGTERM"); - this.proc = null; - this.cleanupPrompt(); + private onStdout(chunk: Buffer | string): void { + const decoded = this.stdoutDecoder.write( + typeof chunk === "string" ? Buffer.from(chunk) : chunk, + ); + this.stdoutBuffer += decoded; + let newlineIndex = this.stdoutBuffer.indexOf("\n"); + while (newlineIndex !== -1) { + const record = this.stdoutBuffer.slice(0, newlineIndex); + this.stdoutBuffer = this.stdoutBuffer.slice(newlineIndex + 1); + if (Buffer.byteLength(record) > this.options.maxRecordBytes) { + this.protocolFailure(`RPC JSONL record exceeds ${this.options.maxRecordBytes} bytes`); + return; + } + this.processRecord(record.endsWith("\r") ? record.slice(0, -1) : record); + if (this.disposed) return; + newlineIndex = this.stdoutBuffer.indexOf("\n"); + } + if (Buffer.byteLength(this.stdoutBuffer) > this.options.maxRecordBytes) { + this.protocolFailure(`RPC JSONL record exceeds ${this.options.maxRecordBytes} bytes`); + } } - private send(payload: Record): void { - if (!this.proc?.stdin) return; - this.proc.stdin.write(`${JSON.stringify(payload)}\n`); + private onStderr(chunk: Buffer | string): void { + const decoded = this.stderrDecoder.write( + typeof chunk === "string" ? Buffer.from(chunk) : chunk, + ); + this.stderr = `${this.stderr}${decoded}`.slice(-this.options.maxStderrBytes); } - private onData(chunk: string): void { - this.buffer += chunk; - const lines = this.buffer.split("\n"); - this.buffer = lines.pop() || ""; - for (const line of lines) this.processLine(line); + private processRecord(record: string): void { + if (!record.trim()) return; + let event: unknown; + try { + event = JSON.parse(record); + } catch (error) { + this.protocolFailure(`Malformed RPC JSONL record: ${errorMessage(error)}`); + return; + } + this.processEvent(event as Record); } - private processLine(line: string): void { - if (!line.trim()) return; - let event: any; - try { - event = JSON.parse(line); - } catch { + private processEvent(event: Record): void { + const type = event.type; + if (type === "response") { + this.processResponse(event); return; } - if (event.type === "message_update" && event.assistantMessageEvent?.type === "text_delta") { - this.currentRun?.onUpdate?.({ - type: "text_delta", - delta: event.assistantMessageEvent.delta ?? "", - }); + const run = this.currentRun; + if (type === "message_update") { + const assistantEvent = event.assistantMessageEvent as + | { type?: string; delta?: string } + | undefined; + if (assistantEvent?.type === "text_delta") { + const delta = assistantEvent.delta ?? ""; + if (run) { + run.outputText += delta; + run.onUpdate?.({ type: "text_delta", delta }); + } + } + return; + } + if (type === "message_end") { + const text = getTextFromMessage(event.message); + if (run && text) run.outputText = text; + const message = event.message as + | { stopReason?: string; errorMessage?: string; usage?: unknown } + | undefined; + if (run && message?.usage !== undefined) run.usage = message.usage; + if (run && message?.stopReason === "error" && message.errorMessage) + run.assistantError = message.errorMessage; + return; + } + if (!run) { + if ( + type === "queue_update" || + type === "entry_appended" || + type === "session_info_changed" || + type === "extension_ui_request" + ) { + return; + } + if (type === "agent_settled") return; + this.protocolFailure(`RPC event ${String(type)} arrived without an active prompt`); + return; } - if (event.type === "tool_execution_start") { - // Capture tool call arguments for structured output - this.currentRun?.toolCalls.push({ name: event.toolName, arguments: event.args ?? {} }); - this.currentRun?.onUpdate?.({ - type: "tool_start", - toolName: event.toolName, - args: event.args, + if (type === "agent_start") { + if (!run.promptAccepted || run.lifecycle === "prompt_pending") { + this.protocolFailure("agent_start arrived before prompt acceptance"); + return; + } + run.lifecycle = "running"; + run.turnCount += 1; + run.lifecycleEvents.push("agent_start"); + return; + } + if (type === "agent_end") { + if (!run.promptAccepted || run.lifecycle === "prompt_pending") { + this.protocolFailure("agent_end arrived before prompt acceptance"); + return; + } + const messages = Array.isArray(event.messages) ? event.messages : []; + for (const message of messages) { + const text = getTextFromMessage(message); + if (text) run.outputText = text; + const candidate = message as { + stopReason?: string; + errorMessage?: string; + usage?: unknown; + }; + if (candidate.usage !== undefined) run.usage = candidate.usage; + if (candidate.stopReason === "error" && candidate.errorMessage) + run.assistantError = candidate.errorMessage; + } + run.lifecycle = "settling"; + run.lifecycleEvents.push(`agent_end:${event.willRetry === true ? "retry" : "done"}`); + return; + } + if (type === "agent_settled") { + if (!run.promptAccepted || run.lifecycle === "prompt_pending") { + this.protocolFailure("agent_settled arrived before prompt acceptance"); + return; + } + if ([...run.executions.values()].some((execution) => execution.endedAt === undefined)) { + this.protocolFailure("agent_settled arrived with unfinished tool execution"); + return; + } + this.finishRun(run); + return; + } + if (type === "auto_retry_start") { + run.retryCount += 1; + run.lifecycleEvents.push("auto_retry_start"); + run.onUpdate?.({ + type: "retry", + attempt: Number(event.attempt ?? 0), + maxAttempts: Number(event.maxAttempts ?? 0), + delayMs: Number(event.delayMs ?? 0), + errorMessage: String(event.errorMessage ?? ""), }); + return; } - - if (event.type === "tool_execution_update") { - this.currentRun?.onUpdate?.({ + if (type === "auto_retry_end") { + const success = event.success === true; + if (success) { + run.assistantError = undefined; + run.retrySucceeded = true; + } + run.lifecycleEvents.push(`auto_retry_end:${success ? "success" : "failure"}`); + return; + } + if (type === "tool_execution_start") { + const toolCallId = event.toolCallId; + const toolName = event.toolName; + if ( + typeof toolCallId !== "string" || + !toolCallId || + typeof toolName !== "string" || + !toolName + ) { + this.protocolFailure("tool_execution_start is missing toolCallId or toolName"); + return; + } + if (run.executions.has(toolCallId)) { + this.protocolFailure(`duplicate tool execution start: ${toolCallId}`); + return; + } + const execution: RpcToolExecution = { + toolCallId, + name: toolName, + attemptedArgs: event.args, + startedAt: Date.now(), + }; + run.executions.set(toolCallId, execution); + run.onUpdate?.({ type: "tool_start", toolCallId, toolName, args: event.args }); + return; + } + if (type === "tool_execution_update") { + const toolCallId = event.toolCallId; + const execution = typeof toolCallId === "string" ? run.executions.get(toolCallId) : undefined; + if (!execution || execution.endedAt !== undefined) { + this.protocolFailure(`tool execution update without active start: ${String(toolCallId)}`); + return; + } + run.onUpdate?.({ type: "tool_update", - toolName: event.toolName, + toolCallId: execution.toolCallId, + toolName: execution.name, partialResult: event.partialResult, }); + return; } - - if (event.type === "tool_execution_end") { - this.currentRun?.onUpdate?.({ + if (type === "tool_execution_end") { + const toolCallId = event.toolCallId; + const execution = typeof toolCallId === "string" ? run.executions.get(toolCallId) : undefined; + if (!execution) { + this.protocolFailure(`tool execution end without start: ${String(toolCallId)}`); + return; + } + if (execution.endedAt !== undefined) { + this.protocolFailure(`duplicate tool execution end: ${toolCallId}`); + return; + } + if (event.toolName !== execution.name) { + this.protocolFailure(`tool execution name changed for ${toolCallId}`); + return; + } + if (typeof event.isError !== "boolean") { + this.protocolFailure(`tool execution end is missing boolean isError for ${toolCallId}`); + return; + } + execution.endedAt = Date.now(); + execution.isError = event.isError === true; + execution.result = event.result; + run.onUpdate?.({ type: "tool_end", - toolName: event.toolName, - isError: event.isError, + toolCallId: execution.toolCallId, + toolName: execution.name, + isError: execution.isError, }); + return; } + if ( + type === "turn_start" || + type === "turn_end" || + type === "queue_update" || + type === "compaction_start" || + type === "compaction_end" || + type === "entry_appended" || + type === "session_info_changed" || + type === "thinking_level_changed" || + type === "extension_ui_request" + ) { + run.lifecycleEvents.push(String(type)); + return; + } + this.protocolFailure(`Unknown RPC event type: ${String(type)}`); + } - if (event.type === "message_end" && event.message?.role === "assistant") { - const run = this.currentRun; - if (!run) return; // Race condition: agent_end may have cleared currentRun - - if (event.message.stopReason === "error" && event.message.errorMessage) { - this.lastToolCalls = [...run.toolCalls]; - this.currentRun = null; - run.reject(new Error(event.message.errorMessage)); - return; - } - - const msg = event.message as Message; - for (const part of msg.content) { - if (typeof part === "string") { - run.lastAssistantText = part; - } else if (part.type === "text") { - run.lastAssistantText = part.text; - } else if (part.type === "toolCall") { - // Capture tool call from message content - run.toolCalls.push({ name: part.name, arguments: part.arguments }); - run.onUpdate?.({ - type: "tool_start", - toolName: part.name, - args: part.arguments, - }); - } - } + private processResponse(event: Record): void { + const id = event.id; + if (typeof id !== "string") { + this.protocolFailure("RPC response is missing id"); + return; } + const pending = this.pending.get(id); + if (!pending) { + this.protocolFailure(`RPC response has unknown or duplicate id: ${id}`); + return; + } + this.pending.delete(id); + clearTimeout(pending.timeout); + if (event.command !== pending.command) { + this.protocolFailure(`RPC response command mismatch for ${id}`); + return; + } + if (event.success !== true) { + pending.reject(new Error(String(event.error ?? `Pi rejected ${pending.command}`))); + return; + } + if (pending.command === "prompt" && this.currentRun && !this.currentRun.promptAccepted) { + this.currentRun.promptAccepted = true; + this.currentRun.lifecycle = "running"; + this.currentRun.lifecycleEvents.push("prompt_accepted"); + this.currentRun.resolvePromptAccepted(); + } + pending.resolve(event.data); + } - if (event.type === "agent_end") { - const run = this.currentRun; - if (!run) return; - // Save tool calls before clearing currentRun - this.lastToolCalls = [...run.toolCalls]; - this.currentRun = null; - if (run.aborted) return; - run.resolve(run.lastAssistantText || ""); + private finishRun(run: RpcRunState): void { + if (run.settled || this.currentRun !== run) return; + run.settled = true; + this.clearRunTimers(run); + if (run.signal && run.onAbort) run.signal.removeEventListener("abort", run.onAbort); + const executions = [...run.executions.values()]; + const successfulToolExecutions = executions.filter( + (execution) => execution.endedAt !== undefined && execution.isError === false, + ); + const failedToolExecutions = executions.filter( + (execution) => execution.endedAt !== undefined && execution.isError === true, + ); + const result: RpcRunResult = { + outputText: run.outputText, + executions, + successfulToolExecutions, + failedToolExecutions, + stderr: this.boundedStderr(), + lifecycleEvents: [...run.lifecycleEvents, "agent_settled"], + usage: run.usage, + metrics: { + turns: run.turnCount, + retries: run.retryCount, + successfulToolExecutions: successfulToolExecutions.length, + failedToolExecutions: failedToolExecutions.length, + }, + }; + this.lastRunResult = result; + this.lastToolCalls = executions.map((execution) => ({ + name: execution.name, + arguments: (execution.attemptedArgs ?? {}) as Record, + toolCallId: execution.toolCallId, + isError: execution.isError, + })); + this.currentRun = null; + run.resolvePromptAccepted(); + if (run.abortRequested) { + run.reject(new Error("Aborted")); + return; + } + if (run.assistantError && !run.retrySucceeded) { + run.reject(new Error(run.assistantError)); + return; } + run.resolve(result); } - private handleClose(): void { - if (this.currentRun) { - this.currentRun.reject(new Error(this.stderr || "RPC agent terminated")); - this.currentRun = null; + private failRun(error: Error, terminate = false): void { + const run = this.currentRun; + if (!run || run.settled) { + if (terminate) this.terminateProcess(); + return; } + run.settled = true; + this.clearRunTimers(run); + if (run.signal && run.onAbort) run.signal.removeEventListener("abort", run.onAbort); + const executions = [...run.executions.values()]; + const successfulToolExecutions = executions.filter( + (execution) => execution.endedAt !== undefined && execution.isError === false, + ); + const failedToolExecutions = executions.filter( + (execution) => execution.endedAt !== undefined && execution.isError === true, + ); + this.lastRunResult = { + outputText: run.outputText, + executions, + successfulToolExecutions, + failedToolExecutions, + stderr: this.boundedStderr(), + lifecycleEvents: [...run.lifecycleEvents, `failed:${error.message}`], + usage: run.usage, + metrics: { + turns: run.turnCount, + retries: run.retryCount, + successfulToolExecutions: successfulToolExecutions.length, + failedToolExecutions: failedToolExecutions.length, + }, + }; + this.lastToolCalls = executions.map((execution) => ({ + name: execution.name, + arguments: (execution.attemptedArgs ?? {}) as Record, + toolCallId: execution.toolCallId, + isError: execution.isError, + })); + this.currentRun = null; + run.rejectPromptAccepted(error); + run.reject(error); + if (terminate) this.terminateProcess(); + } + + private protocolFailure(message: string): void { + const error = new Error(`RPC protocol error: ${message}`); + this.failRun(error, true); + this.rejectPending(error); + this.disposed = true; + } + + private handleProcessError(error: Error): void { + this.failRun(error, true); + this.rejectPending(error); + } + + private handleClose(code: number | null, signal: string | null): void { + const tail = this.stdoutDecoder.end(); + this.stdoutBuffer += tail; + if (this.stdoutBuffer.trim()) this.protocolFailure("partial final RPC JSONL record"); + this.stderr += this.stderrDecoder.end(); + const error = new Error( + `${this.options.piCommand} exited before RPC completion (code=${code ?? "null"}, signal=${signal ?? "null"})${this.stderr ? `: ${this.boundedStderr()}` : ""}`, + ); + this.failRun(error); + this.rejectPending(error); this.proc = null; + this.ready = false; + this.startupPromise = null; this.cleanupPrompt(); } + private rejectPending(error: Error): void { + for (const pending of this.pending.values()) { + clearTimeout(pending.timeout); + pending.reject(error); + } + this.pending.clear(); + } + + private terminateProcess(): void { + const proc = this.proc; + if (!proc) return; + this.closing = true; + try { + proc.kill("SIGTERM"); + } catch { + /* process already gone */ + } + setTimeout(() => { + if (this.proc !== proc) return; + try { + proc.kill("SIGKILL"); + } catch { + /* process already gone */ + } + }, this.options.killGraceMs); + } + + private clearRunTimers(run: RpcRunState): void { + if (run.timeout) clearTimeout(run.timeout); + if (run.abortTimeout) clearTimeout(run.abortTimeout); + run.timeout = undefined; + run.abortTimeout = undefined; + } + + private boundedStderr(): string { + return this.stderr.slice(-this.options.maxStderrBytes); + } + private cleanupPrompt(): void { - if (this.tmpPromptPath) + if (this.tmpPromptPath) { try { fs.unlinkSync(this.tmpPromptPath); } catch { /* ignore */ } - if (this.tmpPromptDir) + } + if (this.tmpPromptDir) { try { fs.rmdirSync(this.tmpPromptDir); } catch { /* ignore */ } + } this.tmpPromptPath = null; this.tmpPromptDir = null; } } + +/** Backwards-compatible name retained for existing extension consumers. */ +export class RpcAgent extends PiRpcProcess {} diff --git a/.pi/extensions/workflow-orchestrator/setup.ts b/.pi/extensions/workflow-orchestrator/setup.ts new file mode 100644 index 0000000..3213fd2 --- /dev/null +++ b/.pi/extensions/workflow-orchestrator/setup.ts @@ -0,0 +1,91 @@ +import * as fs from "node:fs"; +import * as path from "node:path"; +import { fileURLToPath } from "node:url"; + +export const DEFAULT_WORKFLOW_NAME = "default"; + +export function getPackagePiRoot(): string { + const extensionDir = path.dirname(fileURLToPath(import.meta.url)); + return path.resolve(extensionDir, "..", ".."); +} + +export function resolveWorkflowPath(cwd: string, name: string): string { + const fileName = `${name}.workflow.json`; + const projectPath = path.join(cwd, ".pi", "workflows", fileName); + if (fs.existsSync(projectPath)) return projectPath; + + const packagePath = path.join(getPackagePiRoot(), "workflows", fileName); + if (fs.existsSync(packagePath)) return packagePath; + + throw new Error(`Workflow not found: ${name}`); +} + +export function resolveExtensionPath(cwd: string, extensionPath: string): string { + const normalized = extensionPath.replace(/^\.\//, ""); + const packageRelative = normalized.replace(/^\.pi[\\/]/, ""); + const packageRoot = getPackagePiRoot(); + const candidates = [ + path.resolve(cwd, extensionPath), + path.resolve(packageRoot, packageRelative), + path.resolve(packageRoot, "extensions", packageRelative), + ]; + + for (const candidate of candidates) { + if (fs.existsSync(candidate)) return candidate; + } + + return path.resolve(cwd, extensionPath); +} + +export function resolveExtensionPaths( + cwd: string, + extensionPaths: string[] | undefined, +): string[] | undefined { + if (!extensionPaths) return undefined; + return extensionPaths.map((extensionPath) => resolveExtensionPath(cwd, extensionPath)); +} + +function copyTreeMissing(src: string, dest: string): boolean { + if (!fs.existsSync(src)) return false; + + if (!fs.existsSync(dest)) { + fs.mkdirSync(path.dirname(dest), { recursive: true }); + fs.cpSync(src, dest, { recursive: true }); + return true; + } + + let srcStat: fs.Stats; + let destStat: fs.Stats; + try { + srcStat = fs.statSync(src); + destStat = fs.statSync(dest); + } catch { + return false; + } + + if (!srcStat.isDirectory() || !destStat.isDirectory()) return false; + + let copied = false; + for (const entry of fs.readdirSync(src)) { + copied = copyTreeMissing(path.join(src, entry), path.join(dest, entry)) || copied; + } + return copied; +} + +/** Copy editable defaults into the project without overwriting existing files. */ +export function materializeProjectDefaults(cwd: string): string[] { + const packagePiRoot = getPackagePiRoot(); + const created: string[] = []; + const copies = [ + { src: path.join(packagePiRoot, "workflows"), dest: path.join(cwd, ".pi", "workflows") }, + { src: path.join(packagePiRoot, "agents"), dest: path.join(cwd, ".pi", "agents") }, + ]; + + for (const { src, dest } of copies) { + if (copyTreeMissing(src, dest)) { + created.push(path.relative(cwd, dest)); + } + } + + return created; +} diff --git a/.pi/extensions/workflow-orchestrator/state.ts b/.pi/extensions/workflow-orchestrator/state.ts index d9dc3d6..675c0c9 100644 --- a/.pi/extensions/workflow-orchestrator/state.ts +++ b/.pi/extensions/workflow-orchestrator/state.ts @@ -1,20 +1,138 @@ -import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent"; +import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent"; import type { WorkflowTask, WorkflowWave } from "./config.js"; +import { Type } from "typebox"; +import { Check, Errors } from "typebox/value"; +import { + IdentifierSchema, + PriorWaveSummarySchema, + StageOutputSchema, + SemanticStageIdSchema, + WaveSchema, + type PriorWaveSummary, + type StageOutput, + type SemanticStageId, +} from "./contracts.js"; -export type TaskStatus = "pending" | "in_progress" | "verified" | "failed" | "stopped"; +export type TaskStatus = "pending" | "in_progress" | "stopping" | "verified" | "failed" | "stopped"; + +export type WorkflowStatus = + | "idle" + | "running" + | "waiting_for_clarification" + | "stopping" + | "completed" + | "exhausted" + | "failed" + | "stopped" + | "partial"; + +export const TaskStatusSchema = Type.Union([ + Type.Literal("pending"), + Type.Literal("in_progress"), + Type.Literal("stopping"), + Type.Literal("verified"), + Type.Literal("failed"), + Type.Literal("stopped"), +]); + +export const WorkflowStatusSchema = Type.Union([ + Type.Literal("idle"), + Type.Literal("running"), + Type.Literal("waiting_for_clarification"), + Type.Literal("stopping"), + Type.Literal("completed"), + Type.Literal("exhausted"), + Type.Literal("failed"), + Type.Literal("stopped"), + Type.Literal("partial"), +]); + +const StageOutputByIdSchema = Type.Partial( + Type.Object({ + develop: Type.Optional(StageOutputSchema), + verify: Type.Optional(StageOutputSchema), + }), +); + +export const TaskStateSchema = Type.Intersect([ + Type.Object({ + id: IdentifierSchema, + title: Type.String({ minLength: 1, maxLength: 1000 }), + description: Type.String({ minLength: 1, maxLength: 4000 }), + requirements: Type.String({ minLength: 1, maxLength: 4000 }), + }), + Type.Object({ + assignee: Type.Optional(Type.Literal("developer")), + status: TaskStatusSchema, + stageId: Type.Optional(SemanticStageIdSchema), + retries: Type.Integer({ minimum: 0 }), + issues: Type.Optional( + Type.Array(Type.String({ minLength: 1, maxLength: 4000 }), { maxItems: 100 }), + ), + stageOutputs: Type.Optional(StageOutputByIdSchema), + lastAgent: Type.Optional(Type.String({ maxLength: 256 })), + lastNote: Type.Optional(Type.String({ maxLength: 4000 })), + lastOutput: Type.Optional(Type.String({ maxLength: 4000 })), + lastActivityAt: Type.Optional(Type.Integer({ minimum: 0 })), + sessionFiles: Type.Optional( + Type.Partial( + Type.Object({ + develop: Type.Optional(Type.String({ minLength: 1, maxLength: 4096 })), + verify: Type.Optional(Type.String({ minLength: 1, maxLength: 4096 })), + }), + ), + ), + sessionResetCounts: Type.Optional( + Type.Partial( + Type.Object({ + develop: Type.Optional(Type.Integer({ minimum: 0 })), + verify: Type.Optional(Type.Integer({ minimum: 0 })), + }), + ), + ), + resumeMessage: Type.Optional(Type.String({ maxLength: 4000 })), + }), +]); + +export const WorkflowStateSchema = Type.Object({ + runId: IdentifierSchema, + workflowName: IdentifierSchema, + goal: Type.String({ minLength: 1, maxLength: 4000 }), + model: Type.Optional(Type.String({ minLength: 1, maxLength: 256 })), + status: WorkflowStatusSchema, + active: Type.Boolean(), + waveIndex: Type.Integer({ minimum: 0 }), + wave: Type.Optional(WaveSchema), + tasks: Type.Array(TaskStateSchema, { maxItems: 100 }), + updatedAt: Type.Integer({ minimum: 0 }), + allowedExtensions: Type.Optional(Type.Array(Type.String(), { maxItems: 100 })), + allowedExtensionsByAgent: Type.Optional( + Type.Partial( + Type.Object({ + pm: Type.Optional(Type.Array(Type.String(), { maxItems: 100 })), + developer: Type.Optional(Type.Array(Type.String(), { maxItems: 100 })), + verifier: Type.Optional(Type.Array(Type.String(), { maxItems: 100 })), + }), + ), + ), + previousSummary: Type.Optional(PriorWaveSummarySchema), + waveSummaries: Type.Optional(Type.Array(PriorWaveSummarySchema, { maxItems: 100 })), + waitingForClarification: Type.Optional(Type.Boolean()), + clarificationToken: Type.Optional(Type.String({ minLength: 1, maxLength: 256 })), +}); export interface TaskState extends WorkflowTask { status: TaskStatus; - stageId?: string; + stageId?: SemanticStageId; retries: number; issues?: string[]; - stageOutputs?: Record>; + stageOutputs?: Partial>; lastAgent?: string; lastNote?: string; lastOutput?: string; lastActivityAt?: number; - sessionFiles?: Record; - sessionResetCounts?: Record; + sessionFiles?: Partial>; + sessionResetCounts?: Partial>; resumeMessage?: string; } @@ -22,6 +140,9 @@ export interface WorkflowState { runId: string; workflowName: string; goal: string; + model?: string; + status: WorkflowStatus; + /** Compatibility field for older renderers. `status` is authoritative. */ active: boolean; waveIndex: number; wave?: WorkflowWave; @@ -33,24 +154,118 @@ export interface WorkflowState { developer?: string[]; verifier?: string[]; }; - previousSummary?: string; - waveSummaries?: string[]; + previousSummary?: PriorWaveSummary; + waveSummaries?: PriorWaveSummary[]; waitingForClarification?: boolean; clarificationToken?: string; } export const STATE_TYPE = "workflow-state"; +export function isWorkflowActive(status: WorkflowStatus): boolean { + return status === "running" || status === "waiting_for_clarification" || status === "stopping"; +} + +function migrateTask(value: unknown): TaskState | undefined { + if (!value || typeof value !== "object") return undefined; + const candidate = { ...(value as Record) }; + if (typeof candidate.requirements !== "string" || !candidate.requirements.trim()) { + candidate.requirements = "Verification requirements were not recorded in the saved state."; + } + if (candidate.status === undefined) candidate.status = "pending"; + if (!Number.isInteger(candidate.retries) || (candidate.retries as number) < 0) { + candidate.retries = 0; + } + if (candidate.stageId !== "develop" && candidate.stageId !== "verify") { + delete candidate.stageId; + } + if (candidate.assignee !== undefined && candidate.assignee !== "developer") { + delete candidate.assignee; + } + if (candidate.stageOutputs && typeof candidate.stageOutputs === "object") { + const outputs: Record = {}; + for (const [stageId, output] of Object.entries(candidate.stageOutputs)) { + if ((stageId === "develop" || stageId === "verify") && Check(StageOutputSchema, output)) { + outputs[stageId] = output as StageOutput; + } + } + candidate.stageOutputs = Object.keys(outputs).length > 0 ? outputs : undefined; + } + return candidate as unknown as TaskState; +} + +function migrateWave(value: unknown): WorkflowWave | undefined { + if (!value || typeof value !== "object") return undefined; + const candidate = value as { tasks?: unknown[] } & Record; + if (!Array.isArray(candidate.tasks)) return undefined; + const tasks = candidate.tasks.map(migrateTask); + if (tasks.some((task) => !task)) return undefined; + const wave = { ...candidate, tasks }; + return Check(WaveSchema, wave) ? (wave as WorkflowWave) : undefined; +} + +function migrateSummary(value: unknown, waveIndex: number): PriorWaveSummary | undefined { + if (Check(PriorWaveSummarySchema, value)) return value as PriorWaveSummary; + if (typeof value !== "string") return undefined; + return { + waveIndex, + goal: value.slice(0, 4000), + outcome: "partial", + tasks: [], + }; +} + export function appendState(pi: ExtensionAPI, state: WorkflowState): void { + if (!Check(WorkflowStateSchema, state)) { + const errors = [...Errors(WorkflowStateSchema, state)].map( + (error) => `${"path" in error && error.path ? error.path : "value"} ${error.message}`, + ); + throw new Error(`Workflow state validation failed: ${errors.join("; ")}`); + } pi.appendEntry(STATE_TYPE, state); } +function migrateState(data: unknown): WorkflowState | undefined { + if (!data || typeof data !== "object") return undefined; + const candidate = data as Partial & { active?: boolean }; + const status = candidate.status ?? (candidate.active ? "running" : "completed"); + const waveIndex = + Number.isInteger(candidate.waveIndex) && candidate.waveIndex! >= 0 ? candidate.waveIndex! : 0; + const tasks = Array.isArray(candidate.tasks) + ? candidate.tasks.map(migrateTask).filter((task): task is TaskState => task !== undefined) + : []; + const previousSummary = migrateSummary(candidate.previousSummary, waveIndex); + const waveSummaries = Array.isArray(candidate.waveSummaries) + ? candidate.waveSummaries + .map((summary, index) => migrateSummary(summary, index)) + .filter((summary): summary is PriorWaveSummary => summary !== undefined) + : undefined; + const migrated: WorkflowState = { + ...(candidate as WorkflowState), + status, + active: isWorkflowActive(status), + waveIndex, + wave: migrateWave(candidate.wave), + tasks, + previousSummary, + waveSummaries, + updatedAt: + Number.isInteger(candidate.updatedAt) && candidate.updatedAt! >= 0 + ? candidate.updatedAt! + : Date.now(), + }; + + if (!migrated.runId || !migrated.workflowName || !migrated.goal) return undefined; + if (!Check(WorkflowStateSchema, migrated)) return undefined; + return migrated; +} + export function restoreState(ctx: ExtensionContext): WorkflowState | undefined { const entries = ctx.sessionManager.getBranch(); for (let i = entries.length - 1; i >= 0; i--) { const entry = entries[i]; if (entry.type === "custom" && entry.customType === STATE_TYPE) { - return entry.data as WorkflowState; + return migrateState(entry.data); } } return undefined; diff --git a/.pi/extensions/workflow-orchestrator/utils.ts b/.pi/extensions/workflow-orchestrator/utils.ts index de43991..b00debb 100644 --- a/.pi/extensions/workflow-orchestrator/utils.ts +++ b/.pi/extensions/workflow-orchestrator/utils.ts @@ -15,13 +15,30 @@ export function extractJson(text: string): any { */ export function normalizeGoal(goal?: string): string | undefined { if (!goal) return undefined; - const trimmed = goal.trim(); + let trimmed = goal.trim(); if (!trimmed) return undefined; if ( (trimmed.startsWith('"') && trimmed.endsWith('"')) || (trimmed.startsWith("'") && trimmed.endsWith("'")) ) { - return trimmed.slice(1, -1).trim(); + trimmed = trimmed.slice(1, -1).trim(); + } else { + const first = trimmed[0]; + const last = trimmed[trimmed.length - 1]; + if ((first === '"' || first === "'") && !trimmed.slice(1).includes(first)) { + trimmed = trimmed.slice(1).trim(); + } + if ((last === '"' || last === "'") && !trimmed.slice(0, -1).includes(last)) { + trimmed = trimmed.slice(0, -1).trim(); + } + } + return trimmed || undefined; +} + +export function formatSubagentError(message: string): string { + const trimmed = message.trim(); + if (/model is unavailable|model not found|404/i.test(trimmed)) { + return `${trimmed}\nHint: pick a model in Pi (/model) or set model: in .pi/agents/*.md.`; } return trimmed; } diff --git a/.pi/extensions/workflow-pm-tools/index.ts b/.pi/extensions/workflow-pm-tools/index.ts index 6d08718..42022d2 100644 --- a/.pi/extensions/workflow-pm-tools/index.ts +++ b/.pi/extensions/workflow-pm-tools/index.ts @@ -4,62 +4,30 @@ * Provides the generate_wave tool for PM agents to generate task waves. */ -import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"; -import { Type } from "@sinclair/typebox"; +import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; +import { GenerateWaveSchema, validateGenerateWave } from "../workflow-orchestrator/contracts.js"; export default function (pi: ExtensionAPI): void { pi.registerTool({ name: "generate_wave", label: "Generate Wave", description: "Generate a new wave of tasks (for PM agent)", - parameters: Type.Object({ - done: Type.Boolean({ - description: "True if all work is complete, false if generating a new wave", - }), - wave: Type.Optional( - Type.Object( - { - goal: Type.String({ description: "Goal for this wave" }), - tasks: Type.Array( - Type.Object({ - id: Type.String({ description: "Unique task identifier" }), - title: Type.String({ description: "Short task title" }), - description: Type.String({ description: "Detailed instructions for developer" }), - requirements: Type.Optional( - Type.String({ description: "Verification requirements for verifier" }), - ), - assignee: Type.Optional( - Type.String({ description: "Task assignee (default: developer)" }), - ), - }), - { description: "Tasks in this wave" }, - ), - }, - { description: "Wave details (required when done=false)" }, - ), - ), - }), + parameters: GenerateWaveSchema, async execute(_toolCallId, params) { - if (params.done) { + const validated = validateGenerateWave(params); + if (validated.done) { return { content: [{ type: "text", text: "Project completion reported." }], - details: { params }, + details: { params: validated }, }; } - if (!params.wave) { - return { - content: [{ type: "text", text: "Error: wave is required when done=false" }], - details: { params }, - }; - } + const wave = validated.wave!; - const taskCount = params.wave.tasks.length; + const taskCount = wave.tasks.length; return { - content: [ - { type: "text", text: `Wave generated: "${params.wave.goal}" (${taskCount} tasks)` }, - ], - details: { params }, + content: [{ type: "text", text: `Wave generated: "${wave.goal}" (${taskCount} tasks)` }], + details: { params: validated }, }; }, }); diff --git a/.pi/extensions/workflow-task-tools/index.ts b/.pi/extensions/workflow-task-tools/index.ts index 54ef654..63eb880 100644 --- a/.pi/extensions/workflow-task-tools/index.ts +++ b/.pi/extensions/workflow-task-tools/index.ts @@ -5,55 +5,56 @@ * to report task completion. */ -import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"; -import { Type } from "@sinclair/typebox"; +import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; +import { Type } from "typebox"; +import { + DeveloperReportSchema, + VerifierReportSchema, + validateDeveloperReport, + validateVerifierReport, + type DeveloperReport, + type VerifierReport, +} from "../workflow-orchestrator/contracts.js"; export default function (pi: ExtensionAPI): void { pi.registerTool({ name: "report_task_result", label: "Report Task Result", description: "Report completion of a task (for developer/verifier agents)", - parameters: Type.Object({ - status: Type.Union([Type.Literal("done"), Type.Literal("pass"), Type.Literal("fail")], { - description: "Task status: 'done' for developer, 'pass'/'fail' for verifier", - }), - summary: Type.Optional( - Type.String({ - description: "Brief summary of what was done (for developer)", - }), - ), - filesChanged: Type.Optional( - Type.Array(Type.String(), { - description: "List of files created or modified (for developer)", - }), - ), - notes: Type.Optional( - Type.String({ - description: "Additional notes (for developer)", - }), - ), - issues: Type.Optional( - Type.Array(Type.String(), { - description: "List of issues found (for verifier when status='fail')", - }), - ), - }), + parameters: Type.Union([DeveloperReportSchema, VerifierReportSchema]), async execute(_toolCallId, params) { const status = params.status; let message: string; + let report: DeveloperReport | VerifierReport; + if ((status === "pass" || status === "fail") && "filesChanged" in params) { + throw new Error( + "Developer report validation failed: developer reports cannot use verifier status", + ); + } + if (status === "done") report = validateDeveloperReport(params); + else if (status === "pass" || status === "fail") report = validateVerifierReport(params); + else + report = + "filesChanged" in params + ? validateDeveloperReport(params) + : validateVerifierReport(params); if (status === "done") { - message = `Task completed. Summary: ${params.summary ?? "N/A"}. Files: ${(params.filesChanged ?? []).join(", ") || "none"}`; + const developer = report as DeveloperReport; + message = `Task completed. Summary: ${developer.summary}. Files: ${developer.filesChanged.join(", ") || "none"}`; + } else if (status === "partial") { + message = `Task partially complete. Summary: ${report.summary}`; } else if (status === "pass") { message = "Verification passed. No issues found."; } else { - const issues = params.issues ?? []; - message = `Verification failed. Issues:\n${issues.map((i) => `- ${i}`).join("\n")}`; + message = `Verification failed. Issues:\n${report.issues + .map((issue) => `- ${issue.description}`) + .join("\n")}`; } return { content: [{ type: "text", text: message }], - details: { params }, + details: { params: report }, }; }, }); diff --git a/.pi/workflows/default.workflow.json b/.pi/workflows/default.workflow.json index 0199b45..977b972 100644 --- a/.pi/workflows/default.workflow.json +++ b/.pi/workflows/default.workflow.json @@ -5,13 +5,6 @@ "maxTaskRetries": 6, "maxPmRetries": 3, "parallelism": 4, - "agentRetry": { - "maxAttempts": 5, - "initialDelayMs": 5000, - "maxDelayMs": 120000, - "backoffMultiplier": 2, - "jitterMs": 1000 - }, "allowedExtensionsByAgent": { "pm": ["./.pi/extensions/workflow-pm-tools/index.ts"], "developer": ["./.pi/extensions/workflow-task-tools/index.ts"], @@ -35,22 +28,12 @@ { "id": "develop", "agent": "developer", - "inputTemplate": "Project goal: {{workflow.goal}}\nTask: {{task.title}}\n{{task.description}}\nIssues: {{task.issues}}", - "outputSchema": { - "status": "done", - "summary": "string", - "filesChanged": "string[]", - "notes": "string" - } + "inputTemplate": "Project goal: {{workflow.goal}}\nTask: {{task.title}}\n{{task.description}}\nIssues: {{task.issues}}" }, { "id": "verify", "agent": "verifier", - "inputTemplate": "Project goal: {{workflow.goal}}\nVerify task {{task.title}}.\nDescription: {{task.description}}\nRequirements: {{task.requirements}}\nDev summary: {{task.stageOutputs.develop.summary}}", - "outputSchema": { - "status": "pass|fail", - "issues": "string[]" - }, + "inputTemplate": "Project goal: {{workflow.goal}}\nVerify task {{task.title}}.\nDescription: {{task.description}}\nRequirements: {{task.requirements}}\nDev summary: {{task.stageOutputs.develop.report.summary}}\nDev files: {{task.stageOutputs.develop.report.filesChanged}}", "transitions": [ { "when": { "field": "status", "equals": "fail" }, "next": "develop" }, { "when": { "field": "status", "equals": "pass" }, "next": "complete" } diff --git a/AGENTS.md b/AGENTS.md index 955c036..efeded2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -15,9 +15,11 @@ pi Then: ``` -/workflow start default "Your goal" +/workflow "Your goal" ``` +First `/workflow` run copies editable agent and workflow files into `.pi/`. Edit them to customize, then `/reload`. + Reload extensions: ``` @@ -41,20 +43,29 @@ Reload extensions: - Subagents run in **RPC mode** with per-task session files at: `.pi/workflows/sessions//-.jsonl` +- The PM session is also scoped to the run at: + `.pi/workflows/sessions//pm.jsonl` - `/workflow stop-task ` aborts a task but keeps session context. - `/workflow message ` sends a steer message to the running task, or resumes a stopped task. - `/workflow resume` restarts the workflow loop from the saved state without starting new agents automatically. - While workflow is active, normal chat is routed to PM (commands still work). +Pi `0.80.10` is the supported child runtime (`>=0.80.10 <0.81.0`); workflow +startup performs a version preflight and refuses unsupported executables. +Persisted workflow status is authoritative: `running`, +`waiting_for_clarification`, `stopping`, `completed`, `exhausted`, `failed`, +`stopped`, and `partial` distinguish active and terminal outcomes. + ## Session file format -Session files are JSONL (one JSON event per line): +Session files are Pi session trees stored as JSONL (one JSON entry per line): ```jsonl {"type":"prompt","message":"Project goal: Build a bot\nTask: T1..."} {"type":"message_end","message":{"role":"assistant","content":[...]}} -{"type":"tool_execution_start","toolName":"report_task_result","args":{"status":"done",...}} -{"type":"agent_end"} +{"type":"tool_execution_start","toolCallId":"call-1","toolName":"report_task_result","args":{"status":"done",...}} +{"type":"tool_execution_end","toolCallId":"call-1","toolName":"report_task_result","isError":false,"result":{"details":{"params":{...}}}} +{"type":"agent_settled"} ``` **Key event types:** @@ -62,11 +73,13 @@ Session files are JSONL (one JSON event per line): - `prompt` - The prompt sent to the agent - `message_end` - Agent's text response - `tool_execution_start` - Tool call with arguments (this is captured for structured output) -- `agent_end` - Agent completed +- `tool_execution_start` / `tool_execution_end` - Correlated custom-tool lifecycle +- `agent_end` - One low-level agent turn ended; it is not the accepted terminal event +- `agent_settled` - Pi finished the accepted prompt, including retry/continuation handling **Locations:** -- PM sessions: `.pi/workflows/sessions/pm-.jsonl` +- PM sessions: `.pi/workflows/sessions//pm.jsonl` - Task sessions: `.pi/workflows/sessions//-.jsonl` ## UI notes @@ -114,29 +127,38 @@ Agents report via structured tools instead of JSON text: **Why tools?** Previously, agents output JSON text that was parsed with `extractJson()`. Malformed JSON caused silent failures where verifier reports were lost. Tools provide structured arguments that are captured directly from `tool_execution_start` events. -**Fallback:** If an agent doesn't call the tool, their text output is captured and stored in `stageOutputs[stageId]`. This ensures workflow continuity. +There is no prose/JSON fallback for stage results. If the required tool does not +successfully execute exactly once before `agent_settled`, the stage fails with a +diagnostic rather than being accepted. **Tool isolation:** Each extension provides specific tools, and `allowedExtensionsByAgent` ensures agents only see their relevant tools. -**PM wave validation:** If the PM returns a wave without a valid `tasks` array, the workflow automatically retries (up to `maxPmRetries` times from workflow config), passing the error message back to the PM so it can correct its output. This prevents crashes from malformed PM responses. +**PM wave validation:** If the PM makes an invalid tool attempt or returns a +wave that fails semantic validation, the workflow retries up to `maxPmRetries` +times and passes the error back to the PM. Assistant prose alone pauses for +clarification; it never becomes a wave result. + +Pi owns transient provider retry through its settings (`retry.enabled`, +`retry.maxRetries`, and `retry.baseDelayMs`). Workflow retries remain semantic +retries between PM/developer/verifier stages. ## Template variables In workflow JSON `inputTemplate`, you can reference: -| Variable | Description | -| --------------------------------- | --------------------------------------- | -| `{{task.title}}` | Task title | -| `{{task.description}}` | Task description | -| `{{task.requirements}}` | Verification requirements | -| `{{task.issues}}` | Current issues (from previous failures) | -| `{{task.stageOutputs.}}` | Output from a previous stage | -| `{{workflow.goal}}` | Project goal | -| `{{wave.goal}}` | Current wave goal | -| `{{wave.index}}` | Wave number (0-based) | +| Variable | Description | +| ---------------------------------------- | --------------------------------------- | +| `{{task.title}}` | Task title | +| `{{task.description}}` | Task description | +| `{{task.requirements}}` | Verification requirements | +| `{{task.issues}}` | Current issues (from previous failures) | +| `{{task.stageOutputs..report}}` | Validated report from a previous stage | +| `{{workflow.goal}}` | Project goal | +| `{{wave.goal}}` | Current wave goal | +| `{{wave.index}}` | Wave number (0-based) | **Example:** ```json -"inputTemplate": "Verify task {{task.title}}.\nDev summary: {{task.stageOutputs.develop.summary}}" +"inputTemplate": "Verify task {{task.title}}.\nDev summary: {{task.stageOutputs.develop.report.summary}}" ``` diff --git a/README.md b/README.md index ed10a12..1e555d7 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,8 @@ Customizable PM → Dev → Verifier workflow for **pi** using an extension and - PM chat in the main pi conversation - Per‑agent model selection - Persistent workflow state in the session +- Typed structured reports with evidence and issue validation +- Pi 0.80.10 runtime preflight and correlated RPC handling ## Repository Layout @@ -45,7 +47,7 @@ Customizable PM → Dev → Verifier workflow for **pi** using an extension and pi install git:github.com/hlibr/piorch ``` -This automatically installs the extension and agents to your project. +This installs the extension and bundled defaults. On first run, editable workflow and agent files are copied into your project's `.pi/` directory. ### Configure Models (Optional) @@ -64,7 +66,7 @@ Then run: ```bash pi -/workflow start default "Your goal" +/workflow "Your goal" ``` ### Option 2: Manual Copy @@ -103,7 +105,7 @@ Note: When working from the repo root, the agent will see test files and develop ## Quick Start ```text -/workflow start default "Build a Telegram bot that replies pong to /ping" +/workflow "Build a Telegram bot that replies pong to /ping" ``` You’ll see: @@ -146,6 +148,8 @@ tools: read,edit,write,bash Run `/reload` after changes. +You can also choose the run model with `/workflow --model "Your goal"`. + ### Workflow Configuration Edit `.pi/workflows/default.workflow.json` to customize: @@ -153,9 +157,9 @@ Edit `.pi/workflows/default.workflow.json` to customize: - **agent names** - Which agent files to use (pm, developer, verifier) - **stages and transitions** - Customize the dev/verify loop - **wave source** - PM-driven or static task waves -- **parallelism** - How many tasks to run concurrently (default: 4) +- **parallelism** - How many tasks to run concurrently (default: 1) - **maxWaves** - Maximum number of waves (default: 10) -- **maxTaskRetries** - Retry limit per task (default: 6) +- **maxTaskRetries** - Retry limit per task (default: 2) - **maxPmRetries** - Retry limit for PM wave generation (default: 3) - **allowedExtensions** - Whitelist extensions for all subagents - **allowedExtensionsByAgent** - Per-agent extension allowlists @@ -166,6 +170,11 @@ Edit `.pi/workflows/default.workflow.json` to customize: - `reset` - `reset_on_malformed_output` +The workflow configuration intentionally has no `agentRetry` field. Pi owns +provider retry behavior through `retry.enabled`, `retry.maxRetries`, and +`retry.baseDelayMs` in Pi settings. `maxTaskRetries` and `maxPmRetries` remain +workflow-level semantic retries. + Example: ```json @@ -195,7 +204,7 @@ Edit `.pi/agents/*.md` to customize agent behavior: name: developer description: Implements assigned tasks model: anthropic/claude-sonnet-4-5 # Your preferred model (or remove to use Pi default) -tools: read,edit,write,bash # Built-in tools to enable +tools: read,edit,write,bash,report_task_result # Built-in and role tools to enable --- ``` @@ -239,7 +248,7 @@ Run `/reload` after changing configuration files. ## Commands -- `/workflow start [goal]` +- `/workflow "goal"` (or `/workflow start [goal]`) - `/workflow resume` (continue from saved state) - `/workflow stop` - `/workflow stop-task ` diff --git a/claude_code_enhancements.md b/claude_code_enhancements.md new file mode 100644 index 0000000..de77f5a --- /dev/null +++ b/claude_code_enhancements.md @@ -0,0 +1,1056 @@ +# piorch Implementation Brief + +Status: ready for implementation +Prepared: 2026-07-17 +Audience: the agent implementing the next correctness milestone in `piorch` + +## 1. Objective + +Make `piorch` a trustworthy workflow orchestrator on the Pi runtime that it +actually launches. The immediate goal is not to add more orchestration features. +It is to ensure that a PM wave, a developer result, and a verifier result can be +accepted only when the corresponding Pi process really ran, the required custom +tool really succeeded, and the run really settled. + +This document is the implementation handoff. It consolidates the runtime analysis, the applicable Claude Code design lessons in `CLAUDE_CODE_INSPIRATIONS.md`, and direct inspection of both repositories and Pi itself. + +The implementing agent should be able to begin with Milestone 1 below without performing another architecture investigation. + +## 2. Repository and runtime baseline + +### Local repositories + +- `piorch`: `.` +- Claude Code reference source: + `../claude-code-main` + +### Pi baseline used for this analysis + +- Upstream: +- Documentation: +- Inspected upstream revision: + `3da591ab74ab9ab407e72ed882600b2c851fae21` +- Installed executable: `pi` (resolved from `PATH`) +- Installed executable version: `0.80.10` +- Current `piorch` TypeScript dependencies: `@mariozechner/pi-*` at `^0.57.1` + +The repository therefore compiles against Pi 0.57-era types but launches Pi +0.80.10 from `PATH`. The child executable, not the local TypeScript package, owns +the behavior of every PM, developer, and verifier run. + +### Verified baseline + +Before this handoff was written: + +- `npm run typecheck` passed. +- All 188 existing tests passed. + +## 3. Current architecture in one page + +`piorch` is a Pi extension that is itself loaded into a parent Pi session. It +implements a deterministic workflow state machine and starts separate child Pi +processes for the roles: + +```text +parent Pi session + workflow-orchestrator extension + PM RpcAgent -> pi --mode rpc --session ... + developer RpcAgent -> pi --mode rpc --session ... + verifier RpcAgent -> pi --mode rpc --session ... +``` + +Each child is started in the project working directory with approximately: + +```text +pi --mode rpc + --session + --no-extensions + --no-skills + --no-prompt-templates + -e + --model + --tools + --append-system-prompt +``` + +Important consequences: + +- The parent owns workflow state, scheduling, transitions, retries between + semantic stages, and persisted custom state entries. +- Each child Pi owns model interaction, coding tools, explicitly loaded role + tools, context-file discovery, its session tree, provider retry, and RPC + events. +- Child processes inherit the host filesystem, process, environment, and network + authority. `piorch` does not add a security boundary. +- Project context files still load because the child does not pass + `--no-context-files`. +- Discovered extensions, skills, and prompt templates are disabled. Only paths + supplied with `-e` load as extensions. +- Every task currently uses the same `ctx.cwd`. `parallelism` limits count; it + does not isolate writes. + +### Current control flow + +1. The PM must call `generate_wave`. +2. The orchestrator creates task state for the wave. +3. Each developer must call `report_task_result({status: "done", ...})`. +4. Each verifier must call `report_task_result({status: "pass"|"fail", ...})`. +5. The deterministic engine follows configured transitions. +6. A failed verification returns the task to development until the workflow + retry limit is exhausted. +7. The next PM turn receives a compact wave summary. + +The architecture is sound in principle: Pi performs agent work and `piorch` +controls the workflow. The current protocol handling makes the reported outcomes +untrustworthy, however. + +## 4. Non-negotiable design decisions + +These decisions define Milestone 1. An implementation that changes one of them +must update this brief and explain why. + +### 4.1 Support modern Pi explicitly + +Target Pi `>=0.80.10 <0.81.0` for the first implementation. Pin the compile-time +packages to exactly `0.80.10` and fail fast if the executable does not satisfy the +supported range. + +Do not attempt a dual 0.57/0.80 compatibility layer in the same change. The +`--tools` contract differs in a way that makes a single role declaration unsafe: + +- Pi 0.57.1 treats `--tools` as a built-in-tool selection and then adds explicit + extension tools. +- Pi 0.80.10 treats it as an allowlist for built-in and extension tools. + +Consequently, the current role files hide `generate_wave` and +`report_task_result` under Pi 0.80.10. Adding those names fixes 0.80.10 but is not +a safe 0.57-era declaration. + +### 4.2 Keep child processes for this milestone + +Pi recommends direct `AgentSession` use for Node integrations, but changing from +subprocesses to in-process sessions would combine a runtime migration with a +protocol rewrite. Keep subprocess isolation and make the RPC client correct. + +Do not instantiate upstream `RpcClient` directly in Milestone 1. Its public +constructor launches `node `; `piorch` deliberately launches the +`PATH`-resolved `pi` executable. A small local client is easier to test against a +fake executable and preserves that deployment contract. Reuse Pi's exported +types where practical, not its process-launch assumption. + +### 4.3 A successful tool execution is the only structured result + +Assistant prose is progress information, never a stage result. An attempted +tool call is not a result. Only a `tool_execution_start` correlated with a +`tool_execution_end` having `isError === false` may produce a PM wave or task +outcome. + +### 4.4 Wait for `agent_settled` + +`agent_end` means one low-level agent run ended. Pi can still retry, compact and +retry, or process a queued continuation. `agent_settled` is the terminal event +for an accepted prompt. The client must not resolve a run on `agent_end`. + +See Pi's current RPC contract: +. + +### 4.5 Pi owns transient provider retry + +There must be one retry owner. Remove `RpcAgent.runPrompt()`'s outer provider +retry loop and rely on Pi's agent-level retry. Settlement makes that behavior +observable. Keep workflow semantic retries (`maxTaskRetries`) in `piorch`. + +The existing `agentRetry` object is misleading because the RPC API only toggles +Pi auto-retry; it cannot apply all of `piorch`'s delay and attempt fields. Remove +`agentRetry` from the workflow schema and sample configuration in this milestone. +Document that Pi retry behavior comes from Pi settings (`retry.enabled`, +`retry.maxRetries`, and `retry.baseDelayMs`). Reject old `agentRetry` input with a +specific migration error rather than silently ignoring it. + +### 4.6 Preserve the deterministic orchestrator + +Do not turn the PM into a free-form scheduler. The PM proposes one wave through a +typed tool. The orchestrator validates IDs, schedules work, applies results, +enforces verification, and determines terminal state. + +### 4.7 Keep internal state typed + +Claude Code uses XML-like messages when injecting task notifications into an LLM +conversation. That is useful at an LLM boundary, not for `piorch`'s internal +state. Use TypeScript objects and runtime schemas internally. + +## 5. Why Milestone 1 is required + +The following are confirmed correctness defects, not speculative improvements. + +### P0 — role tools are filtered out + +The three role frontmatter files list only built-ins. Under Pi 0.80.10 the +custom role tools are not active even though their extensions load with `-e`. +The agents are asked to call tools they cannot see. + +### P0 — rejected RPC commands can hang + +`runner.ts` sends commands without IDs and ignores `type: "response"`. If Pi +rejects a prompt, for example because the child is already streaming, no agent +run starts and `agent_end` never arrives. The promise can remain unresolved. + +### P0 — attempted or duplicated calls can be accepted + +The client records `tool_execution_start` before schema validation and execution. +It then records the same assistant `toolCall` again from `message_end`. The +orchestrator searches for the first matching name. A malformed first report can +therefore override a later corrected report, and a failed tool execution can be +accepted as success. + +### P0 — the wrong completion event is used + +The client resolves on `agent_end`, before Pi's retry, compaction-retry, or queued +continuation paths are necessarily complete. + +### P1 — JSONL parsing is not protocol-safe + +Calling `data.toString()` per chunk can corrupt a multibyte UTF-8 character split +across chunks. Invalid JSON is silently discarded. A corrupt structured result +can thus become a missing-result retry with no useful diagnosis. + +Pi requires strict LF-delimited JSONL and recommends `StringDecoder`: +. + +### P1 — result semantics are not enforced + +`outputSchema` is parsed but unused. A developer can report `pass`; a verifier can +report `done`; a verifier can pass without evidence. The extension validates only +the broad tool shape, not the role-specific contract. + +### P1 — stop/resume has two prompt owners + +`stopTask()` aborts a runner but leaves lifecycle cleanup to the original +promise. `messageTask()` can then steer it or start a fire-and-forget prompt that +is not owned by the task-flow state machine. Output from that prompt is not +reliably applied, and a task can remain stopped. + +### P1 — task and stage IDs can collide or escape paths + +PM-produced task IDs and configured stage IDs are used in filenames and map keys +without validation. Duplicate IDs collide. Path-like IDs can escape the intended +session directory. + +### P1 — PM memory leaks across workflow runs + +Task sessions are scoped by `runId`; the PM session is scoped only by workflow +name. Independent goals can inherit unrelated PM history. + +### P1 — completion is overstated + +The UI can announce completion after maximum waves are exhausted or tasks have +failed. Terminal workflow outcomes need distinct states. + +## 6. Milestone 1 scope + +Name this milestone **Runtime and Result Correctness**. + +It includes: + +1. Align package namespace and types with Pi 0.80.10. +2. Verify the launched Pi executable version before starting a workflow. +3. Add each custom role tool to its role's `tools` allowlist. +4. Replace ad hoc RPC parsing with a request/response-aware client. +5. Resolve accepted runs only on `agent_settled`. +6. Correlate tool start/end events by `toolCallId`. +7. Accept only successful terminal structured reports. +8. Enforce role-specific result schemas and evidence rules. +9. Validate workflow, task, and stage identifiers and uniqueness. +10. Scope all session files by `runId`. +11. Give every active task prompt one lifecycle owner. +12. Persist accurate workflow terminal outcomes. +13. Give the PM a bounded, typed summary of the previous wave. +14. Add deterministic RPC and workflow tests that do not require a paid model. + +### Explicitly out of scope + +- Git worktree creation, integration, or cleanup. +- A dependency graph or general DAG scheduler. +- Fully generic stage semantics. +- MCP support. +- Background daemons or remote workers. +- A rewrite around in-process `AgentSession`. +- Major TUI redesign. +- Automated commit, push, PR, or merge behavior. + +Those belong to later milestones after result correctness is established. + +## 7. Required data contracts + +Put shared contracts in a new +`.pi/extensions/workflow-orchestrator/contracts.ts`. Use TypeBox schemas for every +object that crosses an LLM, RPC, configuration, or persisted-state boundary, and +derive TypeScript types with `Static`. + +### 7.1 Identifier + +```ts +export const IdentifierSchema = Type.String({ + pattern: "^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$", +}); +``` + +Apply it to workflow names, task IDs, and stage IDs. After schema validation, +perform cross-field validation: + +- task IDs must be unique within a wave; +- stage IDs must be unique within a workflow; +- every transition target must be another stage ID or `complete`; +- every configured agent reference must resolve; +- Milestone 1 supports the semantic stage IDs `develop` and `verify`; reject a + configuration that omits them or uses another semantic shape. + +The last restriction makes the actual implementation honest. Generic stage kinds +can replace it in a later version. + +### 7.2 Evidence and issues + +```ts +export const EvidenceSchema = Type.Object({ + kind: Type.Union([ + Type.Literal("command"), + Type.Literal("test"), + Type.Literal("inspection"), + Type.Literal("manual"), + ]), + description: Type.String({ minLength: 1, maxLength: 2000 }), + command: Type.Optional(Type.String({ maxLength: 2000 })), + outcome: Type.Union([Type.Literal("pass"), Type.Literal("fail"), Type.Literal("blocked")]), +}); + +export const IssueSchema = Type.Object({ + severity: Type.Union([Type.Literal("blocking"), Type.Literal("non_blocking")]), + description: Type.String({ minLength: 1, maxLength: 4000 }), + reproduction: Type.Optional(Type.String({ maxLength: 4000 })), +}); +``` + +### 7.3 Developer report + +```ts +export const DeveloperReportSchema = Type.Object({ + status: Type.Union([Type.Literal("done"), Type.Literal("partial")]), + summary: Type.String({ minLength: 1, maxLength: 4000 }), + filesChanged: Type.Array(Type.String({ minLength: 1, maxLength: 1000 }), { + maxItems: 500, + }), + evidence: Type.Array(EvidenceSchema, { maxItems: 100 }), + issues: Type.Array(IssueSchema, { maxItems: 100 }), +}); +``` + +Rules beyond the schema: + +- `done` means the requested implementation is complete. +- `partial` requires at least one blocking issue explaining why it is incomplete. +- `filesChanged` paths must be relative to `ctx.cwd`, normalized, must not contain + `..`, and should be de-duplicated. +- The orchestrator should compare declared changed files with `git diff --name-only` + when Git is available. In Milestone 1, mismatch is recorded as an issue and + supplied to the verifier; it does not automatically rewrite Git state. + +### 7.4 Verifier report + +```ts +export const VerifierReportSchema = Type.Object({ + status: Type.Union([Type.Literal("pass"), Type.Literal("fail"), Type.Literal("partial")]), + summary: Type.String({ minLength: 1, maxLength: 4000 }), + evidence: Type.Array(EvidenceSchema, { maxItems: 100 }), + issues: Type.Array(IssueSchema, { maxItems: 100 }), +}); +``` + +Rules beyond the schema: + +- `pass` requires at least one evidence item with `outcome: "pass"` and no + blocking issue. +- `fail` requires at least one actionable blocking issue. +- `partial` is reserved for an environmental block and requires both blocked + evidence and a blocking issue. +- A failed command cannot be represented as passing evidence. +- The verifier remains structurally read-only: `read`, `grep`, `find`, `ls`, and + `bash`; it must not receive `edit` or `write`. + +### 7.5 Orchestrator-owned result envelope + +Do not ask the model to echo identifiers already known by the orchestrator. +Attach them after report validation: + +```ts +export interface StageResultEnvelope { + runId: string; + waveIndex: number; + taskId: string; + stageId: "develop" | "verify"; + role: "developer" | "verifier"; + report: TReport; + toolCallId: string; + startedAt: number; + completedAt: number; +} +``` + +Persist envelopes in `TaskState.stageOutputs`. Do not persist an unvalidated +`Record` as a successful output. + +### 7.6 RPC tool execution + +```ts +export interface RpcToolExecution { + toolCallId: string; + name: string; + attemptedArgs: unknown; + startedAt: number; + endedAt?: number; + isError?: boolean; + result?: unknown; +} +``` + +Maintain these in a `Map` for one prompt. A duplicate +start ID, end without start, or second end is a protocol error. On successful +completion, use the validated tool result's `details.params` when available; +otherwise use the correlated attempted arguments. The two values should match; +a mismatch is a protocol error. + +## 8. RPC client specification + +Replace the protocol portion of `RpcAgent` with a focused local class, for +example `PiRpcProcess`. Keep workflow-specific report interpretation outside it. + +### 8.1 Process startup + +1. Resolve the executable from a configurable `piCommand`, defaulting to `pi`. +2. Run `pi --version` once per parent workflow start. +3. Parse numeric `major.minor.patch`; reject nonmatching output. +4. Require `>=0.80.10 <0.81.0` and show the resolved command and version on + failure. +5. Spawn without `shell`. +6. Register `error`, `exit`, `close`, stdin `error`, stdout `error`, and stderr + `error` handlers before sending commands. +7. Bound captured stderr to the last 64 KiB. +8. Start a startup timeout. Readiness is established by a successful correlated + `get_state` response, not by sleeping for 100 ms. + +Keep prompt files at mode `0600`, and clean them up on normal completion, failed +startup, process exit, and disposal. + +### 8.2 JSONL framing + +- Decode stdout and stderr with `StringDecoder("utf8")`. +- Split stdout only on LF (`\n`). +- Strip one trailing CR from a record. +- Parse every nonempty stdout record as JSON. +- Treat malformed JSON, an over-limit record, or a partial final record as a + protocol error. Do not silently continue. +- Use a bounded record buffer, recommended maximum 4 MiB. +- Never write logs to child stdout; stdout is protocol-only. + +### 8.3 Commands + +Every command gets a monotonically increasing ID such as `piorch-1`. Maintain a +pending command map and enforce a command response timeout. + +```ts +interface PendingCommand { + command: string; + resolve: (data: unknown) => void; + reject: (error: Error) => void; + timeout: ReturnType; +} +``` + +For a prompt: + +1. Create the run state before writing. +2. Send `{id, type: "prompt", message}`. +3. Wait for the matching response. +4. If `success: false`, reject immediately with Pi's error and clear run state. +5. If `success: true`, wait for events. +6. Resolve only on `agent_settled`. + +Unknown response IDs, duplicate responses, events that violate lifecycle order, +and process exit with pending commands are protocol errors. + +### 8.4 Run lifecycle + +Use an explicit state machine: + +```text +idle + -> prompt_pending + -> running + -> settling + -> settled + -> idle + +any active state -> abort_pending -> settled/terminated -> idle +any state -> failed -> disposed +``` + +Required invariants: + +- One accepted prompt at a time per process. +- A run has exactly one owner promise. +- `agent_start` is valid only after prompt acceptance. +- `agent_end` updates state and diagnostics but does not resolve. +- `agent_settled` resolves only if the prompt was accepted. +- A run-level assistant error is retained; settlement rejects with that error + unless a later Pi retry succeeded. +- `auto_retry_start` and `auto_retry_end` update progress and metrics. +- Abort has its own correlated response and grace timeout. +- After the grace timeout, send `SIGTERM`; after another bounded grace period, + send `SIGKILL`. +- All pending commands and the active run reject exactly once on termination. + +Do not harvest `toolCall` parts from `message_end`. Tool execution events are the +single source of truth for execution. + +### 8.5 Timeouts + +Use constants in Milestone 1, with dependency injection for tests: + +```ts +startupTimeoutMs = 10_000; +commandTimeoutMs = 10_000; +runTimeoutMs = 30 * 60_000; +abortGraceMs = 5_000; +killGraceMs = 2_000; +``` + +A run timeout is an orchestrator failure, not a verifier failure. Include task, +stage, session path, last lifecycle event, and bounded stderr in the error. + +## 9. Structured-result selection algorithm + +Use the same selection rules for `generate_wave` and `report_task_result`. + +```text +on tool_execution_start: + validate event fields + create execution keyed by toolCallId + +on tool_execution_end: + find the matching start + record result, end time, and isError + +on agent_settled: + select executions with the expected tool name and isError === false + reject if zero exist + reject if more than one successful terminal report exists + derive candidate params from result.details.params + compare with attempted args + validate candidate against the role-specific runtime schema + apply cross-field semantic rules + attach the orchestrator-owned envelope +``` + +Multiple failed attempts followed by one successful report are valid. Multiple +successful reports are ambiguous and must fail the stage rather than choosing +the first or last silently. + +For `generate_wave`: + +- `done: true` must not include a wave. +- `done: false` must include a nonempty wave. +- task IDs must pass `IdentifierSchema` and be unique. +- every task needs a nonempty title, description, and verification requirements. +- `assignee`, if retained in Milestone 1, must equal `developer`; otherwise remove + it from the schema because the engine does not honor arbitrary assignees. + +Update both role-tool extensions so semantic invalidity throws an error. Returning +text beginning with `Error:` while reporting a successful tool execution is not +acceptable. + +## 10. Task lifecycle, stop, message, and resume + +Represent prompt ownership explicitly in the task runner: + +```ts +interface TaskRunner { + key: string; + agent: PiRpcProcess; + stageId: "develop" | "verify"; + lifecycle: "idle" | "running" | "aborting" | "stopped" | "disposed"; + activePrompt?: Promise>; +} +``` + +Required behavior: + +### Stop + +1. Mark the task `stopping`, not immediately `stopped`. +2. Abort the active prompt. +3. Await the original prompt owner's settlement or termination. +4. Persist `stopped` only after cleanup completes. +5. Keep the stage session file for an intentional resume. + +### Message while running + +If the original prompt is active, send a correlated `steer` command. The original +prompt promise remains the only result owner. Do not create a second +fire-and-forget `runPrompt()`. + +### Resume after stopped + +1. Ensure the previous prompt has settled and no task lock is held. +2. Set `resumeMessage`. +3. Set the task back to `pending` at the same stage. +4. Re-enter `processTask()` through the normal scheduler. +5. Reopen the same Pi stage session unless the configured memory policy requires + a reset. +6. Apply the eventual report through the same validation and transition path as + an uninterrupted run. + +There must be no direct path from a UI command to an unowned prompt. + +## 11. Sessions and memory + +Use this layout: + +```text +.pi/workflows/sessions// + pm.jsonl + -develop.jsonl + -verify.jsonl + --r.jsonl +``` + +All path components must already satisfy `IdentifierSchema`. Resolve each final +path and assert that it remains beneath the run session directory. + +The files are Pi session trees, not RPC event logs. Resume by reopening the +session file and issuing a new prompt. Do not replay RPC events as conversation +messages. + +Memory policy remains: + +- developer memory can survive verifier failure; +- verifier memory can survive developer correction when configured; +- malformed verifier output may trigger verifier memory reset; +- independent workflow runs never share PM memory. + +## 12. PM synthesis contract + +The current wave summary drops most useful output. Replace the string-building +shortcut with a typed summary and serialize a bounded human-readable form into +the next PM prompt. + +```ts +interface PriorWaveSummary { + waveIndex: number; + goal: string; + outcome: "verified" | "failed" | "partial" | "stopped"; + tasks: Array<{ + id: string; + title: string; + status: TaskStatus; + retries: number; + developerSummary?: string; + filesChanged: string[]; + verifierSummary?: string; + evidence: Evidence[]; + issues: Issue[]; + }>; +} +``` + +Bound the prompt representation, for example: + +- maximum 100 tasks; +- maximum 20 evidence items and 20 issues per task; +- maximum 2,000 characters per summary or issue; +- explicit truncation markers. + +The PM prompt should require it to synthesize conclusions, unresolved risks, and +the reason for either finishing or producing the next wave. Do not tell it merely +that files changed. + +The PM's session path must be `/pm.jsonl`, so its conversational continuity +is useful within one workflow run and cannot leak into another goal. + +## 13. Terminal workflow outcomes + +Replace `active: boolean` as the sole terminal signal with: + +```ts +type WorkflowStatus = + | "idle" + | "running" + | "waiting_for_clarification" + | "stopping" + | "completed" + | "exhausted" + | "failed" + | "stopped" + | "partial"; +``` + +Rules: + +- `completed`: PM reported done and every required task is verified. +- `exhausted`: `maxWaves` was reached before PM reported valid completion. +- `failed`: an unrecoverable runtime, protocol, configuration, or workflow error + ended the run. +- `stopped`: the user stopped the workflow and active children were cleaned up. +- `partial`: PM reported done while unresolved nonverified tasks remain; this is + visible as incomplete, never announced as success. + +Keep a compatibility `active` getter or derived field only if rendering code +needs it temporarily. Persist `status` as the source of truth. + +## 14. File-by-file implementation plan + +### `package.json` and lockfile + +- Replace `@mariozechner/pi-ai`, `@mariozechner/pi-coding-agent`, and + `@mariozechner/pi-tui` with `@earendil-works/*` at exact `0.80.10`. +- Update all imports in the extension and tests. +- Add a `test:integration` script only if real-Pi smoke tests are separated from + default unit tests. +- Regenerate `package-lock.json`; do not hand-edit it. + +### `.pi/agents/pm.md` + +- Add `generate_wave` to `tools`. +- Require nonempty verification requirements and valid unique IDs. +- State that one successful `generate_wave` call is allowed per turn. + +### `.pi/agents/developer.md` + +- Add `report_task_result` to `tools`. +- Update the example to the developer report contract. +- Require concrete evidence and truthful relative changed-file paths. + +### `.pi/agents/verifier.md` + +- Add `report_task_result` to `tools`. +- Update the example to the verifier report contract. +- Require verification against task requirements, adversarial checks where + appropriate, and evidence. Keep the toolset read-only except for `bash`. + +### `.pi/extensions/workflow-pm-tools/index.ts` + +- Import the shared wave/report schema. +- Throw on conditional invalidity (`done=false` without a wave, duplicates, + invalid IDs). +- Return validated params in `details.params`. + +### `.pi/extensions/workflow-task-tools/index.ts` + +- Replace the weak shared shape with the report union or register separate + developer/verifier tools if role-specific extension paths are preferable. +- Keep the external tool name `report_task_result` for this milestone. +- Throw on invalid semantics. +- Return validated params in `details.params`. + +### `.pi/extensions/workflow-orchestrator/contracts.ts` (new) + +- Define identifier, evidence, issue, report, wave, envelope, and persisted + summary schemas and derived types. +- Export focused validation helpers with actionable errors. + +### `.pi/extensions/workflow-orchestrator/runner.ts` + +- Migrate imports to `@earendil-works/*`. +- Add executable-version preflight. +- Implement strict JSONL decoding and correlated command responses. +- Correlate tool events by `toolCallId`. +- Resolve on `agent_settled`. +- Remove duplicate message-content harvesting. +- Remove the outer provider retry loop and its regex-based error classifier. +- Add startup, command, run, abort, and kill timeouts. +- Add complete process and stream error handling. +- Expose a typed `RpcRunResult` containing output text, successful and failed tool + executions, usage/metrics, lifecycle diagnostics, and bounded stderr. +- Preserve `runAgent()` only if it has a tested caller; otherwise remove it after + confirming with `rg`. + +### `.pi/extensions/workflow-orchestrator/config.ts` + +- Apply `IdentifierSchema` to workflow/task/stage IDs. +- Remove misleading `agentRetry` configuration with a migration error. +- Validate uniqueness, transition targets, resolved agents, and the supported + develop/verify semantic shape after TypeBox validation. +- Validate positive integer limits, not just `number` values. +- Either remove `outputSchema` in Milestone 1 or enforce it. The recommended + choice is to remove it because role-specific shared schemas replace it. +- Either remove `assignee` or restrict it to `developer` until scheduling honors + other assignees. + +### `.pi/extensions/workflow-orchestrator/state.ts` + +- Add `WorkflowStatus`. +- Add typed stage envelopes, evidence, issues, and metrics. +- Add `stopping` to task status. +- Replace broad `Record>` successful outputs. +- Keep restore compatibility for existing state entries by migrating absent + `status` from `active`; test the migration. + +### `.pi/extensions/workflow-orchestrator/engine.ts` + +- Accept a typed stage outcome rather than arbitrary output. +- Remove hidden semantic acceptance: only validated developer/verifier envelopes + reach the transition engine. +- Keep semantic workflow retries here; do not mix them with provider retries. +- Return an explicit task terminal outcome instead of relying entirely on + mutation callbacks. +- In Milestone 1, make the `develop`/`verify` special case explicit and tested. + +### `.pi/extensions/workflow-orchestrator/index.ts` + +- Preflight Pi before marking a workflow running. +- Select and validate exactly one successful role report. +- Scope PM sessions by `runId`. +- Use one owner promise for every active task prompt. +- Rework stop, steer, and resume according to Section 10. +- Persist typed previous-wave summaries. +- Emit accurate terminal notices. +- Dispose every PM/task child on terminal workflow state and extension shutdown. + +### `.pi/extensions/workflow-orchestrator/render.ts` + +- Render `stopping`, `exhausted`, `partial`, and protocol/runtime failure states. +- Keep this change narrow; do not redesign the UI. + +### Tests + +- Extend current tests rather than replacing them wholesale. +- Add `tests/fixtures/fake-pi.mjs` as a deterministic executable controlled by an + environment scenario name or fixture argument. +- Add focused contract, RPC protocol, result-selection, lifecycle, config, and + state-migration tests. + +## 15. Test plan + +No default test may call a paid or remote model. + +### 15.1 Fake Pi process scenarios + +The fake executable should support `--version` and RPC JSONL. Cover at least: + +1. Successful readiness handshake and prompt response. +2. Rejected prompt response with matching ID. +3. Response with unknown ID. +4. Process exits before readiness. +5. Spawn error / nonexistent executable. +6. UTF-8 character split across chunks. +7. Two JSON records in one chunk. +8. CRLF input accepted by stripping CR. +9. Malformed JSON is fatal. +10. Oversized record is fatal. +11. Partial final record is fatal. +12. `agent_end` followed later by `agent_settled`; promise resolves only on the + latter. +13. `agent_end {willRetry:true}`, retry events, second run, then settlement. +14. Tool start and successful matching end. +15. Tool start and failed matching end. +16. Failed report followed by one successful corrected report. +17. Two successful reports rejected as ambiguous. +18. Tool end without start rejected. +19. Duplicate tool-call ID rejected. +20. Abort response and normal settlement. +21. Abort timeout escalates through TERM and KILL. +22. Run timeout includes diagnostics and disposes the child. + +Inject clocks/timeouts where needed; do not make unit tests wait real seconds. + +### 15.2 Role and result tests + +- PM `done=false` without a wave fails the tool execution. +- PM `done=true` with a wave is rejected. +- Duplicate or unsafe task IDs are rejected. +- Developer `pass` is rejected. +- Developer `partial` without a blocking issue is rejected. +- Verifier `done` is rejected. +- Verifier `pass` without passing evidence is rejected. +- Verifier `fail` without a blocking issue is rejected. +- Verifier `partial` without blocked evidence is rejected. +- Changed file paths cannot be absolute or traverse upward. +- Only one successful terminal result is accepted. + +### 15.3 Workflow lifecycle tests + +- PM session path differs for two run IDs of the same workflow. +- Task and stage session paths remain under the run directory. +- A running task message steers the owned prompt. +- A stopped task resumes through `processTask()` and applies its result. +- No fire-and-forget prompt is created on resume. +- Workflow stop waits for child cleanup. +- Max waves produces `exhausted`, not `completed`. +- PM done with failed/unverified tasks produces `partial`. +- Runtime/protocol failure produces `failed` with a useful notice. +- Restoring old persisted state derives the correct new status. + +### 15.4 Real Pi compatibility smoke test + +Add an opt-in test gated by an environment variable, for example +`PIORCH_REAL_PI_TEST=1`. It must not invoke a model. + +Use a test-only explicit extension that registers an extension command. The +command can call `pi.getActiveTools()` and send a hidden custom message. Start a +real Pi RPC process with the production role extension plus the test extension, +invoke the command, and assert: + +- PM active tools include `generate_wave`. +- Developer and verifier active tools include `report_task_result`. +- Verifier active tools exclude `edit` and `write`. +- The actual executable passes the version preflight. + +This directly catches the 0.57/0.80 tool-allowlist regression without spending +tokens. + +### 15.5 Required commands before handoff + +```sh +npm ci +npm run typecheck +npm run lint +npm run format:check +npm run markdownlint +npm test +PIORCH_REAL_PI_TEST=1 npm run test:integration # if the opt-in script exists +``` + +Report exact counts and any skipped opt-in tests. + +## 16. Milestone 1 acceptance criteria + +Milestone 1 is complete only when all of the following are true: + +- The code compiles against `@earendil-works/pi-*` 0.80.10. +- A workflow refuses to start on an unsupported Pi executable version. +- The real-Pi no-model smoke test proves each role's custom report tool is active. +- Every RPC command is correlated with a response or a bounded timeout. +- A rejected prompt cannot hang a task. +- Runs resolve on `agent_settled`, never merely on `agent_end`. +- Tool executions are correlated by ID and failed executions are never accepted. +- Exactly one successful terminal report is required per PM/stage turn. +- Developer and verifier reports satisfy distinct schemas and semantic rules. +- Stop, steer, and resume retain exactly one owner for the active prompt. +- PM and task sessions are scoped beneath the current `runId`. +- Unsafe or duplicate identifiers are rejected before filesystem use. +- PM receives developer summary, changed files, verifier evidence, issues, and + retry state from the previous wave. +- Terminal workflow notices distinguish success, exhaustion, failure, stop, and + partial completion. +- All unit, integration, lint, formatting, markdown, and type checks pass. + +## 17. Later milestones + +Do not implement these as opportunistic additions to Milestone 1. + +### Milestone 2 — safe concurrency + +Before worktrees, add explicit task resources: + +```ts +interface TaskResources { + mode: "read" | "write"; + declaredPaths: string[]; +} +``` + +Scheduling rules: + +- read-only tasks may run together; +- overlapping declared writers serialize; +- an unknown write set is globally exclusive; +- actual changed files are recorded and undeclared writes are flagged. + +Then add one Git worktree per task, retained across developer and verifier stages. +Persist base SHA, worktree path, branch, task commit, and integration status. +Serialize integration into the target branch and run integration verification. +Never copy changed files blindly or delete a failed worktree automatically. + +### Milestone 3 — generic workflow semantics + +- Add explicit stage kinds instead of hard-coded IDs. +- Honor arbitrary assignees through a role registry. +- Define dependency-aware scheduling. +- Validate transition exhaustiveness. +- Make memory policy role/stage generic. + +### Milestone 4 — observability + +- Aggregate Pi usage, cost, and timing events by run/wave/task/stage. +- Show human-readable tool activity derived from arguments. +- Persist bounded lifecycle diagnostics. +- Add explicit progress and pending-message display inspired by Claude Code's + background task lifecycle. + +## 18. Boundaries learned from Claude Code + +Use these patterns, not a direct port: + +- Explicit task lifecycle: running, pending message, stopping, resumed, terminal. +- Independent role tool pools, especially a structurally constrained verifier. +- Research/synthesis before implementation and verification after it. +- Resume through owned transcript/session state, not an untracked prompt. +- Worktree lifecycle that preserves failed work for inspection. + +Do not port: + +- Claude Code's in-process `AgentTool` infrastructure; +- its XML task envelopes as internal state; +- its coordinator as a replacement for deterministic transitions; +- team/mailbox concepts without a demonstrated need; +- assumptions about built-in MCP, permissions UI, or background tasks that Pi + intentionally does not provide. + +## 19. Implementation order + +Keep commits or review units aligned to this order: + +1. **Runtime contract:** package namespace, version preflight, role tool lists, + real-Pi no-model smoke test. +2. **RPC correctness:** strict framing, command IDs/responses, settlement, + process errors, timeouts, fake-Pi tests. +3. **Report correctness:** correlated executions, shared schemas, role validation, + PM wave validation. +4. **Lifecycle correctness:** one prompt owner, stop/steer/resume, session scope, + terminal workflow status. +5. **Synthesis:** typed prior-wave summary and verifier evidence in PM context. +6. **Final verification:** full test/check suite and manual review against the + acceptance criteria. + +Avoid mixing safe-concurrency/worktree work into these commits. + +## 20. Instructions to the implementing agent + +1. Read this document first. +2. Inspect the current implementation before editing; line numbers may have + shifted since this brief was prepared. +3. Preserve unrelated user changes and the three documentation files. +4. Use the installed Pi only for no-model compatibility tests unless explicitly + authorized to spend model tokens. +5. Do not silently broaden compatibility below Pi 0.80.10. +6. Do not accept prose, attempted calls, or failed tool executions as structured + workflow state. +7. Do not add an unowned background prompt as a resume shortcut. +8. Keep Milestone 1 focused. Record later opportunities instead of implementing + them opportunistically. +9. When finished, report changed files, exact test results, opt-in tests skipped, + and any acceptance criterion not met. + +## 21. Primary references + +- Pi RPC documentation: +- Pi extension lifecycle: +- Pi settings and retry ownership: +- Pi SDK guidance: +- Pi upstream repository: +- Pi 0.80.10 RPC client source: + +- Pi 0.80.10 RPC mode source: + +- Pi 0.80.10 tool filtering source: + +- Pi 0.57.1 main source for historical comparison: + diff --git a/package-lock.json b/package-lock.json index dc08058..275cb60 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,10 +8,9 @@ "name": "pi-workflow-orchestrator", "version": "0.1.0", "devDependencies": { - "@mariozechner/pi-ai": "^0.57.1", - "@mariozechner/pi-coding-agent": "^0.57.1", - "@mariozechner/pi-tui": "^0.57.1", - "@sinclair/typebox": "^0.34.49", + "@earendil-works/pi-ai": "0.80.10", + "@earendil-works/pi-coding-agent": "0.80.10", + "@earendil-works/pi-tui": "0.80.10", "@types/bun": "^1.3.14", "@types/node": "^25.9.1", "@typescript-eslint/eslint-plugin": "^8.59.4", @@ -22,6 +21,7 @@ "globals": "^17.6.0", "markdownlint-cli": "^0.42.0", "prettier": "^3.8.3", + "typebox": "1.1.38", "typescript": "^5.9.3", "vitest": "^2.1.9" } @@ -40,42 +40,6 @@ "node": ">=6.0.0" } }, - "node_modules/@anthropic-ai/sdk": { - "version": "0.73.0", - "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.73.0.tgz", - "integrity": "sha512-URURVzhxXGJDGUGFunIOtBlSl7KWvZiAAKY/ttTkZAkXT9bTPqdk2eK0b8qqSxXpikh3QKPnPYpiyX98zf5ebw==", - "dev": true, - "license": "MIT", - "dependencies": { - "json-schema-to-ts": "^3.1.1" - }, - "bin": { - "anthropic-ai-sdk": "bin/cli" - }, - "peerDependencies": { - "zod": "^3.25.0 || ^4.0.0" - }, - "peerDependenciesMeta": { - "zod": { - "optional": true - } - } - }, - "node_modules/@aws-crypto/crc32": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", - "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/util": "^5.2.0", - "@aws-sdk/types": "^3.222.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, "node_modules/@aws-crypto/sha256-browser": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", @@ -92,47 +56,6 @@ "tslib": "^2.6.2" } }, - "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", - "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", - "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/is-array-buffer": "^2.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", - "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/util-buffer-from": "^2.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, "node_modules/@aws-crypto/sha256-js": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", @@ -170,190 +93,122 @@ "tslib": "^2.6.2" } }, - "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", - "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", - "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/is-array-buffer": "^2.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", - "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "node_modules/@aws-sdk/client-bedrock-runtime": { + "version": "3.1048.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-bedrock-runtime/-/client-bedrock-runtime-3.1048.0.tgz", + "integrity": "sha512-u+NT61JZEkRFtpL0CAw1N1dwxnaLgwVXQl/zjJxTGgLyS/jTIdg2SdoEoCTHxgDyCnqa1HEi9QOoE9/pYRNpOQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/util-buffer-from": "^2.2.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.974.11", + "@aws-sdk/credential-provider-node": "^3.972.42", + "@aws-sdk/eventstream-handler-node": "^3.972.16", + "@aws-sdk/middleware-eventstream": "^3.972.12", + "@aws-sdk/middleware-websocket": "^3.972.19", + "@aws-sdk/token-providers": "3.1048.0", + "@aws-sdk/types": "^3.973.8", + "@smithy/core": "^3.24.2", + "@smithy/fetch-http-handler": "^5.4.2", + "@smithy/node-http-handler": "^4.7.2", + "@smithy/types": "^4.14.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/client-bedrock-runtime": { - "version": "3.1007.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-bedrock-runtime/-/client-bedrock-runtime-3.1007.0.tgz", - "integrity": "sha512-X7iWTQAZrCvQH2lfrZktVPfR3jdLPNtI4zkk4NA/vXzW5k8VNgdVuWUSm8cAzIXnhV3YThvDpLhEk87igNyGWQ==", + "node_modules/@aws-sdk/core": { + "version": "3.975.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.975.3.tgz", + "integrity": "sha512-7ur3kCKuvPLqlsZ2XlvnNBVQ7KkpSu6Y6dOTwSPHLrFpTEfZM8isLBJc4cgv96WB7GifeVM436mpycwxBd2vEA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.19", - "@aws-sdk/credential-provider-node": "^3.972.19", - "@aws-sdk/eventstream-handler-node": "^3.972.10", - "@aws-sdk/middleware-eventstream": "^3.972.7", - "@aws-sdk/middleware-host-header": "^3.972.7", - "@aws-sdk/middleware-logger": "^3.972.7", - "@aws-sdk/middleware-recursion-detection": "^3.972.7", - "@aws-sdk/middleware-user-agent": "^3.972.20", - "@aws-sdk/middleware-websocket": "^3.972.12", - "@aws-sdk/region-config-resolver": "^3.972.7", - "@aws-sdk/token-providers": "3.1007.0", - "@aws-sdk/types": "^3.973.5", - "@aws-sdk/util-endpoints": "^3.996.4", - "@aws-sdk/util-user-agent-browser": "^3.972.7", - "@aws-sdk/util-user-agent-node": "^3.973.5", - "@smithy/config-resolver": "^4.4.10", - "@smithy/core": "^3.23.9", - "@smithy/eventstream-serde-browser": "^4.2.11", - "@smithy/eventstream-serde-config-resolver": "^4.3.11", - "@smithy/eventstream-serde-node": "^4.2.11", - "@smithy/fetch-http-handler": "^5.3.13", - "@smithy/hash-node": "^4.2.11", - "@smithy/invalid-dependency": "^4.2.11", - "@smithy/middleware-content-length": "^4.2.11", - "@smithy/middleware-endpoint": "^4.4.23", - "@smithy/middleware-retry": "^4.4.40", - "@smithy/middleware-serde": "^4.2.12", - "@smithy/middleware-stack": "^4.2.11", - "@smithy/node-config-provider": "^4.3.11", - "@smithy/node-http-handler": "^4.4.14", - "@smithy/protocol-http": "^5.3.11", - "@smithy/smithy-client": "^4.12.3", - "@smithy/types": "^4.13.0", - "@smithy/url-parser": "^4.2.11", - "@smithy/util-base64": "^4.3.2", - "@smithy/util-body-length-browser": "^4.2.2", - "@smithy/util-body-length-node": "^4.2.3", - "@smithy/util-defaults-mode-browser": "^4.3.39", - "@smithy/util-defaults-mode-node": "^4.2.42", - "@smithy/util-endpoints": "^3.3.2", - "@smithy/util-middleware": "^4.2.11", - "@smithy/util-retry": "^4.2.11", - "@smithy/util-stream": "^4.5.17", - "@smithy/util-utf8": "^4.2.2", + "@aws-sdk/types": "^3.974.2", + "@aws-sdk/xml-builder": "^3.972.36", + "@aws/lambda-invoke-store": "^0.3.0", + "@smithy/core": "^3.29.4", + "@smithy/signature-v4": "^5.6.5", + "@smithy/types": "^4.16.1", + "bowser": "^2.11.0", "tslib": "^2.6.2" }, "engines": { "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/core": { - "version": "3.973.19", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.19.tgz", - "integrity": "sha512-56KePyOcZnKTWCd89oJS1G6j3HZ9Kc+bh/8+EbvtaCCXdP6T7O7NzCiPuHRhFLWnzXIaXX3CxAz0nI5My9spHQ==", + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.972.59", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.59.tgz", + "integrity": "sha512-Ny5e4Mfh3QPmiAc0AiUe+cbTXDlxkU3Rc+EpWOfyWeWEy6yp7Fa1KmfNeCc+1a8by9zQ9gtohmiQUkMPScF3ng==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.5", - "@aws-sdk/xml-builder": "^3.972.10", - "@smithy/core": "^3.23.9", - "@smithy/node-config-provider": "^4.3.11", - "@smithy/property-provider": "^4.2.11", - "@smithy/protocol-http": "^5.3.11", - "@smithy/signature-v4": "^5.3.11", - "@smithy/smithy-client": "^4.12.3", - "@smithy/types": "^4.13.0", - "@smithy/util-base64": "^4.3.2", - "@smithy/util-middleware": "^4.2.11", - "@smithy/util-utf8": "^4.2.2", + "@aws-sdk/core": "^3.975.3", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.29.4", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.972.17", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.17.tgz", - "integrity": "sha512-MBAMW6YELzE1SdkOniqr51mrjapQUv8JXSGxtwRjQV0mwVDutVsn22OPAUt4RcLRvdiHQmNBDEFP9iTeSVCOlA==", + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.972.61", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.61.tgz", + "integrity": "sha512-8jAjgStl5Ytq4+HF3X/9f+EmRinaRbGRRtQGktlPfBRVx73H+R1y48vIeXerQtYGFaUqkEp3fT6jP854rVO2yQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.19", - "@aws-sdk/types": "^3.973.5", - "@smithy/property-provider": "^4.2.11", - "@smithy/types": "^4.13.0", + "@aws-sdk/core": "^3.975.3", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.29.4", + "@smithy/fetch-http-handler": "^5.6.6", + "@smithy/node-http-handler": "^4.9.6", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.972.19", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.19.tgz", - "integrity": "sha512-9EJROO8LXll5a7eUFqu48k6BChrtokbmgeMWmsH7lBb6lVbtjslUYz/ShLi+SHkYzTomiGBhmzTW7y+H4BxsnA==", + "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/node-http-handler": { + "version": "4.9.7", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.9.7.tgz", + "integrity": "sha512-wCU8HCLjAtAVqxxe0j2xff9LcEPw3yjBbg5IdQDIYFnxnPxbxcSLc7rgex7kqm9L/WYOnJEgaWQlfDkZleozMA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.19", - "@aws-sdk/types": "^3.973.5", - "@smithy/fetch-http-handler": "^5.3.13", - "@smithy/node-http-handler": "^4.4.14", - "@smithy/property-provider": "^4.2.11", - "@smithy/protocol-http": "^5.3.11", - "@smithy/smithy-client": "^4.12.3", - "@smithy/types": "^4.13.0", - "@smithy/util-stream": "^4.5.17", + "@smithy/core": "^3.29.5", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=20.0.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.972.18", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.18.tgz", - "integrity": "sha512-vthIAXJISZnj2576HeyLBj4WTeX+I7PwWeRkbOa0mVX39K13SCGxCgOFuKj2ytm9qTlLOmXe4cdEnroteFtJfw==", + "version": "3.973.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.973.4.tgz", + "integrity": "sha512-e6ZvVsj90aRALf1kHP+J4iqC1496ZpVgqI/+u0LJ5HL7q7ATauGy4gdDvRCP13L1pN/fMiZLah162PGIYkbUVQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.19", - "@aws-sdk/credential-provider-env": "^3.972.17", - "@aws-sdk/credential-provider-http": "^3.972.19", - "@aws-sdk/credential-provider-login": "^3.972.18", - "@aws-sdk/credential-provider-process": "^3.972.17", - "@aws-sdk/credential-provider-sso": "^3.972.18", - "@aws-sdk/credential-provider-web-identity": "^3.972.18", - "@aws-sdk/nested-clients": "^3.996.8", - "@aws-sdk/types": "^3.973.5", - "@smithy/credential-provider-imds": "^4.2.11", - "@smithy/property-provider": "^4.2.11", - "@smithy/shared-ini-file-loader": "^4.4.6", - "@smithy/types": "^4.13.0", + "@aws-sdk/core": "^3.975.3", + "@aws-sdk/credential-provider-env": "^3.972.59", + "@aws-sdk/credential-provider-http": "^3.972.61", + "@aws-sdk/credential-provider-login": "^3.972.66", + "@aws-sdk/credential-provider-process": "^3.972.59", + "@aws-sdk/credential-provider-sso": "^3.973.3", + "@aws-sdk/credential-provider-web-identity": "^3.972.65", + "@aws-sdk/nested-clients": "^3.997.33", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.29.4", + "@smithy/credential-provider-imds": "^4.4.9", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { @@ -361,19 +216,17 @@ } }, "node_modules/@aws-sdk/credential-provider-login": { - "version": "3.972.18", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.18.tgz", - "integrity": "sha512-kINzc5BBxdYBkPZ0/i1AMPMOk5b5QaFNbYMElVw5QTX13AKj6jcxnv/YNl9oW9mg+Y08ti19hh01HhyEAxsSJQ==", + "version": "3.972.66", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.66.tgz", + "integrity": "sha512-g2fsqm87r/nKthLZ0VkkDBElkGg0PvSa8d97HQ6EilMbJTZ6hxa8FxkSZyJfgPfFdZn0TTmkOffQmTSUcAHIng==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.19", - "@aws-sdk/nested-clients": "^3.996.8", - "@aws-sdk/types": "^3.973.5", - "@smithy/property-provider": "^4.2.11", - "@smithy/protocol-http": "^5.3.11", - "@smithy/shared-ini-file-loader": "^4.4.6", - "@smithy/types": "^4.13.0", + "@aws-sdk/core": "^3.975.3", + "@aws-sdk/nested-clients": "^3.997.33", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.29.4", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { @@ -381,23 +234,22 @@ } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.972.19", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.19.tgz", - "integrity": "sha512-yDWQ9dFTr+IMxwanFe7+tbN5++q8psZBjlUwOiCXn1EzANoBgtqBwcpYcHaMGtn0Wlfj4NuXdf2JaEx1lz5RaQ==", + "version": "3.972.70", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.70.tgz", + "integrity": "sha512-3xzvkGdykBunxqh8WudmUpSyLWvIhfI6aBQo1b5rb3mDO5mNLadK+0hiI0qBQBMVynJbfLO+Ajy9dztMwy9O8w==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "^3.972.17", - "@aws-sdk/credential-provider-http": "^3.972.19", - "@aws-sdk/credential-provider-ini": "^3.972.18", - "@aws-sdk/credential-provider-process": "^3.972.17", - "@aws-sdk/credential-provider-sso": "^3.972.18", - "@aws-sdk/credential-provider-web-identity": "^3.972.18", - "@aws-sdk/types": "^3.973.5", - "@smithy/credential-provider-imds": "^4.2.11", - "@smithy/property-provider": "^4.2.11", - "@smithy/shared-ini-file-loader": "^4.4.6", - "@smithy/types": "^4.13.0", + "@aws-sdk/credential-provider-env": "^3.972.59", + "@aws-sdk/credential-provider-http": "^3.972.61", + "@aws-sdk/credential-provider-ini": "^3.973.4", + "@aws-sdk/credential-provider-process": "^3.972.59", + "@aws-sdk/credential-provider-sso": "^3.973.3", + "@aws-sdk/credential-provider-web-identity": "^3.972.65", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.29.4", + "@smithy/credential-provider-imds": "^4.4.9", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { @@ -405,17 +257,16 @@ } }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.972.17", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.17.tgz", - "integrity": "sha512-c8G8wT1axpJDgaP3xzcy+q8Y1fTi9A2eIQJvyhQ9xuXrUZhlCfXbC0vM9bM1CUXiZppFQ1p7g0tuUMvil/gCPg==", + "version": "3.972.59", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.59.tgz", + "integrity": "sha512-DlZF2/MhLlatDdlrIy3CUCpfdbLrKx+3SMjVo+WyHnPpwzkc/M3vwAHw4OVJf7DMvO+4vfRqSCMc/E9I1auN0g==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.19", - "@aws-sdk/types": "^3.973.5", - "@smithy/property-provider": "^4.2.11", - "@smithy/shared-ini-file-loader": "^4.4.6", - "@smithy/types": "^4.13.0", + "@aws-sdk/core": "^3.975.3", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.29.4", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { @@ -423,19 +274,18 @@ } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.972.18", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.18.tgz", - "integrity": "sha512-YHYEfj5S2aqInRt5ub8nDOX8vAxgMvd84wm2Y3WVNfFa/53vOv9T7WOAqXI25qjj3uEcV46xxfqdDQk04h5XQA==", + "version": "3.973.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.973.3.tgz", + "integrity": "sha512-hmdDHoy2G5Es2e8IgelNMYUuSQI6uCIAKZMJ2u2PdKDhxvbk1uWD/g4+R7R5c/tJfKEB1+KjjWiaoCr/S+ZTiQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.19", - "@aws-sdk/nested-clients": "^3.996.8", - "@aws-sdk/token-providers": "3.1005.0", - "@aws-sdk/types": "^3.973.5", - "@smithy/property-provider": "^4.2.11", - "@smithy/shared-ini-file-loader": "^4.4.6", - "@smithy/types": "^4.13.0", + "@aws-sdk/core": "^3.975.3", + "@aws-sdk/nested-clients": "^3.997.33", + "@aws-sdk/token-providers": "3.1088.0", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.29.4", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { @@ -443,18 +293,17 @@ } }, "node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers": { - "version": "3.1005.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1005.0.tgz", - "integrity": "sha512-vMxd+ivKqSxU9bHx5vmAlFKDAkjGotFU56IOkDa5DaTu1WWwbcse0yFHEm9I537oVvodaiwMl3VBwgHfzQ2rvw==", + "version": "3.1088.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1088.0.tgz", + "integrity": "sha512-4ObatWt2qpJg5FBk4LOOKrTQYzaqeewAtdO3r9ZO8lH9YqLtpTzLyIdy0mJ+nVdfYOnqISkKNfmzP22bNDhwyw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.19", - "@aws-sdk/nested-clients": "^3.996.8", - "@aws-sdk/types": "^3.973.5", - "@smithy/property-provider": "^4.2.11", - "@smithy/shared-ini-file-loader": "^4.4.6", - "@smithy/types": "^4.13.0", + "@aws-sdk/core": "^3.975.3", + "@aws-sdk/nested-clients": "^3.997.33", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.29.4", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { @@ -462,18 +311,17 @@ } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.972.18", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.18.tgz", - "integrity": "sha512-OqlEQpJ+J3T5B96qtC1zLLwkBloechP+fezKbCH0sbd2cCc0Ra55XpxWpk/hRj69xAOYtHvoC4orx6eTa4zU7g==", + "version": "3.972.65", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.65.tgz", + "integrity": "sha512-gHQb/Kt0chjk/JQDa/GJDqmAvEuVn8n7z10wK2h0LFM9TUDRkohgOO4aEF+s2sBLM0br7Cl5W6P7phgjrrJvLQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.19", - "@aws-sdk/nested-clients": "^3.996.8", - "@aws-sdk/types": "^3.973.5", - "@smithy/property-provider": "^4.2.11", - "@smithy/shared-ini-file-loader": "^4.4.6", - "@smithy/types": "^4.13.0", + "@aws-sdk/core": "^3.975.3", + "@aws-sdk/nested-clients": "^3.997.33", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.29.4", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { @@ -481,15 +329,15 @@ } }, "node_modules/@aws-sdk/eventstream-handler-node": { - "version": "3.972.10", - "resolved": "https://registry.npmjs.org/@aws-sdk/eventstream-handler-node/-/eventstream-handler-node-3.972.10.tgz", - "integrity": "sha512-g2Z9s6Y4iNh0wICaEqutgYgt/Pmhv5Ev9G3eKGFe2w9VuZDhc76vYdop6I5OocmpHV79d4TuLG+JWg5rQIVDVA==", + "version": "3.972.29", + "resolved": "https://registry.npmjs.org/@aws-sdk/eventstream-handler-node/-/eventstream-handler-node-3.972.29.tgz", + "integrity": "sha512-t3tKQRTVXsI2QNPE3CaNjHl0wRO9Xi3acZkAyti2RQsiFmZ9Gi0kArX2ighlRJ1BtDVuul413gThAgzyTfgmWA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.5", - "@smithy/eventstream-codec": "^4.2.11", - "@smithy/types": "^4.13.0", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.29.4", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { @@ -497,83 +345,15 @@ } }, "node_modules/@aws-sdk/middleware-eventstream": { - "version": "3.972.7", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-eventstream/-/middleware-eventstream-3.972.7.tgz", - "integrity": "sha512-VWndapHYCfwLgPpCb/xwlMKG4imhFzKJzZcKOEioGn7OHY+6gdr0K7oqy1HZgbLa3ACznZ9fku+DzmAi8fUC0g==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.5", - "@smithy/protocol-http": "^5.3.11", - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-host-header": { - "version": "3.972.7", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.7.tgz", - "integrity": "sha512-aHQZgztBFEpDU1BB00VWCIIm85JjGjQW1OG9+98BdmaOpguJvzmXBGbnAiYcciCd+IS4e9BEq664lhzGnWJHgQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.5", - "@smithy/protocol-http": "^5.3.11", - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-logger": { - "version": "3.972.7", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.7.tgz", - "integrity": "sha512-LXhiWlWb26txCU1vcI9PneESSeRp/RYY/McuM4SpdrimQR5NgwaPb4VJCadVeuGWgh6QmqZ6rAKSoL1ob16W6w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.5", - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.972.7", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.7.tgz", - "integrity": "sha512-l2VQdcBcYLzIzykCHtXlbpiVCZ94/xniLIkAj0jpnpjY4xlgZx7f56Ypn+uV1y3gG0tNVytJqo3K9bfMFee7SQ==", + "version": "3.972.24", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-eventstream/-/middleware-eventstream-3.972.24.tgz", + "integrity": "sha512-oykin4mDWxNOuYQ7SF1cHzgYeuFEkF4cdRwgvjFFbIklkx09qIFBiOgsORafG9sXZFO3TayMmQuAQYgADXhI8w==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.5", - "@aws/lambda-invoke-store": "^0.2.2", - "@smithy/protocol-http": "^5.3.11", - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.972.20", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.20.tgz", - "integrity": "sha512-3kNTLtpUdeahxtnJRnj/oIdLAUdzTfr9N40KtxNhtdrq+Q1RPMdCJINRXq37m4t5+r3H70wgC3opW46OzFcZYA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.973.19", - "@aws-sdk/types": "^3.973.5", - "@aws-sdk/util-endpoints": "^3.996.4", - "@smithy/core": "^3.23.9", - "@smithy/protocol-http": "^5.3.11", - "@smithy/types": "^4.13.0", - "@smithy/util-retry": "^4.2.11", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.29.4", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { @@ -581,23 +361,18 @@ } }, "node_modules/@aws-sdk/middleware-websocket": { - "version": "3.972.12", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-websocket/-/middleware-websocket-3.972.12.tgz", - "integrity": "sha512-iyPP6FVDKe/5wy5ojC0akpDFG1vX3FeCUU47JuwN8xfvT66xlEI8qUJZPtN55TJVFzzWZJpWL78eqUE31md08Q==", + "version": "3.972.41", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-websocket/-/middleware-websocket-3.972.41.tgz", + "integrity": "sha512-LSbGvvYmjc4Br9BPYI2dTLnIclmrSiQbahkP4D6nRGVEv4qsCZ8csVuKBPVEEFCVD+EEngGh8ROls6XpumtwMg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.5", - "@aws-sdk/util-format-url": "^3.972.7", - "@smithy/eventstream-codec": "^4.2.11", - "@smithy/eventstream-serde-browser": "^4.2.11", - "@smithy/fetch-http-handler": "^5.3.13", - "@smithy/protocol-http": "^5.3.11", - "@smithy/signature-v4": "^5.3.11", - "@smithy/types": "^4.13.0", - "@smithy/util-base64": "^4.3.2", - "@smithy/util-hex-encoding": "^4.2.2", - "@smithy/util-utf8": "^4.2.2", + "@aws-sdk/core": "^3.975.3", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.29.4", + "@smithy/fetch-http-handler": "^5.6.6", + "@smithy/signature-v4": "^5.6.5", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { @@ -605,132 +380,82 @@ } }, "node_modules/@aws-sdk/nested-clients": { - "version": "3.996.8", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.996.8.tgz", - "integrity": "sha512-6HlLm8ciMW8VzfB80kfIx16PBA9lOa9Dl+dmCBi78JDhvGlx3I7Rorwi5PpVRkL31RprXnYna3yBf6UKkD/PqA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.19", - "@aws-sdk/middleware-host-header": "^3.972.7", - "@aws-sdk/middleware-logger": "^3.972.7", - "@aws-sdk/middleware-recursion-detection": "^3.972.7", - "@aws-sdk/middleware-user-agent": "^3.972.20", - "@aws-sdk/region-config-resolver": "^3.972.7", - "@aws-sdk/types": "^3.973.5", - "@aws-sdk/util-endpoints": "^3.996.4", - "@aws-sdk/util-user-agent-browser": "^3.972.7", - "@aws-sdk/util-user-agent-node": "^3.973.5", - "@smithy/config-resolver": "^4.4.10", - "@smithy/core": "^3.23.9", - "@smithy/fetch-http-handler": "^5.3.13", - "@smithy/hash-node": "^4.2.11", - "@smithy/invalid-dependency": "^4.2.11", - "@smithy/middleware-content-length": "^4.2.11", - "@smithy/middleware-endpoint": "^4.4.23", - "@smithy/middleware-retry": "^4.4.40", - "@smithy/middleware-serde": "^4.2.12", - "@smithy/middleware-stack": "^4.2.11", - "@smithy/node-config-provider": "^4.3.11", - "@smithy/node-http-handler": "^4.4.14", - "@smithy/protocol-http": "^5.3.11", - "@smithy/smithy-client": "^4.12.3", - "@smithy/types": "^4.13.0", - "@smithy/url-parser": "^4.2.11", - "@smithy/util-base64": "^4.3.2", - "@smithy/util-body-length-browser": "^4.2.2", - "@smithy/util-body-length-node": "^4.2.3", - "@smithy/util-defaults-mode-browser": "^4.3.39", - "@smithy/util-defaults-mode-node": "^4.2.42", - "@smithy/util-endpoints": "^3.3.2", - "@smithy/util-middleware": "^4.2.11", - "@smithy/util-retry": "^4.2.11", - "@smithy/util-utf8": "^4.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/region-config-resolver": { - "version": "3.972.7", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.7.tgz", - "integrity": "sha512-/Ev/6AI8bvt4HAAptzSjThGUMjcWaX3GX8oERkB0F0F9x2dLSBdgFDiyrRz3i0u0ZFZFQ1b28is4QhyqXTUsVA==", + "version": "3.997.33", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.33.tgz", + "integrity": "sha512-dVZOroI/r3/ENvqNGgjMPul+jjlz9GddfVusgTXlVjfZj5isibOxecLkGQbRPp8XOuX+RAfjXLFgPkD1JS5xrw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.5", - "@smithy/config-resolver": "^4.4.10", - "@smithy/node-config-provider": "^4.3.11", - "@smithy/types": "^4.13.0", + "@aws-sdk/core": "^3.975.3", + "@aws-sdk/signature-v4-multi-region": "^3.996.41", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.29.4", + "@smithy/fetch-http-handler": "^5.6.6", + "@smithy/node-http-handler": "^4.9.6", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/token-providers": { - "version": "3.1007.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1007.0.tgz", - "integrity": "sha512-kKvVyr53vvVc5k6RbvI6jhafxufxO2SkEw8QeEzJqwOXH/IMY7Cm0IyhnBGdqj80iiIIiIM2jGe7Fn3TIdwdrw==", + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/node-http-handler": { + "version": "4.9.7", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.9.7.tgz", + "integrity": "sha512-wCU8HCLjAtAVqxxe0j2xff9LcEPw3yjBbg5IdQDIYFnxnPxbxcSLc7rgex7kqm9L/WYOnJEgaWQlfDkZleozMA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.19", - "@aws-sdk/nested-clients": "^3.996.8", - "@aws-sdk/types": "^3.973.5", - "@smithy/property-provider": "^4.2.11", - "@smithy/shared-ini-file-loader": "^4.4.6", - "@smithy/types": "^4.13.0", + "@smithy/core": "^3.29.5", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=20.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/types": { - "version": "3.973.5", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.5.tgz", - "integrity": "sha512-hl7BGwDCWsjH8NkZfx+HgS7H2LyM2lTMAI7ba9c8O0KqdBLTdNJivsHpqjg9rNlAlPyREb6DeDRXUl0s8uFdmQ==", + "node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.996.41", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.41.tgz", + "integrity": "sha512-QMUytg+FQMGouc8gHS00KoYih3+N6cqmVI/pQGOIo7Nr7OpQaiXjSYOuL+vsPZ1tymY4LAQ8MYcHJmws5LRxng==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.13.0", + "@aws-sdk/types": "^3.974.2", + "@smithy/signature-v4": "^5.6.5", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/util-endpoints": { - "version": "3.996.4", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.996.4.tgz", - "integrity": "sha512-Hek90FBmd4joCFj+Vc98KLJh73Zqj3s2W56gjAcTkrNLMDI5nIFkG9YpfcJiVI1YlE2Ne1uOQNe+IgQ/Vz2XRA==", + "node_modules/@aws-sdk/token-providers": { + "version": "3.1048.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1048.0.tgz", + "integrity": "sha512-k0y/GcuesuSfWyUM0WamrGyeZmltRYaPbHO82UDA6mZ/doB+FOHKutikPAtSXMn/hDz970cF+iRuuiYO9VEbAA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.5", - "@smithy/types": "^4.13.0", - "@smithy/url-parser": "^4.2.11", - "@smithy/util-endpoints": "^3.3.2", + "@aws-sdk/core": "^3.974.11", + "@aws-sdk/nested-clients": "^3.997.9", + "@aws-sdk/types": "^3.973.8", + "@smithy/core": "^3.24.2", + "@smithy/types": "^4.14.1", "tslib": "^2.6.2" }, "engines": { "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/util-format-url": { - "version": "3.972.7", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-format-url/-/util-format-url-3.972.7.tgz", - "integrity": "sha512-V+PbnWfUl93GuFwsOHsAq7hY/fnm9kElRqR8IexIJr5Rvif9e614X5sGSyz3mVSf1YAZ+VTy63W1/pGdA55zyA==", + "node_modules/@aws-sdk/types": { + "version": "3.974.2", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.974.2.tgz", + "integrity": "sha512-3W6IUtSxFbH6X7Wb7DzGCV5QiFQsd0g8bOfntpmDxQlzBoKWUMBu/JPQR0DwkE+Hpnxd6db1tXbOwdeHddG6cA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.5", - "@smithy/querystring-builder": "^4.2.11", - "@smithy/types": "^4.13.0", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { @@ -738,66 +463,26 @@ } }, "node_modules/@aws-sdk/util-locate-window": { - "version": "3.965.5", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.5.tgz", - "integrity": "sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.972.7", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.7.tgz", - "integrity": "sha512-7SJVuvhKhMF/BkNS1n0QAJYgvEwYbK2QLKBrzDiwQGiTRU6Yf1f3nehTzm/l21xdAOtWSfp2uWSddPnP2ZtsVw==", + "version": "3.965.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.8.tgz", + "integrity": "sha512-uUbMs1cBZPafD0ohUj6EwNf0fPZ534NvBxHox4hjX+0Rxq5paSYUem7+hi833pYrzrcnBATKIYpR02MDXT5M9g==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.5", - "@smithy/types": "^4.13.0", - "bowser": "^2.11.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.973.5", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.973.5.tgz", - "integrity": "sha512-Dyy38O4GeMk7UQ48RupfHif//gqnOPbq/zlvRssc11E2mClT+aUfc3VS2yD8oLtzqO3RsqQ9I3gOBB4/+HjPOw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/middleware-user-agent": "^3.972.20", - "@aws-sdk/types": "^3.973.5", - "@smithy/node-config-provider": "^4.3.11", - "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "engines": { "node": ">=20.0.0" - }, - "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } } }, "node_modules/@aws-sdk/xml-builder": { - "version": "3.972.25", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.25.tgz", - "integrity": "sha512-GH+Kjz4nPKWKHnsiQpnhP1MJdTGIcK4rAka6tzakgjjUkVgNsmPeEbbRAf09SzS1hjGu6duGHCBsxYke0BhHjQ==", + "version": "3.972.36", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.36.tgz", + "integrity": "sha512-RdGmS1GLrtaTOLE1ElSluMldNrpk9Emq6uYs8SS8iHlu5xTAmM9rRkM91o48+rIRryBtyO9t+uLYCoMG6jVMVA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@nodable/entities": "2.1.0", - "@smithy/types": "^4.14.2", - "fast-xml-parser": "5.7.3", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { @@ -805,9 +490,9 @@ } }, "node_modules/@aws/lambda-invoke-store": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.4.tgz", - "integrity": "sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.3.0.tgz", + "integrity": "sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -881,629 +566,632 @@ "dev": true, "license": "MIT" }, - "node_modules/@borewit/text-codec": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.2.2.tgz", - "integrity": "sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", - "cpu": [ - "ppc64" - ], + "node_modules/@earendil-works/pi-ai": { + "version": "0.80.10", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-ai/-/pi-ai-0.80.10.tgz", + "integrity": "sha512-Moe/H8c87yacDGK9dPbWphZNjVsrb3nTrIHycOQJAkFEnY9PYxOOd74+ny44kATfPU9Dm7aTHefar3pZF+UKUA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "aix" - ], + "dependencies": { + "@anthropic-ai/sdk": "0.91.1", + "@aws-sdk/client-bedrock-runtime": "3.1048.0", + "@google/genai": "1.52.0", + "@mistralai/mistralai": "2.2.6", + "@opentelemetry/api": "1.9.0", + "@smithy/node-http-handler": "4.7.3", + "http-proxy-agent": "7.0.2", + "https-proxy-agent": "7.0.6", + "openai": "6.26.0", + "partial-json": "0.1.7", + "typebox": "1.1.38" + }, + "bin": { + "pi-ai": "dist/cli.js" + }, "engines": { - "node": ">=12" + "node": ">=22.19.0" } }, - "node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", - "cpu": [ - "arm" - ], + "node_modules/@earendil-works/pi-ai/node_modules/@anthropic-ai/sdk": { + "version": "0.91.1", + "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.91.1.tgz", + "integrity": "sha512-LAmu761tSN9r66ixvmciswUj/ZC+1Q4iAfpedTfSVLeswRwnY3n2Nb6Tsk+cLPP28aLOPWeMgIuTuCcMC6W/iw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" + "dependencies": { + "json-schema-to-ts": "^3.1.1" + }, + "bin": { + "anthropic-ai-sdk": "bin/cli" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } } }, - "node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", - "cpu": [ - "arm64" - ], + "node_modules/@earendil-works/pi-ai/node_modules/@mistralai/mistralai": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-2.2.6.tgz", + "integrity": "sha512-W8pX7zHxjJvMIpw8JMxeJEleapXX0Q9NPszdNzqkM3MIEoIGPObdodujj+WHteXEvGfaP/AMwlNyRfEzSY6dQQ==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.40.0", + "ws": "^8.18.0", + "zod": "^3.25.0 || ^4.0.0", + "zod-to-json-schema": "^3.25.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.9.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + } + } + }, + "node_modules/@earendil-works/pi-coding-agent": { + "version": "0.80.10", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-coding-agent/-/pi-coding-agent-0.80.10.tgz", + "integrity": "sha512-aL4apbupCHiVLSXASXvRzH4Q2vmtfrDa+0s909CJuVu/GgGylbDzr7oyF1mPmip5E+VxYYxKWmph4hV04wUcQg==", + "dev": true, + "hasShrinkwrap": true, + "license": "MIT", + "dependencies": { + "@earendil-works/pi-agent-core": "^0.80.10", + "@earendil-works/pi-ai": "^0.80.10", + "@earendil-works/pi-tui": "^0.80.10", + "@silvia-odwyer/photon-node": "0.3.4", + "chalk": "5.6.2", + "cross-spawn": "7.0.6", + "diff": "8.0.4", + "glob": "13.0.6", + "highlight.js": "10.7.3", + "hosted-git-info": "9.0.3", + "ignore": "7.0.5", + "jiti": "2.7.0", + "minimatch": "10.2.5", + "proper-lockfile": "4.1.2", + "semver": "7.8.0", + "typebox": "1.1.38", + "undici": "8.5.0", + "yaml": "2.9.0" + }, + "bin": { + "pi": "dist/cli.js" + }, "engines": { - "node": ">=12" + "node": ">=22.19.0" + }, + "optionalDependencies": { + "@mariozechner/clipboard": "0.3.9" } }, - "node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", - "cpu": [ - "x64" - ], + "node_modules/@earendil-works/pi-coding-agent/node_modules/@anthropic-ai/sdk": { + "version": "0.91.1", + "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.91.1.tgz", + "integrity": "sha512-LAmu761tSN9r66ixvmciswUj/ZC+1Q4iAfpedTfSVLeswRwnY3n2Nb6Tsk+cLPP28aLOPWeMgIuTuCcMC6W/iw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" + "dependencies": { + "json-schema-to-ts": "^3.1.1" + }, + "bin": { + "anthropic-ai-sdk": "bin/cli" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } } }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", - "cpu": [ - "arm64" - ], + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-crypto/crc32": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=12" + "node": ">=16.0.0" } }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", - "cpu": [ - "x64" - ], + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" } }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", - "cpu": [ - "arm64" - ], + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=12" + "node": ">=16.0.0" } }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", - "cpu": [ - "x64" - ], + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" } }, - "node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", - "cpu": [ - "arm" - ], + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" } }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", - "cpu": [ - "arm64" - ], + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/client-bedrock-runtime": { + "version": "3.1048.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-bedrock-runtime/-/client-bedrock-runtime-3.1048.0.tgz", + "integrity": "sha512-u+NT61JZEkRFtpL0CAw1N1dwxnaLgwVXQl/zjJxTGgLyS/jTIdg2SdoEoCTHxgDyCnqa1HEi9QOoE9/pYRNpOQ==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.974.11", + "@aws-sdk/credential-provider-node": "^3.972.42", + "@aws-sdk/eventstream-handler-node": "^3.972.16", + "@aws-sdk/middleware-eventstream": "^3.972.12", + "@aws-sdk/middleware-websocket": "^3.972.19", + "@aws-sdk/token-providers": "3.1048.0", + "@aws-sdk/types": "^3.973.8", + "@smithy/core": "^3.24.2", + "@smithy/fetch-http-handler": "^5.4.2", + "@smithy/node-http-handler": "^4.7.2", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=12" + "node": ">=20.0.0" } }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", - "cpu": [ - "ia32" - ], + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/core": { + "version": "3.974.11", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.974.11.tgz", + "integrity": "sha512-QpnINq5FZH6EOaDEkmHdT7eUunbvD27pDNQypaWjFyYz7Zl1q3UCMQErBZxpmfGfI7MvI2TlK8KTkgNpv8b1ug==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.8", + "@aws-sdk/xml-builder": "^3.972.24", + "@aws/lambda-invoke-store": "^0.2.2", + "@smithy/core": "^3.24.2", + "@smithy/signature-v4": "^5.4.2", + "@smithy/types": "^4.14.1", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=12" + "node": ">=20.0.0" } }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", - "cpu": [ - "loong64" - ], + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-env": { + "version": "3.972.37", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.37.tgz", + "integrity": "sha512-/jpPvEh6f7ntmIzf7dNxoNX6Q8vt8UpesCjbW6mFfk4V1NW6bIy9qxcQ6WbA8As5yQhsZOe+xeNd4xHX8kdY2Q==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.11", + "@aws-sdk/types": "^3.973.8", + "@smithy/core": "^3.24.2", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=12" + "node": ">=20.0.0" } }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", - "cpu": [ - "mips64el" - ], + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.972.39", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.39.tgz", + "integrity": "sha512-pIgTpisWyWg7X1bUbzSjuUYosYTD0Ghz2M0hkSTmb3a6i3qV3uU+NYJPI/E2XSC0HcsZh5rsLPzeXrkb2DS0Cg==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.11", + "@aws-sdk/types": "^3.973.8", + "@smithy/core": "^3.24.2", + "@smithy/fetch-http-handler": "^5.4.2", + "@smithy/node-http-handler": "^4.7.2", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=12" + "node": ">=20.0.0" } }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", - "cpu": [ - "ppc64" - ], + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.972.41", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.41.tgz", + "integrity": "sha512-u2tyjaxJJzW8UtW4SM1ZcPMDwO6y+kV+llvou+Adts0FAKyzes5jG4izQN+KX3yE8ZROpS5y1LJ//xL2iSf76w==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.11", + "@aws-sdk/credential-provider-env": "^3.972.37", + "@aws-sdk/credential-provider-http": "^3.972.39", + "@aws-sdk/credential-provider-login": "^3.972.41", + "@aws-sdk/credential-provider-process": "^3.972.37", + "@aws-sdk/credential-provider-sso": "^3.972.41", + "@aws-sdk/credential-provider-web-identity": "^3.972.41", + "@aws-sdk/nested-clients": "^3.997.9", + "@aws-sdk/types": "^3.973.8", + "@smithy/core": "^3.24.2", + "@smithy/credential-provider-imds": "^4.3.2", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=12" + "node": ">=20.0.0" } }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", - "cpu": [ - "riscv64" - ], + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-login": { + "version": "3.972.41", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.41.tgz", + "integrity": "sha512-0LBitxXiAiaE5nlFPfpNIww/8FRY/I7WIndWsc9GmNFOM7cE1wNpVNQEGEk9Outg5l8xl+3vybxFyUy4l9q/LQ==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.11", + "@aws-sdk/nested-clients": "^3.997.9", + "@aws-sdk/types": "^3.973.8", + "@smithy/core": "^3.24.2", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=12" + "node": ">=20.0.0" } }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", - "cpu": [ - "s390x" - ], + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.972.42", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.42.tgz", + "integrity": "sha512-D4oon2zbqqsWOJUM99Gm3/ZyJ0IJvTXVN3PyloGb3kQEyI36fjCZheZj422lAgTWWd6TSHgiImLt3RIaLdv3dQ==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "^3.972.37", + "@aws-sdk/credential-provider-http": "^3.972.39", + "@aws-sdk/credential-provider-ini": "^3.972.41", + "@aws-sdk/credential-provider-process": "^3.972.37", + "@aws-sdk/credential-provider-sso": "^3.972.41", + "@aws-sdk/credential-provider-web-identity": "^3.972.41", + "@aws-sdk/types": "^3.973.8", + "@smithy/core": "^3.24.2", + "@smithy/credential-provider-imds": "^4.3.2", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=12" + "node": ">=20.0.0" } }, - "node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", - "cpu": [ - "x64" - ], + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-process": { + "version": "3.972.37", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.37.tgz", + "integrity": "sha512-7nVaHBUaWIddASYfVaA9O4D5ZVjewU3sCol9WqZPGfW0nR+0WqE0xHZnD/U2L33PlOB8KNXGKZ6wOES/QijKzg==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.11", + "@aws-sdk/types": "^3.973.8", + "@smithy/core": "^3.24.2", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=12" + "node": ">=20.0.0" } }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", - "cpu": [ - "x64" - ], + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.972.41", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.41.tgz", + "integrity": "sha512-IOWAWEHe5LkjSKkkUUX9ciV6Y1scHTsnfEkdt5yyC4Slrc7AGbkLPrpntjqh18ksJAMOaVhoBsO8p2WyTcY2wQ==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.11", + "@aws-sdk/nested-clients": "^3.997.9", + "@aws-sdk/token-providers": "3.1048.0", + "@aws-sdk/types": "^3.973.8", + "@smithy/core": "^3.24.2", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=12" + "node": ">=20.0.0" } }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", - "cpu": [ - "x64" - ], + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.972.41", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.41.tgz", + "integrity": "sha512-mbACk9Yypa8nm4iGZLs0PofOXEcTDOUw6wDnsPXNDNSd2WNXs1tSo+6nc/fh0jLYdfVZThhBL98PHW4aXFsG5A==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.11", + "@aws-sdk/nested-clients": "^3.997.9", + "@aws-sdk/types": "^3.973.8", + "@smithy/core": "^3.24.2", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=12" + "node": ">=20.0.0" } }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", - "cpu": [ - "x64" - ], + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/eventstream-handler-node": { + "version": "3.972.16", + "resolved": "https://registry.npmjs.org/@aws-sdk/eventstream-handler-node/-/eventstream-handler-node-3.972.16.tgz", + "integrity": "sha512-yedpPgKftqjU5SlPFHfqWpOw6xSCRieWRG1euWOlXn4WJxt2VX92VprCa2PpSOXjVCAeK6dTjW9eJRXVig9yGA==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.8", + "@smithy/core": "^3.24.2", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=12" + "node": ">=20.0.0" } }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", - "cpu": [ - "arm64" - ], + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/middleware-eventstream": { + "version": "3.972.12", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-eventstream/-/middleware-eventstream-3.972.12.tgz", + "integrity": "sha512-tHTHHCHNrq6XklQvlzHBDJG4Iuhh7NVPRdtmvP+nHFA+5sxPlIDzlAHHgfoYHGvT3NXP1yVP/L5c3opUn6T3Qg==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.8", + "@smithy/core": "^3.24.2", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=12" + "node": ">=20.0.0" } }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", - "cpu": [ - "ia32" - ], + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/middleware-websocket": { + "version": "3.972.19", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-websocket/-/middleware-websocket-3.972.19.tgz", + "integrity": "sha512-mkEhOGYozqKQkbFaVrjwr0faiwwZza1v5/jSY6Tucm3bD+uKTazIUH/4Yo6aMnQD2ua2W9cMP6s8mvwTcjtqHw==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.11", + "@aws-sdk/types": "^3.973.8", + "@smithy/core": "^3.24.2", + "@smithy/fetch-http-handler": "^5.4.2", + "@smithy/signature-v4": "^5.4.2", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=12" + "node": ">= 14.0.0" } }, - "node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", - "cpu": [ - "x64" - ], + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/nested-clients": { + "version": "3.997.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.9.tgz", + "integrity": "sha512-jPR3rnmRI4hWYyzfmTGBr7NblMp8QYYeflHXba1H6+7CGrWVqWKQzaXFQ4qbExqPRsXN3T3L3JxFhr6aouXUGQ==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.974.11", + "@aws-sdk/signature-v4-multi-region": "^3.996.27", + "@aws-sdk/types": "^3.973.8", + "@smithy/core": "^3.24.2", + "@smithy/fetch-http-handler": "^5.4.2", + "@smithy/node-http-handler": "^4.7.2", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=12" + "node": ">=20.0.0" } }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", - "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.996.27", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.27.tgz", + "integrity": "sha512-0Phbz4t6HI3D3skxvG2uI+VWU034/nSIw1T8d+FPzzQG9EQTrw94o9mOKO2Gv3n3Oc8P7JD7RAUxkoneLWv5Eg==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "eslint-visitor-keys": "^3.4.3" + "@aws-sdk/types": "^3.973.8", + "@smithy/core": "^3.24.2", + "@smithy/signature-v4": "^5.4.2", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + "node": ">=20.0.0" } }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", - "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/token-providers": { + "version": "3.1048.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1048.0.tgz", + "integrity": "sha512-k0y/GcuesuSfWyUM0WamrGyeZmltRYaPbHO82UDA6mZ/doB+FOHKutikPAtSXMn/hDz970cF+iRuuiYO9VEbAA==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.11", + "@aws-sdk/nested-clients": "^3.997.9", + "@aws-sdk/types": "^3.973.8", + "@smithy/core": "^3.24.2", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + "node": ">=20.0.0" } }, - "node_modules/@eslint/config-array": { - "version": "0.21.2", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", - "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/types": { + "version": "3.973.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.8.tgz", + "integrity": "sha512-gjlAdtHMbtR9X5iIhVUvbVcy55KnznpC6bkDUWW9z915bi0ckdUr5cjf16Kp6xq0bP5HBD2xzgbL9F9Quv5vUw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/object-schema": "^2.1.7", - "debug": "^4.3.1", - "minimatch": "^3.1.5" + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=20.0.0" } }, - "node_modules/@eslint/config-array/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@eslint/config-array/node_modules/brace-expansion": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", - "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/util-locate-window": { + "version": "3.965.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.5.tgz", + "integrity": "sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" } }, - "node_modules/@eslint/config-array/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/xml-builder": { + "version": "3.972.24", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.24.tgz", + "integrity": "sha512-V8z5YcDPfsvzrBlj0xR1vhRtocblhYbqdreCJB/voGd4Sr5zjNAeWxexbnqVtskTJe0vFb5KMqbSL++ePl+zRw==", "dev": true, - "license": "ISC", + "license": "Apache-2.0", "dependencies": { - "brace-expansion": "^1.1.7" + "@nodable/entities": "2.1.0", + "@smithy/types": "^4.14.1", + "fast-xml-parser": "5.7.3", + "tslib": "^2.6.2" }, "engines": { - "node": "*" + "node": ">=20.0.0" } }, - "node_modules/@eslint/config-helpers": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", - "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws/lambda-invoke-store": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.4.tgz", + "integrity": "sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==", "dev": true, "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.17.0" - }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=18.0.0" } }, - "node_modules/@eslint/core": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", - "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "node_modules/@earendil-works/pi-coding-agent/node_modules/@babel/runtime": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, + "license": "MIT", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=6.9.0" } }, - "node_modules/@eslint/eslintrc": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", - "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", + "node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-agent-core": { + "version": "0.80.10", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-agent-core/-/pi-agent-core-0.80.10.tgz", "dev": true, "license": "MIT", "dependencies": { - "ajv": "^6.14.0", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.1", - "minimatch": "^3.1.5", - "strip-json-comments": "^3.1.1" + "@earendil-works/pi-ai": "^0.80.10", + "ignore": "7.0.5", + "typebox": "1.1.38", + "yaml": "2.9.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">=22.19.0" } }, - "node_modules/@eslint/eslintrc/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", - "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-ai": { + "version": "0.80.10", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-ai/-/pi-ai-0.80.10.tgz", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" + "@anthropic-ai/sdk": "0.91.1", + "@aws-sdk/client-bedrock-runtime": "3.1048.0", + "@google/genai": "1.52.0", + "@mistralai/mistralai": "2.2.6", + "@opentelemetry/api": "1.9.0", + "@smithy/node-http-handler": "4.7.3", + "http-proxy-agent": "7.0.2", + "https-proxy-agent": "7.0.6", + "openai": "6.26.0", + "partial-json": "0.1.7", + "typebox": "1.1.38" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@eslint/eslintrc/node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" + "bin": { + "pi-ai": "./dist/cli.js" }, "engines": { - "node": "*" + "node": ">=22.19.0" } }, - "node_modules/@eslint/js": { - "version": "9.39.4", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", - "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", + "node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-tui": { + "version": "0.80.10", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-tui/-/pi-tui-0.80.10.tgz", "dev": true, "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - } - }, - "node_modules/@eslint/object-schema": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", - "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/plugin-kit": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", - "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", - "dev": true, - "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.17.0", - "levn": "^0.4.1" + "get-east-asian-width": "1.6.0", + "marked": "18.0.5" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=22.19.0" } }, - "node_modules/@google/genai": { - "version": "1.44.0", - "resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.44.0.tgz", - "integrity": "sha512-kRt9ZtuXmz+tLlcNntN/VV4LRdpl6ZOu5B1KbfNgfR65db15O6sUQcwnwLka8sT/V6qysD93fWrgJHF2L7dA9A==", + "node_modules/@earendil-works/pi-coding-agent/node_modules/@google/genai": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.52.0.tgz", + "integrity": "sha512-gwSvbpiN/17O9TbsqSsE/OzZcpv5Fo4RQjdngGgogtuB9RsyJ8ZHhX5KjHj1bp5N9snN2eK8LDGXSaWW2hof8Q==", "dev": true, + "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { "google-auth-library": "^10.3.0", @@ -1523,144 +1211,10 @@ } } }, - "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node": { - "version": "0.16.7", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", - "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.4.0" - }, - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", - "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@isaacs/balanced-match": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", - "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@isaacs/brace-expansion": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.1.tgz", - "integrity": "sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@isaacs/balanced-match": "^4.0.1" - }, - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@isaacs/cliui": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-9.0.0.tgz", - "integrity": "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@mariozechner/clipboard": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard/-/clipboard-0.3.2.tgz", - "integrity": "sha512-IHQpksNjo7EAtGuHFU+tbWDp5LarH3HU/8WiB9O70ZEoBPHOg0/6afwSLK0QyNMMmx4Bpi/zl6+DcBXe95nWYA==", + "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard/-/clipboard-0.3.9.tgz", + "integrity": "sha512-ABnA53mdfkGZwOFUdZNv2S0CWGO/EIuPj8Vv9xmBFmSYg/qFc7ihO6q5FcQjvoE67kZpWkEc4AhD6B/os04yuA==", "dev": true, "license": "MIT", "optional": true, @@ -1668,22 +1222,22 @@ "node": ">= 10" }, "optionalDependencies": { - "@mariozechner/clipboard-darwin-arm64": "0.3.2", - "@mariozechner/clipboard-darwin-universal": "0.3.2", - "@mariozechner/clipboard-darwin-x64": "0.3.2", - "@mariozechner/clipboard-linux-arm64-gnu": "0.3.2", - "@mariozechner/clipboard-linux-arm64-musl": "0.3.2", - "@mariozechner/clipboard-linux-riscv64-gnu": "0.3.2", - "@mariozechner/clipboard-linux-x64-gnu": "0.3.2", - "@mariozechner/clipboard-linux-x64-musl": "0.3.2", - "@mariozechner/clipboard-win32-arm64-msvc": "0.3.2", - "@mariozechner/clipboard-win32-x64-msvc": "0.3.2" - } - }, - "node_modules/@mariozechner/clipboard-darwin-arm64": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-arm64/-/clipboard-darwin-arm64-0.3.2.tgz", - "integrity": "sha512-uBf6K7Je1ihsgvmWxA8UCGCeI+nbRVRXoarZdLjl6slz94Zs1tNKFZqx7aCI5O1i3e0B6ja82zZ06BWrl0MCVw==", + "@mariozechner/clipboard-darwin-arm64": "0.3.9", + "@mariozechner/clipboard-darwin-universal": "0.3.9", + "@mariozechner/clipboard-darwin-x64": "0.3.9", + "@mariozechner/clipboard-linux-arm64-gnu": "0.3.9", + "@mariozechner/clipboard-linux-arm64-musl": "0.3.9", + "@mariozechner/clipboard-linux-riscv64-gnu": "0.3.9", + "@mariozechner/clipboard-linux-x64-gnu": "0.3.9", + "@mariozechner/clipboard-linux-x64-musl": "0.3.9", + "@mariozechner/clipboard-win32-arm64-msvc": "0.3.9", + "@mariozechner/clipboard-win32-x64-msvc": "0.3.9" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-darwin-arm64": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-arm64/-/clipboard-darwin-arm64-0.3.9.tgz", + "integrity": "sha512-BfgV7vCEWZwJwZJw03r6bP5+tf0iI/ANuQYCxi9RNn7FrWB3yzGuMKCrNLRl6V761vXRdL8+OqZ0wd4TqlsNOQ==", "cpu": [ "arm64" ], @@ -1697,10 +1251,10 @@ "node": ">= 10" } }, - "node_modules/@mariozechner/clipboard-darwin-universal": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-universal/-/clipboard-darwin-universal-0.3.2.tgz", - "integrity": "sha512-mxSheKTW2U9LsBdXy0SdmdCAE5HqNS9QUmpNHLnfJ+SsbFKALjEZc5oRrVMXxGQSirDvYf5bjmRyT0QYYonnlg==", + "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-darwin-universal": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-universal/-/clipboard-darwin-universal-0.3.9.tgz", + "integrity": "sha512-BGGR4iA9Z2shAjI65eI5xtyb3LYNlDW9X3gxKxDbqtbnREohsrqznov6zpKoIrsRWpzlYVEdKphS7ksJ0/ndSQ==", "dev": true, "license": "MIT", "optional": true, @@ -1711,10 +1265,10 @@ "node": ">= 10" } }, - "node_modules/@mariozechner/clipboard-darwin-x64": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-x64/-/clipboard-darwin-x64-0.3.2.tgz", - "integrity": "sha512-U1BcVEoidvwIp95+HJswSW+xr28EQiHR7rZjH6pn8Sja5yO4Yoe3yCN0Zm8Lo72BbSOK/fTSq0je7CJpaPCspg==", + "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-darwin-x64": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-x64/-/clipboard-darwin-x64-0.3.9.tgz", + "integrity": "sha512-4kURmCbS6nt8uYhtmWpUcJWyPHfmAr5dTpXD1nO3pIfa+TSQ9DbrGOYCKH+aEFW47XhQ4Vp8ZTszie+wfFvDKg==", "cpu": [ "x64" ], @@ -1728,14 +1282,17 @@ "node": ">= 10" } }, - "node_modules/@mariozechner/clipboard-linux-arm64-gnu": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-arm64-gnu/-/clipboard-linux-arm64-gnu-0.3.2.tgz", - "integrity": "sha512-BsinwG3yWTIjdgNCxsFlip7LkfwPk+ruw/aFCXHUg/fb5XC/Ksp+YMQ7u0LUtiKzIv/7LMXgZInJQH6gxbAaqQ==", + "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-linux-arm64-gnu": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-arm64-gnu/-/clipboard-linux-arm64-gnu-0.3.9.tgz", + "integrity": "sha512-g59OkUGP2DDfCOIKypHeYgv2M55u/cKvXa5dSxFbEJ34XvIQMdcVmpKCkGUro3ZgefXiGVdwguvTMQGpHWzIXw==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1745,14 +1302,17 @@ "node": ">= 10" } }, - "node_modules/@mariozechner/clipboard-linux-arm64-musl": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-arm64-musl/-/clipboard-linux-arm64-musl-0.3.2.tgz", - "integrity": "sha512-0/Gi5Xq2V6goXBop19ePoHvXsmJD9SzFlO3S+d6+T2b+BlPcpOu3Oa0wTjl+cZrLAAEzA86aPNBI+VVAFDFPKw==", + "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-linux-arm64-musl": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-arm64-musl/-/clipboard-linux-arm64-musl-0.3.9.tgz", + "integrity": "sha512-AGuJdgKsmJdm4Pych7kv3sqe591ERRaAHW3xjLooiFzn8J+PxUyof++7YZrB5Y5tpnTO+K18Og3taj2NpluCRQ==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1762,14 +1322,17 @@ "node": ">= 10" } }, - "node_modules/@mariozechner/clipboard-linux-riscv64-gnu": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-riscv64-gnu/-/clipboard-linux-riscv64-gnu-0.3.2.tgz", - "integrity": "sha512-2AFFiXB24qf0zOZsxI1GJGb9wQGlOJyN6UwoXqmKS3dpQi/l6ix30IzDDA4c4ZcCcx4D+9HLYXhC1w7Sov8pXA==", + "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-linux-riscv64-gnu": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-riscv64-gnu/-/clipboard-linux-riscv64-gnu-0.3.9.tgz", + "integrity": "sha512-DXBEAiuMpk7dhS1a9NzNxVAFi1vaKoPu7rQNgY8LIDLGrK3lnIp3nT10DUum+PKVJoJppIP+NAA8IZe4DMNDPw==", "cpu": [ "riscv64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1779,14 +1342,17 @@ "node": ">= 10" } }, - "node_modules/@mariozechner/clipboard-linux-x64-gnu": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-x64-gnu/-/clipboard-linux-x64-gnu-0.3.2.tgz", - "integrity": "sha512-v6fVnsn7WMGg73Dab8QMwyFce7tzGfgEixKgzLP8f1GJqkJZi5zO4k4FOHzSgUufgLil63gnxvMpjWkgfeQN7A==", + "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-linux-x64-gnu": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-x64-gnu/-/clipboard-linux-x64-gnu-0.3.9.tgz", + "integrity": "sha512-WORrMLd6EpElEME7JRKfSaY34nW1P5LbdgK5YNCS1ncG2LqmITsSMEJ8nh2mpvxb3TxqbOOKgY7k9eMJYlW9Mw==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1796,14 +1362,17 @@ "node": ">= 10" } }, - "node_modules/@mariozechner/clipboard-linux-x64-musl": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-x64-musl/-/clipboard-linux-x64-musl-0.3.2.tgz", - "integrity": "sha512-xVUtnoMQ8v2JVyfJLKKXACA6avdnchdbBkTsZs8BgJQo29qwCp5NIHAUO8gbJ40iaEGToW5RlmVk2M9V0HsHEw==", + "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-linux-x64-musl": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-x64-musl/-/clipboard-linux-x64-musl-0.3.9.tgz", + "integrity": "sha512-/DHn+1DrfL6oRaPPWXaOKvonFFrni666fxd+zFqiQEfvBH0tsHVWjq9iqBk0oDp0qaPA72lIMy5BptxISBEhZQ==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1813,10 +1382,10 @@ "node": ">= 10" } }, - "node_modules/@mariozechner/clipboard-win32-arm64-msvc": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-win32-arm64-msvc/-/clipboard-win32-arm64-msvc-0.3.2.tgz", - "integrity": "sha512-AEgg95TNi8TGgak2wSXZkXKCvAUTjWoU1Pqb0ON7JHrX78p616XUFNTJohtIon3e0w6k0pYPZeCuqRCza/Tqeg==", + "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-win32-arm64-msvc": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-win32-arm64-msvc/-/clipboard-win32-arm64-msvc-0.3.9.tgz", + "integrity": "sha512-O5FHD3ErkMwMhNzAfu3ggy0ug4z7btZuoQgwwxlzPrwV2bxlD6WDpqBY4NCgICAgZdDKdp+loUEKVAVt8aYnhQ==", "cpu": [ "arm64" ], @@ -1830,10 +1399,10 @@ "node": ">= 10" } }, - "node_modules/@mariozechner/clipboard-win32-x64-msvc": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-win32-x64-msvc/-/clipboard-win32-x64-msvc-0.3.2.tgz", - "integrity": "sha512-tGRuYpZwDOD7HBrCpyRuhGnHHSCknELvqwKKUG4JSfSB7JIU7LKRh6zx6fMUOQd8uISK35TjFg5UcNih+vJhFA==", + "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-win32-x64-msvc": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-win32-x64-msvc/-/clipboard-win32-x64-msvc-0.3.9.tgz", + "integrity": "sha512-ihQC3EufqEY81vhXBgVBtK4prL+wc62zJsSvxrgz7K1hsdt6OObz6v9p3Rn1OG3GJksTTKMJF0u/guMISHPhSA==", "cpu": [ "x64" ], @@ -1847,211 +1416,28 @@ "node": ">= 10" } }, - "node_modules/@mariozechner/jiti": { - "version": "2.6.5", - "resolved": "https://registry.npmjs.org/@mariozechner/jiti/-/jiti-2.6.5.tgz", - "integrity": "sha512-faGUlTcXka5l7rv0lP3K3vGW/ejRuOS24RR2aSFWREUQqzjgdsuWNo/IiPqL3kWRGt6Ahl2+qcDAwtdeWeuGUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "std-env": "^3.10.0", - "yoctocolors": "^2.1.2" - }, - "bin": { - "jiti": "lib/jiti-cli.mjs" - } - }, - "node_modules/@mariozechner/pi-agent-core": { - "version": "0.57.1", - "resolved": "https://registry.npmjs.org/@mariozechner/pi-agent-core/-/pi-agent-core-0.57.1.tgz", - "integrity": "sha512-WXsBbkNWOObFGHkhixaT8GXJpHDd3+fn8QntYF+4R8Sa9WB90ENXWidO6b7vcKX+JX0jjO5dIsQxmzosARJKlg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@mariozechner/pi-ai": "^0.57.1" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@mariozechner/pi-ai": { - "version": "0.57.1", - "resolved": "https://registry.npmjs.org/@mariozechner/pi-ai/-/pi-ai-0.57.1.tgz", - "integrity": "sha512-Bd/J4a3YpdzJVyHLih0vDSdB0QPL4ti0XsAwtHOK/8eVhB0fHM1CpcgIrcBFJ23TMcKXMi0qamz18ERfp8tmgg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@anthropic-ai/sdk": "^0.73.0", - "@aws-sdk/client-bedrock-runtime": "^3.983.0", - "@google/genai": "^1.40.0", - "@mistralai/mistralai": "1.14.1", - "@sinclair/typebox": "^0.34.41", - "ajv": "^8.17.1", - "ajv-formats": "^3.0.1", - "chalk": "^5.6.2", - "openai": "6.26.0", - "partial-json": "^0.1.7", - "proxy-agent": "^6.5.0", - "undici": "^7.19.1", - "zod-to-json-schema": "^3.24.6" - }, - "bin": { - "pi-ai": "dist/cli.js" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@mariozechner/pi-ai/node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/@mariozechner/pi-ai/node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@mariozechner/pi-ai/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" - }, - "node_modules/@mariozechner/pi-coding-agent": { - "version": "0.57.1", - "resolved": "https://registry.npmjs.org/@mariozechner/pi-coding-agent/-/pi-coding-agent-0.57.1.tgz", - "integrity": "sha512-u5MQEduj68rwVIsRsqrWkJYiJCyPph/a6bMoJAQKo1sb+Pc17Y/ojwa+wGssnUMjEB38AQKofWTVe8NFEpSWNw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@mariozechner/jiti": "^2.6.2", - "@mariozechner/pi-agent-core": "^0.57.1", - "@mariozechner/pi-ai": "^0.57.1", - "@mariozechner/pi-tui": "^0.57.1", - "@silvia-odwyer/photon-node": "^0.3.4", - "chalk": "^5.5.0", - "cli-highlight": "^2.1.11", - "diff": "^8.0.2", - "extract-zip": "^2.0.1", - "file-type": "^21.1.1", - "glob": "^13.0.1", - "hosted-git-info": "^9.0.2", - "ignore": "^7.0.5", - "marked": "^15.0.12", - "minimatch": "^10.2.3", - "proper-lockfile": "^4.1.2", - "strip-ansi": "^7.1.0", - "undici": "^7.19.1", - "yaml": "^2.8.2" - }, - "bin": { - "pi": "dist/cli.js" - }, - "engines": { - "node": ">=20.6.0" - }, - "optionalDependencies": { - "@mariozechner/clipboard": "^0.3.2" - } - }, - "node_modules/@mariozechner/pi-coding-agent/node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@mariozechner/pi-coding-agent/node_modules/glob": { - "version": "13.0.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", - "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "minimatch": "^10.2.2", - "minipass": "^7.1.3", - "path-scurry": "^2.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@mariozechner/pi-tui": { - "version": "0.57.1", - "resolved": "https://registry.npmjs.org/@mariozechner/pi-tui/-/pi-tui-0.57.1.tgz", - "integrity": "sha512-cjoRghLbeAHV0tTJeHgZXaryUi5zzBZofeZ7uJun1gztnckLLRjoVeaPTujNlc5BIfyKvFqhh1QWCZng/MXlpg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/mime-types": "^2.1.4", - "chalk": "^5.5.0", - "get-east-asian-width": "^1.3.0", - "marked": "^15.0.12", - "mime-types": "^3.0.1" - }, - "engines": { - "node": ">=20.0.0" - }, - "optionalDependencies": { - "koffi": "^2.9.0" - } - }, - "node_modules/@mariozechner/pi-tui/node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@mistralai/mistralai": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-1.14.1.tgz", - "integrity": "sha512-IiLmmZFCCTReQgPAT33r7KQ1nYo5JPdvGkrkZqA8qQ2qB1GHgs5LoP5K2ICyrjnpw2n8oSxMM/VP+liiKcGNlQ==", + "node_modules/@earendil-works/pi-coding-agent/node_modules/@mistralai/mistralai": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-2.2.6.tgz", + "integrity": "sha512-W8pX7zHxjJvMIpw8JMxeJEleapXX0Q9NPszdNzqkM3MIEoIGPObdodujj+WHteXEvGfaP/AMwlNyRfEzSY6dQQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { + "@opentelemetry/semantic-conventions": "^1.40.0", "ws": "^8.18.0", "zod": "^3.25.0 || ^4.0.0", - "zod-to-json-schema": "^3.24.1" + "zod-to-json-schema": "^3.25.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.9.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + } } }, - "node_modules/@nodable/entities": { + "node_modules/@earendil-works/pi-coding-agent/node_modules/@nodable/entities": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.1.0.tgz", "integrity": "sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA==", @@ -2064,46 +1450,55 @@ ], "license": "MIT" }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "node_modules/@earendil-works/pi-coding-agent/node_modules/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", "dev": true, - "license": "MIT", - "optional": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@opentelemetry/semantic-conventions": { + "version": "1.41.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.41.1.tgz", + "integrity": "sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA==", + "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=14" } }, - "node_modules/@protobufjs/aspromise": { + "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", "dev": true, "license": "BSD-3-Clause" }, - "node_modules/@protobufjs/base64": { + "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/base64": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", "dev": true, "license": "BSD-3-Clause" }, - "node_modules/@protobufjs/codegen": { + "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/codegen": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", "dev": true, "license": "BSD-3-Clause" }, - "node_modules/@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/eventemitter": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz", + "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==", "dev": true, "license": "BSD-3-Clause" }, - "node_modules/@protobufjs/fetch": { + "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/fetch": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz", "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==", @@ -2113,1133 +1508,2480 @@ "@protobufjs/aspromise": "^1.1.1" } }, - "node_modules/@protobufjs/float": { + "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/float": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", "dev": true, "license": "BSD-3-Clause" }, - "node_modules/@protobufjs/inquire": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.2.tgz", - "integrity": "sha512-pa0vFRuws4wkvaXKK1uXZMAwAX4/t8ANaJo45iw/oQHNQ9q5xUzwgFmVJGXiga2BeN+zpX7Vf9vmsiIa2J+MUw==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/path": { + "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/path": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", "dev": true, "license": "BSD-3-Clause" }, - "node_modules/@protobufjs/pool": { + "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/pool": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", "dev": true, "license": "BSD-3-Clause" }, - "node_modules/@protobufjs/utf8": { + "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/utf8": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz", "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==", "dev": true, "license": "BSD-3-Clause" }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", - "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", - "cpu": [ - "arm" - ], + "node_modules/@earendil-works/pi-coding-agent/node_modules/@silvia-odwyer/photon-node": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@silvia-odwyer/photon-node/-/photon-node-0.3.4.tgz", + "integrity": "sha512-bnly4BKB3KDTFxrUIcgCLbaeVVS8lrAkri1pEzskpmxu9MdfGQTy8b8EgcD83ywD3RPMsIulY8xJH5Awa+t9fA==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] + "license": "Apache-2.0" }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", - "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", - "cpu": [ - "arm64" - ], + "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/core": { + "version": "3.24.3", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.24.3.tgz", + "integrity": "sha512-Ep/7tPamGY8mgESE3LyLKtxJyy6U52WWAqr/3wial47Sj4u3PiIF73AOGI27UyLy9duTkhZbgzodOfLV4TduZg==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^4.14.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", - "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", - "cpu": [ - "arm64" - ], + "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/credential-provider-imds": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.3.3.tgz", + "integrity": "sha512-I2Bti0DKFo2IJyN28ijCsx51BAumEYR4/1yZ1FXyBygy9MqbnMqCev4JPth/MbpRfBSRAX35hITSnAdJRo1u5w==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.3", + "@smithy/types": "^4.14.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", - "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", - "cpu": [ - "x64" - ], + "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/fetch-http-handler": { + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.4.3.tgz", + "integrity": "sha512-F+DRf8IJazRJgYog2A/yJK7eYVc0rqTlRzO+5ZxjJd4WkZoKz0IJRncf7G6t1pdVT3kryJcwuTFhN1c5m6N47A==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.3", + "@smithy/types": "^4.14.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", - "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", - "cpu": [ - "arm64" - ], + "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", - "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", - "cpu": [ - "x64" - ], + "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/node-http-handler": { + "version": "4.7.3", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.7.3.tgz", + "integrity": "sha512-/jPhevcTFPMVl6KNjbaI47iOg1zxC7IsnX4PQDGVZKMFceOXtB8IEYaB7a9VvkP/3oC60WzTeKocvSI7vLT0vA==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.3", + "@smithy/types": "^4.14.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", - "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", - "cpu": [ - "arm" - ], + "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/signature-v4": { + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.4.3.tgz", + "integrity": "sha512-53+75QuPl6DL+ct6vVEB51FDO5oulXr20TPV46VvJZg76lIlXNWfxi8j+G2V/t0I2qxCBOa3vX/8bmjrpFVo9g==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.3", + "@smithy/types": "^4.14.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", - "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", - "cpu": [ - "arm" - ], + "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/types": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.14.2.tgz", + "integrity": "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", - "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", - "cpu": [ - "arm64" - ], + "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", - "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", - "cpu": [ - "arm64" - ], + "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", - "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", - "cpu": [ - "loong64" - ], + "node_modules/@earendil-works/pi-coding-agent/node_modules/@types/node": { + "version": "22.19.19", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.19.tgz", + "integrity": "sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "undici-types": "~6.21.0" + } }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", - "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", - "cpu": [ - "loong64" - ], + "node_modules/@earendil-works/pi-coding-agent/node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": ">= 14" + } }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", - "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", - "cpu": [ - "ppc64" - ], + "node_modules/@earendil-works/pi-coding-agent/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": "18 || 20 || >=22" + } }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", - "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", - "cpu": [ - "ppc64" + "node_modules/@earendil-works/pi-coding-agent/node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } ], + "license": "MIT" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": "*" + } }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", - "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", - "cpu": [ - "riscv64" - ], + "node_modules/@earendil-works/pi-coding-agent/node_modules/bowser": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", + "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "license": "MIT" }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", - "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", - "cpu": [ - "riscv64" - ], + "node_modules/@earendil-works/pi-coding-agent/node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", - "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", - "cpu": [ - "s390x" - ], + "node_modules/@earendil-works/pi-coding-agent/node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "license": "BSD-3-Clause" }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", - "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", - "cpu": [ - "x64" - ], + "node_modules/@earendil-works/pi-coding-agent/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", - "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", - "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ] - }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", - "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", - "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", - "cpu": [ - "arm64" - ], + "node_modules/@earendil-works/pi-coding-agent/node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", - "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", - "cpu": [ - "ia32" - ], + "node_modules/@earendil-works/pi-coding-agent/node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "engines": { + "node": ">= 12" + } }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", - "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", - "cpu": [ - "x64" - ], + "node_modules/@earendil-works/pi-coding-agent/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", - "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", - "cpu": [ - "x64" - ], + "node_modules/@earendil-works/pi-coding-agent/node_modules/diff": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz", + "integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } }, - "node_modules/@silvia-odwyer/photon-node": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@silvia-odwyer/photon-node/-/photon-node-0.3.4.tgz", - "integrity": "sha512-bnly4BKB3KDTFxrUIcgCLbaeVVS8lrAkri1pEzskpmxu9MdfGQTy8b8EgcD83ywD3RPMsIulY8xJH5Awa+t9fA==", + "node_modules/@earendil-works/pi-coding-agent/node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", "dev": true, - "license": "Apache-2.0" + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } }, - "node_modules/@sinclair/typebox": { - "version": "0.34.49", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz", - "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==", + "node_modules/@earendil-works/pi-coding-agent/node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "dev": true, "license": "MIT" }, - "node_modules/@smithy/abort-controller": { - "version": "4.2.12", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.12.tgz", - "integrity": "sha512-xolrFw6b+2iYGl6EcOL7IJY71vvyZ0DJ3mcKtpykqPe2uscwtzDZJa1uVQXyP7w9Dd+kGwYnPbMsJrGISKiY/Q==", + "node_modules/@earendil-works/pi-coding-agent/node_modules/fast-xml-builder": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.2.0.tgz", + "integrity": "sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q==", "dev": true, - "license": "Apache-2.0", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", "dependencies": { - "@smithy/types": "^4.13.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" + "path-expression-matcher": "^1.5.0", + "xml-naming": "^0.1.0" } }, - "node_modules/@smithy/config-resolver": { - "version": "4.4.11", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.11.tgz", - "integrity": "sha512-YxFiiG4YDAtX7WMN7RuhHZLeTmRRAOyCbr+zB8e3AQzHPnUhS8zXjB1+cniPVQI3xbWsQPM0X2aaIkO/ME0ymw==", + "node_modules/@earendil-works/pi-coding-agent/node_modules/fast-xml-parser": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.7.3.tgz", + "integrity": "sha512-C0AaNuC+mscy6vrAQKAc/rMq+zAPHodfHGZu4sGVehvAQt/JLG1O5zEcYcXSY5zSqr4YVgxsB+pHXTq0i7eDlg==", "dev": true, - "license": "Apache-2.0", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", "dependencies": { - "@smithy/node-config-provider": "^4.3.12", - "@smithy/types": "^4.13.1", - "@smithy/util-config-provider": "^4.2.2", - "@smithy/util-endpoints": "^3.3.3", - "@smithy/util-middleware": "^4.2.12", - "tslib": "^2.6.2" + "@nodable/entities": "^2.1.0", + "fast-xml-builder": "^1.1.7", + "path-expression-matcher": "^1.5.0", + "strnum": "^2.2.3" }, - "engines": { - "node": ">=18.0.0" + "bin": { + "fxparser": "src/cli/cli.js" } }, - "node_modules/@smithy/core": { - "version": "3.23.10", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.23.10.tgz", - "integrity": "sha512-pn0HaJpxmdeCLdbAm79SUjX8IPiej9ANHNHec4K4u5Bkf5BqYCbAgK3c8NTCVf44DnlWJK7W1mimlgBPUQ3IlA==", + "node_modules/@earendil-works/pi-coding-agent/node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", "dev": true, - "license": "Apache-2.0", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", "dependencies": { - "@smithy/protocol-http": "^5.3.12", - "@smithy/types": "^4.13.1", - "@smithy/url-parser": "^4.2.12", - "@smithy/util-base64": "^4.3.2", - "@smithy/util-body-length-browser": "^4.2.2", - "@smithy/util-middleware": "^4.2.12", - "@smithy/util-stream": "^4.5.18", - "@smithy/util-utf8": "^4.2.2", - "@smithy/uuid": "^1.1.2", - "tslib": "^2.6.2" + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" }, "engines": { - "node": ">=18.0.0" + "node": "^12.20 || >= 14.13" } }, - "node_modules/@smithy/credential-provider-imds": { - "version": "4.2.12", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.12.tgz", - "integrity": "sha512-cr2lR792vNZcYMriSIj+Um3x9KWrjcu98kn234xA6reOAFMmbRpQMOv8KPgEmLLtx3eldU6c5wALKFqNOhugmg==", + "node_modules/@earendil-works/pi-coding-agent/node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@smithy/node-config-provider": "^4.3.12", - "@smithy/property-provider": "^4.2.12", - "@smithy/types": "^4.13.1", - "@smithy/url-parser": "^4.2.12", - "tslib": "^2.6.2" + "fetch-blob": "^3.1.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=12.20.0" } }, - "node_modules/@smithy/eventstream-codec": { - "version": "4.2.12", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.2.12.tgz", - "integrity": "sha512-FE3bZdEl62ojmy8x4FHqxq2+BuOHlcxiH5vaZ6aqHJr3AIZzwF5jfx8dEiU/X0a8RboyNDjmXjlbr8AdEyLgiA==", + "node_modules/@earendil-works/pi-coding-agent/node_modules/gaxios": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.4.tgz", + "integrity": "sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-crypto/crc32": "5.2.0", - "@smithy/types": "^4.13.1", - "@smithy/util-hex-encoding": "^4.2.2", - "tslib": "^2.6.2" + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=18" } }, - "node_modules/@smithy/eventstream-serde-browser": { - "version": "4.2.12", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.2.12.tgz", - "integrity": "sha512-XUSuMxlTxV5pp4VpqZf6Sa3vT/Q75FVkLSpSSE3KkWBvAQWeuWt1msTv8fJfgA4/jcJhrbrbMzN1AC/hvPmm5A==", + "node_modules/@earendil-works/pi-coding-agent/node_modules/gcp-metadata": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz", + "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/eventstream-serde-universal": "^4.2.12", - "@smithy/types": "^4.13.1", - "tslib": "^2.6.2" + "gaxios": "^7.0.0", + "google-logging-utils": "^1.0.0", + "json-bigint": "^1.0.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=18" } }, - "node_modules/@smithy/eventstream-serde-config-resolver": { - "version": "4.3.12", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.3.12.tgz", - "integrity": "sha512-7epsAZ3QvfHkngz6RXQYseyZYHlmWXSTPOfPmXkiS+zA6TBNo1awUaMFL9vxyXlGdoELmCZyZe1nQE+imbmV+Q==", + "node_modules/@earendil-works/pi-coding-agent/node_modules/get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.13.1", - "tslib": "^2.6.2" - }, + "license": "MIT", "engines": { - "node": ">=18.0.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@smithy/eventstream-serde-node": { - "version": "4.2.12", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.2.12.tgz", - "integrity": "sha512-D1pFuExo31854eAvg89KMn9Oab/wEeJR6Buy32B49A9Ogdtx5fwZPqBHUlDzaCDpycTFk2+fSQgX689Qsk7UGA==", + "node_modules/@earendil-works/pi-coding-agent/node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", "dev": true, - "license": "Apache-2.0", + "license": "BlueOak-1.0.0", "dependencies": { - "@smithy/eventstream-serde-universal": "^4.2.12", - "@smithy/types": "^4.13.1", - "tslib": "^2.6.2" + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" }, "engines": { - "node": ">=18.0.0" + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@smithy/eventstream-serde-universal": { - "version": "4.2.12", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.2.12.tgz", - "integrity": "sha512-+yNuTiyBACxOJUTvbsNsSOfH9G9oKbaJE1lNL3YHpGcuucl6rPZMi3nrpehpVOVR2E07YqFFmtwpImtpzlouHQ==", + "node_modules/@earendil-works/pi-coding-agent/node_modules/google-auth-library": { + "version": "10.6.2", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.6.2.tgz", + "integrity": "sha512-e27Z6EThmVNNvtYASwQxose/G57rkRuaRbQyxM2bvYLLX/GqWZ5chWq2EBoUchJbCc57eC9ArzO5wMsEmWftCw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/eventstream-codec": "^4.2.12", - "@smithy/types": "^4.13.1", - "tslib": "^2.6.2" + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^7.1.4", + "gcp-metadata": "8.1.2", + "google-logging-utils": "1.1.3", + "jws": "^4.0.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=18" } }, - "node_modules/@smithy/fetch-http-handler": { - "version": "5.3.14", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.14.tgz", - "integrity": "sha512-Aswg1yMsujkikRVv+JIDw2ybTgx0cnTnv7pMee46OX6lTMwk/QpH1lbx3vN3feMwyNrFcSUbYBtbgwHXXn3CIA==", + "node_modules/@earendil-works/pi-coding-agent/node_modules/google-logging-utils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", + "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", "dev": true, "license": "Apache-2.0", - "dependencies": { - "@smithy/protocol-http": "^5.3.12", - "@smithy/querystring-builder": "^4.2.12", - "@smithy/types": "^4.13.1", - "@smithy/util-base64": "^4.3.2", - "tslib": "^2.6.2" - }, "engines": { - "node": ">=18.0.0" + "node": ">=14" } }, - "node_modules/@smithy/hash-node": { - "version": "4.2.12", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.12.tgz", - "integrity": "sha512-QhBYbGrbxTkZ43QoTPrK72DoYviDeg6YKDrHTMJbbC+A0sml3kSjzFtXP7BtbyJnXojLfTQldGdUR0RGD8dA3w==", + "node_modules/@earendil-works/pi-coding-agent/node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.13.1", - "@smithy/util-buffer-from": "^4.2.2", - "@smithy/util-utf8": "^4.2.2", - "tslib": "^2.6.2" - }, + "license": "ISC" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "dev": true, + "license": "BSD-3-Clause", "engines": { - "node": ">=18.0.0" + "node": "*" } }, - "node_modules/@smithy/invalid-dependency": { - "version": "4.2.12", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.12.tgz", - "integrity": "sha512-/4F1zb7Z8LOu1PalTdESFHR0RbPwHd3FcaG1sI3UEIriQTWakysgJr65lc1jj6QY5ye7aFsisajotH6UhWfm/g==", + "node_modules/@earendil-works/pi-coding-agent/node_modules/hosted-git-info": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.3.tgz", + "integrity": "sha512-Hc+ghLoSt6QaYZUv0WBiIvmMDZuZZ7oaDvdH8MbfOO4lOsxdXLEvuC6ePoGs9H1X9oCLyq6+NVN0MKqD+ydxyg==", "dev": true, - "license": "Apache-2.0", + "license": "ISC", "dependencies": { - "@smithy/types": "^4.13.1", - "tslib": "^2.6.2" + "lru-cache": "^11.1.0" }, "engines": { - "node": ">=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/@smithy/is-array-buffer": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz", - "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==", + "node_modules/@earendil-works/pi-coding-agent/node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "tslib": "^2.6.2" + "agent-base": "^7.1.0", + "debug": "^4.3.4" }, "engines": { - "node": ">=18.0.0" + "node": ">= 14" } }, - "node_modules/@smithy/middleware-content-length": { - "version": "4.2.12", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.12.tgz", - "integrity": "sha512-YE58Yz+cvFInWI/wOTrB+DbvUVz/pLn5mC5MvOV4fdRUc6qGwygyngcucRQjAhiCEbmfLOXX0gntSIcgMvAjmA==", + "node_modules/@earendil-works/pi-coding-agent/node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@smithy/protocol-http": "^5.3.12", - "@smithy/types": "^4.13.1", - "tslib": "^2.6.2" + "agent-base": "^7.1.2", + "debug": "4" }, "engines": { - "node": ">=18.0.0" + "node": ">= 14" } }, - "node_modules/@smithy/middleware-endpoint": { - "version": "4.4.24", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.24.tgz", - "integrity": "sha512-k7SZG+7IbS4fVAI47p+QixmcjqliCoZ7T5ZtAJMHyViiv7AhMC9aXtgxvNQ8TQmbUe7kotsvW2XeEEqnTmdOXg==", + "node_modules/@earendil-works/pi-coding-agent/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/core": "^3.23.10", - "@smithy/middleware-serde": "^4.2.13", - "@smithy/node-config-provider": "^4.3.12", - "@smithy/shared-ini-file-loader": "^4.4.7", - "@smithy/types": "^4.13.1", - "@smithy/url-parser": "^4.2.12", - "@smithy/util-middleware": "^4.2.12", - "tslib": "^2.6.2" - }, + "license": "MIT", "engines": { - "node": ">=18.0.0" + "node": ">= 4" } }, - "node_modules/@smithy/middleware-retry": { - "version": "4.4.41", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.41.tgz", - "integrity": "sha512-qjeS0KGftfz2CL4/IziPmQurzemKRPh6sekt3IFbj1519nkj+JM+RcdjVrC1AQFFZhmW3zz7KqwOgN+qJZeVlQ==", + "node_modules/@earendil-works/pi-coding-agent/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true, - "license": "Apache-2.0", + "license": "ISC" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "dev": true, + "license": "MIT", "dependencies": { - "@smithy/node-config-provider": "^4.3.12", - "@smithy/protocol-http": "^5.3.12", - "@smithy/service-error-classification": "^4.2.12", - "@smithy/smithy-client": "^4.12.4", - "@smithy/types": "^4.13.1", - "@smithy/util-middleware": "^4.2.12", - "@smithy/util-retry": "^4.2.12", - "@smithy/uuid": "^1.1.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" + "bignumber.js": "^9.0.0" } }, - "node_modules/@smithy/middleware-serde": { - "version": "4.2.13", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.13.tgz", - "integrity": "sha512-appEschlOmriCVGLYTTjKdbnXIZ55XT9TsV+aGuj5Jiw988gmEZwJwPkYqlZdwajMKgfxt5epjFTGriyYf4Kiw==", + "node_modules/@earendil-works/pi-coding-agent/node_modules/json-schema-to-ts": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.1.tgz", + "integrity": "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@smithy/core": "^3.23.10", - "@smithy/protocol-http": "^5.3.12", - "@smithy/types": "^4.13.1", - "tslib": "^2.6.2" + "@babel/runtime": "^7.18.3", + "ts-algebra": "^2.0.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=16" } }, - "node_modules/@smithy/middleware-stack": { - "version": "4.2.12", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.12.tgz", - "integrity": "sha512-kruC5gRHwsCOuyCd4ouQxYjgRAym2uDlCvQ5acuMtRrcdfg7mFBg6blaxcJ09STpt3ziEkis6bhg1uwrWU7txw==", + "node_modules/@earendil-works/pi-coding-agent/node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@smithy/types": "^4.13.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" } }, - "node_modules/@smithy/node-config-provider": { - "version": "4.3.12", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.12.tgz", - "integrity": "sha512-tr2oKX2xMcO+rBOjobSwVAkV05SIfUKz8iI53rzxEmgW3GOOPOv0UioSDk+J8OpRQnpnhsO3Af6IEBabQBVmiw==", + "node_modules/@earendil-works/pi-coding-agent/node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@smithy/property-provider": "^4.2.12", - "@smithy/shared-ini-file-loader": "^4.4.7", - "@smithy/types": "^4.13.1", - "tslib": "^2.6.2" - }, + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/lru-cache": { + "version": "11.4.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.4.0.tgz", + "integrity": "sha512-W+R+kFL4HgVxONq2bhXPi3bGpzGe/yEhVOp233qw9wCRtgncJ15P3bC+e4zZMu4Cq7d+WAJjXGW0uUkifhcatA==", + "dev": true, + "license": "BlueOak-1.0.0", "engines": { - "node": ">=18.0.0" + "node": "20 || >=22" } }, - "node_modules/@smithy/node-http-handler": { - "version": "4.4.15", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.15.tgz", - "integrity": "sha512-2z3Z7Qfts2Eui5Oy+MLJjwKx1LT0Hm/b6W0XJXkUIFHP1W9D4BhdvxWW2W5xPP92CoXO+B4C/zSH67uIxMkWoA==", + "node_modules/@earendil-works/pi-coding-agent/node_modules/marked": { + "version": "18.0.5", + "resolved": "https://registry.npmjs.org/marked/-/marked-18.0.5.tgz", + "integrity": "sha512-S6GcvALHg6K4ohtu4E7x0a1AqhAjp6cV8KhLSyN9qVapnzJkusVBxZRcIU9AeYsbe6P1hKDusSbEOzGyyuce6w==", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/abort-controller": "^4.2.12", - "@smithy/protocol-http": "^5.3.12", - "@smithy/querystring-builder": "^4.2.12", - "@smithy/types": "^4.13.1", - "tslib": "^2.6.2" + "license": "MIT", + "bin": { + "marked": "bin/marked.js" }, "engines": { - "node": ">=18.0.0" + "node": ">= 20" } }, - "node_modules/@smithy/property-provider": { - "version": "4.2.12", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.12.tgz", - "integrity": "sha512-jqve46eYU1v7pZ5BM+fmkbq3DerkSluPr5EhvOcHxygxzD05ByDRppRwRPPpFrsFo5yDtCYLKu+kreHKVrvc7A==", + "node_modules/@earendil-works/pi-coding-agent/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "dev": true, - "license": "Apache-2.0", + "license": "BlueOak-1.0.0", "dependencies": { - "@smithy/types": "^4.13.1", - "tslib": "^2.6.2" + "brace-expansion": "^5.0.5" }, "engines": { - "node": ">=18.0.0" + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@smithy/protocol-http": { - "version": "5.3.12", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.12.tgz", - "integrity": "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw==", + "node_modules/@earendil-works/pi-coding-agent/node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.13.1", - "tslib": "^2.6.2" - }, + "license": "BlueOak-1.0.0", "engines": { - "node": ">=18.0.0" + "node": ">=16 || 14 >=14.17" } }, - "node_modules/@smithy/querystring-builder": { - "version": "4.2.12", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.12.tgz", - "integrity": "sha512-6wTZjGABQufekycfDGMEB84BgtdOE/rCVTov+EDXQ8NHKTUNIp/j27IliwP7tjIU9LR+sSzyGBOXjeEtVgzCHg==", + "node_modules/@earendil-works/pi-coding-agent/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.13.1", - "@smithy/util-uri-escape": "^4.2.2", - "tslib": "^2.6.2" - }, + "license": "MIT" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", "engines": { - "node": ">=18.0.0" + "node": ">=10.5.0" } }, - "node_modules/@smithy/querystring-parser": { - "version": "4.2.12", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.12.tgz", - "integrity": "sha512-P2OdvrgiAKpkPNKlKUtWbNZKB1XjPxM086NeVhK+W+wI46pIKdWBe5QyXvhUm3MEcyS/rkLvY8rZzyUdmyDZBw==", + "node_modules/@earendil-works/pi-coding-agent/node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@smithy/types": "^4.13.1", - "tslib": "^2.6.2" + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" }, "engines": { - "node": ">=18.0.0" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" } }, - "node_modules/@smithy/service-error-classification": { - "version": "4.2.12", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.12.tgz", - "integrity": "sha512-LlP29oSQN0Tw0b6D0Xo6BIikBswuIiGYbRACy5ujw/JgWSzTdYj46U83ssf6Ux0GyNJVivs2uReU8pt7Eu9okQ==", + "node_modules/@earendil-works/pi-coding-agent/node_modules/openai": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-6.26.0.tgz", + "integrity": "sha512-zd23dbWTjiJ6sSAX6s0HrCZi41JwTA1bQVs0wLQPZ2/5o2gxOJA5wh7yOAUgwYybfhDXyhwlpeQf7Mlgx8EOCA==", "dev": true, "license": "Apache-2.0", + "bin": { + "openai": "bin/cli" + }, + "peerDependencies": { + "ws": "^8.18.0", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "ws": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "dev": true, + "license": "MIT", "dependencies": { - "@smithy/types": "^4.13.1" + "@types/retry": "0.12.0", + "retry": "^0.13.1" }, "engines": { - "node": ">=18.0.0" + "node": ">=8" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/p-retry/node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/partial-json": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/partial-json/-/partial-json-0.1.7.tgz", + "integrity": "sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/path-expression-matcher": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.5.0.tgz", + "integrity": "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "engines": { + "node": ">=14.0.0" } }, - "node_modules/@smithy/shared-ini-file-loader": { - "version": "4.4.7", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.7.tgz", - "integrity": "sha512-HrOKWsUb+otTeo1HxVWeEb99t5ER1XrBi/xka2Wv6NVmTbuCUC1dvlrksdvxFtODLBjsC+PHK+fuy2x/7Ynyiw==", + "node_modules/@earendil-works/pi-coding-agent/node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/proper-lockfile": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", + "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/proper-lockfile/node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/protobufjs": { + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.4.tgz", + "integrity": "sha512-RJJPTTpvFfHcWLkIa2JFWK4XvtSzS0yEWDmunqHXli1h3JlkbcQZXDZdcWxv+JK3Xsl5/UFDPZ0iGm7DAengYw==", + "dev": true, + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.5", + "@protobufjs/eventemitter": "^1.1.1", + "@protobufjs/fetch": "^1.1.1", + "@protobufjs/float": "^1.0.2", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.1", + "@types/node": ">=13.7.0", + "long": "^5.3.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/semver": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/strnum": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.3.0.tgz", + "integrity": "sha512-ums3KNd42PGyx5xaoVTO1mjU1bH3NpY4vsrVlnv9PNGqQj8wd7rJ6nEypLrJ7z5vxK5RP0yMLo6J/Gsm62DI5Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/ts-algebra": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz", + "integrity": "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/typebox": { + "version": "1.1.38", + "resolved": "https://registry.npmjs.org/typebox/-/typebox-1.1.38.tgz", + "integrity": "sha512-pZ0aQPmMmXoUvSbeuWf/Hzsc+avNw/Zd6VeE8CFgkVGWyuHPJvqeJJDeJqLve+K70LvjYIoleGcoJHPT17cWoA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/undici": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-8.5.0.tgz", + "integrity": "sha512-xamtWoB1EshgjpmlXd7GGm2VfdDtw1+rD8uhry8pSNW3If6S8E0m2T2+orSKeZXEn/aPJMviCpDBA65WJt8zhg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/ws": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/xml-naming": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.1.0.tgz", + "integrity": "sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/yaml": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/zod-to-json-schema": { + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", + "dev": true, + "license": "ISC", + "peerDependencies": { + "zod": "^3.25.28 || ^4" + } + }, + "node_modules/@earendil-works/pi-tui": { + "version": "0.80.10", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-tui/-/pi-tui-0.80.10.tgz", + "integrity": "sha512-c2JO29PbhKPEQ6fgHQKAl0WhwuFqzWfzspMmP+8B5tpDuP+0mvarRbKKg8gq4b+pQx/QX+6aVS4ko7deoyjQjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "1.6.0", + "marked": "18.0.5" + }, + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/@earendil-works/pi-tui/node_modules/marked": { + "version": "18.0.5", + "resolved": "https://registry.npmjs.org/marked/-/marked-18.0.5.tgz", + "integrity": "sha512-S6GcvALHg6K4ohtu4E7x0a1AqhAjp6cV8KhLSyN9qVapnzJkusVBxZRcIU9AeYsbe6P1hKDusSbEOzGyyuce6w==", + "dev": true, + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.5" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", + "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.14.0", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.5", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", + "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@google/genai": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.52.0.tgz", + "integrity": "sha512-gwSvbpiN/17O9TbsqSsE/OzZcpv5Fo4RQjdngGgogtuB9RsyJ8ZHhX5KjHj1bp5N9snN2eK8LDGXSaWW2hof8Q==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "google-auth-library": "^10.3.0", + "p-retry": "^4.6.2", + "protobufjs": "^7.5.4", + "ws": "^8.18.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@modelcontextprotocol/sdk": "^1.25.2" + }, + "peerDependenciesMeta": { + "@modelcontextprotocol/sdk": { + "optional": true + } + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@isaacs/balanced-match": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", + "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/brace-expansion": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.1.tgz", + "integrity": "sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@isaacs/balanced-match": "^4.0.1" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/cliui": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-9.0.0.tgz", + "integrity": "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/semantic-conventions": { + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.43.0.tgz", + "integrity": "sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", + "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz", + "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.2.tgz", + "integrity": "sha512-pa0vFRuws4wkvaXKK1uXZMAwAX4/t8ANaJo45iw/oQHNQ9q5xUzwgFmVJGXiga2BeN+zpX7Vf9vmsiIa2J+MUw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz", + "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", + "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", + "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", + "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", + "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", + "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", + "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", + "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", + "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", + "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", + "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", + "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", + "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", + "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", + "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", + "cpu": [ + "ppc64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.13.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@smithy/signature-v4": { - "version": "5.3.12", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.12.tgz", - "integrity": "sha512-B/FBwO3MVOL00DaRSXfXfa/TRXRheagt/q5A2NM13u7q+sHS59EOVGQNfG7DkmVtdQm5m3vOosoKAXSqn/OEgw==", + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", + "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", + "cpu": [ + "riscv64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/is-array-buffer": "^4.2.2", - "@smithy/protocol-http": "^5.3.12", - "@smithy/types": "^4.13.1", - "@smithy/util-hex-encoding": "^4.2.2", - "@smithy/util-middleware": "^4.2.12", - "@smithy/util-uri-escape": "^4.2.2", - "@smithy/util-utf8": "^4.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@smithy/smithy-client": { - "version": "4.12.4", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.12.4.tgz", - "integrity": "sha512-kbFGh3QrUj7Z9zYHCip+dGVyRGiFo6JK0A+9InOwmU4ZCkJs3HKhjLL/ABe5I8kp9uScqrftcWrDh7YxlWmmZA==", + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", + "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", + "cpu": [ + "riscv64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/core": "^3.23.10", - "@smithy/middleware-endpoint": "^4.4.24", - "@smithy/middleware-stack": "^4.2.12", - "@smithy/protocol-http": "^5.3.12", - "@smithy/types": "^4.13.1", - "@smithy/util-stream": "^4.5.18", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@smithy/types": { - "version": "4.14.2", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.14.2.tgz", - "integrity": "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw==", + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", + "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", + "cpu": [ + "s390x" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@smithy/url-parser": { - "version": "4.2.12", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.12.tgz", - "integrity": "sha512-wOPKPEpso+doCZGIlr+e1lVI6+9VAKfL4kZWFgzVgGWY2hZxshNKod4l2LXS3PRC9otH/JRSjtEHqQ/7eLciRA==", + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", + "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", + "cpu": [ + "x64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/querystring-parser": "^4.2.12", - "@smithy/types": "^4.13.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@smithy/util-base64": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.2.tgz", - "integrity": "sha512-XRH6b0H/5A3SgblmMa5ErXQ2XKhfbQB+Fm/oyLZ2O2kCUrwgg55bU0RekmzAhuwOjA9qdN5VU2BprOvGGUkOOQ==", + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", + "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", + "cpu": [ + "x64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/util-buffer-from": "^4.2.2", - "@smithy/util-utf8": "^4.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@smithy/util-body-length-browser": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.2.tgz", - "integrity": "sha512-JKCrLNOup3OOgmzeaKQwi4ZCTWlYR5H4Gm1r2uTMVBXoemo1UEghk5vtMi1xSu2ymgKVGW631e2fp9/R610ZjQ==", + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", + "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", + "cpu": [ + "x64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] }, - "node_modules/@smithy/util-body-length-node": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.3.tgz", - "integrity": "sha512-ZkJGvqBzMHVHE7r/hcuCxlTY8pQr1kMtdsVPs7ex4mMU+EAbcXppfo5NmyxMYi2XU49eqaz56j2gsk4dHHPG/g==", + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", + "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] }, - "node_modules/@smithy/util-buffer-from": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz", - "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==", + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", + "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/is-array-buffer": "^4.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/@smithy/util-config-provider": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.2.tgz", - "integrity": "sha512-dWU03V3XUprJwaUIFVv4iOnS1FC9HnMHDfUrlNDSh4315v0cWyaIErP8KiqGVbf5z+JupoVpNM7ZB3jFiTejvQ==", + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", + "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", + "cpu": [ + "ia32" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/@smithy/util-defaults-mode-browser": { - "version": "4.3.40", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.40.tgz", - "integrity": "sha512-TB++dVe/aHkhCw8+fVUiGEEyz70Drftze6uk5VGBDJAjEj2mqNFftkeY7Jyit3uui346NkZxzLMGM0yzD/S8og==", + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", + "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", + "cpu": [ + "x64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/property-provider": "^4.2.12", - "@smithy/smithy-client": "^4.12.4", - "@smithy/types": "^4.13.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/@smithy/util-defaults-mode-node": { - "version": "4.2.43", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.43.tgz", - "integrity": "sha512-cHmr8Q1BJstJC8ahvYrcyqjSIwrgLbpphOYmfMvF+EVsKUU52b3DDLb0SyiAzR16o7FR1r2IVUFfWWu7ADh1iw==", + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", + "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", + "cpu": [ + "x64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/config-resolver": "^4.4.11", - "@smithy/credential-provider-imds": "^4.2.12", - "@smithy/node-config-provider": "^4.3.12", - "@smithy/property-provider": "^4.2.12", - "@smithy/smithy-client": "^4.12.4", - "@smithy/types": "^4.13.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/@smithy/util-endpoints": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.3.3.tgz", - "integrity": "sha512-VACQVe50j0HZPjpwWcjyT51KUQ4AnsvEaQ2lKHOSL4mNLD0G9BjEniQ+yCt1qqfKfiAHRAts26ud7hBjamrwig==", + "node_modules/@smithy/core": { + "version": "3.29.5", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.29.5.tgz", + "integrity": "sha512-i0dk2t5B+CwV/dcJdUHILYkOQF5lof8f44dFCfDWToGCxjT9YQ+CgHqTAvJxzc3+zqQwm2QtVoJ5IqiNar/CnQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.12", - "@smithy/types": "^4.13.1", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@smithy/util-hex-encoding": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.2.tgz", - "integrity": "sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg==", + "node_modules/@smithy/credential-provider-imds": { + "version": "4.4.10", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.4.10.tgz", + "integrity": "sha512-MJenAe4OKRZUo1LdYYFDCsSHxaHvInIU/z52GsheO9vl1/VSySVCr0zkyKD6TFiGkSUaWGxvKZ/70OvgUZR5HQ==", "dev": true, "license": "Apache-2.0", "dependencies": { + "@smithy/core": "^3.29.5", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@smithy/util-middleware": { - "version": "4.2.12", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.12.tgz", - "integrity": "sha512-Er805uFUOvgc0l8nv0e0su0VFISoxhJ/AwOn3gL2NWNY2LUEldP5WtVcRYSQBcjg0y9NfG8JYrCJaYDpupBHJQ==", + "node_modules/@smithy/fetch-http-handler": { + "version": "5.6.7", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.6.7.tgz", + "integrity": "sha512-3zpg8yqqyXzoK2TsRDdkqVOj2RDBFfLXwCczOZ5c7TWB4eiaebfSCsbMjDPYB3PJ9ihV62QaeadZ+wLadZtNGA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.13.1", + "@smithy/core": "^3.29.5", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@smithy/util-retry": { - "version": "4.2.12", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.12.tgz", - "integrity": "sha512-1zopLDUEOwumjcHdJ1mwBHddubYF8GMQvstVCLC54Y46rqoHwlIU+8ZzUeaBcD+WCJHyDGSeZ2ml9YSe9aqcoQ==", + "node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/service-error-classification": "^4.2.12", - "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=14.0.0" } }, - "node_modules/@smithy/util-stream": { - "version": "4.5.18", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.18.tgz", - "integrity": "sha512-o0hxsNp2rC7Kz93RNER/mv5G60kntYPPjV9e9Zoa3Mm455bCGHlFW6TywziCQRlLzvrQj/mmWJimAvJWF/wfjg==", + "node_modules/@smithy/node-http-handler": { + "version": "4.7.3", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.7.3.tgz", + "integrity": "sha512-/jPhevcTFPMVl6KNjbaI47iOg1zxC7IsnX4PQDGVZKMFceOXtB8IEYaB7a9VvkP/3oC60WzTeKocvSI7vLT0vA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/fetch-http-handler": "^5.3.14", - "@smithy/node-http-handler": "^4.4.15", - "@smithy/types": "^4.13.1", - "@smithy/util-base64": "^4.3.2", - "@smithy/util-buffer-from": "^4.2.2", - "@smithy/util-hex-encoding": "^4.2.2", - "@smithy/util-utf8": "^4.2.2", + "@smithy/core": "^3.24.3", + "@smithy/types": "^4.14.2", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@smithy/util-uri-escape": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.2.tgz", - "integrity": "sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw==", + "node_modules/@smithy/signature-v4": { + "version": "5.6.6", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.6.6.tgz", + "integrity": "sha512-efP6DN3UTFrzIsGO42/xcabv8jU7+9nwEdphFUH7yL0k010ERyAWaO41KFQIDLcFZLZ8xzIQr4wplFxNzslSGQ==", "dev": true, "license": "Apache-2.0", "dependencies": { + "@smithy/core": "^3.29.5", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@smithy/util-utf8": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz", - "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==", + "node_modules/@smithy/types": { + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.16.1.tgz", + "integrity": "sha512-0JFs3V2y2M9tKW5na/qxe69Zv+uxLMO7QBbhxF/FHu/Gp2NFZAAL9tWl9PU02xxo07pb3G9FTyjNc6D5uZrJIg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@smithy/uuid": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.2.tgz", - "integrity": "sha512-O/IEdcCUKkubz60tFbGA7ceITTAJsty+lBjNoorP4Z6XRqaFb/OjQjZODophEcuq68nKm6/0r+6/lLQ+XVpk8g==", + "node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", "dev": true, "license": "Apache-2.0", "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=14.0.0" } }, - "node_modules/@tokenizer/inflate": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.4.1.tgz", - "integrity": "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==", + "node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "debug": "^4.4.3", - "token-types": "^6.1.1" + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" + "node": ">=14.0.0" } }, - "node_modules/@tokenizer/token": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", - "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tootallnate/quickjs-emscripten": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", - "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/bun": { "version": "1.3.14", "resolved": "https://registry.npmjs.org/@types/bun/-/bun-1.3.14.tgz", @@ -3264,13 +4006,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/mime-types": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.4.tgz", - "integrity": "sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/node": { "version": "25.9.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.1.tgz", @@ -3288,17 +4023,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/yauzl": { - "version": "2.10.3", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", - "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.59.4", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.4.tgz", @@ -3728,48 +4452,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/ajv-formats": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", - "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" - }, "node_modules/ansi-regex": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", @@ -3799,13 +4481,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", - "dev": true, - "license": "MIT" - }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -3823,19 +4498,6 @@ "node": ">=12" } }, - "node_modules/ast-types": { - "version": "0.13.4", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", - "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", - "dev": true, - "license": "MIT", - "dependencies": { - "tslib": "^2.0.1" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/balanced-match": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", @@ -3867,16 +4529,6 @@ ], "license": "MIT" }, - "node_modules/basic-ftp": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.3.1.tgz", - "integrity": "sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/bignumber.js": { "version": "9.3.1", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", @@ -3907,16 +4559,6 @@ "node": "18 || 20 || >=22" } }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", @@ -3998,63 +4640,6 @@ "node": ">= 16" } }, - "node_modules/cli-highlight": { - "version": "2.1.11", - "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", - "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==", - "dev": true, - "license": "ISC", - "dependencies": { - "chalk": "^4.0.0", - "highlight.js": "^10.7.1", - "mz": "^2.4.0", - "parse5": "^5.1.1", - "parse5-htmlparser2-tree-adapter": "^6.0.0", - "yargs": "^16.0.0" - }, - "bin": { - "highlight": "bin/highlight" - }, - "engines": { - "node": ">=8.0.0", - "npm": ">=5.0.0" - } - }, - "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/cliui/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -4162,31 +4747,6 @@ "dev": true, "license": "MIT" }, - "node_modules/degenerator": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", - "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ast-types": "^0.13.4", - "escodegen": "^2.1.0", - "esprima": "^4.0.1" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/diff": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.3.tgz", - "integrity": "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -4211,16 +4771,6 @@ "dev": true, "license": "MIT" }, - "node_modules/end-of-stream": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", - "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", - "dev": true, - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - } - }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", @@ -4280,16 +4830,6 @@ "@esbuild/win32-x64": "0.21.5" } }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -4303,28 +4843,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/escodegen": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", - "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=6.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, "node_modules/eslint": { "version": "9.39.4", "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", @@ -4513,20 +5031,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/esquery": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", @@ -4597,116 +5101,29 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true, - "license": "MIT" - }, - "node_modules/extract-zip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - }, - "bin": { - "extract-zip": "cli.js" - }, - "engines": { - "node": ">= 10.17.0" - }, - "optionalDependencies": { - "@types/yauzl": "^2.9.1" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", - "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/fast-xml-builder": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.2.0.tgz", - "integrity": "sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "dependencies": { - "path-expression-matcher": "^1.5.0", - "xml-naming": "^0.1.0" - } - }, - "node_modules/fast-xml-parser": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.7.3.tgz", - "integrity": "sha512-C0AaNuC+mscy6vrAQKAc/rMq+zAPHodfHGZu4sGVehvAQt/JLG1O5zEcYcXSY5zSqr4YVgxsB+pHXTq0i7eDlg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "dependencies": { - "@nodable/entities": "^2.1.0", - "fast-xml-builder": "^1.1.7", - "path-expression-matcher": "^1.5.0", - "strnum": "^2.2.3" - }, - "bin": { - "fxparser": "src/cli/cli.js" - } + "dev": true, + "license": "MIT" }, - "node_modules/fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true, - "license": "MIT", - "dependencies": { - "pend": "~1.2.0" - } + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" }, "node_modules/fdir": { "version": "6.5.0", @@ -4763,25 +5180,6 @@ "node": ">=16.0.0" } }, - "node_modules/file-type": { - "version": "21.3.4", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.3.4.tgz", - "integrity": "sha512-Ievi/yy8DS3ygGvT47PjSfdFoX+2isQueoYP1cntFW1JLYAuS4GD7NUPGg4zv2iZfV52uDyk5w5Z0TdpRS6Q1g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@tokenizer/inflate": "^0.4.1", - "strtok3": "^10.3.4", - "token-types": "^6.1.1", - "uint8array-extras": "^1.4.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sindresorhus/file-type?sponsor=1" - } - }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -4896,20 +5294,10 @@ "node": ">=18" } }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, "node_modules/get-east-asian-width": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", - "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", "dev": true, "license": "MIT", "engines": { @@ -4932,47 +5320,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "license": "MIT", - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-uri": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz", - "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==", - "dev": true, - "license": "MIT", - "dependencies": { - "basic-ftp": "^5.0.2", - "data-uri-to-buffer": "^6.0.2", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/get-uri/node_modules/data-uri-to-buffer": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", - "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, "node_modules/glob": { "version": "11.0.3", "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.3.tgz", @@ -5052,13 +5399,6 @@ "node": ">=14" } }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, - "license": "ISC" - }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -5069,29 +5409,6 @@ "node": ">=8" } }, - "node_modules/highlight.js": { - "version": "10.7.3", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", - "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": "*" - } - }, - "node_modules/hosted-git-info": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.2.tgz", - "integrity": "sha512-M422h7o/BR3rmCQ8UHi7cyyMqKltdP9Uo+J2fXK+RSAY+wTcKOIRyhTuKv4qn+DJf3g+PL890AzId5KZpX+CBg==", - "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^11.1.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -5127,27 +5444,6 @@ "node": ">= 14" } }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" - }, "node_modules/ignore": { "version": "7.0.5", "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", @@ -5195,16 +5491,6 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/ip-address": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", - "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -5423,18 +5709,6 @@ "json-buffer": "3.0.1" } }, - "node_modules/koffi": { - "version": "2.15.2", - "resolved": "https://registry.npmjs.org/koffi/-/koffi-2.15.2.tgz", - "integrity": "sha512-r9tjJLVRSOhCRWdVyQlF3/Ugzeg13jlzS4czS82MAgLff4W+BcYOW7g8Y62t9O5JYjYOLAjAovAZDNlDfZNu+g==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "funding": { - "url": "https://liberapay.com/Koromix" - } - }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -5644,19 +5918,6 @@ "url": "https://github.com/sponsors/DavidAnson" } }, - "node_modules/marked": { - "version": "15.0.12", - "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz", - "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", - "dev": true, - "license": "MIT", - "bin": { - "marked": "bin/marked.js" - }, - "engines": { - "node": ">= 18" - } - }, "node_modules/mdurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", @@ -5664,33 +5925,6 @@ "dev": true, "license": "MIT" }, - "node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", - "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/minimatch": { "version": "10.2.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", @@ -5734,18 +5968,6 @@ "dev": true, "license": "MIT" }, - "node_modules/mz": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", - "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "any-promise": "^1.0.0", - "object-assign": "^4.0.1", - "thenify-all": "^1.0.0" - } - }, "node_modules/nanoid": { "version": "3.3.12", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", @@ -5772,16 +5994,6 @@ "dev": true, "license": "MIT" }, - "node_modules/netmask": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", - "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4.0" - } - }, "node_modules/node-domexception": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", @@ -5822,26 +6034,6 @@ "url": "https://opencollective.com/node-fetch" } }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, "node_modules/openai": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/openai/-/openai-6.26.0.tgz", @@ -5928,40 +6120,6 @@ "node": ">=8" } }, - "node_modules/pac-proxy-agent": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", - "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@tootallnate/quickjs-emscripten": "^0.23.0", - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "get-uri": "^6.0.1", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.6", - "pac-resolver": "^7.0.1", - "socks-proxy-agent": "^8.0.5" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/pac-resolver": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", - "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", - "dev": true, - "license": "MIT", - "dependencies": { - "degenerator": "^5.0.0", - "netmask": "^2.0.2" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", @@ -5982,30 +6140,6 @@ "node": ">=6" } }, - "node_modules/parse5": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", - "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", - "dev": true, - "license": "MIT" - }, - "node_modules/parse5-htmlparser2-tree-adapter": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", - "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", - "dev": true, - "license": "MIT", - "dependencies": { - "parse5": "^6.0.1" - } - }, - "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", - "dev": true, - "license": "MIT" - }, "node_modules/partial-json": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/partial-json/-/partial-json-0.1.7.tgz", @@ -6023,22 +6157,6 @@ "node": ">=8" } }, - "node_modules/path-expression-matcher": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.5.0.tgz", - "integrity": "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -6083,13 +6201,6 @@ "node": ">= 14.16" } }, - "node_modules/pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", - "dev": true, - "license": "MIT" - }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -6149,51 +6260,22 @@ "node": ">= 0.8.0" } }, - "node_modules/prettier": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz", - "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", - "dev": true, - "license": "MIT", - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/proper-lockfile": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", - "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "retry": "^0.12.0", - "signal-exit": "^3.0.2" - } - }, - "node_modules/proper-lockfile/node_modules/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "node_modules/prettier": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz", + "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", "dev": true, "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, "engines": { - "node": ">= 4" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "node_modules/proper-lockfile/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" - }, "node_modules/protobufjs": { "version": "7.6.0", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.0.tgz", @@ -6219,54 +6301,6 @@ "node": ">=12.0.0" } }, - "node_modules/proxy-agent": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", - "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "http-proxy-agent": "^7.0.1", - "https-proxy-agent": "^7.0.6", - "lru-cache": "^7.14.1", - "pac-proxy-agent": "^7.1.0", - "proxy-from-env": "^1.1.0", - "socks-proxy-agent": "^8.0.5" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/proxy-agent/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true, - "license": "MIT" - }, - "node_modules/pump": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", - "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -6287,26 +6321,6 @@ "node": ">=6" } }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -6650,17 +6664,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" - } - }, "node_modules/smol-toml": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.3.4.tgz", @@ -6674,47 +6677,6 @@ "url": "https://github.com/sponsors/cyyynthia" } }, - "node_modules/socks": { - "version": "2.8.7", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", - "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ip-address": "^10.0.1", - "smart-buffer": "^4.2.0" - }, - "engines": { - "node": ">= 10.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks-proxy-agent": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", - "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "socks": "^2.8.3" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "license": "BSD-3-Clause", - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -6869,36 +6831,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/strnum": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.3.0.tgz", - "integrity": "sha512-ums3KNd42PGyx5xaoVTO1mjU1bH3NpY4vsrVlnv9PNGqQj8wd7rJ6nEypLrJ7z5vxK5RP0yMLo6J/Gsm62DI5Q==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT" - }, - "node_modules/strtok3": { - "version": "10.3.4", - "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.4.tgz", - "integrity": "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@tokenizer/token": "^0.3.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -7096,29 +7028,6 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/thenify": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", - "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "any-promise": "^1.0.0" - } - }, - "node_modules/thenify-all": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", - "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "thenify": ">= 3.1.0 < 4" - }, - "engines": { - "node": ">=0.8" - } - }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", @@ -7180,25 +7089,6 @@ "node": ">=14.0.0" } }, - "node_modules/token-types": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.2.tgz", - "integrity": "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==", - "dev": true, - "license": "MIT", - "dependencies": { - "@borewit/text-codec": "^0.2.1", - "@tokenizer/token": "^0.3.0", - "ieee754": "^1.2.1" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, "node_modules/ts-algebra": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz", @@ -7239,6 +7129,13 @@ "node": ">= 0.8.0" } }, + "node_modules/typebox": { + "version": "1.1.38", + "resolved": "https://registry.npmjs.org/typebox/-/typebox-1.1.38.tgz", + "integrity": "sha512-pZ0aQPmMmXoUvSbeuWf/Hzsc+avNw/Zd6VeE8CFgkVGWyuHPJvqeJJDeJqLve+K70LvjYIoleGcoJHPT17cWoA==", + "dev": true, + "license": "MIT" + }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", @@ -7260,29 +7157,6 @@ "dev": true, "license": "MIT" }, - "node_modules/uint8array-extras": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz", - "integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/undici": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.25.0.tgz", - "integrity": "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20.18.1" - } - }, "node_modules/undici-types": { "version": "7.24.6", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", @@ -7502,24 +7376,6 @@ "node": ">=0.10.0" } }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/wrap-ansi-cjs": { "name": "wrap-ansi", "version": "7.0.0", @@ -7562,36 +7418,6 @@ "node": ">=8" } }, - "node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "license": "ISC" - }, "node_modules/ws": { "version": "8.20.1", "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.1.tgz", @@ -7614,88 +7440,6 @@ } } }, - "node_modules/xml-naming": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.1.0.tgz", - "integrity": "sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/yaml": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", - "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", - "dev": true, - "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" - } - }, - "node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -7709,19 +7453,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/yoctocolors": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", - "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/zod": { "version": "4.3.6", "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", diff --git a/package.json b/package.json index de6f464..8df4668 100644 --- a/package.json +++ b/package.json @@ -28,13 +28,14 @@ "format:check": "prettier --check \"**/*.{ts,md,json}\"", "format": "prettier --write \"**/*.{ts,md,json}\"", "markdownlint": "markdownlint \"**/*.md\" --ignore node_modules", - "test": "vitest run" + "test": "vitest run", + "test:integration": "vitest run tests/real-pi.integration.test.ts" }, "devDependencies": { - "@mariozechner/pi-ai": "^0.57.1", - "@mariozechner/pi-coding-agent": "^0.57.1", - "@mariozechner/pi-tui": "^0.57.1", - "@sinclair/typebox": "^0.34.49", + "@earendil-works/pi-ai": "0.80.10", + "@earendil-works/pi-coding-agent": "0.80.10", + "@earendil-works/pi-tui": "0.80.10", + "typebox": "1.1.38", "@types/bun": "^1.3.14", "@types/node": "^25.9.1", "@typescript-eslint/eslint-plugin": "^8.59.4", diff --git a/tests/agents.test.ts b/tests/agents.test.ts index d4a318e..eae55ad 100644 --- a/tests/agents.test.ts +++ b/tests/agents.test.ts @@ -273,7 +273,7 @@ Prompt. `--- name: pm description: PM with model -model: openrouter/stepfun/step-3.5-flash:free +model: openrouter/free --- Prompt. @@ -282,7 +282,7 @@ Prompt. const result = discoverAgents(tempDir); - expect(result.agents[0].model).toBe("openrouter/stepfun/step-3.5-flash:free"); + expect(result.agents[0].model).toBe("openrouter/free"); }); }); @@ -367,9 +367,18 @@ Prompt. expect(developer).toBeDefined(); expect(verifier).toBeDefined(); - expect(pm?.tools).toEqual(["read", "grep", "find", "ls"]); - expect(developer?.tools).toEqual(["read", "edit", "write", "bash", "grep", "find", "ls"]); - expect(verifier?.tools).toEqual(["read", "grep", "find", "ls", "bash"]); + expect(pm?.tools).toEqual(["read", "grep", "find", "ls", "generate_wave"]); + expect(developer?.tools).toEqual([ + "read", + "edit", + "write", + "bash", + "grep", + "find", + "ls", + "report_task_result", + ]); + expect(verifier?.tools).toEqual(["read", "grep", "find", "ls", "bash", "report_task_result"]); }); }); }); diff --git a/tests/config-extended.test.ts b/tests/config-extended.test.ts index 9decca0..6d2fe6d 100644 --- a/tests/config-extended.test.ts +++ b/tests/config-extended.test.ts @@ -1,586 +1,274 @@ -import { describe, expect, it, beforeEach, afterEach } from "vitest"; +import { describe, expect, it } from "vitest"; import * as fs from "node:fs"; import * as os from "node:os"; import * as path from "node:path"; import { loadWorkflowConfig } from "../.pi/extensions/workflow-orchestrator/config.js"; -describe("config.ts - additional coverage", () => { - let tempDir: string; - - beforeEach(() => { - tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "workflow-config-test-")); - }); - - afterEach(() => { - fs.rmSync(tempDir, { recursive: true, force: true }); +function writeConfig(config: Record, name = "temp"): string { + const cwd = fs.mkdtempSync(path.join(os.tmpdir(), "workflow-config-extended-")); + const directory = path.join(cwd, ".pi", "workflows"); + fs.mkdirSync(directory, { recursive: true }); + fs.writeFileSync(path.join(directory, `${name}.workflow.json`), JSON.stringify(config)); + return cwd; +} + +function stageConfig(overrides: Record = {}) { + return { + name: "temp", + goal: "test", + agents: { pm: "pm", developer: "developer", verifier: "verifier" }, + waveSource: { type: "static", staticWaves: [] }, + taskFlow: { + stages: [ + { id: "develop", agent: "developer", inputTemplate: "Implement {{task.title}}" }, + { id: "verify", agent: "verifier", inputTemplate: "Verify {{task.title}}" }, + ], + }, + ...overrides, + }; +} + +function task(id = "T1") { + return { + id, + title: "Task", + description: "Do the task", + requirements: "Run verification", + assignee: "developer", + }; +} + +describe("config.ts extended coverage", () => { + it("accepts safe workflow identifiers and rejects unsafe names", () => { + for (const name of ["my-workflow", "my_workflow", "workflow123"]) { + const cwd = writeConfig({ ...stageConfig(), name }, name); + expect(loadWorkflowConfig(cwd, name).config.name).toBe(name); + } + const cwd = writeConfig(stageConfig()); + expect(() => loadWorkflowConfig(cwd, "my workflow")).toThrow("Invalid workflow name"); + expect(() => loadWorkflowConfig(cwd, "a".repeat(65))).toThrow("too long"); }); - function createWorkflowConfig(name: string, content: string): string { - const workflowDir = path.join(tempDir, ".pi", "workflows"); - fs.mkdirSync(workflowDir, { recursive: true }); - const workflowPath = path.join(workflowDir, `${name}.workflow.json`); - fs.writeFileSync(workflowPath, content); - return workflowPath; - } - - describe("sanitizeWorkflowName", () => { - it("rejects empty name", () => { - createWorkflowConfig("", JSON.stringify({})); - expect(() => loadWorkflowConfig(tempDir, "")).toThrow("name is required"); - }); - - it("rejects name with invalid characters", () => { - createWorkflowConfig("my@workflow", JSON.stringify({})); - expect(() => loadWorkflowConfig(tempDir, "my@workflow")).toThrow("Invalid workflow name"); - }); - - it("rejects name with spaces", () => { - createWorkflowConfig("my workflow", JSON.stringify({})); - expect(() => loadWorkflowConfig(tempDir, "my workflow")).toThrow("Invalid workflow name"); - }); - - it("rejects name that is too long", () => { - const longName = "a".repeat(51); - createWorkflowConfig(longName, JSON.stringify({})); - expect(() => loadWorkflowConfig(tempDir, longName)).toThrow("too long"); - }); - - it("accepts valid name with dash", () => { - const config = { - name: "my-workflow", - goal: "test", - agents: { pm: "pm", developer: "dev", verifier: "ver" }, - waveSource: { type: "static", staticWaves: [] }, - taskFlow: { stages: [{ id: "s1", agent: "dev", inputTemplate: "t", outputSchema: {} }] }, - }; - createWorkflowConfig("my-workflow", JSON.stringify(config)); - expect(() => loadWorkflowConfig(tempDir, "my-workflow")).not.toThrow(); - }); - - it("accepts valid name with underscore", () => { - const config = { - name: "my_workflow", - goal: "test", - agents: { pm: "pm", developer: "dev", verifier: "ver" }, - waveSource: { type: "static", staticWaves: [] }, - taskFlow: { stages: [{ id: "s1", agent: "dev", inputTemplate: "t", outputSchema: {} }] }, - }; - createWorkflowConfig("my_workflow", JSON.stringify(config)); - expect(() => loadWorkflowConfig(tempDir, "my_workflow")).not.toThrow(); - }); - - it("accepts name with numbers", () => { - const config = { - name: "workflow123", - goal: "test", - agents: { pm: "pm", developer: "dev", verifier: "ver" }, - waveSource: { type: "static", staticWaves: [] }, - taskFlow: { stages: [{ id: "s1", agent: "dev", inputTemplate: "t", outputSchema: {} }] }, - }; - createWorkflowConfig("workflow123", JSON.stringify(config)); - expect(() => loadWorkflowConfig(tempDir, "workflow123")).not.toThrow(); - }); + it("rejects missing required configuration sections", () => { + for (const invalid of [ + { name: "temp" }, + { ...stageConfig(), agents: undefined }, + { ...stageConfig(), waveSource: undefined }, + { ...stageConfig(), taskFlow: undefined }, + ]) { + const cwd = writeConfig(invalid); + expect(() => loadWorkflowConfig(cwd, "temp")).toThrow(); + } }); - describe("workflow validation", () => { - it("validates required fields", () => { - const config = { - name: "test", - // Missing goal - agents: { pm: "pm", developer: "dev", verifier: "ver" }, - waveSource: { type: "static", staticWaves: [] }, - taskFlow: { stages: [{ id: "s1", agent: "dev", inputTemplate: "t", outputSchema: {} }] }, - }; - createWorkflowConfig("test", JSON.stringify(config)); - expect(() => loadWorkflowConfig(tempDir, "test")).toThrow(); - }); - - it("validates agents configuration", () => { - const config = { - name: "test", - goal: "test", - // Missing agents - waveSource: { type: "static", staticWaves: [] }, - taskFlow: { stages: [{ id: "s1", agent: "dev", inputTemplate: "t", outputSchema: {} }] }, - }; - createWorkflowConfig("test", JSON.stringify(config)); - expect(() => loadWorkflowConfig(tempDir, "test")).toThrow(); - }); - - it("validates waveSource configuration", () => { - const config = { - name: "test", - goal: "test", - agents: { pm: "pm", developer: "dev", verifier: "ver" }, - // Missing waveSource - taskFlow: { stages: [{ id: "s1", agent: "dev", inputTemplate: "t", outputSchema: {} }] }, - }; - createWorkflowConfig("test", JSON.stringify(config)); - expect(() => loadWorkflowConfig(tempDir, "test")).toThrow(); - }); - - it("validates taskFlow configuration", () => { - const config = { - name: "test", - goal: "test", - agents: { pm: "pm", developer: "dev", verifier: "ver" }, - waveSource: { type: "static", staticWaves: [] }, - // Missing taskFlow - }; - createWorkflowConfig("test", JSON.stringify(config)); - expect(() => loadWorkflowConfig(tempDir, "test")).toThrow(); - }); - - it("validates stage configuration", () => { - const config = { - name: "test", - goal: "test", - agents: { pm: "pm", developer: "dev", verifier: "ver" }, - waveSource: { type: "static", staticWaves: [] }, - taskFlow: { - stages: [ - { - // Missing required fields - id: "s1", - // Missing agent, inputTemplate, outputSchema - }, - ], - }, - }; - createWorkflowConfig("test", JSON.stringify(config)); - expect(() => loadWorkflowConfig(tempDir, "test")).toThrow(); - }); + it("validates parallelism and retry limits", () => { + for (const field of ["parallelism", "maxWaves", "maxPmRetries"]) { + const cwd = writeConfig({ ...stageConfig(), [field]: 0 }); + expect(() => loadWorkflowConfig(cwd, "temp")).toThrow(); + } + const zeroTaskRetry = writeConfig({ ...stageConfig(), maxTaskRetries: 0 }); + expect(loadWorkflowConfig(zeroTaskRetry, "temp").config.maxTaskRetries).toBe(0); + const negativeTaskRetry = writeConfig({ ...stageConfig(), maxTaskRetries: -1 }); + expect(() => loadWorkflowConfig(negativeTaskRetry, "temp")).toThrow(); }); - describe("parallelism validation", () => { - it("rejects parallelism less than 1", () => { - const config = { - name: "test", - goal: "test", - parallelism: 0, - agents: { pm: "pm", developer: "dev", verifier: "ver" }, - waveSource: { type: "static", staticWaves: [] }, - taskFlow: { stages: [{ id: "s1", agent: "dev", inputTemplate: "t", outputSchema: {} }] }, - }; - createWorkflowConfig("test", JSON.stringify(config)); - expect(() => loadWorkflowConfig(tempDir, "test")).toThrow("parallelism must be at least 1"); - }); - - it("accepts parallelism of 1", () => { - const config = { - name: "test", - goal: "test", - parallelism: 1, - agents: { pm: "pm", developer: "dev", verifier: "ver" }, - waveSource: { type: "static", staticWaves: [] }, - taskFlow: { stages: [{ id: "s1", agent: "dev", inputTemplate: "t", outputSchema: {} }] }, - }; - createWorkflowConfig("test", JSON.stringify(config)); - expect(() => loadWorkflowConfig(tempDir, "test")).not.toThrow(); - }); - - it("accepts high parallelism", () => { - const config = { - name: "test", - goal: "test", - parallelism: 10, - agents: { pm: "pm", developer: "dev", verifier: "ver" }, - waveSource: { type: "static", staticWaves: [] }, - taskFlow: { stages: [{ id: "s1", agent: "dev", inputTemplate: "t", outputSchema: {} }] }, - }; - createWorkflowConfig("test", JSON.stringify(config)); - const loaded = loadWorkflowConfig(tempDir, "test"); - expect(loaded.config.parallelism).toBe(10); - }); + it("validates semantic stage IDs and stage agent references", () => { + const invalidId = writeConfig({ + ...stageConfig(), + taskFlow: { + stages: [ + { id: "plan", agent: "developer", inputTemplate: "x" }, + { id: "verify", agent: "verifier", inputTemplate: "x" }, + ], + }, + }); + expect(() => loadWorkflowConfig(invalidId, "temp")).toThrow("schema validation"); + + const missingAgent = writeConfig({ + ...stageConfig(), + taskFlow: { + stages: [ + { id: "develop", agent: "unknown", inputTemplate: "x" }, + { id: "verify", agent: "verifier", inputTemplate: "x" }, + ], + }, + }); + expect(() => loadWorkflowConfig(missingAgent, "temp")).toThrow("unknown agent"); }); - describe("transitions validation", () => { - it("accepts valid transitions", () => { - const config = { - name: "test", - goal: "test", - agents: { pm: "pm", developer: "dev", verifier: "ver" }, - waveSource: { type: "static", staticWaves: [] }, - taskFlow: { - stages: [ - { - id: "develop", - agent: "dev", - inputTemplate: "t", - outputSchema: {}, - transitions: [ - { when: { field: "status", equals: "fail" }, next: "develop" }, - { when: { field: "status", equals: "pass" }, next: "complete" }, - ], - }, - ], - }, - }; - createWorkflowConfig("test", JSON.stringify(config)); - const loaded = loadWorkflowConfig(tempDir, "test"); - expect(loaded.config.taskFlow.stages[0].transitions).toHaveLength(2); - }); - - it("accepts stages without transitions", () => { - const config = { - name: "test", - goal: "test", - agents: { pm: "pm", developer: "dev", verifier: "ver" }, - waveSource: { type: "static", staticWaves: [] }, - taskFlow: { - stages: [ - { - id: "develop", - agent: "dev", - inputTemplate: "t", - outputSchema: {}, - // No transitions - }, - ], - }, - }; - createWorkflowConfig("test", JSON.stringify(config)); - const loaded = loadWorkflowConfig(tempDir, "test"); - expect(loaded.config.taskFlow.stages[0].transitions).toBeUndefined(); - }); - }); - - describe("task memory configuration", () => { - it("accepts memory configuration values", () => { - const config = { - name: "test", - goal: "test", - agents: { pm: "pm", developer: "dev", verifier: "ver" }, - waveSource: { type: "static", staticWaves: [] }, - taskFlow: { - memory: { - keepDeveloperMemory: false, - keepVerifierMemoryOnDeveloperFailure: false, - verifierSelfFailureMemory: "reset", + it("accepts transitions to complete or known stages", () => { + const cwd = writeConfig({ + ...stageConfig(), + taskFlow: { + stages: [ + { + id: "develop", + agent: "developer", + inputTemplate: "x", + transitions: [{ when: { field: "status", equals: "done" }, next: "verify" }], }, - stages: [{ id: "s1", agent: "dev", inputTemplate: "t", outputSchema: {} }], - }, - }; - createWorkflowConfig("test", JSON.stringify(config)); - const loaded = loadWorkflowConfig(tempDir, "test"); - expect(loaded.config.taskFlow.memory?.keepDeveloperMemory).toBe(false); - expect(loaded.config.taskFlow.memory?.keepVerifierMemoryOnDeveloperFailure).toBe(false); - expect(loaded.config.taskFlow.memory?.verifierSelfFailureMemory).toBe("reset"); - }); - - it("rejects invalid verifierSelfFailureMemory values", () => { - const config = { - name: "test", - goal: "test", - agents: { pm: "pm", developer: "dev", verifier: "ver" }, - waveSource: { type: "static", staticWaves: [] }, - taskFlow: { - memory: { - verifierSelfFailureMemory: "invalid", + { + id: "verify", + agent: "verifier", + inputTemplate: "x", + transitions: [{ when: { field: "status", equals: "pass" }, next: "complete" }], }, - stages: [{ id: "s1", agent: "dev", inputTemplate: "t", outputSchema: {} }], - }, - }; - createWorkflowConfig("test", JSON.stringify(config)); - expect(() => loadWorkflowConfig(tempDir, "test")).toThrow(); + ], + }, }); + expect(loadWorkflowConfig(cwd, "temp").config.taskFlow.stages).toHaveLength(2); }); - describe("task validation", () => { - it("accepts task with requirements", () => { - const config = { - name: "test", - goal: "test", - agents: { pm: "pm", developer: "dev", verifier: "ver" }, - waveSource: { - type: "static", - staticWaves: [ - { - goal: "wave", - tasks: [ - { - id: "T1", - title: "Task", - description: "Do it", - requirements: "Must pass tests", - assignee: "developer", - }, - ], - }, - ], - }, - taskFlow: { stages: [{ id: "s1", agent: "dev", inputTemplate: "t", outputSchema: {} }] }, - }; - createWorkflowConfig("test", JSON.stringify(config)); - const loaded = loadWorkflowConfig(tempDir, "test"); - const task = loaded.config.waveSource.staticWaves?.[0].tasks[0]; - expect(task?.requirements).toBe("Must pass tests"); + it("rejects unknown transition targets", () => { + const cwd = writeConfig({ + ...stageConfig(), + taskFlow: { + stages: [ + { id: "develop", agent: "developer", inputTemplate: "x" }, + { + id: "verify", + agent: "verifier", + inputTemplate: "x", + transitions: [{ when: { field: "status", equals: "pass" }, next: "missing" }], + }, + ], + }, }); + expect(() => loadWorkflowConfig(cwd, "temp")).toThrow("unknown stage"); + }); - it("accepts task without requirements", () => { - const config = { - name: "test", - goal: "test", - agents: { pm: "pm", developer: "dev", verifier: "ver" }, - waveSource: { - type: "static", - staticWaves: [ - { - goal: "wave", - tasks: [ - { - id: "T1", - title: "Task", - description: "Do it", - // No requirements - }, - ], - }, - ], + it("loads all memory policies", () => { + const cwd = writeConfig({ + ...stageConfig(), + taskFlow: { + stages: stageConfig().taskFlow.stages, + memory: { + keepDeveloperMemory: false, + keepVerifierMemoryOnDeveloperFailure: false, + verifierSelfFailureMemory: "reset", }, - taskFlow: { stages: [{ id: "s1", agent: "dev", inputTemplate: "t", outputSchema: {} }] }, - }; - createWorkflowConfig("test", JSON.stringify(config)); - const loaded = loadWorkflowConfig(tempDir, "test"); - const task = loaded.config.waveSource.staticWaves?.[0].tasks[0]; - expect(task?.requirements).toBeUndefined(); + }, }); - - it("accepts task without assignee", () => { - const config = { - name: "test", - goal: "test", - agents: { pm: "pm", developer: "dev", verifier: "ver" }, - waveSource: { - type: "static", - staticWaves: [ - { - goal: "wave", - tasks: [ - { - id: "T1", - title: "Task", - description: "Do it", - // No assignee - }, - ], - }, - ], - }, - taskFlow: { stages: [{ id: "s1", agent: "dev", inputTemplate: "t", outputSchema: {} }] }, - }; - createWorkflowConfig("test", JSON.stringify(config)); - const loaded = loadWorkflowConfig(tempDir, "test"); - const task = loaded.config.waveSource.staticWaves?.[0].tasks[0]; - expect(task?.assignee).toBeUndefined(); + expect(loadWorkflowConfig(cwd, "temp").config.taskFlow.memory).toEqual({ + keepDeveloperMemory: false, + keepVerifierMemoryOnDeveloperFailure: false, + verifierSelfFailureMemory: "reset", }); }); - describe("allowedExtensions", () => { - it("accepts global allowedExtensions", () => { - const config = { - name: "test", - goal: "test", - allowedExtensions: ["/path/to/ext.ts"], - agents: { pm: "pm", developer: "dev", verifier: "ver" }, - waveSource: { type: "static", staticWaves: [] }, - taskFlow: { stages: [{ id: "s1", agent: "dev", inputTemplate: "t", outputSchema: {} }] }, - }; - createWorkflowConfig("test", JSON.stringify(config)); - const loaded = loadWorkflowConfig(tempDir, "test"); - expect(loaded.config.allowedExtensions).toEqual(["/path/to/ext.ts"]); - }); - - it("accepts per-agent allowedExtensions", () => { - const config = { - name: "test", - goal: "test", - allowedExtensionsByAgent: { - pm: ["/pm-ext.ts"], - developer: ["/dev-ext.ts"], - verifier: ["/ver-ext.ts"], - }, - agents: { pm: "pm", developer: "dev", verifier: "ver" }, - waveSource: { type: "static", staticWaves: [] }, - taskFlow: { stages: [{ id: "s1", agent: "dev", inputTemplate: "t", outputSchema: {} }] }, - }; - createWorkflowConfig("test", JSON.stringify(config)); - const loaded = loadWorkflowConfig(tempDir, "test"); - expect(loaded.config.allowedExtensionsByAgent?.pm).toEqual(["/pm-ext.ts"]); - expect(loaded.config.allowedExtensionsByAgent?.developer).toEqual(["/dev-ext.ts"]); - expect(loaded.config.allowedExtensionsByAgent?.verifier).toEqual(["/ver-ext.ts"]); - }); - - it("accepts both global and per-agent allowedExtensions", () => { - const config = { - name: "test", - goal: "test", - allowedExtensions: ["/global-ext.ts"], - allowedExtensionsByAgent: { - pm: ["/pm-ext.ts"], - developer: [], - verifier: [], - }, - agents: { pm: "pm", developer: "dev", verifier: "ver" }, - waveSource: { type: "static", staticWaves: [] }, - taskFlow: { stages: [{ id: "s1", agent: "dev", inputTemplate: "t", outputSchema: {} }] }, - }; - createWorkflowConfig("test", JSON.stringify(config)); - const loaded = loadWorkflowConfig(tempDir, "test"); - expect(loaded.config.allowedExtensions).toEqual(["/global-ext.ts"]); - expect(loaded.config.allowedExtensionsByAgent).toBeDefined(); + it("rejects invalid memory policy values", () => { + const cwd = writeConfig({ + ...stageConfig(), + taskFlow: { + stages: stageConfig().taskFlow.stages, + memory: { verifierSelfFailureMemory: "invalid" }, + }, }); + expect(() => loadWorkflowConfig(cwd, "temp")).toThrow("schema validation"); }); - describe("maxWaves and maxTaskRetries", () => { - it("applies default maxWaves", () => { - const config = { - name: "test", - goal: "test", - agents: { pm: "pm", developer: "dev", verifier: "ver" }, - waveSource: { type: "static", staticWaves: [] }, - taskFlow: { stages: [{ id: "s1", agent: "dev", inputTemplate: "t", outputSchema: {} }] }, - }; - createWorkflowConfig("test", JSON.stringify(config)); - const loaded = loadWorkflowConfig(tempDir, "test"); - expect(loaded.config.maxWaves).toBe(10); + it("accepts complete static waves with requirements", () => { + const cwd = writeConfig({ + ...stageConfig(), + waveSource: { type: "static", staticWaves: [{ goal: "Wave", tasks: [task()] }] }, }); + const loaded = loadWorkflowConfig(cwd, "temp").config; + expect(loaded.waveSource.type).toBe("static"); + expect(loaded.waveSource.staticWaves?.[0].tasks[0].requirements).toBe("Run verification"); + }); - it("applies custom maxWaves", () => { - const config = { - name: "test", - goal: "test", - maxWaves: 5, - agents: { pm: "pm", developer: "dev", verifier: "ver" }, - waveSource: { type: "static", staticWaves: [] }, - taskFlow: { stages: [{ id: "s1", agent: "dev", inputTemplate: "t", outputSchema: {} }] }, - }; - createWorkflowConfig("test", JSON.stringify(config)); - const loaded = loadWorkflowConfig(tempDir, "test"); - expect(loaded.config.maxWaves).toBe(5); - }); + it("rejects duplicate, unsafe, and unassigned static tasks", () => { + for (const tasks of [ + [task("T1"), task("T1")], + [{ ...task(), id: "../escape" }], + [{ ...task(), assignee: "verifier" }], + ]) { + const cwd = writeConfig({ + ...stageConfig(), + waveSource: { type: "static", staticWaves: [{ goal: "Wave", tasks }] }, + }); + expect(() => loadWorkflowConfig(cwd, "temp")).toThrow(); + } + }); - it("applies default maxTaskRetries", () => { - const config = { - name: "test", - goal: "test", - agents: { pm: "pm", developer: "dev", verifier: "ver" }, - waveSource: { type: "static", staticWaves: [] }, - taskFlow: { stages: [{ id: "s1", agent: "dev", inputTemplate: "t", outputSchema: {} }] }, - }; - createWorkflowConfig("test", JSON.stringify(config)); - const loaded = loadWorkflowConfig(tempDir, "test"); - expect(loaded.config.maxTaskRetries).toBe(2); + it("rejects static waves without tasks or requirements", () => { + const empty = writeConfig({ + ...stageConfig(), + waveSource: { type: "static", staticWaves: [{ goal: "Wave", tasks: [] }] }, }); + expect(() => loadWorkflowConfig(empty, "temp")).toThrow("nonempty wave"); - it("applies custom maxTaskRetries", () => { - const config = { - name: "test", - goal: "test", - maxTaskRetries: 5, - agents: { pm: "pm", developer: "dev", verifier: "ver" }, - waveSource: { type: "static", staticWaves: [] }, - taskFlow: { stages: [{ id: "s1", agent: "dev", inputTemplate: "t", outputSchema: {} }] }, - }; - createWorkflowConfig("test", JSON.stringify(config)); - const loaded = loadWorkflowConfig(tempDir, "test"); - expect(loaded.config.maxTaskRetries).toBe(5); + const missing = writeConfig({ + ...stageConfig(), + waveSource: { + type: "static", + staticWaves: [{ goal: "Wave", tasks: [{ id: "T1", title: "Task", description: "Do" }] }], + }, }); + expect(() => loadWorkflowConfig(missing, "temp")).toThrow("schema validation"); }); - describe("waveSource types", () => { - it("accepts pm waveSource type", () => { - const config = { - name: "test", - goal: "test", - agents: { pm: "pm", developer: "dev", verifier: "ver" }, - waveSource: { type: "pm" }, - taskFlow: { stages: [{ id: "s1", agent: "dev", inputTemplate: "t", outputSchema: {} }] }, - }; - createWorkflowConfig("test", JSON.stringify(config)); - const loaded = loadWorkflowConfig(tempDir, "test"); - expect(loaded.config.waveSource.type).toBe("pm"); - }); - - it("accepts static waveSource type", () => { - const config = { - name: "test", - goal: "test", - agents: { pm: "pm", developer: "dev", verifier: "ver" }, - waveSource: { - type: "static", - staticWaves: [ - { goal: "wave1", tasks: [] }, - { goal: "wave2", tasks: [] }, - ], - }, - taskFlow: { stages: [{ id: "s1", agent: "dev", inputTemplate: "t", outputSchema: {} }] }, - }; - createWorkflowConfig("test", JSON.stringify(config)); - const loaded = loadWorkflowConfig(tempDir, "test"); - expect(loaded.config.waveSource.type).toBe("static"); - expect(loaded.config.waveSource.staticWaves).toHaveLength(2); - }); + it("supports PM and static wave sources", () => { + const pm = writeConfig({ ...stageConfig(), waveSource: { type: "pm" } }); + expect(loadWorkflowConfig(pm, "temp").config.waveSource.type).toBe("pm"); - it("rejects invalid waveSource type", () => { - const config = { - name: "test", - goal: "test", - agents: { pm: "pm", developer: "dev", verifier: "ver" }, - waveSource: { type: "invalid" }, - taskFlow: { stages: [{ id: "s1", agent: "dev", inputTemplate: "t", outputSchema: {} }] }, - }; - createWorkflowConfig("test", JSON.stringify(config)); - expect(() => loadWorkflowConfig(tempDir, "test")).toThrow(); + const staticCwd = writeConfig({ + ...stageConfig(), + waveSource: { type: "static", staticWaves: [{ goal: "Wave", tasks: [task()] }] }, }); + expect(loadWorkflowConfig(staticCwd, "temp").config.waveSource.type).toBe("static"); }); - describe("file not found", () => { - it("throws when workflow file does not exist", () => { - expect(() => loadWorkflowConfig(tempDir, "nonexistent")).toThrow("Workflow not found"); - }); + it("loads global and per-agent extension allowlists", () => { + const cwd = writeConfig({ + ...stageConfig(), + allowedExtensions: ["/global.ts"], + allowedExtensionsByAgent: { + pm: ["/pm.ts"], + developer: ["/developer.ts"], + verifier: ["/verifier.ts"], + }, + }); + const config = loadWorkflowConfig(cwd, "temp").config; + expect(config.allowedExtensions).toEqual(["/global.ts"]); + expect(config.allowedExtensionsByAgent?.verifier).toEqual(["/verifier.ts"]); }); - describe("agent retry validation", () => { - function baseConfig(agentRetry: Record) { - return { - name: "test", - goal: "test", - agentRetry, - agents: { pm: "pm", developer: "dev", verifier: "ver" }, - waveSource: { type: "static", staticWaves: [] }, - taskFlow: { stages: [{ id: "s1", agent: "dev", inputTemplate: "t", outputSchema: {} }] }, - }; - } + it("applies configured numeric limits", () => { + const cwd = writeConfig({ + ...stageConfig(), + maxWaves: 5, + maxTaskRetries: 4, + maxPmRetries: 2, + parallelism: 10, + }); + const config = loadWorkflowConfig(cwd, "temp").config; + expect(config.maxWaves).toBe(5); + expect(config.maxTaskRetries).toBe(4); + expect(config.maxPmRetries).toBe(2); + expect(config.parallelism).toBe(10); + }); - it("rejects maxAttempts less than 1", () => { - createWorkflowConfig("test", JSON.stringify(baseConfig({ maxAttempts: 0 }))); - expect(() => loadWorkflowConfig(tempDir, "test")).toThrow( - "agentRetry.maxAttempts must be at least 1", - ); - }); + it("rejects the removed agentRetry configuration", () => { + const cwd = writeConfig({ ...stageConfig(), agentRetry: { maxAttempts: 2 } }); + expect(() => loadWorkflowConfig(cwd, "temp")).toThrow("agentRetry was removed"); + }); - it("rejects negative delays and jitter", () => { - createWorkflowConfig( - "test", - JSON.stringify( - baseConfig({ - initialDelayMs: -1, - maxDelayMs: -1, - jitterMs: -1, - }), - ), - ); - expect(() => loadWorkflowConfig(tempDir, "test")).toThrow( - "agentRetry.initialDelayMs must be at least 0", - ); - }); + it("rejects duplicate stage IDs through the schema", () => { + const cwd = writeConfig({ + ...stageConfig(), + taskFlow: { + stages: [ + { id: "develop", agent: "developer", inputTemplate: "x" }, + { id: "develop", agent: "verifier", inputTemplate: "x" }, + ], + }, + }); + expect(() => loadWorkflowConfig(cwd, "temp")).toThrow(); + }); - it("rejects backoffMultiplier less than 1", () => { - createWorkflowConfig("test", JSON.stringify(baseConfig({ backoffMultiplier: 0 }))); - expect(() => loadWorkflowConfig(tempDir, "test")).toThrow( - "agentRetry.backoffMultiplier must be at least 1", - ); - }); + it("throws when the workflow file is absent", () => { + const cwd = fs.mkdtempSync(path.join(os.tmpdir(), "workflow-missing-")); + expect(() => loadWorkflowConfig(cwd, "missing")).toThrow("Workflow not found"); }); }); diff --git a/tests/config.test.ts b/tests/config.test.ts index 98555d8..b0ac893 100644 --- a/tests/config.test.ts +++ b/tests/config.test.ts @@ -4,122 +4,203 @@ import * as os from "node:os"; import * as path from "node:path"; import { loadWorkflowConfig } from "../.pi/extensions/workflow-orchestrator/config.js"; -function setupTempConfig(content: string): { cwd: string; name: string } { - const dir = fs.mkdtempSync(path.join(os.tmpdir(), "workflow-test-")); - const workflowDir = path.join(dir, ".pi", "workflows"); - fs.mkdirSync(workflowDir, { recursive: true }); - const name = "temp"; - fs.writeFileSync(path.join(workflowDir, `${name}.workflow.json`), content, "utf-8"); - return { cwd: dir, name }; +function setup(content: unknown, name = "temp"): string { + const cwd = fs.mkdtempSync(path.join(os.tmpdir(), "workflow-config-test-")); + const directory = path.join(cwd, ".pi", "workflows"); + fs.mkdirSync(directory, { recursive: true }); + fs.writeFileSync( + path.join(directory, `${name}.workflow.json`), + typeof content === "string" ? content : JSON.stringify(content), + ); + return cwd; +} + +function validConfig(overrides: Record = {}) { + return { + name: "temp", + goal: "Build the feature", + agents: { pm: "pm", developer: "developer", verifier: "verifier" }, + waveSource: { type: "static", staticWaves: [] }, + taskFlow: { + stages: [ + { id: "develop", agent: "developer", inputTemplate: "Implement {{task.title}}" }, + { + id: "verify", + agent: "verifier", + inputTemplate: "Verify {{task.title}}", + transitions: [ + { when: { field: "status", equals: "fail" }, next: "develop" }, + { when: { field: "status", equals: "pass" }, next: "complete" }, + ], + }, + ], + }, + ...overrides, + }; } describe("loadWorkflowConfig", () => { - it("throws on invalid JSON", () => { - const { cwd, name } = setupTempConfig("{ invalid json }"); - expect(() => loadWorkflowConfig(cwd, name)).toThrow(); - }); + it("throws on invalid JSON and schema mismatch", () => { + const invalidJson = setup("{ invalid json }"); + expect(() => loadWorkflowConfig(invalidJson, "temp")).toThrow("Invalid JSON"); - it("throws on schema mismatch", () => { - const { cwd, name } = setupTempConfig(JSON.stringify({ name: "x" })); - expect(() => loadWorkflowConfig(cwd, name)).toThrow(); + const invalidSchema = setup({ name: "temp" }); + expect(() => loadWorkflowConfig(invalidSchema, "temp")).toThrow("schema validation"); }); - it("loads valid config", () => { - const config = { - name: "temp", - goal: "test", - parallelism: 1, - agents: { pm: "pm", developer: "dev", verifier: "ver" }, - waveSource: { type: "static", staticWaves: [{ goal: "g", tasks: [] }] }, - taskFlow: { stages: [{ id: "develop", agent: "dev", inputTemplate: "x", outputSchema: {} }] }, - }; - const { cwd, name } = setupTempConfig(JSON.stringify(config)); - const loaded = loadWorkflowConfig(cwd, name); - expect(loaded.config.name).toBe("temp"); - expect(loaded.config.parallelism).toBe(1); - expect(loaded.config.agentRetry).toEqual({ - maxAttempts: 5, - initialDelayMs: 5000, - maxDelayMs: 120000, - backoffMultiplier: 2, - jitterMs: 1000, - }); - expect(loaded.config.taskFlow.memory?.keepDeveloperMemory).toBe(true); - expect(loaded.config.taskFlow.memory?.keepVerifierMemoryOnDeveloperFailure).toBe(true); - expect(loaded.config.taskFlow.memory?.verifierSelfFailureMemory).toBe("keep"); + it("loads a valid config and applies defaults", () => { + const cwd = setup(validConfig()); + const config = loadWorkflowConfig(cwd, "temp").config; + expect(config.name).toBe("temp"); + expect(config.piCommand).toBe("pi"); + expect(config.maxWaves).toBe(10); + expect(config.maxTaskRetries).toBe(2); + expect(config.maxPmRetries).toBe(3); + expect(config.parallelism).toBe(1); + expect(config.taskFlow.memory?.keepDeveloperMemory).toBe(true); }); - it("loads custom agent retry policy", () => { - const config = { - name: "temp", - goal: "test", - agentRetry: { - maxAttempts: 2, - initialDelayMs: 10, - maxDelayMs: 100, - backoffMultiplier: 3, - jitterMs: 0, - }, - agents: { pm: "pm", developer: "dev", verifier: "ver" }, - waveSource: { type: "static", staticWaves: [{ goal: "g", tasks: [] }] }, - taskFlow: { stages: [{ id: "develop", agent: "dev", inputTemplate: "x", outputSchema: {} }] }, - }; - const { cwd, name } = setupTempConfig(JSON.stringify(config)); - const loaded = loadWorkflowConfig(cwd, name); - expect(loaded.config.agentRetry).toEqual(config.agentRetry); + it("rejects the removed agentRetry setting with migration guidance", () => { + const cwd = setup(validConfig({ agentRetry: { maxAttempts: 2 } })); + expect(() => loadWorkflowConfig(cwd, "temp")).toThrow("agentRetry was removed"); }); it("loads custom task memory policy", () => { - const config = { - name: "temp", - goal: "test", - agents: { pm: "pm", developer: "dev", verifier: "ver" }, - waveSource: { type: "static", staticWaves: [{ goal: "g", tasks: [] }] }, - taskFlow: { - memory: { - keepDeveloperMemory: false, - keepVerifierMemoryOnDeveloperFailure: false, - verifierSelfFailureMemory: "keep", + const cwd = setup( + validConfig({ + taskFlow: { + stages: validConfig().taskFlow.stages, + memory: { + keepDeveloperMemory: false, + keepVerifierMemoryOnDeveloperFailure: false, + verifierSelfFailureMemory: "reset_on_malformed_output", + }, }, - stages: [{ id: "develop", agent: "dev", inputTemplate: "x", outputSchema: {} }], - }, - }; - const { cwd, name } = setupTempConfig(JSON.stringify(config)); - const loaded = loadWorkflowConfig(cwd, name); - expect(loaded.config.taskFlow.memory?.keepDeveloperMemory).toBe(false); - expect(loaded.config.taskFlow.memory?.keepVerifierMemoryOnDeveloperFailure).toBe(false); - expect(loaded.config.taskFlow.memory?.verifierSelfFailureMemory).toBe("keep"); + }), + ); + const memory = loadWorkflowConfig(cwd, "temp").config.taskFlow.memory; + expect(memory).toEqual({ + keepDeveloperMemory: false, + keepVerifierMemoryOnDeveloperFailure: false, + verifierSelfFailureMemory: "reset_on_malformed_output", + }); }); it("accepts requirements and per-agent extensions", () => { - const config = { - name: "temp", - goal: "test", - agents: { pm: "pm", developer: "dev", verifier: "ver" }, - allowedExtensionsByAgent: { pm: ["/ext/pm.ts"], developer: [], verifier: [] }, - waveSource: { - type: "static", - staticWaves: [ - { - goal: "g", - tasks: [ - { - id: "T1", - title: "Task", - description: "Do the thing", - requirements: "Verify the thing", - assignee: "developer", - }, - ], - }, - ], - }, - taskFlow: { stages: [{ id: "develop", agent: "dev", inputTemplate: "x", outputSchema: {} }] }, - }; - const { cwd, name } = setupTempConfig(JSON.stringify(config)); - const loaded = loadWorkflowConfig(cwd, name); - const task = loaded.config.waveSource.staticWaves?.[0].tasks[0]; - expect(task?.requirements).toBe("Verify the thing"); - expect(loaded.config.allowedExtensionsByAgent?.pm?.[0]).toBe("/ext/pm.ts"); + const cwd = setup( + validConfig({ + allowedExtensionsByAgent: { pm: ["/pm.ts"], developer: ["/dev.ts"], verifier: [] }, + waveSource: { + type: "static", + staticWaves: [ + { + goal: "Implement", + tasks: [ + { + id: "T1", + title: "Task", + description: "Do the thing", + requirements: "Verify the thing", + assignee: "developer", + }, + ], + }, + ], + }, + }), + ); + const config = loadWorkflowConfig(cwd, "temp").config; + expect(config.waveSource.staticWaves?.[0].tasks[0].requirements).toBe("Verify the thing"); + expect(config.allowedExtensionsByAgent?.pm).toEqual(["/pm.ts"]); + }); + + it("rejects unsafe workflow names", () => { + const cwd = setup(validConfig(), "temp"); + expect(() => loadWorkflowConfig(cwd, "../escape")).toThrow("Invalid workflow name"); + expect(() => loadWorkflowConfig(cwd, "")).toThrow("Workflow name is required"); + expect(() => loadWorkflowConfig(cwd, "a".repeat(65))).toThrow("too long"); + }); + + it("requires exactly the develop and verify semantic stages", () => { + const cwd = setup( + validConfig({ + taskFlow: { + stages: [{ id: "plan", agent: "developer", inputTemplate: "x" }], + }, + }), + ); + expect(() => loadWorkflowConfig(cwd, "temp")).toThrow("schema validation"); + }); + + it("validates transition targets and configured agents", () => { + const unknownTransition = setup( + validConfig({ + taskFlow: { + stages: [ + { id: "develop", agent: "developer", inputTemplate: "x" }, + { + id: "verify", + agent: "verifier", + inputTemplate: "x", + transitions: [{ when: { field: "status", equals: "pass" }, next: "missing" }], + }, + ], + }, + }), + ); + expect(() => loadWorkflowConfig(unknownTransition, "temp")).toThrow("unknown stage"); + + const unknownAgent = setup( + validConfig({ + taskFlow: { + stages: [ + { id: "develop", agent: "missing", inputTemplate: "x" }, + { id: "verify", agent: "verifier", inputTemplate: "x" }, + ], + }, + }), + ); + expect(() => loadWorkflowConfig(unknownAgent, "temp")).toThrow("unknown agent"); + }); + + it("rejects duplicate or incomplete static tasks", () => { + const cwd = setup( + validConfig({ + waveSource: { + type: "static", + staticWaves: [ + { + goal: "Wave", + tasks: [ + { id: "T1", title: "One", description: "Do one", requirements: "Verify one" }, + { id: "T1", title: "Two", description: "Do two", requirements: "Verify two" }, + ], + }, + ], + }, + }), + ); + expect(() => loadWorkflowConfig(cwd, "temp")).toThrow("duplicate task id"); + + const incomplete = setup( + validConfig({ + waveSource: { + type: "static", + staticWaves: [ + { goal: "Wave", tasks: [{ id: "T1", title: "One", description: "Do one" }] }, + ], + }, + }), + ); + expect(() => loadWorkflowConfig(incomplete, "temp")).toThrow("schema validation"); + }); + + it("requires positive integer limits", () => { + for (const field of ["maxWaves", "maxPmRetries", "parallelism"]) { + const cwd = setup(validConfig({ [field]: 0 })); + expect(() => loadWorkflowConfig(cwd, "temp")).toThrow(); + } + const retries = setup(validConfig({ maxTaskRetries: -1 })); + expect(() => loadWorkflowConfig(retries, "temp")).toThrow(); }); }); diff --git a/tests/fixtures/active-tools-probe.ts b/tests/fixtures/active-tools-probe.ts new file mode 100644 index 0000000..58f50fe --- /dev/null +++ b/tests/fixtures/active-tools-probe.ts @@ -0,0 +1,14 @@ +import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; + +/** Test-only extension used by the opt-in real-Pi compatibility smoke test. */ +export default function registerActiveToolsProbe(pi: ExtensionAPI): void { + pi.registerCommand("piorch-active-tools-probe", { + description: "Report the active tool allowlist to the RPC test host", + handler: async () => { + pi.appendEntry("piorch-active-tools", { + role: process.env.PIORCH_PROBE_ROLE ?? "unknown", + tools: pi.getActiveTools(), + }); + }, + }); +} diff --git a/tests/fixtures/bun-pi-wrapper.mjs b/tests/fixtures/bun-pi-wrapper.mjs new file mode 100755 index 0000000..9169b79 --- /dev/null +++ b/tests/fixtures/bun-pi-wrapper.mjs @@ -0,0 +1,16 @@ +#!/usr/bin/env bun + +const runtime = process.env.PIORCH_REAL_PI_RUNTIME ?? "node"; +const entry = process.env.PIORCH_REAL_PI_ENTRY; +if (!entry) { + console.error("PIORCH_REAL_PI_ENTRY is required when using bun-pi-wrapper.mjs"); + process.exit(2); +} + +const child = Bun.spawn([runtime, entry, ...process.argv.slice(2)], { + stdin: "inherit", + stdout: "inherit", + stderr: "inherit", + env: process.env, +}); +process.exit(await child.exited); diff --git a/tests/fixtures/fake-pi.mjs b/tests/fixtures/fake-pi.mjs new file mode 100755 index 0000000..cd6bcc4 --- /dev/null +++ b/tests/fixtures/fake-pi.mjs @@ -0,0 +1,179 @@ +#!/usr/bin/env node + +import { setTimeout as delay } from "node:timers/promises"; + +const scenario = process.env.PIORCH_FAKE_PI_SCENARIO ?? "success"; +const reportParams = JSON.parse( + process.env.PIORCH_FAKE_PI_PARAMS ?? + JSON.stringify({ + status: "done", + summary: "Implemented", + filesChanged: ["src/feature.ts"], + evidence: [{ kind: "test", description: "Tests pass", outcome: "pass" }], + issues: [], + }), +); + +if (process.argv.includes("--version")) { + process.stdout.write(`${process.env.PIORCH_FAKE_PI_VERSION ?? "0.80.10"}\n`); + process.exit(0); +} + +if (scenario === "exit-before-ready") process.exit(7); + +function write(value, options = {}) { + const line = JSON.stringify(value); + if (options.crlf) process.stdout.write(`${line}\r\n`); + else process.stdout.write(`${line}\n`); +} + +function writeUtf8Split(value) { + const bytes = Buffer.from(`${JSON.stringify(value)}\n`, "utf8"); + const split = Math.max(1, Math.floor(bytes.length / 2)); + process.stdout.write(bytes.subarray(0, split)); + process.stdout.write(bytes.subarray(split)); +} + +function response(command, id, success = true, data, crlf = false) { + if (success) + write( + { id, type: "response", command, success, ...(data === undefined ? {} : { data }) }, + { crlf }, + ); + else write({ id, type: "response", command, success: false, error: "fake Pi rejected command" }); +} + +function toolStart(id, name, args = reportParams) { + write({ type: "tool_execution_start", toolCallId: id, toolName: name, args }); +} + +function toolEnd(id, name, result = { details: { params: reportParams } }, isError = false) { + write({ type: "tool_execution_end", toolCallId: id, toolName: name, result, isError }); +} + +async function settlePrompt() { + write({ type: "agent_start" }); + const toolName = process.env.PIORCH_FAKE_PI_TOOL ?? "report_task_result"; + if (scenario === "retry") { + // The first attempt fails before a report tool executes; the retry is the + // only attempt that can produce an accepted structured result. + } else if (scenario === "end-without-start") { + toolEnd("missing", toolName); + } else if (scenario === "duplicate-start") { + toolStart("duplicate", toolName); + toolStart("duplicate", toolName); + } else if (scenario === "duplicate-success") { + toolStart("one", toolName); + toolEnd("one", toolName); + toolStart("two", toolName); + toolEnd("two", toolName); + } else if (scenario === "failed-tool") { + toolStart("failed", toolName); + toolEnd("failed", toolName, { details: { params: reportParams } }, true); + } else if (scenario === "corrected-tool") { + toolStart("failed", toolName); + toolEnd("failed", toolName, { details: { params: reportParams } }, true); + toolStart("success", toolName); + toolEnd("success", toolName); + } else if (scenario !== "no-tool") { + toolStart("report", toolName); + toolEnd("report", toolName); + } + if (scenario === "agent-end-before-settled") { + write({ type: "agent_end", messages: [], willRetry: false }); + await delay(50); + write({ type: "agent_settled" }); + } + if (scenario === "retry") { + write({ + type: "agent_end", + messages: [ + { role: "assistant", content: [], stopReason: "error", errorMessage: "temporary" }, + ], + willRetry: true, + }); + write({ + type: "auto_retry_start", + attempt: 1, + maxAttempts: 2, + delayMs: 0, + errorMessage: "temporary", + }); + write({ type: "auto_retry_end", success: true, attempt: 1 }); + write({ type: "agent_start" }); + toolStart("retry-report", "report_task_result"); + toolEnd("retry-report", "report_task_result"); + write({ type: "agent_end", messages: [], willRetry: false }); + write({ type: "agent_settled" }); + } else { + write({ type: "agent_end", messages: [], willRetry: false }); + write({ type: "agent_settled" }); + } +} + +let promptCount = 0; +let ready = false; +let buffer = ""; +process.stdin.setEncoding("utf8"); +process.stdin.on("data", (chunk) => { + buffer += chunk; + const lines = buffer.split("\n"); + buffer = lines.pop() ?? ""; + for (const line of lines) { + if (!line.trim()) continue; + const command = JSON.parse(line); + if (command.type === "get_state") { + if (scenario === "malformed") process.stdout.write("not json\n"); + else if (scenario === "oversized") + process.stdout.write(`${"x".repeat(4 * 1024 * 1024 + 10)}\n`); + else { + response( + "get_state", + command.id, + true, + { sessionId: "fake", isStreaming: false }, + scenario === "crlf", + ); + ready = true; + } + continue; + } + if (command.type === "prompt") { + promptCount += 1; + if (scenario === "reject-prompt") { + response("prompt", command.id, false); + continue; + } + response("prompt", command.id); + if (scenario === "utf8-split") { + writeUtf8Split({ + type: "message_update", + assistantMessageEvent: { type: "text_delta", delta: "✓" }, + }); + } + if (scenario === "run-timeout" || scenario === "abort-timeout") continue; + if (scenario === "partial-final") { + process.stdout.write('{"type":"agent_end"'); + process.exit(0); + } + if (scenario === "unknown-response") { + write({ id: "unknown", type: "response", command: "prompt", success: true }); + continue; + } + if (scenario === "retry" && promptCount > 1) continue; + void settlePrompt(); + continue; + } + if (command.type === "steer") { + response("steer", command.id); + continue; + } + if (command.type === "abort") { + if (scenario !== "abort-timeout") { + response("abort", command.id); + write({ type: "agent_end", messages: [], willRetry: false }); + write({ type: "agent_settled" }); + } + } + } +}); diff --git a/tests/index.test.ts b/tests/index.test.ts index cda80a9..cb05d60 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -80,4 +80,14 @@ describe("normalizeGoal", () => { it("returns unquoted text as-is", () => { expect(normalizeGoal("Build a bot")).toBe("Build a bot"); }); + + it("strips stray quotes from broken tokenization", () => { + expect(normalizeGoal('"build something"')).toBe("build something"); + expect(normalizeGoal('"build')).toBe("build"); + expect(normalizeGoal('something"')).toBe("something"); + }); + + it("preserves quotes that are part of the goal", () => { + expect(normalizeGoal('"foo" bug')).toBe('"foo" bug'); + }); }); diff --git a/tests/models.test.ts b/tests/models.test.ts new file mode 100644 index 0000000..273e4ad --- /dev/null +++ b/tests/models.test.ts @@ -0,0 +1,18 @@ +import { describe, expect, it } from "vitest"; +import { pickModel } from "../.pi/extensions/workflow-orchestrator/models.js"; +import { formatSubagentError } from "../.pi/extensions/workflow-orchestrator/utils.js"; + +describe("pickModel", () => { + it("returns the first non-empty candidate", () => { + expect(pickModel(undefined, "google/gemini-2.0-flash")).toBe("google/gemini-2.0-flash"); + expect(pickModel("openrouter/custom", "google/gemini-2.0-flash")).toBe("openrouter/custom"); + expect(pickModel(undefined, undefined)).toBeUndefined(); + }); +}); + +describe("formatSubagentError", () => { + it("adds a short hint for model errors", () => { + const message = formatSubagentError("404: model is unavailable"); + expect(message).toContain("/model"); + }); +}); diff --git a/tests/real-pi.integration.test.ts b/tests/real-pi.integration.test.ts new file mode 100644 index 0000000..15facc9 --- /dev/null +++ b/tests/real-pi.integration.test.ts @@ -0,0 +1,177 @@ +import { afterEach, describe, expect, it } from "vitest"; +import { spawn, type ChildProcessWithoutNullStreams } from "node:child_process"; +import * as fs from "node:fs"; +import * as os from "node:os"; +import * as path from "node:path"; +import { StringDecoder } from "node:string_decoder"; +import { preflightPiExecutable } from "../.pi/extensions/workflow-orchestrator/runner.js"; + +type RpcRecord = Record; + +const enabled = process.env.PIORCH_REAL_PI_TEST === "1"; +const piCommand = process.env.PIORCH_PI_COMMAND ?? "pi"; +const probeExtension = path.resolve("tests/fixtures/active-tools-probe.ts"); +const roleExtensions = { + pm: path.resolve(".pi/extensions/workflow-pm-tools/index.ts"), + developer: path.resolve(".pi/extensions/workflow-task-tools/index.ts"), + verifier: path.resolve(".pi/extensions/workflow-task-tools/index.ts"), +} as const; +const roleTools = { + pm: ["read", "grep", "find", "ls", "generate_wave"], + developer: ["read", "edit", "write", "bash", "grep", "find", "ls", "report_task_result"], + verifier: ["read", "grep", "find", "ls", "bash", "report_task_result"], +} as const; + +const children: ChildProcessWithoutNullStreams[] = []; + +afterEach(() => { + for (const child of children.splice(0)) child.kill("SIGKILL"); +}); + +function waitForRecord( + child: ChildProcessWithoutNullStreams, + predicate: (record: RpcRecord) => boolean, + timeoutMs = 10_000, +): Promise { + const decoder = new StringDecoder("utf8"); + let buffer = ""; + const records: RpcRecord[] = []; + let stderr = ""; + let settled = false; + + return new Promise((resolve, reject) => { + const cleanup = () => { + child.stdout.off("data", onData); + child.stderr.off("data", onStderr); + child.off("close", onClose); + child.off("error", onError); + clearTimeout(timeout); + }; + const finish = (callback: () => void) => { + if (settled) return; + settled = true; + cleanup(); + callback(); + }; + const onData = (chunk: Buffer | string) => { + buffer += decoder.write(typeof chunk === "string" ? Buffer.from(chunk) : chunk); + let newline = buffer.indexOf("\n"); + while (newline !== -1) { + const line = buffer.slice(0, newline).replace(/\r$/, ""); + buffer = buffer.slice(newline + 1); + if (line.trim()) { + try { + const record = JSON.parse(line) as RpcRecord; + records.push(record); + if (predicate(record)) finish(() => resolve(record)); + } catch (error) { + finish(() => reject(new Error(`Real Pi emitted malformed JSON: ${String(error)}`))); + return; + } + } + newline = buffer.indexOf("\n"); + } + }; + const onStderr = (chunk: Buffer | string) => { + stderr = `${stderr}${typeof chunk === "string" ? chunk : chunk.toString("utf8")}`.slice( + -4000, + ); + }; + const onError = (error: Error) => { + finish(() => reject(new Error(`Real Pi process error: ${error.message}`))); + }; + const onClose = (code: number | null, signal: NodeJS.Signals | null) => { + finish(() => + reject( + new Error(`Real Pi exited before the probe completed (code=${code}, signal=${signal})`), + ), + ); + }; + const timeout = setTimeout(() => { + finish(() => + reject( + new Error( + `Timed out waiting for real Pi RPC record: ${JSON.stringify(records.slice(-3))}; stderr=${stderr}`, + ), + ), + ); + }, timeoutMs); + child.stdout.on("data", onData); + child.stderr.on("data", onStderr); + child.on("close", onClose); + child.on("error", onError); + }); +} + +async function startProbe(role: keyof typeof roleExtensions): Promise { + await preflightPiExecutable(piCommand, process.cwd()); + const directory = fs.mkdtempSync(path.join(os.tmpdir(), "piorch-real-pi-")); + const sessionFile = path.join(directory, "session.jsonl"); + const child = spawn( + piCommand, + [ + "--mode", + "rpc", + "--session", + sessionFile, + "--no-extensions", + "--no-skills", + "--no-prompt-templates", + "-e", + roleExtensions[role], + "-e", + probeExtension, + "--tools", + roleTools[role].join(","), + ], + { + cwd: process.cwd(), + shell: false, + env: { ...process.env, PIORCH_PROBE_ROLE: role }, + stdio: ["pipe", "pipe", "pipe"], + }, + ); + children.push(child); + + let counter = 0; + const send = (body: Record) => { + child.stdin.write(`${JSON.stringify({ id: `probe-${++counter}`, ...body })}\n`); + }; + + const ready = waitForRecord( + child, + (record) => record.type === "response" && record.command === "get_state", + ); + send({ type: "get_state" }); + await ready; + + const probePromise = waitForRecord( + child, + (record) => + record.type === "entry_appended" && record.entry?.customType === "piorch-active-tools", + ); + send({ type: "prompt", message: "/piorch-active-tools-probe" }); + const probe = await probePromise; + child.kill("SIGTERM"); + fs.rmSync(directory, { recursive: true, force: true }); + return probe.entry.data.tools as string[]; +} + +describe.skipIf(!enabled)("real Pi role-tool compatibility", () => { + it("activates the PM custom tool", async () => { + const tools = await startProbe("pm"); + expect(tools).toContain("generate_wave"); + }, 15_000); + + it("activates the developer custom tool", async () => { + const tools = await startProbe("developer"); + expect(tools).toContain("report_task_result"); + }, 15_000); + + it("activates verifier reporting without write tools", async () => { + const tools = await startProbe("verifier"); + expect(tools).toContain("report_task_result"); + expect(tools).not.toContain("edit"); + expect(tools).not.toContain("write"); + }, 15_000); +}); diff --git a/tests/render.test.ts b/tests/render.test.ts index 918a0a2..845c0f3 100644 --- a/tests/render.test.ts +++ b/tests/render.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it, vi, beforeEach, afterEach } from "vitest"; -import type { ExtensionContext } from "@mariozechner/pi-coding-agent"; +import type { ExtensionContext } from "@earendil-works/pi-coding-agent"; import type { WorkflowState, TaskState } from "../.pi/extensions/workflow-orchestrator/state.js"; import { updateStatus, @@ -50,6 +50,7 @@ describe("render.ts", () => { runId: "test-run", workflowName: "default", goal: "Test goal", + status: "running", active: true, waveIndex: 0, wave: { goal: "Wave 1", tasks: [] }, @@ -146,7 +147,10 @@ describe("render.ts", () => { updateStatus(mockCtx, state); - expect(mockUi.setStatus).toHaveBeenCalledWith("workflow", "Wave 3: 2/4 verified, 1 failed"); + expect(mockUi.setStatus).toHaveBeenCalledWith( + "workflow", + "running: Wave 3: 2/4 verified, 1 failed", + ); }); it("shows all tasks verified", () => { @@ -156,7 +160,7 @@ describe("render.ts", () => { updateStatus(mockCtx, state); - expect(mockUi.setStatus).toHaveBeenCalledWith("workflow", "Wave 1: 2/2 verified"); + expect(mockUi.setStatus).toHaveBeenCalledWith("workflow", "running: Wave 1: 2/2 verified"); }); it("sorts tasks by status (in_progress first)", () => { @@ -458,7 +462,7 @@ describe("render.ts", () => { updateStatus(mockCtx, state); - expect(mockUi.setStatus).toHaveBeenCalledWith("workflow", "Wave 1: 0/0 verified"); + expect(mockUi.setStatus).toHaveBeenCalledWith("workflow", "running: Wave 1: 0/0 verified"); expect(mockUi.setWidget).toHaveBeenCalled(); }); }); diff --git a/tests/runner.test.ts b/tests/runner.test.ts index a4f1b6a..622e136 100644 --- a/tests/runner.test.ts +++ b/tests/runner.test.ts @@ -1,257 +1,179 @@ -import { describe, expect, it } from "vitest"; +import { afterEach, describe, expect, it } from "vitest"; +import * as fs from "node:fs"; +import * as os from "node:os"; +import * as path from "node:path"; import { - getAgentRetryDelayMs, - isRetryableAgentError, - normalizeAgentRetryOptions, + DEFAULT_RPC_TIMEOUTS, + RpcAgent, + parsePiVersion, + preflightPiExecutable, + selectStructuredToolResult, } from "../.pi/extensions/workflow-orchestrator/runner.js"; -/** - * Tests for tool call capture logic in RpcAgent. - * - * These tests verify that tool calls are properly captured from RPC events. - * The actual RPC communication is tested indirectly through the event parsing logic. - */ +const fixture = path.resolve("tests/fixtures/fake-pi.mjs"); +const children: RpcAgent[] = []; -describe("Tool call capture from RPC events", () => { - interface ToolCallCapture { - name: string; - arguments: Record; +function makeAgent(scenario: string, extraEnv: Record = {}): RpcAgent { + const directory = fs.mkdtempSync(path.join(os.tmpdir(), "piorch-rpc-test-")); + const original = { ...process.env }; + for (const [key, value] of Object.entries({ PIORCH_FAKE_PI_SCENARIO: scenario, ...extraEnv })) { + process.env[key] = value; } - - interface MockRunState { - toolCalls: ToolCallCapture[]; - lastAssistantText: string; - } - - function createMockRunState(): MockRunState { - return { - toolCalls: [], - lastAssistantText: "", - }; - } - - function processMessageEndEvent( - run: MockRunState, - event: { message: { role: string; content: any[] } }, - ) { - if (event.message?.role !== "assistant") return; - - for (const part of event.message.content) { - if (typeof part === "string") { - run.lastAssistantText = part; - } else if (part.type === "text") { - run.lastAssistantText = part.text; - } else if (part.type === "toolCall") { - // This is the fix: capture tool calls from message content - run.toolCalls.push({ name: part.name, arguments: part.arguments }); - } - } - } - - function processToolExecutionStartEvent( - run: MockRunState, - event: { toolName: string; args: any }, - ) { - // Capture tool call arguments for structured output - run.toolCalls.push({ name: event.toolName, arguments: event.args ?? {} }); + const agent = new RpcAgent({ + piCommand: fixture, + cwd: directory, + sessionFile: path.join(directory, "session.jsonl"), + systemPrompt: "", + startupTimeoutMs: 500, + commandTimeoutMs: 500, + runTimeoutMs: 500, + abortGraceMs: 50, + killGraceMs: 50, + }); + children.push(agent); + (agent as unknown as { __restoreEnv: () => void }).__restoreEnv = () => { + process.env = original; + fs.rmSync(directory, { recursive: true, force: true }); + }; + return agent; +} + +afterEach(() => { + for (const agent of children.splice(0)) { + agent.dispose(); + (agent as unknown as { __restoreEnv?: () => void }).__restoreEnv?.(); } +}); - it("captures tool calls from tool_execution_start events", () => { - const run = createMockRunState(); - - processToolExecutionStartEvent(run, { - toolName: "report_task_result", - args: { status: "done", summary: "Test completed" }, - }); - - expect(run.toolCalls).toHaveLength(1); - expect(run.toolCalls[0].name).toBe("report_task_result"); - expect(run.toolCalls[0].arguments).toEqual({ - status: "done", - summary: "Test completed", - }); +describe("Pi version preflight", () => { + it("parses numeric versions from command output", () => { + expect(parsePiVersion("pi 0.80.10\n")).toBe("0.80.10"); + expect(parsePiVersion("no version here")).toBeUndefined(); }); - it("captures tool calls from message_end content parts", () => { - const run = createMockRunState(); - - processMessageEndEvent(run, { - message: { - role: "assistant", - content: [ - { type: "text", text: "I'll call the tool now" }, - { - type: "toolCall", - name: "generate_wave", - arguments: { - done: false, - wave: { - goal: "Test wave", - tasks: [{ id: "T1", title: "Test task", description: "Do something" }], - }, - }, - }, - ], - }, - }); - - expect(run.toolCalls).toHaveLength(1); - expect(run.toolCalls[0].name).toBe("generate_wave"); - expect(run.toolCalls[0].arguments).toEqual({ - done: false, - wave: { - goal: "Test wave", - tasks: [{ id: "T1", title: "Test task", description: "Do something" }], - }, - }); + it("accepts the supported fake executable", async () => { + const info = await preflightPiExecutable(fixture, process.cwd()); + expect(info.version).toBe("0.80.10"); }); - it("captures multiple tool calls from both sources", () => { - const run = createMockRunState(); - - // First: tool_execution_start event - processToolExecutionStartEvent(run, { - toolName: "read", - args: { path: "/test/file.txt" }, - }); - - // Second: message_end with toolCall in content - processMessageEndEvent(run, { - message: { - role: "assistant", - content: [ - { - type: "toolCall", - name: "report_task_result", - arguments: { status: "done", filesChanged: ["/test/file.txt"] }, - }, - ], - }, - }); - - expect(run.toolCalls).toHaveLength(2); - expect(run.toolCalls[0].name).toBe("read"); - expect(run.toolCalls[0].arguments).toEqual({ path: "/test/file.txt" }); - expect(run.toolCalls[1].name).toBe("report_task_result"); - expect(run.toolCalls[1].arguments).toEqual({ - status: "done", - filesChanged: ["/test/file.txt"], - }); + it("rejects an unsupported executable version", async () => { + process.env.PIORCH_FAKE_PI_VERSION = "0.79.9"; + await expect(preflightPiExecutable(fixture, process.cwd())).rejects.toThrow("Unsupported Pi"); + delete process.env.PIORCH_FAKE_PI_VERSION; }); +}); - it("returns empty array when no tool calls", () => { - const run = createMockRunState(); - - processMessageEndEvent(run, { - message: { - role: "assistant", - content: [{ type: "text", text: "Hello, world!" }], - }, - }); +describe("PiRpcProcess protocol", () => { + it("performs readiness and resolves only after agent_settled", async () => { + const agent = makeAgent("agent-end-before-settled"); + const start = Date.now(); + const result = await agent.runPrompt("run"); + expect(Date.now() - start).toBeGreaterThanOrEqual(35); + expect(result.lifecycleEvents).toContain("agent_settled"); + }); - expect(run.toolCalls).toHaveLength(0); + it("rejects a prompt accepted by Pi as unsuccessful", async () => { + const agent = makeAgent("reject-prompt"); + await expect(agent.runPrompt("run")).rejects.toThrow("rejected command"); }); - it("handles text-only message_end events", () => { - const run = createMockRunState(); + it("fails when Pi exits before readiness", async () => { + const agent = makeAgent("exit-before-ready"); + await expect(agent.runPrompt("run")).rejects.toThrow("exited before RPC completion"); + }); - processMessageEndEvent(run, { - message: { - role: "assistant", - content: [ - { type: "text", text: "First part" }, - { type: "text", text: "Second part" }, - ], - }, + it("reports a spawn error for a missing executable", async () => { + const directory = fs.mkdtempSync(path.join(os.tmpdir(), "piorch-rpc-missing-")); + const agent = new RpcAgent({ + piCommand: path.join(directory, "missing-pi"), + cwd: directory, + sessionFile: path.join(directory, "session.jsonl"), + systemPrompt: "", + startupTimeoutMs: 500, + commandTimeoutMs: 500, + runTimeoutMs: 500, + abortGraceMs: 50, + killGraceMs: 50, }); + children.push(agent); + await expect(agent.runPrompt("run")).rejects.toThrow("Pi process error"); + }); - expect(run.toolCalls).toHaveLength(0); - expect(run.lastAssistantText).toBe("Second part"); + it("treats unknown response IDs as fatal protocol errors", async () => { + const agent = makeAgent("unknown-response"); + await expect(agent.runPrompt("run")).rejects.toThrow("unknown or duplicate id"); }); - it("handles mixed text and tool calls in message_end", () => { - const run = createMockRunState(); + it("handles UTF-8 split across stdout chunks and CRLF records", async () => { + const utf8Agent = makeAgent("utf8-split"); + const utf8Result = await utf8Agent.runPrompt("run"); + expect(utf8Result.outputText).toContain("✓"); - processMessageEndEvent(run, { - message: { - role: "assistant", - content: [ - { type: "text", text: "Let me analyze the code..." }, - { - type: "toolCall", - name: "grep", - arguments: { pattern: "function", path: "src/index.ts" }, - }, - { type: "text", text: "Found it!" }, - ], - }, - }); + const crlfAgent = makeAgent("crlf"); + await expect(crlfAgent.runPrompt("run")).resolves.toBeDefined(); + }); - expect(run.toolCalls).toHaveLength(1); - expect(run.toolCalls[0].name).toBe("grep"); - expect(run.lastAssistantText).toBe("Found it!"); + it("fails malformed, oversized, and partial JSONL", async () => { + await expect(makeAgent("malformed").runPrompt("run")).rejects.toThrow("Malformed RPC JSONL"); + await expect(makeAgent("oversized").runPrompt("run")).rejects.toThrow("exceeds"); + await expect(makeAgent("partial-final").runPrompt("run")).rejects.toThrow("partial final"); }); -}); -describe("agent retry helpers", () => { - it("detects OpenRouter-style rate limit errors as retryable", () => { - expect(isRetryableAgentError(new Error("OpenRouter API error 429: rate limit exceeded"))).toBe( - true, - ); - expect(isRetryableAgentError("Too many requests. Retry-After: 12")).toBe(true); + it("correlates successful, failed, and corrected tool executions", async () => { + const failed = await makeAgent("failed-tool").runPrompt("run"); + expect(failed.failedToolExecutions).toHaveLength(1); + expect(() => selectStructuredToolResult(failed, "report_task_result")).toThrow("No successful"); + + const corrected = await makeAgent("corrected-tool").runPrompt("run"); + const selected = selectStructuredToolResult(corrected, "report_task_result"); + expect(selected.execution.toolCallId).toBe("success"); }); - it("detects transient transport errors as retryable", () => { - expect(isRetryableAgentError(new Error("ECONNRESET while reading response"))).toBe(true); - expect(isRetryableAgentError(new Error("provider returned 503 service unavailable"))).toBe( - true, + it("rejects ambiguous reports and malformed tool lifecycle", async () => { + await expect( + (async () => + selectStructuredToolResult( + await makeAgent("duplicate-success").runPrompt("run"), + "report_task_result", + ))(), + ).rejects.toThrow("Ambiguous"); + await expect(makeAgent("end-without-start").runPrompt("run")).rejects.toThrow("without start"); + await expect(makeAgent("duplicate-start").runPrompt("run")).rejects.toThrow( + "duplicate tool execution start", ); }); - it("does not retry ordinary agent failures", () => { - expect(isRetryableAgentError(new Error("Verifier found missing tests"))).toBe(false); - expect(isRetryableAgentError(new Error("Agent already running"))).toBe(false); + it("lets Pi auto-retry settle the same accepted prompt", async () => { + const result = await makeAgent("retry").runPrompt("run"); + expect(result.lifecycleEvents).toContain("auto_retry_end:success"); + expect(result.successfulToolExecutions).toHaveLength(1); }); - it("normalizes invalid retry options to safe bounds", () => { - const retry = normalizeAgentRetryOptions({ - maxAttempts: 0, - initialDelayMs: -1, - maxDelayMs: -1, - backoffMultiplier: 0, - jitterMs: -1, - }); - - expect(retry).toEqual({ - maxAttempts: 1, - initialDelayMs: 0, - maxDelayMs: 0, - backoffMultiplier: 1, - jitterMs: 0, - }); + it("times out a run with bounded diagnostics", async () => { + const agent = makeAgent("run-timeout"); + await expect(agent.runPrompt("run")).rejects.toThrow("RPC run timed out"); + expect(agent.getStderr().length).toBeLessThanOrEqual(DEFAULT_RPC_TIMEOUTS.maxStderrBytes); }); - it("uses exponential backoff with cap and jitter", () => { - const retry = normalizeAgentRetryOptions({ - initialDelayMs: 1000, - maxDelayMs: 2500, - backoffMultiplier: 2, - jitterMs: 100, - }); - - expect(getAgentRetryDelayMs(1, retry, undefined, () => 0.5)).toBe(1050); - expect(getAgentRetryDelayMs(2, retry, undefined, () => 0.5)).toBe(2050); - expect(getAgentRetryDelayMs(3, retry, undefined, () => 0.5)).toBe(2550); + it("uses correlated steer and abort commands", async () => { + const agent = makeAgent("run-timeout"); + const prompt = agent.runPrompt("run"); + for (let attempt = 0; attempt < 20 && !agent.isRunning(); attempt++) { + await new Promise((resolve) => setTimeout(resolve, 10)); + } + expect(agent.isRunning()).toBe(true); + await agent.sendSteer("continue"); + await agent.abort(); + await expect(prompt).rejects.toThrow(); }); - it("honors retry-after hints from provider errors up to maxDelayMs", () => { - const retry = normalizeAgentRetryOptions({ - initialDelayMs: 1000, - maxDelayMs: 2000, - jitterMs: 1000, - }); - - expect(getAgentRetryDelayMs(1, retry, new Error("429 retry-after: 7"), () => 0.5)).toBe(2000); - expect(getAgentRetryDelayMs(1, retry, new Error("try again in 250ms"), () => 0.5)).toBe(250); + it("escalates an abort that does not receive a response", async () => { + const agent = makeAgent("abort-timeout"); + const prompt = agent.runPrompt("run"); + for (let attempt = 0; attempt < 20 && !agent.isRunning(); attempt++) { + await new Promise((resolve) => setTimeout(resolve, 10)); + } + await agent.abort(); + await expect(prompt).rejects.toThrow(); }); }); diff --git a/tests/setup.test.ts b/tests/setup.test.ts new file mode 100644 index 0000000..1586210 --- /dev/null +++ b/tests/setup.test.ts @@ -0,0 +1,184 @@ +import { describe, expect, it } from "vitest"; +import * as fs from "node:fs"; +import * as os from "node:os"; +import * as path from "node:path"; +import { + parseWorkflowShorthandGoal, + parseWorkflowStartArgs, + tokenizeWorkflowArgs, +} from "../.pi/extensions/workflow-orchestrator/commands.js"; +import { + materializeProjectDefaults, + getPackagePiRoot, + resolveExtensionPath, + resolveWorkflowPath, +} from "../.pi/extensions/workflow-orchestrator/setup.js"; +import { loadWorkflowConfig } from "../.pi/extensions/workflow-orchestrator/config.js"; + +describe("setup.ts", () => { + it("resolves package .pi root from extension location", () => { + const root = getPackagePiRoot(); + expect(fs.existsSync(path.join(root, "workflows", "default.workflow.json"))).toBe(true); + expect(fs.existsSync(path.join(root, "agents", "pm.md"))).toBe(true); + }); + + it("resolves default workflow from package when project has no config", () => { + const cwd = fs.mkdtempSync(path.join(os.tmpdir(), "workflow-setup-test-")); + const workflowPath = resolveWorkflowPath(cwd, "default"); + + expect(workflowPath).toBe(path.join(getPackagePiRoot(), "workflows", "default.workflow.json")); + materializeProjectDefaults(cwd); + expect(loadWorkflowConfig(cwd, "default").config.name).toBe("default"); + + fs.rmSync(cwd, { recursive: true, force: true }); + }); + + it("prefers project workflow over package default", () => { + const cwd = fs.mkdtempSync(path.join(os.tmpdir(), "workflow-setup-test-")); + const workflowDir = path.join(cwd, ".pi", "workflows"); + fs.mkdirSync(workflowDir, { recursive: true }); + fs.writeFileSync( + path.join(workflowDir, "default.workflow.json"), + JSON.stringify({ + name: "default", + goal: "project override", + agents: { pm: "pm", developer: "developer", verifier: "verifier" }, + waveSource: { + type: "static", + staticWaves: [ + { + goal: "g", + tasks: [ + { + id: "T1", + title: "Task", + description: "Complete the task", + requirements: "The task is complete", + }, + ], + }, + ], + }, + taskFlow: { + stages: [ + { id: "develop", agent: "developer", inputTemplate: "x" }, + { id: "verify", agent: "verifier", inputTemplate: "x" }, + ], + }, + }), + "utf-8", + ); + + expect(resolveWorkflowPath(cwd, "default")).toBe( + path.join(workflowDir, "default.workflow.json"), + ); + expect(loadWorkflowConfig(cwd, "default").config.goal).toBe("project override"); + + fs.rmSync(cwd, { recursive: true, force: true }); + }); + + it("resolves package extensions from a .pi-prefixed path without basename heuristics", () => { + const cwd = fs.mkdtempSync(path.join(os.tmpdir(), "workflow-setup-test-")); + const resolved = resolveExtensionPath(cwd, "./.pi/extensions/workflow-pm-tools/index.ts"); + + expect(resolved).toBe( + path.join(getPackagePiRoot(), "extensions", "workflow-pm-tools", "index.ts"), + ); + + fs.rmSync(cwd, { recursive: true, force: true }); + }); + + it("materializes editable defaults on first use", () => { + const cwd = fs.mkdtempSync(path.join(os.tmpdir(), "workflow-setup-test-")); + const created = materializeProjectDefaults(cwd); + + expect(created).toContain(".pi/workflows"); + expect(created).toContain(".pi/agents"); + expect(fs.existsSync(path.join(cwd, ".pi", "workflows", "default.workflow.json"))).toBe(true); + expect(fs.existsSync(path.join(cwd, ".pi", "agents", "pm.md"))).toBe(true); + expect(materializeProjectDefaults(cwd)).toEqual([]); + + fs.rmSync(cwd, { recursive: true, force: true }); + }); + + it("fills missing defaults inside existing project directories", () => { + const cwd = fs.mkdtempSync(path.join(os.tmpdir(), "workflow-setup-test-")); + fs.mkdirSync(path.join(cwd, ".pi", "workflows"), { recursive: true }); + fs.mkdirSync(path.join(cwd, ".pi", "agents"), { recursive: true }); + + const created = materializeProjectDefaults(cwd); + + expect(created).toEqual(expect.arrayContaining([".pi/workflows", ".pi/agents"])); + expect(fs.existsSync(path.join(cwd, ".pi", "workflows", "default.workflow.json"))).toBe(true); + expect(fs.existsSync(path.join(cwd, ".pi", "agents", "pm.md"))).toBe(true); + + fs.rmSync(cwd, { recursive: true, force: true }); + }); +}); + +describe("commands.ts", () => { + it("parses shorthand start with goal only", () => { + expect(parseWorkflowStartArgs(["start", "Build a bot"])).toEqual({ + workflowName: "default", + goal: "Build a bot", + }); + }); + + it("parses explicit workflow name and goal", () => { + expect(parseWorkflowStartArgs(["start", "custom", "Build a bot"])).toEqual({ + workflowName: "custom", + goal: "Build a bot", + }); + }); + + it("treats broken quoted goals as default workflow goals", () => { + expect(parseWorkflowStartArgs(["start", '"build', 'something"'])).toEqual({ + workflowName: "default", + goal: "build something", + }); + }); + + it("starts a named workflow without a goal override", () => { + expect(parseWorkflowStartArgs(["start", "default"])).toEqual({ + workflowName: "default", + goal: undefined, + }); + }); + + it("parses quoted shorthand goals", () => { + expect(parseWorkflowShorthandGoal(["Build a bot"])).toEqual({ goal: "Build a bot" }); + expect(parseWorkflowShorthandGoal(['"Build a bot"'])).toEqual({ goal: "Build a bot" }); + }); + + it("parses model flag in shorthand goals", () => { + expect( + parseWorkflowShorthandGoal(["--model", "google/gemini-2.0-flash", "Build a bot"]), + ).toEqual({ + model: "google/gemini-2.0-flash", + goal: "Build a bot", + }); + }); + + it("keeps model-like text inside a quoted goal", () => { + expect(tokenizeWorkflowArgs('"support --model locally"')).toEqual(["support --model locally"]); + expect(parseWorkflowShorthandGoal(tokenizeWorkflowArgs('"support --model locally"'))).toEqual({ + goal: "support --model locally", + }); + }); + + it("normalizes a quoted model flag value", () => { + expect( + parseWorkflowShorthandGoal(tokenizeWorkflowArgs('--model "google/gemini-2.0-flash" Build')), + ).toEqual({ + model: "google/gemini-2.0-flash", + goal: "Build", + }); + }); + + it("does not treat a missing model value as a goal", () => { + expect(parseWorkflowShorthandGoal(tokenizeWorkflowArgs("--model"))).toEqual({ + model: undefined, + goal: undefined, + }); + }); +}); diff --git a/tests/state.test.ts b/tests/state.test.ts index 6791634..285bd70 100644 --- a/tests/state.test.ts +++ b/tests/state.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it, vi } from "vitest"; -import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent"; +import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent"; import { appendState, restoreState, @@ -7,13 +7,18 @@ import { type WorkflowState, type TaskState, } from "../.pi/extensions/workflow-orchestrator/state.js"; +import type { + PriorWaveSummary, + StageOutput, +} from "../.pi/extensions/workflow-orchestrator/contracts.js"; -describe("state.ts", () => { - function createBaseState(overrides?: Partial): WorkflowState { +describe("workflow state", () => { + function createBaseState(overrides: Partial = {}): WorkflowState { return { runId: "test-run-123", workflowName: "default", goal: "Test workflow goal", + status: "running", active: true, waveIndex: 0, wave: { goal: "Wave 1", tasks: [] }, @@ -23,274 +28,177 @@ describe("state.ts", () => { }; } - function createTask(overrides?: Partial): TaskState { + function createTask(overrides: Partial = {}): TaskState { return { id: "T1", title: "Test task", description: "Task description", + requirements: "Run verification", status: "pending", retries: 0, ...overrides, - } as TaskState; + }; } - describe("appendState", () => { - it("appends state to session via extension API", () => { - const mockPi = { - appendEntry: vi.fn(), - } as unknown as ExtensionAPI; + const developerOutput: StageOutput = { + runId: "test-run-123", + waveIndex: 0, + taskId: "T1", + stageId: "develop", + role: "developer", + report: { + status: "done", + summary: "Implemented", + filesChanged: ["src/index.ts"], + evidence: [{ kind: "test", description: "Unit tests pass", outcome: "pass" }], + issues: [], + }, + toolCallId: "developer-call", + startedAt: 1, + completedAt: 2, + }; + + const verifierOutput: StageOutput = { + runId: "test-run-123", + waveIndex: 0, + taskId: "T1", + stageId: "verify", + role: "verifier", + report: { + status: "pass", + summary: "Verified", + evidence: [{ kind: "test", description: "Verification pass", outcome: "pass" }], + issues: [], + }, + toolCallId: "verifier-call", + startedAt: 3, + completedAt: 4, + }; + + const summary: PriorWaveSummary = { + waveIndex: 0, + goal: "Wave 1", + outcome: "verified", + tasks: [ + { + id: "T1", + title: "Test task", + status: "verified", + retries: 0, + developerSummary: "Implemented", + filesChanged: ["src/index.ts"], + verifierSummary: "Verified", + evidence: [{ kind: "test", description: "Pass", outcome: "pass" }], + issues: [], + }, + ], + }; + describe("appendState", () => { + it("appends a valid state through the extension API", () => { + const pi = { appendEntry: vi.fn() } as unknown as ExtensionAPI; const state = createBaseState(); - appendState(mockPi, state); - - expect(mockPi.appendEntry).toHaveBeenCalledWith(STATE_TYPE, state); - }); - - it("appends state with tasks", () => { - const mockPi = { - appendEntry: vi.fn(), - } as unknown as ExtensionAPI; - - const state = createBaseState({ - tasks: [ - createTask({ id: "T1", status: "in_progress" }), - createTask({ id: "T2", status: "verified" }), - ], - }); - - appendState(mockPi, state); - - expect(mockPi.appendEntry).toHaveBeenCalledWith( - STATE_TYPE, - expect.objectContaining({ - tasks: expect.arrayContaining([ - expect.objectContaining({ id: "T1" }), - expect.objectContaining({ id: "T2" }), - ]), - }), - ); + appendState(pi, state); + expect(pi.appendEntry).toHaveBeenCalledWith(STATE_TYPE, state); }); - it("appends state with allowedExtensions", () => { - const mockPi = { - appendEntry: vi.fn(), - } as unknown as ExtensionAPI; - + it("persists typed tasks, stage outputs, summaries, and extension allowlists", () => { + const pi = { appendEntry: vi.fn() } as unknown as ExtensionAPI; const state = createBaseState({ allowedExtensions: ["/path/to/extension.ts"], - }); - - appendState(mockPi, state); - - expect(mockPi.appendEntry).toHaveBeenCalledWith( - STATE_TYPE, - expect.objectContaining({ - allowedExtensions: ["/path/to/extension.ts"], - }), - ); - }); - - it("appends state with allowedExtensionsByAgent", () => { - const mockPi = { - appendEntry: vi.fn(), - } as unknown as ExtensionAPI; - - const state = createBaseState({ allowedExtensionsByAgent: { - pm: ["./.pi/extensions/workflow-pm-tools/index.ts"], - developer: ["./.pi/extensions/workflow-task-tools/index.ts"], - verifier: ["./.pi/extensions/workflow-task-tools/index.ts"], + pm: ["./pm.ts"], + developer: ["./developer.ts"], + verifier: ["./verifier.ts"], }, + previousSummary: summary, + waveSummaries: [summary], + tasks: [ + createTask({ + status: "verified", + stageId: "verify", + stageOutputs: { develop: developerOutput, verify: verifierOutput }, + sessionFiles: { develop: ".pi/sessions/T1-develop.jsonl" }, + sessionResetCounts: { develop: 1 }, + }), + ], }); - - appendState(mockPi, state); - - expect(mockPi.appendEntry).toHaveBeenCalledWith( - STATE_TYPE, - expect.objectContaining({ - allowedExtensionsByAgent: { - pm: expect.arrayContaining(["./.pi/extensions/workflow-pm-tools/index.ts"]), - developer: expect.arrayContaining(["./.pi/extensions/workflow-task-tools/index.ts"]), - verifier: expect.arrayContaining(["./.pi/extensions/workflow-task-tools/index.ts"]), - }, - }), - ); - }); - - it("appends state with previousSummary", () => { - const mockPi = { - appendEntry: vi.fn(), - } as unknown as ExtensionAPI; - - const state = createBaseState({ - previousSummary: "Previous wave completed successfully", - }); - - appendState(mockPi, state); - - expect(mockPi.appendEntry).toHaveBeenCalledWith( - STATE_TYPE, - expect.objectContaining({ - previousSummary: "Previous wave completed successfully", - }), - ); - }); - - it("appends state with waveSummaries", () => { - const mockPi = { - appendEntry: vi.fn(), - } as unknown as ExtensionAPI; - - const state = createBaseState({ - waveSummaries: ["Wave 1: Setup project", "Wave 2: Implement features"], - }); - - appendState(mockPi, state); - - expect(mockPi.appendEntry).toHaveBeenCalledWith( - STATE_TYPE, - expect.objectContaining({ - waveSummaries: expect.arrayContaining([ - "Wave 1: Setup project", - "Wave 2: Implement features", - ]), - }), - ); + appendState(pi, state); + expect(pi.appendEntry).toHaveBeenCalledWith(STATE_TYPE, state); }); - it("appends state with waitingForClarification flag", () => { - const mockPi = { - appendEntry: vi.fn(), - } as unknown as ExtensionAPI; - + it("preserves clarification state and stopped tasks", () => { + const pi = { appendEntry: vi.fn() } as unknown as ExtensionAPI; const state = createBaseState({ + status: "waiting_for_clarification", + active: true, waitingForClarification: true, + clarificationToken: "clarification-1", + tasks: [createTask({ status: "stopped", resumeMessage: "Continue from here" })], }); - - appendState(mockPi, state); - - expect(mockPi.appendEntry).toHaveBeenCalledWith( - STATE_TYPE, - expect.objectContaining({ - waitingForClarification: true, - }), - ); + appendState(pi, state); + expect(pi.appendEntry).toHaveBeenCalledWith(STATE_TYPE, state); }); - it("updates timestamp on append", () => { - const mockPi = { - appendEntry: vi.fn(), - } as unknown as ExtensionAPI; - - const originalTime = Date.now() - 10000; - const state = createBaseState({ updatedAt: originalTime }); - - appendState(mockPi, state); - - const capturedState = (mockPi.appendEntry as any).mock.calls[0][1] as WorkflowState; - expect(capturedState.updatedAt).toBeGreaterThanOrEqual(originalTime); + it("rejects malformed state before persistence", () => { + const pi = { appendEntry: vi.fn() } as unknown as ExtensionAPI; + const state = createBaseState({ status: "invalid" as WorkflowState["status"] }); + expect(() => appendState(pi, state)).toThrow("Workflow state validation failed"); + expect(pi.appendEntry).not.toHaveBeenCalled(); }); }); describe("restoreState", () => { - it("restores state from session entries", () => { - const stateToRestore = createBaseState(); - - const mockCtx = { + it("restores the latest typed state and skips unrelated entries", () => { + const state = createBaseState(); + const ctx = { sessionManager: { - getBranch: vi.fn().mockReturnValue([ - { type: "message", role: "user", content: "Hello" }, - { type: "custom", customType: STATE_TYPE, data: stateToRestore }, - { type: "message", role: "assistant", content: "Hi" }, - ]), + getBranch: vi + .fn() + .mockReturnValue([ + { type: "message" }, + { type: "custom", customType: "other", data: {} }, + { type: "custom", customType: STATE_TYPE, data: state }, + ]), }, } as unknown as ExtensionContext; - - const restored = restoreState(mockCtx); - - expect(restored).toEqual(stateToRestore); + expect(restoreState(ctx)).toEqual(state); }); - it("returns latest state when multiple states exist", () => { + it("returns the latest state when multiple state entries exist", () => { const oldState = createBaseState({ waveIndex: 0 }); const newState = createBaseState({ waveIndex: 1 }); - - const mockCtx = { + const ctx = { sessionManager: { getBranch: vi.fn().mockReturnValue([ { type: "custom", customType: STATE_TYPE, data: oldState }, - { type: "message", role: "user", content: "Continue" }, { type: "custom", customType: STATE_TYPE, data: newState }, ]), }, } as unknown as ExtensionContext; - - const restored = restoreState(mockCtx); - - expect(restored).toEqual(newState); - }); - - it("returns undefined when no state in session", () => { - const mockCtx = { - sessionManager: { - getBranch: vi.fn().mockReturnValue([ - { type: "message", role: "user", content: "Hello" }, - { type: "message", role: "assistant", content: "Hi" }, - ]), - }, - } as unknown as ExtensionContext; - - const restored = restoreState(mockCtx); - - expect(restored).toBeUndefined(); + expect(restoreState(ctx)).toEqual(newState); }); - it("returns undefined for empty session", () => { - const mockCtx = { - sessionManager: { - getBranch: vi.fn().mockReturnValue([]), - }, + it("returns undefined when the session has no state", () => { + const ctx = { + sessionManager: { getBranch: vi.fn().mockReturnValue([{ type: "message" }]) }, } as unknown as ExtensionContext; - - const restored = restoreState(mockCtx); - - expect(restored).toBeUndefined(); + expect(restoreState(ctx)).toBeUndefined(); }); - it("skips non-custom entries", () => { - const stateToRestore = createBaseState(); - - const mockCtx = { - sessionManager: { - getBranch: vi.fn().mockReturnValue([ - { type: "message", role: "user", content: "Hello" }, - { type: "tool", name: "read", content: "" }, - { type: "custom", customType: "other-type", data: { foo: "bar" } }, - { type: "custom", customType: STATE_TYPE, data: stateToRestore }, - ]), - }, - } as unknown as ExtensionContext; - - const restored = restoreState(mockCtx); - - expect(restored).toEqual(stateToRestore); - }); - - it("restores state with complex task data", () => { - const complexState = createBaseState({ + it("restores complex typed task data", () => { + const state = createBaseState({ tasks: [ createTask({ - id: "T1", status: "verified", stageId: "verify", retries: 1, issues: ["Initial issue fixed"], - stageOutputs: { - develop: { status: "done", summary: "Implemented", filesChanged: ["src/index.ts"] }, - verify: { status: "pass", issues: [] }, - }, + stageOutputs: { develop: developerOutput, verify: verifierOutput }, lastAgent: "verifier", lastNote: "Verification passed", + lastOutput: "Verified", + lastActivityAt: 10, sessionFiles: { develop: ".pi/workflows/sessions/run1/T1-develop.jsonl", verify: ".pi/workflows/sessions/run1/T1-verify.jsonl", @@ -298,78 +206,71 @@ describe("state.ts", () => { }), ], }); - - const mockCtx = { + const ctx = { sessionManager: { getBranch: vi .fn() - .mockReturnValue([{ type: "custom", customType: STATE_TYPE, data: complexState }]), + .mockReturnValue([{ type: "custom", customType: STATE_TYPE, data: state }]), }, } as unknown as ExtensionContext; - - const restored = restoreState(mockCtx); - - expect(restored).toEqual(complexState); - expect(restored?.tasks[0].stageOutputs?.develop).toEqual({ - status: "done", - summary: "Implemented", - filesChanged: ["src/index.ts"], - }); + expect(restoreState(ctx)).toEqual(state); }); - it("restores state with resumeMessage", () => { - const stateWithResume = createBaseState({ + it("restores resume messages and last output", () => { + const state = createBaseState({ tasks: [ createTask({ - id: "T1", status: "stopped", - resumeMessage: "Please continue from where you left off", + resumeMessage: "Please continue from here", + lastOutput: "Working on implementation", }), ], }); - - const mockCtx = { + const ctx = { sessionManager: { getBranch: vi .fn() - .mockReturnValue([{ type: "custom", customType: STATE_TYPE, data: stateWithResume }]), + .mockReturnValue([{ type: "custom", customType: STATE_TYPE, data: state }]), }, } as unknown as ExtensionContext; - - const restored = restoreState(mockCtx); - - expect(restored?.tasks[0].resumeMessage).toBe("Please continue from where you left off"); + const restored = restoreState(ctx); + expect(restored?.tasks[0].resumeMessage).toBe("Please continue from here"); + expect(restored?.tasks[0].lastOutput).toBe("Working on implementation"); }); - it("restores state with lastOutput", () => { - const stateWithOutput = createBaseState({ - tasks: [ - createTask({ - id: "T1", - status: "in_progress", - lastOutput: "Working on implementation...", - lastActivityAt: Date.now(), - }), - ], + it("migrates active-only state to a status and supplies missing persisted metadata", () => { + const ctx = { + sessionManager: { + getBranch: vi.fn().mockReturnValue([ + { + type: "custom", + customType: STATE_TYPE, + data: { runId: "run-1", workflowName: "default", goal: "g", active: true, tasks: [] }, + }, + ]), + }, + } as unknown as ExtensionContext; + expect(restoreState(ctx)).toMatchObject({ + status: "running", + active: true, + waveIndex: 0, }); + expect(restoreState(ctx)?.updatedAt).toEqual(expect.any(Number)); + }); - const mockCtx = { + it("ignores malformed state entries", () => { + const ctx = { sessionManager: { getBranch: vi .fn() - .mockReturnValue([{ type: "custom", customType: STATE_TYPE, data: stateWithOutput }]), + .mockReturnValue([{ type: "custom", customType: STATE_TYPE, data: null }]), }, } as unknown as ExtensionContext; - - const restored = restoreState(mockCtx); - - expect(restored?.tasks[0].lastOutput).toBe("Working on implementation..."); + expect(restoreState(ctx)).toBeUndefined(); }); }); - describe("STATE_TYPE constant", () => { - it("is exported as workflow-state", () => { - expect(STATE_TYPE).toBe("workflow-state"); - }); + it("exports the expected state entry type", () => { + expect(STATE_TYPE).toBe("workflow-state"); }); }); diff --git a/tests/workflow-pm-tools.test.ts b/tests/workflow-pm-tools.test.ts index d9488a9..ad89ed2 100644 --- a/tests/workflow-pm-tools.test.ts +++ b/tests/workflow-pm-tools.test.ts @@ -1,347 +1,141 @@ -import { describe, expect, it, vi, beforeEach } from "vitest"; -import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; import workflowPmTools from "../.pi/extensions/workflow-pm-tools/index.js"; describe("workflow-pm-tools extension", () => { let mockPi: ExtensionAPI; let registeredTool: any; - beforeEach(() => { - registeredTool = null; + const task = (id = "T1") => ({ + id, + title: "Task", + description: "Implement the feature", + requirements: "Run the verifier", + assignee: "developer", + }); + beforeEach(() => { + registeredTool = undefined; mockPi = { - registerTool: vi.fn((toolDef) => { - registeredTool = toolDef; + registerTool: vi.fn((definition) => { + registeredTool = definition; }), sendMessage: vi.fn(), appendEntry: vi.fn(), } as unknown as ExtensionAPI; + workflowPmTools(mockPi); }); - describe("extension initialization", () => { - it("registers generate_wave tool", () => { - workflowPmTools(mockPi); - - expect(mockPi.registerTool).toHaveBeenCalled(); - expect(registeredTool).toBeDefined(); - }); - - it("generate_wave tool has correct metadata", () => { - workflowPmTools(mockPi); - - expect(registeredTool.name).toBe("generate_wave"); - expect(registeredTool.label).toBe("Generate Wave"); - expect(registeredTool.description).toContain("PM agent"); - }); - - it("generate_wave tool has parameters schema", () => { - workflowPmTools(mockPi); - - expect(registeredTool.parameters).toBeDefined(); - // Schema is a TypeBox object - expect(typeof registeredTool.parameters).toBe("object"); - }); + it("registers generate_wave with a TypeBox schema", () => { + expect(mockPi.registerTool).toHaveBeenCalled(); + expect(registeredTool.name).toBe("generate_wave"); + expect(registeredTool.parameters).toBeDefined(); }); - describe("generate_wave tool execute", () => { - it("returns success for done=true", async () => { - workflowPmTools(mockPi); - - const result = await registeredTool.execute( - "tool-call-123", - { done: true }, - vi.fn(), - {} as any, - new AbortController().signal, - ); - - expect(result.content).toHaveLength(1); - expect(result.content[0].type).toBe("text"); - expect(result.content[0].text).toBe("Project completion reported."); - expect(result.details).toEqual({ params: { done: true } }); - }); - - it("returns error for done=false without wave", async () => { - workflowPmTools(mockPi); - - const result = await registeredTool.execute( - "tool-call-123", - { done: false }, - vi.fn(), - {} as any, - new AbortController().signal, - ); - - expect(result.content[0].text).toBe("Error: wave is required when done=false"); - expect(result.details).toEqual({ params: { done: false } }); - }); - - it("returns success for done=false with wave", async () => { - workflowPmTools(mockPi); - - const wave = { - goal: "Implement features", - tasks: [ - { - id: "T1", - title: "Create module", - description: "Create the main module", - assignee: "developer", - }, - { - id: "T2", - title: "Write tests", - description: "Write unit tests", - assignee: "developer", - }, - ], - }; - - const result = await registeredTool.execute( - "tool-call-123", - { done: false, wave }, - vi.fn(), - {} as any, - new AbortController().signal, - ); - - expect(result.content[0].text).toContain('Wave generated: "Implement features"'); - expect(result.content[0].text).toContain("(2 tasks)"); - expect(result.details.params.wave).toEqual(wave); - }); - - it("handles empty tasks array", async () => { - workflowPmTools(mockPi); - - const wave = { - goal: "Empty wave", - tasks: [], - }; - - const result = await registeredTool.execute( - "tool-call-123", - { done: false, wave }, - vi.fn(), - {} as any, - new AbortController().signal, - ); - - expect(result.content[0].text).toContain('Wave generated: "Empty wave"'); - expect(result.content[0].text).toContain("(0 tasks)"); - }); - - it("handles wave with requirements", async () => { - workflowPmTools(mockPi); - - const wave = { - goal: "Feature with requirements", - tasks: [ - { - id: "T1", - title: "Implement auth", - description: "Add authentication", - requirements: "Must use JWT tokens", - assignee: "developer", - }, - ], - }; - - const result = await registeredTool.execute( - "tool-call-123", - { done: false, wave }, - vi.fn(), - {} as any, - new AbortController().signal, - ); - - expect(result.content[0].text).toContain("(1 tasks)"); - expect(result.details.params.wave).toEqual(wave); - }); - - it("handles task without assignee", async () => { - workflowPmTools(mockPi); - - const wave = { - goal: "Default assignee", - tasks: [ - { - id: "T1", - title: "Task without assignee", - description: "Should default to developer", - }, - ], - }; - - const result = await registeredTool.execute( - "tool-call-123", - { done: false, wave }, - vi.fn(), - {} as any, - new AbortController().signal, - ); + it("accepts project completion with no wave", async () => { + const result = await registeredTool.execute( + "call", + { done: true }, + vi.fn(), + {}, + new AbortController().signal, + ); + expect(result.content[0].text).toContain("Project completion"); + expect(result.details.params).toEqual({ done: true }); + }); - expect(result.content[0].text).toContain("(1 tasks)"); - }); + it("accepts a nonempty valid wave and returns exact params", async () => { + const params = { done: false, wave: { goal: "Implement", tasks: [task()] } }; + const result = await registeredTool.execute( + "call", + params, + vi.fn(), + {}, + new AbortController().signal, + ); + expect(result.content[0].text).toContain("(1 tasks)"); + expect(result.details.params).toEqual(params); + }); - it("handles multiple tasks", async () => { - workflowPmTools(mockPi); + it("accepts tasks without an explicit assignee", async () => { + const params = { + done: false, + wave: { goal: "Implement", tasks: [{ ...task(), assignee: undefined }] }, + }; + const result = await registeredTool.execute( + "call", + params, + vi.fn(), + {}, + new AbortController().signal, + ); + expect(result.details.params).toEqual(params); + }); - const wave = { - goal: "Big wave", - tasks: Array(5) - .fill(null) - .map((_, i) => ({ - id: `T${i + 1}`, - title: `Task ${i + 1}`, - description: `Description ${i + 1}`, - assignee: "developer", - })), - }; + it("rejects done=false without a wave", async () => { + await expect( + registeredTool.execute("call", { done: false }, vi.fn(), {}, new AbortController().signal), + ).rejects.toThrow("requires a wave"); + }); - const result = await registeredTool.execute( - "tool-call-123", - { done: false, wave }, + it("rejects done=false with an empty wave", async () => { + await expect( + registeredTool.execute( + "call", + { done: false, wave: { goal: "Empty", tasks: [] } }, vi.fn(), - {} as any, + {}, new AbortController().signal, - ); - - expect(result.content[0].text).toContain("(5 tasks)"); - }); - - it("includes params in details", async () => { - workflowPmTools(mockPi); - - const params = { - done: false, - wave: { - goal: "Test", - tasks: [{ id: "T1", title: "T", description: "D" }], - }, - }; + ), + ).rejects.toThrow("nonempty wave"); + }); - const result = await registeredTool.execute( - "tool-call-123", - params, + it("rejects done=true with a wave", async () => { + await expect( + registeredTool.execute( + "call", + { done: true, wave: { goal: "No", tasks: [task()] } }, vi.fn(), - {} as any, + {}, new AbortController().signal, - ); - - expect(result.details.params).toEqual(params); - }); + ), + ).rejects.toThrow("done=true"); }); - describe("tool parameter validation (schema)", () => { - it("done parameter is boolean", () => { - workflowPmTools(mockPi); - - // The schema is TypeBox, we verify the structure exists - expect(registeredTool.parameters).toBeDefined(); - - // TypeBox schemas have $schema and type properties - const schema = registeredTool.parameters; - expect(schema).toHaveProperty("type"); - }); - - it("wave parameter is optional", () => { - workflowPmTools(mockPi); - - // When done=true, wave is not required - // This is tested by the execute function accepting {done: true} - expect(() => { + it("rejects duplicate, unsafe, and unsupported task IDs", async () => { + for (const tasks of [ + [task("T1"), task("T1")], + [{ ...task(), id: "../escape" }], + [{ ...task(), id: "bad id" }], + [{ ...task(), assignee: "verifier" }], + ]) { + await expect( registeredTool.execute( - "id", - { done: true }, + "call", + { done: false, wave: { goal: "Bad", tasks } }, vi.fn(), - {} as any, + {}, new AbortController().signal, - ); - }).not.toThrow(); - }); - - it("wave is required when done=false", async () => { - workflowPmTools(mockPi); - - // When done=false, wave should be provided - // The execute function returns an error message if missing - const result = await registeredTool.execute( - "id", - { done: false }, - vi.fn(), - {} as any, - new AbortController().signal, - ); - - expect(result).toMatchObject({ - content: [{ text: expect.stringContaining("wave is required") }], - }); - }); + ), + ).rejects.toThrow(); + } }); - describe("tool behavior", () => { - it("does not call sendMessage", async () => { - workflowPmTools(mockPi); - - await registeredTool.execute( - "tool-call-123", - { done: true }, - vi.fn(), - {} as any, - new AbortController().signal, - ); - - expect(mockPi.sendMessage).not.toHaveBeenCalled(); - }); - - it("does not call appendEntry", async () => { - workflowPmTools(mockPi); - - await registeredTool.execute( - "tool-call-123", - { done: true }, + it("rejects missing task requirements", async () => { + await expect( + registeredTool.execute( + "call", + { done: false, wave: { goal: "Bad", tasks: [{ id: "T1", title: "T", description: "D" }] } }, vi.fn(), - {} as any, + {}, new AbortController().signal, - ); - - expect(mockPi.appendEntry).not.toHaveBeenCalled(); - }); - - it("returns consistent response structure", async () => { - workflowPmTools(mockPi); - - const doneResult = await registeredTool.execute( - "id1", - { done: true }, - vi.fn(), - {} as any, - new AbortController().signal, - ); - - const waveResult = await registeredTool.execute( - "id2", - { done: false, wave: { goal: "G", tasks: [] } }, - vi.fn(), - {} as any, - new AbortController().signal, - ); - - const errorResult = await registeredTool.execute( - "id3", - { done: false }, - vi.fn(), - {} as any, - new AbortController().signal, - ); + ), + ).rejects.toThrow("requirements"); + }); - // All should have content and details - expect(doneResult).toHaveProperty("content"); - expect(doneResult).toHaveProperty("details"); - expect(waveResult).toHaveProperty("content"); - expect(waveResult).toHaveProperty("details"); - expect(errorResult).toHaveProperty("content"); - expect(errorResult).toHaveProperty("details"); - }); + it("does not mutate Pi state", async () => { + await registeredTool.execute("call", { done: true }, vi.fn(), {}, new AbortController().signal); + expect(mockPi.sendMessage).not.toHaveBeenCalled(); + expect(mockPi.appendEntry).not.toHaveBeenCalled(); }); }); diff --git a/tests/workflow-start-clarification.test.ts b/tests/workflow-start-clarification.test.ts index fa3d384..2121dd9 100644 --- a/tests/workflow-start-clarification.test.ts +++ b/tests/workflow-start-clarification.test.ts @@ -1,9 +1,10 @@ import { describe, expect, it, vi, beforeEach, afterEach } from "vitest"; +import * as path from "node:path"; import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext, -} from "@mariozechner/pi-coding-agent"; +} from "@earendil-works/pi-coding-agent"; type RegisteredHandlers = { commands: Record Promise | void>; @@ -14,6 +15,27 @@ type FakeRpcOptions = { systemPrompt?: string; }; +function fakeResult(outputText: string, toolCalls: Array<{ name: string; arguments: any }> = []) { + const calls = Array.isArray(toolCalls) ? toolCalls : []; + const executions = calls.map((call, index) => ({ + toolCallId: `fake-${index + 1}`, + name: call.name, + attemptedArgs: call.arguments, + startedAt: 1, + endedAt: 2, + isError: false, + result: { details: { params: call.arguments } }, + })); + return { + outputText, + executions, + successfulToolExecutions: executions, + failedToolExecutions: [], + stderr: "", + lifecycleEvents: ["agent_settled"], + }; +} + const rpcInstances: FakeRpcAgent[] = []; class FakeRpcAgent { @@ -44,17 +66,17 @@ class FakeRpcAgent { return this.toolCalls; } - async runPrompt(message: string): Promise { + async runPrompt(message: string): Promise { this.toolCalls = []; if (this.role === "pm") { if (message.includes("User message:")) { - return "Thanks, I have the clarification."; + return fakeResult("Thanks, I have the clarification."); } this.pmWaveCalls += 1; if (this.pmWaveCalls === 1) { - return "I need a little clarification before I can generate the wave."; + return fakeResult("I need a little clarification before I can generate the wave."); } if (this.pmWaveCalls === 2) { @@ -78,7 +100,7 @@ class FakeRpcAgent { }, }, ]; - return ""; + return fakeResult("", this.toolCalls); } this.toolCalls = [ @@ -87,7 +109,7 @@ class FakeRpcAgent { arguments: { done: true }, }, ]; - return ""; + return fakeResult("", this.toolCalls); } if (this.role === "developer") { @@ -98,11 +120,14 @@ class FakeRpcAgent { status: "done", summary: "Implemented the feature.", filesChanged: ["src/feature.ts"], - notes: "Looks good.", + evidence: [ + { kind: "test", description: "The implementation tests pass", outcome: "pass" }, + ], + issues: [], }, }, ]; - return ""; + return fakeResult("", this.toolCalls); } if (this.role === "verifier") { @@ -111,19 +136,34 @@ class FakeRpcAgent { name: "report_task_result", arguments: { status: "pass", + summary: "Verification passed.", + evidence: [{ kind: "test", description: "The tests pass", outcome: "pass" }], issues: [], }, }, ]; - return ""; + return fakeResult("", this.toolCalls); } - return ""; + return fakeResult(""); } } -vi.mock("../.pi/extensions/workflow-orchestrator/runner.js", () => { - return { RpcAgent: FakeRpcAgent }; +vi.doMock(path.resolve(".pi/extensions/workflow-orchestrator/runner.ts"), () => { + return { + RpcAgent: FakeRpcAgent, + preflightPiExecutable: vi.fn().mockResolvedValue({ command: "pi", version: "0.80.10" }), + selectStructuredToolResult: (result: any, expectedToolName: string) => { + const matches = result.executions.filter( + (execution: any) => + execution.name === expectedToolName && + execution.endedAt !== undefined && + execution.isError === false, + ); + if (matches.length !== 1) throw new Error(`expected one ${expectedToolName} result`); + return { execution: matches[0], params: matches[0].result.details.params }; + }, + }; }); const { default: registerWorkflowExtension } = @@ -133,6 +173,10 @@ function createMockContext(branch: any[] = []): ExtensionCommandContext { return { cwd: process.cwd(), hasUI: false, + model: { id: "test/model" }, + modelRegistry: { + getAvailable: () => [{ id: "test/model", provider: "test" }], + }, sessionManager: { getBranch: vi.fn().mockImplementation(() => branch), }, diff --git a/tests/workflow-task-tools.test.ts b/tests/workflow-task-tools.test.ts index 105729c..bef02a2 100644 --- a/tests/workflow-task-tools.test.ts +++ b/tests/workflow-task-tools.test.ts @@ -1,586 +1,240 @@ -import { describe, expect, it, vi, beforeEach } from "vitest"; -import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; import workflowTaskTools from "../.pi/extensions/workflow-task-tools/index.js"; describe("workflow-task-tools extension", () => { let mockPi: ExtensionAPI; let registeredTool: any; - beforeEach(() => { - registeredTool = null; + const developerReport = { + status: "done", + summary: "Implemented the feature", + filesChanged: ["src/feature.ts"], + evidence: [{ kind: "test", description: "Unit tests pass", outcome: "pass" }], + issues: [], + }; + + const verifierReport = { + status: "pass", + summary: "Verified the feature", + evidence: [{ kind: "test", description: "Acceptance tests pass", outcome: "pass" }], + issues: [], + }; + beforeEach(() => { + registeredTool = undefined; mockPi = { - registerTool: vi.fn((toolDef) => { - registeredTool = toolDef; + registerTool: vi.fn((definition) => { + registeredTool = definition; }), sendMessage: vi.fn(), appendEntry: vi.fn(), } as unknown as ExtensionAPI; + workflowTaskTools(mockPi); }); - describe("extension initialization", () => { - it("registers report_task_result tool", () => { - workflowTaskTools(mockPi); - - expect(mockPi.registerTool).toHaveBeenCalled(); - expect(registeredTool).toBeDefined(); - }); - - it("report_task_result tool has correct metadata", () => { - workflowTaskTools(mockPi); - - expect(registeredTool.name).toBe("report_task_result"); - expect(registeredTool.label).toBe("Report Task Result"); - expect(registeredTool.description).toContain("developer/verifier"); - }); - - it("report_task_result tool has parameters schema", () => { - workflowTaskTools(mockPi); - - expect(registeredTool.parameters).toBeDefined(); - expect(typeof registeredTool.parameters).toBe("object"); - }); + it("registers the shared report tool and schema", () => { + expect(mockPi.registerTool).toHaveBeenCalled(); + expect(registeredTool.name).toBe("report_task_result"); + expect(registeredTool.description).toContain("developer/verifier"); + expect(registeredTool.parameters).toBeDefined(); }); - describe("report_task_result tool execute - developer status", () => { - it("returns success for status=done with summary", async () => { - workflowTaskTools(mockPi); - - const result = await registeredTool.execute( - "tool-call-123", - { status: "done", summary: "Implemented feature" }, - vi.fn(), - {} as any, - new AbortController().signal, - ); - - expect(result.content[0].text).toContain("Task completed"); - expect(result.content[0].text).toContain("Implemented feature"); - }); - - it("handles done with filesChanged", async () => { - workflowTaskTools(mockPi); - - const result = await registeredTool.execute( - "tool-call-123", - { - status: "done", - summary: "Created config module", - filesChanged: ["src/config.ts", "src/config.test.ts"], - }, - vi.fn(), - {} as any, - new AbortController().signal, - ); - - expect(result.content[0].text).toContain("src/config.ts"); - expect(result.content[0].text).toContain("src/config.test.ts"); - }); - - it("handles done with empty filesChanged", async () => { - workflowTaskTools(mockPi); - - const result = await registeredTool.execute( - "tool-call-123", - { - status: "done", - summary: "Refactored code", - filesChanged: [], - }, - vi.fn(), - {} as any, - new AbortController().signal, - ); - - expect(result.content[0].text).toContain("Files: none"); - }); - - it("handles done without filesChanged", async () => { - workflowTaskTools(mockPi); - - const result = await registeredTool.execute( - "tool-call-123", - { - status: "done", - summary: "Updated documentation", - }, - vi.fn(), - {} as any, - new AbortController().signal, - ); - - expect(result.content[0].text).toContain("Files: none"); - }); - - it("handles done with notes", async () => { - workflowTaskTools(mockPi); - - const result = await registeredTool.execute( - "tool-call-123", - { - status: "done", - summary: "Added tests", - notes: "Used vitest framework", - }, - vi.fn(), - {} as any, - new AbortController().signal, - ); - - // Notes are included in details but not in the summary message - expect(result.content[0].text).toContain("Task completed"); - expect(result.details.params.notes).toBe("Used vitest framework"); - }); - - it("handles done with all fields", async () => { - workflowTaskTools(mockPi); - - const params = { - status: "done" as const, - summary: "Complete implementation", - filesChanged: ["src/index.ts", "src/utils.ts"], - notes: "Ready for review", - }; - - const result = await registeredTool.execute( - "tool-call-123", - params, - vi.fn(), - {} as any, - new AbortController().signal, - ); - - expect(result.content[0].text).toContain("Complete implementation"); - expect(result.content[0].text).toContain("src/index.ts"); - expect(result.details.params).toEqual(params); - }); + it("accepts a complete developer report", async () => { + const result = await registeredTool.execute( + "call", + developerReport, + vi.fn(), + {}, + new AbortController().signal, + ); + expect(result.content[0].text).toContain("Task completed"); + expect(result.content[0].text).toContain("src/feature.ts"); + expect(result.details.params).toEqual(developerReport); }); - describe("report_task_result tool execute - verifier status=pass", () => { - it("returns success for status=pass", async () => { - workflowTaskTools(mockPi); - - const result = await registeredTool.execute( - "tool-call-123", - { status: "pass", issues: [] }, - vi.fn(), - {} as any, - new AbortController().signal, - ); - - expect(result.content[0].text).toBe("Verification passed. No issues found."); - }); - - it("handles pass with empty issues", async () => { - workflowTaskTools(mockPi); - - const result = await registeredTool.execute( - "tool-call-123", - { status: "pass", issues: [] }, - vi.fn(), - {} as any, - new AbortController().signal, - ); - - expect(result.content[0].text).toContain("No issues found"); - }); - - it("ignores issues array when status is pass", async () => { - workflowTaskTools(mockPi); + it("accepts a partial developer report only with a blocking issue", async () => { + const params = { + ...developerReport, + status: "partial", + issues: [{ severity: "blocking", description: "The implementation is incomplete" }], + }; + const result = await registeredTool.execute( + "call", + params, + vi.fn(), + {}, + new AbortController().signal, + ); + expect(result.content[0].text).toContain("partially"); + }); - // If someone mistakenly provides issues with pass status - const result = await registeredTool.execute( - "tool-call-123", - { status: "pass", issues: ["should be ignored"] }, + it("rejects developer reports with verifier statuses", async () => { + await expect( + registeredTool.execute( + "call", + { ...developerReport, status: "pass" }, vi.fn(), - {} as any, + {}, new AbortController().signal, - ); - - // The message still says passed - expect(result.content[0].text).toBe("Verification passed. No issues found."); - }); + ), + ).rejects.toThrow("developer reports"); }); - describe("report_task_result tool execute - verifier status=fail", () => { - it("returns failure message with issues", async () => { - workflowTaskTools(mockPi); - - const result = await registeredTool.execute( - "tool-call-123", - { - status: "fail", - issues: ["File not found", "Tests failing"], - }, + it("rejects incomplete developer reports", async () => { + await expect( + registeredTool.execute( + "call", + { status: "done", summary: "Missing fields" }, vi.fn(), - {} as any, + {}, new AbortController().signal, - ); + ), + ).rejects.toThrow("Developer report validation"); + }); - expect(result.content[0].text).toContain("Verification failed"); - expect(result.content[0].text).toContain("- File not found"); - expect(result.content[0].text).toContain("- Tests failing"); - }); + it("rejects unsafe developer file paths", async () => { + for (const filesChanged of [["/absolute.ts"], ["../escape.ts"], ["src/../escape.ts"]]) { + await expect( + registeredTool.execute( + "call", + { ...developerReport, filesChanged }, + vi.fn(), + {}, + new AbortController().signal, + ), + ).rejects.toThrow("relative and safe"); + } + }); - it("handles single issue", async () => { - workflowTaskTools(mockPi); + it("accepts a passing verifier report with passing evidence", async () => { + const result = await registeredTool.execute( + "call", + verifierReport, + vi.fn(), + {}, + new AbortController().signal, + ); + expect(result.content[0].text).toContain("Verification passed"); + expect(result.details.params).toEqual(verifierReport); + }); - const result = await registeredTool.execute( - "tool-call-123", - { - status: "fail", - issues: ["Missing export statement"], - }, + it("rejects verifier pass without passing evidence", async () => { + await expect( + registeredTool.execute( + "call", + { ...verifierReport, evidence: [] }, vi.fn(), - {} as any, + {}, new AbortController().signal, - ); + ), + ).rejects.toThrow("passing evidence"); + }); - expect(result.content[0].text).toContain("- Missing export statement"); - }); + it("rejects verifier pass with a failed evidence item or blocking issue", async () => { + for (const params of [ + { ...verifierReport, evidence: [{ kind: "test", description: "Failed", outcome: "fail" }] }, + { + ...verifierReport, + issues: [{ severity: "blocking", description: "Blocked" }], + }, + ]) { + await expect( + registeredTool.execute("call", params, vi.fn(), {}, new AbortController().signal), + ).rejects.toThrow(); + } + }); - it("handles empty issues array", async () => { - workflowTaskTools(mockPi); + it("accepts verifier fail only with an actionable blocking issue", async () => { + const params = { + status: "fail", + summary: "Verification found a defect", + evidence: [{ kind: "test", description: "Acceptance test fails", outcome: "fail" }], + issues: [{ severity: "blocking", description: "The feature does not satisfy requirement" }], + }; + const result = await registeredTool.execute( + "call", + params, + vi.fn(), + {}, + new AbortController().signal, + ); + expect(result.content[0].text).toContain("Verification failed"); + expect(result.content[0].text).toContain("does not satisfy"); + }); - const result = await registeredTool.execute( - "tool-call-123", + it("rejects verifier fail without a blocking issue", async () => { + await expect( + registeredTool.execute( + "call", { status: "fail", + summary: "Failure", + evidence: [{ kind: "test", description: "Failed", outcome: "fail" }], issues: [], }, vi.fn(), - {} as any, - new AbortController().signal, - ); - - expect(result.content[0].text).toContain("Verification failed"); - expect(result.content[0].text).not.toContain("- "); - }); - - it("handles multiple issues with formatting", async () => { - workflowTaskTools(mockPi); - - const result = await registeredTool.execute( - "tool-call-123", - { - status: "fail", - issues: ["Issue 1", "Issue 2", "Issue 3"], - }, - vi.fn(), - {} as any, + {}, new AbortController().signal, - ); - - const text = result.content[0].text; - expect(text).toContain("- Issue 1"); - expect(text).toContain("- Issue 2"); - expect(text).toContain("- Issue 3"); - }); + ), + ).rejects.toThrow("blocking issue"); }); - describe("report_task_result tool execute - edge cases", () => { - it("handles undefined optional fields", async () => { - workflowTaskTools(mockPi); - - const result = await registeredTool.execute( - "tool-call-123", - { status: "done" }, - vi.fn(), - {} as any, - new AbortController().signal, - ); - - expect(result.content[0].text).toContain("Summary: N/A"); - expect(result.content[0].text).toContain("Files: none"); - }); - - it("handles null-like values", async () => { - workflowTaskTools(mockPi); - - const result = await registeredTool.execute( - "tool-call-123", - { status: "done", summary: "", filesChanged: null as any }, - vi.fn(), - {} as any, - new AbortController().signal, - ); - - // Empty string summary - expect(result.content[0].text).toContain("Summary: "); - }); - - it("handles special characters in summary", async () => { - workflowTaskTools(mockPi); - - const result = await registeredTool.execute( - "tool-call-123", - { - status: "done", - summary: 'Created file with\nnewlines and "quotes"', - }, - vi.fn(), - {} as any, - new AbortController().signal, - ); - - expect(result.content[0].text).toContain("newlines"); - expect(result.content[0].text).toContain("quotes"); - }); - - it("handles special characters in issues", async () => { - workflowTaskTools(mockPi); - - const result = await registeredTool.execute( - "tool-call-123", - { - status: "fail", - issues: ['Error: "Module not found"', "Line 42: Unexpected token"], - }, - vi.fn(), - {} as any, - new AbortController().signal, - ); - - expect(result.content[0].text).toContain('"Module not found"'); - expect(result.content[0].text).toContain("Unexpected token"); - }); - - it("handles long file paths", async () => { - workflowTaskTools(mockPi); + it("accepts an environmental verifier partial report", async () => { + const params = { + status: "partial", + summary: "Verification is blocked by the environment", + evidence: [{ kind: "manual", description: "Environment unavailable", outcome: "blocked" }], + issues: [{ severity: "blocking", description: "Required environment is unavailable" }], + }; + const result = await registeredTool.execute( + "call", + params, + vi.fn(), + {}, + new AbortController().signal, + ); + expect(result.content[0].text).toContain("partially"); + }); - const result = await registeredTool.execute( - "tool-call-123", + it("rejects verifier partial without blocked evidence", async () => { + await expect( + registeredTool.execute( + "call", { - status: "done", - summary: "Refactored", - filesChanged: [ - "src/very/long/path/to/some/deeply/nested/module/file.ts", - "tests/very/long/path/to/some/deeply/nested/module/file.test.ts", - ], + status: "partial", + summary: "Blocked", + evidence: [{ kind: "test", description: "No run", outcome: "pass" }], + issues: [{ severity: "blocking", description: "Environment unavailable" }], }, vi.fn(), - {} as any, - new AbortController().signal, - ); - - expect(result.content[0].text).toContain("src/very/long"); - }); - }); - - describe("tool behavior", () => { - it("does not call sendMessage", async () => { - workflowTaskTools(mockPi); - - await registeredTool.execute( - "tool-call-123", - { status: "done", summary: "Test" }, - vi.fn(), - {} as any, - new AbortController().signal, - ); - - expect(mockPi.sendMessage).not.toHaveBeenCalled(); - }); - - it("does not call appendEntry", async () => { - workflowTaskTools(mockPi); - - await registeredTool.execute( - "tool-call-123", - { status: "done", summary: "Test" }, - vi.fn(), - {} as any, - new AbortController().signal, - ); - - expect(mockPi.appendEntry).not.toHaveBeenCalled(); - }); - - it("returns consistent response structure", async () => { - workflowTaskTools(mockPi); - - const doneResult = await registeredTool.execute( - "id1", - { status: "done", summary: "Test" }, - vi.fn(), - {} as any, - new AbortController().signal, - ); - - const passResult = await registeredTool.execute( - "id2", - { status: "pass", issues: [] }, - vi.fn(), - {} as any, - new AbortController().signal, - ); - - const failResult = await registeredTool.execute( - "id3", - { status: "fail", issues: ["Bug"] }, - vi.fn(), - {} as any, - new AbortController().signal, - ); - - // All should have content and details - expect(doneResult).toHaveProperty("content"); - expect(doneResult).toHaveProperty("details"); - expect(passResult).toHaveProperty("content"); - expect(passResult).toHaveProperty("details"); - expect(failResult).toHaveProperty("content"); - expect(failResult).toHaveProperty("details"); - }); - - it("includes params in details", async () => { - workflowTaskTools(mockPi); - - const params = { status: "done", summary: "Test", filesChanged: ["a.ts"] }; - - const result = await registeredTool.execute( - "tool-call-123", - params, - vi.fn(), - {} as any, + {}, new AbortController().signal, - ); - - expect(result.details.params).toEqual(params); - }); + ), + ).rejects.toThrow("blocked evidence"); }); - describe("tool parameter schema", () => { - it("status accepts done, pass, or fail", () => { - workflowTaskTools(mockPi); - - // Schema uses Type.Union with literals - expect(registeredTool.parameters).toBeDefined(); - - // The schema structure is TypeBox - const schema = registeredTool.parameters; - expect(schema).toHaveProperty("type"); - }); - - it("summary is optional", async () => { - workflowTaskTools(mockPi); - - const result = await registeredTool.execute( - "id", - { status: "done" }, - vi.fn(), - {} as any, - new AbortController().signal, - ); - - // Should not throw, summary is optional - expect(result.content[0].text).toContain("N/A"); - }); - - it("filesChanged is optional", async () => { - workflowTaskTools(mockPi); - - const result = await registeredTool.execute( - "id", - { status: "done", summary: "Test" }, + it("rejects verifier reports containing developer-only filesChanged", async () => { + await expect( + registeredTool.execute( + "call", + { ...verifierReport, filesChanged: ["src/feature.ts"] }, vi.fn(), - {} as any, + {}, new AbortController().signal, - ); - - // Should not throw - expect(result).toBeDefined(); - }); - - it("notes is optional", async () => { - workflowTaskTools(mockPi); - - const result = await registeredTool.execute( - "id", - { status: "done", summary: "Test" }, - vi.fn(), - {} as any, - new AbortController().signal, - ); - - // Should not throw - expect(result.details.params.notes).toBeUndefined(); - }); - - it("issues is optional", async () => { - workflowTaskTools(mockPi); - - const result = await registeredTool.execute( - "id", - { status: "pass" }, - vi.fn(), - {} as any, - new AbortController().signal, - ); - - // Should not throw - expect(result).toBeDefined(); - }); + ), + ).rejects.toThrow(); }); - describe("usage scenarios", () => { - it("developer completes task", async () => { - workflowTaskTools(mockPi); - - const result = await registeredTool.execute( - "dev-123", - { - status: "done", - summary: "Implemented user authentication", - filesChanged: ["src/auth.ts", "src/middleware.ts"], - notes: "Uses JWT for tokens", - }, - vi.fn(), - {} as any, - new AbortController().signal, - ); - - expect(result.content[0].text).toContain("Implemented user authentication"); - expect(result.content[0].text).toContain("src/auth.ts"); - }); - - it("verifier passes task", async () => { - workflowTaskTools(mockPi); - - const result = await registeredTool.execute( - "verifier-123", - { - status: "pass", - issues: [], - }, - vi.fn(), - {} as any, - new AbortController().signal, - ); - - expect(result.content[0].text).toBe("Verification passed. No issues found."); - }); - - it("verifier fails task with multiple issues", async () => { - workflowTaskTools(mockPi); - - const result = await registeredTool.execute( - "verifier-123", - { - status: "fail", - issues: [ - "Missing error handling in auth.ts", - "No tests for edge cases", - "TypeScript errors in middleware.ts", - ], - }, - vi.fn(), - {} as any, - new AbortController().signal, - ); - - expect(result.content[0].text).toContain("Verification failed"); - expect(result.content[0].text).toContain("Missing error handling"); - expect(result.content[0].text).toContain("No tests for edge cases"); - }); + it("returns no side effects beyond the tool result", async () => { + await registeredTool.execute( + "call", + developerReport, + vi.fn(), + {}, + new AbortController().signal, + ); + expect(mockPi.sendMessage).not.toHaveBeenCalled(); + expect(mockPi.appendEntry).not.toHaveBeenCalled(); }); });