programmatic subagents
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(pi-mill): bundle vendored mill CLI during prepack

+174 -8
+1
.gitignore
··· 4 4 dist/ 5 5 implement.factory.ts 6 6 packages/pi-mill/.pi-skills/ 7 + packages/pi-mill/.vendor/
+3 -3
packages/pi-mill/README.md
··· 20 20 21 21 ## Prerequisites 22 22 23 - 1. `mill` must be on your `PATH` — see [install instructions](https://github.com/laulauland/mill#install). 24 - 2. A `mill.config.ts` with at least one driver/executor configured (`mill init` to scaffold one). 23 + 1. A `mill.config.ts` with at least one driver/executor configured (`mill init` to scaffold one). 24 + 2. If no bundled CLI is available, `mill` must be on your `PATH` — see [install instructions](https://github.com/laulauland/mill#install). 25 25 26 26 ## How it works 27 27 ··· 95 95 | Option | Description | 96 96 | ------------- | --------------------------------------------------------------------------------------------------------------------- | 97 97 | `maxDepth` | Subagent nesting limit. `1` = agents can spawn subagents, but those subagents cannot spawn their own. `0` = disabled. | 98 - | `millCommand` | Executable name or path for mill. | 98 + | `millCommand` | Executable name or path for mill. If set to `"mill"` (default), pi-mill prefers a bundled CLI when present. | 99 99 | `millArgs` | Extra args prepended to every mill invocation. | 100 100 | `millRunsDir` | Override for `--runs-dir`. | 101 101 | `prompt` | Additional guidance appended to the tool description (model selection hints, project conventions, etc). |
+1 -1
packages/pi-mill/index.ts
··· 218 218 export const config: ExtensionConfig = { 219 219 /** Maximum nesting depth for subagent spawning. 1 = orchestrator can spawn subagents, but those subagents cannot spawn their own. 0 = no subagents at all. */ 220 220 maxDepth: 1, 221 - /** mill executable path/name. */ 221 + /** mill executable path/name. "mill" (default) prefers bundled CLI when available. */ 222 222 millCommand: "mill", 223 223 /** Optional static args prepended to every mill invocation. */ 224 224 millArgs: [],
+3 -2
packages/pi-mill/package.json
··· 21 21 "files": [ 22 22 "*.ts", 23 23 ".pi-skills", 24 + ".vendor", 24 25 "executors", 25 26 "package.json", 26 27 "README.md", ··· 28 29 ], 29 30 "type": "module", 30 31 "scripts": { 31 - "clean": "rm -rf .pi-skills", 32 + "clean": "rm -rf .pi-skills .vendor", 32 33 "build": "echo 'nothing to build'", 33 34 "check": "echo 'nothing to check'", 34 35 "migrate:runs": "node ./scripts/migrate-factory-to-mill.mjs", 35 - "prepack": "node ./scripts/sync-skills.mjs" 36 + "prepack": "node ./scripts/prepack.mjs" 36 37 }, 37 38 "peerDependencies": { 38 39 "@mariozechner/pi-agent-core": "*",
+44 -2
packages/pi-mill/runtime.ts
··· 2 2 import * as fs from "node:fs"; 3 3 import * as os from "node:os"; 4 4 import * as path from "node:path"; 5 + import { fileURLToPath } from "node:url"; 5 6 import type { ExtensionContext } from "@mariozechner/pi-coding-agent"; 6 7 import { MillError } from "./errors.js"; 7 8 import type { ObservabilityStore } from "./observability.js"; ··· 791 792 return model; 792 793 } 793 794 795 + function resolveBundledMillCliPath(): string | undefined { 796 + const currentFile = fileURLToPath(import.meta.url); 797 + const extensionDir = path.dirname(currentFile); 798 + const bundledCliPath = path.join(extensionDir, ".vendor", "mill.mjs"); 799 + return fs.existsSync(bundledCliPath) ? bundledCliPath : undefined; 800 + } 801 + 802 + function resolveMillCommand(options?: { millCommand?: string; millArgs?: string[] }): { 803 + millCommand: string; 804 + millArgs: string[]; 805 + } { 806 + const configuredCommand = options?.millCommand?.trim() || process.env.PI_FACTORY_MILL_CMD?.trim(); 807 + const configuredArgs = options?.millArgs ?? []; 808 + const bundledCliPath = resolveBundledMillCliPath(); 809 + 810 + if (configuredCommand && configuredCommand.length > 0 && configuredCommand !== "mill") { 811 + return { 812 + millCommand: configuredCommand, 813 + millArgs: configuredArgs, 814 + }; 815 + } 816 + 817 + if (bundledCliPath) { 818 + return { 819 + millCommand: process.execPath, 820 + millArgs: [bundledCliPath, ...configuredArgs], 821 + }; 822 + } 823 + 824 + if (configuredCommand && configuredCommand.length > 0) { 825 + return { 826 + millCommand: configuredCommand, 827 + millArgs: configuredArgs, 828 + }; 829 + } 830 + 831 + return { 832 + millCommand: "mill", 833 + millArgs: configuredArgs, 834 + }; 835 + } 836 + 794 837 export function createMillRuntime( 795 838 ctx: ExtensionContext, 796 839 runId: string, ··· 813 856 { controller: AbortController; promise: Promise<ExecutionResult> } 814 857 >(); 815 858 816 - const millCommand = options?.millCommand?.trim() || process.env.PI_FACTORY_MILL_CMD || "mill"; 817 - const millArgs = options?.millArgs ?? []; 859 + const { millCommand, millArgs } = resolveMillCommand(options); 818 860 const millRunsDir = options?.millRunsDir ?? process.env.PI_FACTORY_MILL_RUNS_DIR; 819 861 820 862 const millRuntime: MillRuntime = {
+50
packages/pi-mill/scripts/prepack.mjs
··· 1 + import { spawnSync } from "node:child_process"; 2 + import fs from "node:fs"; 3 + import path from "node:path"; 4 + import { fileURLToPath } from "node:url"; 5 + 6 + const __dirname = path.dirname(fileURLToPath(import.meta.url)); 7 + const packageDir = path.resolve(__dirname, ".."); 8 + const repoRoot = path.resolve(packageDir, "..", ".."); 9 + const vendorDir = path.join(packageDir, ".vendor"); 10 + const bundledCliPath = path.join(vendorDir, "mill.mjs"); 11 + 12 + const run = (command, args, cwd) => { 13 + const result = spawnSync(command, args, { 14 + cwd, 15 + encoding: "utf-8", 16 + stdio: "pipe", 17 + }); 18 + 19 + if (result.stdout && result.stdout.length > 0) { 20 + process.stdout.write(result.stdout); 21 + } 22 + if (result.stderr && result.stderr.length > 0) { 23 + process.stderr.write(result.stderr); 24 + } 25 + 26 + if (result.status !== 0) { 27 + throw new Error(`${command} ${args.join(" ")} failed with exit code ${String(result.status)}`); 28 + } 29 + }; 30 + 31 + run(process.execPath, [path.join(__dirname, "sync-skills.mjs")], packageDir); 32 + 33 + fs.rmSync(vendorDir, { recursive: true, force: true }); 34 + fs.mkdirSync(vendorDir, { recursive: true }); 35 + 36 + run( 37 + "bun", 38 + [ 39 + "build", 40 + path.join(repoRoot, "packages", "cli", "src", "bin", "mill.ts"), 41 + "--bundle", 42 + "--target=node", 43 + "--format=esm", 44 + "--outfile", 45 + bundledCliPath, 46 + ], 47 + repoRoot, 48 + ); 49 + 50 + console.log(`Bundled mill CLI into ${bundledCliPath}`);
+72
packages/pi-mill/tests/prepack.e2e.test.ts
··· 1 + import { describe, expect, it } from "bun:test"; 2 + import { spawnSync } from "node:child_process"; 3 + import { rm } from "node:fs/promises"; 4 + import * as path from "node:path"; 5 + 6 + const packageDir = path.resolve("packages/pi-mill"); 7 + 8 + function runCommand( 9 + command: string, 10 + args: ReadonlyArray<string>, 11 + options?: { 12 + cwd?: string; 13 + env?: NodeJS.ProcessEnv; 14 + }, 15 + ): { stdout: string; stderr: string } { 16 + const result = spawnSync(command, [...args], { 17 + cwd: options?.cwd, 18 + env: options?.env, 19 + encoding: "utf-8", 20 + stdio: "pipe", 21 + }); 22 + 23 + const stdout = result.stdout ?? ""; 24 + const stderr = result.stderr ?? ""; 25 + 26 + if (result.status !== 0) { 27 + throw new Error( 28 + [ 29 + `${command} ${args.join(" ")} failed with exit code ${String(result.status)}`, 30 + stdout, 31 + stderr, 32 + ] 33 + .filter((line) => line.length > 0) 34 + .join("\n"), 35 + ); 36 + } 37 + 38 + return { stdout, stderr }; 39 + } 40 + 41 + describe("pi-mill prepack (e2e)", () => { 42 + it("vendors bundled mill CLI during bun pack dry-run", async () => { 43 + const bundledCli = path.join(packageDir, ".vendor", "mill.mjs"); 44 + 45 + try { 46 + const packed = runCommand("bun", ["pm", "pack", "--dry-run"], { 47 + cwd: packageDir, 48 + }); 49 + const packOutput = `${packed.stdout}\n${packed.stderr}`; 50 + 51 + expect(packOutput).toContain(".vendor/mill.mjs"); 52 + expect(packOutput).toContain(".pi-skills/mill/SKILL.md"); 53 + 54 + const help = runCommand(process.execPath, [bundledCli, "--help"], { 55 + cwd: packageDir, 56 + env: { 57 + ...process.env, 58 + MILL_RUN_DEPTH: "", 59 + }, 60 + }); 61 + const helpOutput = `${help.stdout}\n${help.stderr}`; 62 + 63 + expect(helpOutput).toContain("Usage: mill <command>"); 64 + expect(helpOutput).toContain("run <program.ts>"); 65 + } finally { 66 + await Promise.all([ 67 + rm(path.join(packageDir, ".vendor"), { recursive: true, force: true }), 68 + rm(path.join(packageDir, ".pi-skills"), { recursive: true, force: true }), 69 + ]); 70 + } 71 + }); 72 + });