@fitchmultz/pi-subagents 0.0.0-stage → 0.44.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1645 -0
- package/LICENSE +21 -0
- package/README.md +1529 -2
- package/agents/context-builder.md +97 -0
- package/agents/debugger.md +51 -0
- package/agents/delegate.md +13 -0
- package/agents/fixer.md +37 -0
- package/agents/oracle.md +71 -0
- package/agents/planner.md +72 -0
- package/agents/researcher.md +51 -0
- package/agents/reviewer-claude.md +69 -0
- package/agents/reviewer-gpt.md +68 -0
- package/agents/reviewer-ponytail.md +82 -0
- package/agents/reviewer-security.md +81 -0
- package/agents/reviewer.md +69 -0
- package/agents/scout.md +66 -0
- package/agents/ui-designer.md +51 -0
- package/agents/watcher.md +26 -0
- package/agents/worker.md +46 -0
- package/agents/writer.md +36 -0
- package/dist/agents/agent-management.js +923 -0
- package/dist/agents/agent-scope.js +7 -0
- package/dist/agents/agent-selection.js +20 -0
- package/dist/agents/agent-serializer.js +100 -0
- package/dist/agents/agents.js +752 -0
- package/dist/agents/chain-serializer.js +367 -0
- package/dist/agents/frontmatter.js +21 -0
- package/dist/agents/identity.js +29 -0
- package/dist/agents/skills.js +570 -0
- package/dist/extension/build-info.js +1 -0
- package/dist/extension/compact-tools.js +112 -0
- package/dist/extension/config.js +22 -0
- package/dist/extension/control-notices.js +29 -0
- package/dist/extension/doctor.js +143 -0
- package/dist/extension/fanout-child.js +326 -0
- package/dist/extension/index.js +577 -0
- package/dist/extension/schemas.js +416 -0
- package/dist/extension/tool-input.js +20 -0
- package/dist/extension/tool-result.js +14 -0
- package/dist/history/canonical-result.js +81 -0
- package/dist/history/database.js +72 -0
- package/dist/history/index.js +194 -0
- package/dist/history/ingest.js +447 -0
- package/dist/history/queries.js +350 -0
- package/dist/history/store.js +216 -0
- package/dist/history/types.js +4 -0
- package/dist/history/worker.js +461 -0
- package/dist/intercom/intercom-bridge.js +112 -0
- package/dist/intercom/live-intercom.js +94 -0
- package/dist/intercom/result-intercom.js +373 -0
- package/dist/pi-intercom/agent-dir.js +10 -0
- package/dist/pi-intercom/broker/broker.js +426 -0
- package/dist/pi-intercom/broker/client.js +492 -0
- package/dist/pi-intercom/broker/framing.js +65 -0
- package/dist/pi-intercom/broker/paths.js +42 -0
- package/dist/pi-intercom/broker/pid.js +66 -0
- package/dist/pi-intercom/broker/spawn.js +221 -0
- package/dist/pi-intercom/config.js +102 -0
- package/dist/pi-intercom/index.js +2942 -0
- package/dist/pi-intercom/reply-tracker.js +182 -0
- package/dist/pi-intercom/session-targets.js +128 -0
- package/dist/pi-intercom/subagent-live-events.js +132 -0
- package/dist/pi-intercom/topics.js +157 -0
- package/dist/pi-intercom/types.js +163 -0
- package/dist/pi-intercom/ui/compose.js +269 -0
- package/dist/pi-intercom/ui/inline-message.js +80 -0
- package/dist/pi-intercom/ui/session-list.js +124 -0
- package/dist/runs/background/async-control.js +95 -0
- package/dist/runs/background/async-execution.js +676 -0
- package/dist/runs/background/async-job-tracker.js +383 -0
- package/dist/runs/background/async-result-file.js +58 -0
- package/dist/runs/background/async-resume.js +305 -0
- package/dist/runs/background/async-status.js +442 -0
- package/dist/runs/background/completion-dedupe.js +69 -0
- package/dist/runs/background/completion-delivery.js +245 -0
- package/dist/runs/background/notify.js +71 -0
- package/dist/runs/background/parallel-groups.js +55 -0
- package/dist/runs/background/result-watcher.js +426 -0
- package/dist/runs/background/run-id-resolver.js +64 -0
- package/dist/runs/background/run-status.js +382 -0
- package/dist/runs/background/stale-run-reconciler.js +335 -0
- package/dist/runs/background/subagent-runner-launcher.js +55 -0
- package/dist/runs/background/subagent-runner.js +1872 -0
- package/dist/runs/background/top-level-async.js +5 -0
- package/dist/runs/foreground/chain-clarify.js +1152 -0
- package/dist/runs/foreground/clarify-invocation.js +72 -0
- package/dist/runs/foreground/execution-input.js +423 -0
- package/dist/runs/foreground/foreground-control.js +817 -0
- package/dist/runs/foreground/question-control.js +77 -0
- package/dist/runs/foreground/run-async-path.js +205 -0
- package/dist/runs/foreground/subagent-executor.js +442 -0
- package/dist/runs/foreground/subagent-params.js +165 -0
- package/dist/runs/foreground/wait-run.js +139 -0
- package/dist/runs/shared/acceptance-contract.js +253 -0
- package/dist/runs/shared/acceptance-evaluation.js +256 -0
- package/dist/runs/shared/acceptance-finalization.js +280 -0
- package/dist/runs/shared/acceptance-reports.js +147 -0
- package/dist/runs/shared/acceptance.js +4 -0
- package/dist/runs/shared/chain-outputs.js +110 -0
- package/dist/runs/shared/child-attempt.js +693 -0
- package/dist/runs/shared/child-execution-cwd.js +96 -0
- package/dist/runs/shared/child-observations.js +114 -0
- package/dist/runs/shared/claude-code.js +199 -0
- package/dist/runs/shared/completion-guard.js +31 -0
- package/dist/runs/shared/dynamic-fanout.js +274 -0
- package/dist/runs/shared/history-index.js +72 -0
- package/dist/runs/shared/mcp-direct-tool-allowlist.js +334 -0
- package/dist/runs/shared/model-fallback.js +185 -0
- package/dist/runs/shared/mutating-tool-guard.js +170 -0
- package/dist/runs/shared/native-finalization.js +101 -0
- package/dist/runs/shared/native-usage.js +153 -0
- package/dist/runs/shared/nested-events.js +700 -0
- package/dist/runs/shared/nested-path.js +48 -0
- package/dist/runs/shared/nested-render.js +108 -0
- package/dist/runs/shared/parallel-utils.js +61 -0
- package/dist/runs/shared/parent-receipts.js +68 -0
- package/dist/runs/shared/parent-usage.js +121 -0
- package/dist/runs/shared/pi-args.js +310 -0
- package/dist/runs/shared/pi-spawn.js +52 -0
- package/dist/runs/shared/run-history-queries.js +45 -0
- package/dist/runs/shared/run-history.js +103 -0
- package/dist/runs/shared/run-id-ambiguity.js +4 -0
- package/dist/runs/shared/run-records.js +679 -0
- package/dist/runs/shared/session-cwd-preload.js +33 -0
- package/dist/runs/shared/single-output.js +222 -0
- package/dist/runs/shared/streaming-text.js +15 -0
- package/dist/runs/shared/structured-output.js +55 -0
- package/dist/runs/shared/subagent-control.js +179 -0
- package/dist/runs/shared/subagent-prompt-runtime.js +197 -0
- package/dist/runs/shared/subagent-tool-loop-guard.js +66 -0
- package/dist/runs/shared/supervisor-questions.js +320 -0
- package/dist/runs/shared/workflow-graph.js +214 -0
- package/dist/runs/shared/workflow-policy.js +84 -0
- package/dist/runs/shared/worktree.js +525 -0
- package/dist/shared/agent-context-policy.js +126 -0
- package/dist/shared/artifacts.js +96 -0
- package/dist/shared/atomic-json.js +13 -0
- package/dist/shared/cooperative.js +20 -0
- package/dist/shared/execution-cwd.js +14 -0
- package/dist/shared/file-coalescer.js +21 -0
- package/dist/shared/fork-context.js +49 -0
- package/dist/shared/formatters.js +85 -0
- package/dist/shared/journal-reader.js +407 -0
- package/dist/shared/lazy-tools.js +32 -0
- package/dist/shared/model-info.js +68 -0
- package/dist/shared/native-session.js +15 -0
- package/dist/shared/native-tui.js +18 -0
- package/dist/shared/native-typebox.js +20 -0
- package/dist/shared/post-exit-stdio-guard.js +197 -0
- package/dist/shared/prompt-sections.js +8 -0
- package/dist/shared/session-entries.js +52 -0
- package/dist/shared/session-identity.js +14 -0
- package/dist/shared/settings.js +254 -0
- package/dist/shared/status-format.js +111 -0
- package/dist/shared/temp-root.js +114 -0
- package/dist/shared/types.js +207 -0
- package/dist/shared/ui-mode.js +3 -0
- package/dist/shared/utils.js +334 -0
- package/dist/slash/prompt-template-bridge.js +298 -0
- package/dist/slash/slash-bridge.js +116 -0
- package/dist/slash/slash-commands.js +607 -0
- package/dist/slash/slash-live-state.js +246 -0
- package/dist/tui/action-hints.js +75 -0
- package/dist/tui/agent-history.js +197 -0
- package/dist/tui/agent-view.js +1656 -0
- package/dist/tui/render.js +1515 -0
- package/docs/intercom.md +579 -0
- package/docs/performance.md +121 -0
- package/package.json +101 -4
- package/prompts/gather-context-and-clarify.md +14 -0
- package/prompts/parallel-cleanup.md +62 -0
- package/prompts/parallel-context-build.md +56 -0
- package/prompts/parallel-handoff-plan.md +62 -0
- package/prompts/parallel-research.md +51 -0
- package/prompts/parallel-review.md +59 -0
- package/prompts/review-loop.md +42 -0
- package/scripts/build.mjs +134 -0
- package/scripts/check-compat.mjs +48 -0
- package/scripts/compat-host.mjs +19 -0
- package/scripts/linux-smoke.sh +51 -0
- package/scripts/local-install-smoke.mjs +66 -0
- package/scripts/native-package-smoke.mjs +82 -0
- package/scripts/package-smoke.mjs +205 -0
- package/scripts/prepare.mjs +67 -0
- package/scripts/real-pi-smoke.mjs +415 -0
- package/scripts/run-tests.mjs +149 -0
- package/skills/pi-intercom/SKILL.md +113 -0
- package/skills/pi-intercom/evals/evals.json +29 -0
- package/skills/pi-intercom/evals/trigger-evals.json +47 -0
- package/skills/pi-intercom/references/peer-sessions.md +69 -0
- package/skills/pi-subagents/SKILL.md +76 -0
- package/skills/pi-subagents/evals/evals.json +29 -0
- package/skills/pi-subagents/evals/trigger-evals.json +42 -0
- package/skills/pi-subagents/references/full-orchestration-guide.md +863 -0
- package/src/agents/agent-management.ts +813 -0
- package/src/agents/agent-scope.ts +7 -0
- package/src/agents/agent-selection.ts +23 -0
- package/src/agents/agent-serializer.ts +99 -0
- package/src/agents/agents.ts +916 -0
- package/src/agents/chain-serializer.ts +329 -0
- package/src/agents/frontmatter.ts +21 -0
- package/src/agents/identity.ts +30 -0
- package/src/agents/skills.ts +637 -0
- package/src/extension/build-info.ts +2 -0
- package/src/extension/compact-tools.ts +114 -0
- package/src/extension/config.ts +23 -0
- package/src/extension/control-notices.ts +47 -0
- package/src/extension/doctor.ts +171 -0
- package/src/extension/fanout-child.ts +312 -0
- package/src/extension/index.ts +649 -0
- package/src/extension/schemas.ts +444 -0
- package/src/extension/tool-input.ts +19 -0
- package/src/extension/tool-result.ts +16 -0
- package/src/history/canonical-result.ts +62 -0
- package/src/history/database.ts +59 -0
- package/src/history/index.ts +152 -0
- package/src/history/ingest.ts +332 -0
- package/src/history/queries.ts +241 -0
- package/src/history/store.ts +203 -0
- package/src/history/types.ts +166 -0
- package/src/history/worker.ts +317 -0
- package/src/intercom/intercom-bridge.ts +123 -0
- package/src/intercom/live-intercom.ts +104 -0
- package/src/intercom/result-intercom.ts +429 -0
- package/src/pi-intercom/agent-dir.ts +9 -0
- package/src/pi-intercom/broker/broker.ts +463 -0
- package/src/pi-intercom/broker/client.ts +558 -0
- package/src/pi-intercom/broker/framing.ts +77 -0
- package/src/pi-intercom/broker/paths.ts +47 -0
- package/src/pi-intercom/broker/pid.ts +59 -0
- package/src/pi-intercom/broker/spawn.ts +248 -0
- package/src/pi-intercom/config.ts +142 -0
- package/src/pi-intercom/index.ts +2929 -0
- package/src/pi-intercom/reply-tracker.ts +211 -0
- package/src/pi-intercom/session-targets.ts +172 -0
- package/src/pi-intercom/subagent-live-events.ts +133 -0
- package/src/pi-intercom/topics.ts +125 -0
- package/src/pi-intercom/types.ts +271 -0
- package/src/pi-intercom/ui/compose.ts +289 -0
- package/src/pi-intercom/ui/inline-message.ts +92 -0
- package/src/pi-intercom/ui/session-list.ts +139 -0
- package/src/runs/background/async-control.ts +89 -0
- package/src/runs/background/async-execution.ts +850 -0
- package/src/runs/background/async-job-tracker.ts +385 -0
- package/src/runs/background/async-result-file.ts +62 -0
- package/src/runs/background/async-resume.ts +333 -0
- package/src/runs/background/async-status.ts +481 -0
- package/src/runs/background/completion-dedupe.ts +79 -0
- package/src/runs/background/completion-delivery.ts +195 -0
- package/src/runs/background/notify.ts +123 -0
- package/src/runs/background/parallel-groups.ts +53 -0
- package/src/runs/background/result-watcher.ts +423 -0
- package/src/runs/background/run-id-resolver.ts +69 -0
- package/src/runs/background/run-status.ts +384 -0
- package/src/runs/background/stale-run-reconciler.ts +369 -0
- package/src/runs/background/subagent-runner-launcher.ts +56 -0
- package/src/runs/background/subagent-runner.ts +2053 -0
- package/src/runs/background/top-level-async.ts +13 -0
- package/src/runs/foreground/chain-clarify.ts +1340 -0
- package/src/runs/foreground/clarify-invocation.ts +77 -0
- package/src/runs/foreground/execution-input.ts +467 -0
- package/src/runs/foreground/foreground-control.ts +821 -0
- package/src/runs/foreground/question-control.ts +70 -0
- package/src/runs/foreground/run-async-path.ts +243 -0
- package/src/runs/foreground/subagent-executor.ts +501 -0
- package/src/runs/foreground/subagent-params.ts +320 -0
- package/src/runs/foreground/wait-run.ts +121 -0
- package/src/runs/shared/acceptance-contract.ts +257 -0
- package/src/runs/shared/acceptance-evaluation.ts +275 -0
- package/src/runs/shared/acceptance-finalization.ts +353 -0
- package/src/runs/shared/acceptance-reports.ts +152 -0
- package/src/runs/shared/acceptance.ts +33 -0
- package/src/runs/shared/chain-outputs.ts +114 -0
- package/src/runs/shared/child-attempt.ts +623 -0
- package/src/runs/shared/child-execution-cwd.ts +91 -0
- package/src/runs/shared/child-observations.ts +99 -0
- package/src/runs/shared/claude-code.ts +243 -0
- package/src/runs/shared/completion-guard.ts +35 -0
- package/src/runs/shared/dynamic-fanout.ts +295 -0
- package/src/runs/shared/history-index.ts +69 -0
- package/src/runs/shared/mcp-direct-tool-allowlist.ts +376 -0
- package/src/runs/shared/model-fallback.ts +219 -0
- package/src/runs/shared/mutating-tool-guard.ts +194 -0
- package/src/runs/shared/native-finalization.ts +128 -0
- package/src/runs/shared/native-usage.ts +113 -0
- package/src/runs/shared/nested-events.ts +752 -0
- package/src/runs/shared/nested-path.ts +52 -0
- package/src/runs/shared/nested-render.ts +119 -0
- package/src/runs/shared/parallel-utils.ts +153 -0
- package/src/runs/shared/parent-receipts.ts +59 -0
- package/src/runs/shared/parent-usage.ts +121 -0
- package/src/runs/shared/pi-args.ts +350 -0
- package/src/runs/shared/pi-spawn.ts +52 -0
- package/src/runs/shared/run-history-queries.ts +44 -0
- package/src/runs/shared/run-history.ts +93 -0
- package/src/runs/shared/run-id-ambiguity.ts +4 -0
- package/src/runs/shared/run-records.ts +607 -0
- package/src/runs/shared/session-cwd-preload.ts +27 -0
- package/src/runs/shared/single-output.ts +267 -0
- package/src/runs/shared/streaming-text.ts +18 -0
- package/src/runs/shared/structured-output.ts +68 -0
- package/src/runs/shared/subagent-control.ts +219 -0
- package/src/runs/shared/subagent-prompt-runtime.ts +201 -0
- package/src/runs/shared/subagent-tool-loop-guard.ts +89 -0
- package/src/runs/shared/supervisor-questions.ts +372 -0
- package/src/runs/shared/workflow-graph.ts +220 -0
- package/src/runs/shared/workflow-policy.ts +125 -0
- package/src/runs/shared/worktree.ts +635 -0
- package/src/shared/agent-context-policy.ts +184 -0
- package/src/shared/artifacts.ts +95 -0
- package/src/shared/atomic-json.ts +16 -0
- package/src/shared/cooperative.ts +21 -0
- package/src/shared/execution-cwd.ts +14 -0
- package/src/shared/file-coalescer.ts +29 -0
- package/src/shared/fork-context.ts +68 -0
- package/src/shared/formatters.ts +84 -0
- package/src/shared/journal-reader.ts +333 -0
- package/src/shared/lazy-tools.ts +36 -0
- package/src/shared/model-info.ts +81 -0
- package/src/shared/native-session.ts +8 -0
- package/src/shared/native-tui.ts +10 -0
- package/src/shared/native-typebox.ts +14 -0
- package/src/shared/post-exit-stdio-guard.ts +204 -0
- package/src/shared/prompt-sections.ts +10 -0
- package/src/shared/session-entries.ts +49 -0
- package/src/shared/session-identity.ts +18 -0
- package/src/shared/settings.ts +384 -0
- package/src/shared/status-format.ts +123 -0
- package/src/shared/temp-root.ts +107 -0
- package/src/shared/types.ts +1259 -0
- package/src/shared/ui-mode.ts +5 -0
- package/src/shared/utils.ts +342 -0
- package/src/slash/prompt-template-bridge.ts +408 -0
- package/src/slash/slash-bridge.ts +178 -0
- package/src/slash/slash-commands.ts +617 -0
- package/src/slash/slash-live-state.ts +292 -0
- package/src/tui/action-hints.ts +69 -0
- package/src/tui/agent-history.ts +210 -0
- package/src/tui/agent-view.ts +1323 -0
- package/src/tui/render.ts +1550 -0
- package/tsconfig.build.json +12 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Purpose: Produce the compiled runtime files that the Pi extension manifest loads.
|
|
4
|
+
* Responsibilities: Run TypeScript emit into a staging directory, then atomically swap it
|
|
5
|
+
* into dist/ so a failed TypeScript emit never destroys a previously working dist.
|
|
6
|
+
* Usage: `npm run build`; also invoked by scripts/prepare.mjs during install lifecycles, which may pass
|
|
7
|
+
* the path of a throwaway compiler's `bin/tsc` as the first argument.
|
|
8
|
+
* Invariants/Assumptions: `node_modules` (or that argument) provides `typescript`; deleting `dist/` is safe generated output.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { execFile as execFileCallback } from "node:child_process";
|
|
12
|
+
import { createHash } from "node:crypto";
|
|
13
|
+
import { existsSync } from "node:fs";
|
|
14
|
+
import { readFile, readdir, rename, rm, writeFile } from "node:fs/promises";
|
|
15
|
+
import { join } from "node:path";
|
|
16
|
+
import process from "node:process";
|
|
17
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
18
|
+
import { promisify } from "node:util";
|
|
19
|
+
|
|
20
|
+
const execFile = promisify(execFileCallback);
|
|
21
|
+
const RM_OPTIONS = { force: true, maxRetries: 5, recursive: true, retryDelay: 100 };
|
|
22
|
+
const RENAME_RETRY_LIMIT = 50;
|
|
23
|
+
const RENAME_RETRY_MS = 50;
|
|
24
|
+
// Use the current Node binary without a shell, including install paths with spaces.
|
|
25
|
+
const tscPath = process.argv[2] ?? join(process.cwd(), "node_modules", "typescript", "bin", "tsc");
|
|
26
|
+
|
|
27
|
+
async function discardStaging(path) {
|
|
28
|
+
try {
|
|
29
|
+
await rm(path, RM_OPTIONS);
|
|
30
|
+
} catch (error) {
|
|
31
|
+
// Cleanup is always best-effort: it must not turn a successful race loss
|
|
32
|
+
// into failure or replace the compiler/publish error that matters.
|
|
33
|
+
console.warn(`could not remove staging ${path}: ${error?.message ?? error}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function isOwnerGone(pid) {
|
|
38
|
+
try {
|
|
39
|
+
process.kill(pid, 0);
|
|
40
|
+
return false;
|
|
41
|
+
} catch (error) {
|
|
42
|
+
return error?.code === "ESRCH";
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function hasPublishedDist(path) {
|
|
47
|
+
try {
|
|
48
|
+
return (await readdir(path)).length > 0;
|
|
49
|
+
} catch {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function reapStrandedStaging(cwd) {
|
|
55
|
+
// Signal 0 proves only that the owner was gone at probe time. Pid reuse
|
|
56
|
+
// between this probe and removal is inherent to pid-based reaping.
|
|
57
|
+
for (const entry of await readdir(cwd, { withFileTypes: true })) {
|
|
58
|
+
const match = /^dist\.staging\.(\d+)$/.exec(entry.name);
|
|
59
|
+
if (!match || !entry.isDirectory()) continue;
|
|
60
|
+
const ownerPid = Number(match[1]);
|
|
61
|
+
if (ownerPid === process.pid || !isOwnerGone(ownerPid)) continue;
|
|
62
|
+
await discardStaging(join(cwd, entry.name));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function compileToStaging(cwd, stagingDir) {
|
|
67
|
+
try {
|
|
68
|
+
const { stderr, stdout } = await execFile(
|
|
69
|
+
process.execPath,
|
|
70
|
+
[tscPath, "-p", "tsconfig.build.json", "--outDir", stagingDir],
|
|
71
|
+
{ cwd, maxBuffer: 10 * 1024 * 1024 },
|
|
72
|
+
);
|
|
73
|
+
if (stdout) process.stdout.write(stdout);
|
|
74
|
+
if (stderr) process.stderr.write(stderr);
|
|
75
|
+
const stamp = "extension/build-info.js";
|
|
76
|
+
const hash = createHash("sha256");
|
|
77
|
+
for (const file of (await readdir(stagingDir, { recursive: true })).filter((file) => file.endsWith(".js") && file !== stamp).sort()) {
|
|
78
|
+
hash.update(file).update("\0").update(await readFile(join(stagingDir, file))).update("\0");
|
|
79
|
+
}
|
|
80
|
+
const { version } = JSON.parse(await readFile(join(cwd, "package.json"), "utf8"));
|
|
81
|
+
await writeFile(join(stagingDir, stamp), `export const EXTENSION_BUILD = Object.freeze(${JSON.stringify({ version, sha256: hash.digest("hex") })});\n`);
|
|
82
|
+
} catch (error) {
|
|
83
|
+
if (error?.stdout) process.stdout.write(error.stdout);
|
|
84
|
+
if (error?.stderr) process.stderr.write(error.stderr);
|
|
85
|
+
await discardStaging(stagingDir);
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function publishStaging(stagingDir, distDir) {
|
|
91
|
+
try {
|
|
92
|
+
// A failed dist removal must fail loudly; keep it outside the retry loop.
|
|
93
|
+
// Remove dist exactly once so retries never delete a concurrent winner.
|
|
94
|
+
await rm(distDir, RM_OPTIONS);
|
|
95
|
+
for (let retry = 0; ; retry++) {
|
|
96
|
+
try {
|
|
97
|
+
await rename(stagingDir, distDir);
|
|
98
|
+
return;
|
|
99
|
+
} catch (error) {
|
|
100
|
+
// A completed staged build is non-empty; a bare directory is not proof
|
|
101
|
+
// that a concurrent publisher won the race.
|
|
102
|
+
if (await hasPublishedDist(distDir)) {
|
|
103
|
+
console.warn("dist/ was published by a concurrent build; discarding this build's staging tree.");
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
// At most 50 retries (~2.5s) keep persistent filesystem errors bounded.
|
|
107
|
+
if (existsSync(stagingDir) && retry < RENAME_RETRY_LIMIT) {
|
|
108
|
+
await delay(RENAME_RETRY_MS);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
throw error;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
} finally {
|
|
115
|
+
// Safe after success too: rename moved stagingDir, so force makes this a no-op.
|
|
116
|
+
await discardStaging(stagingDir);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function main() {
|
|
121
|
+
const cwd = process.cwd();
|
|
122
|
+
if (!existsSync(tscPath)) throw new Error(`typescript is not installed at ${tscPath}; run npm install first.`);
|
|
123
|
+
await reapStrandedStaging(cwd);
|
|
124
|
+
// Pid-scoped staging isolates concurrent emits; only a complete tree publishes.
|
|
125
|
+
const stagingDir = join(cwd, `dist.staging.${process.pid}`);
|
|
126
|
+
await rm(stagingDir, RM_OPTIONS);
|
|
127
|
+
await compileToStaging(cwd, stagingDir);
|
|
128
|
+
await publishStaging(stagingDir, join(cwd, "dist"));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
main().catch((error) => {
|
|
132
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
133
|
+
process.exitCode = 1;
|
|
134
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
import { existsSync, mkdtempSync, rmSync } from "node:fs";
|
|
5
|
+
import { join, resolve } from "node:path";
|
|
6
|
+
import { tmpdir } from "node:os";
|
|
7
|
+
|
|
8
|
+
if (process.argv.includes("--help") || process.argv.includes("-h")) {
|
|
9
|
+
console.log("Usage: node scripts/check-compat.mjs\n\nRun selected-host full CI and native package checks with actual editor receipts.\nAn owned temporary editor checkout is removed after checks; supplied sources stay read-only.\nEnvironment: PI_EDITOR_RECEIPT_TEST_ROOT optionally supplies a clean frozen Git checkout.\nExamples:\n npm run check:compat\n PI_EDITOR_RECEIPT_TEST_ROOT=/path/to/pi-apply-edits npm run check:compat\nExit codes: 0 passed; 1 invalid input, unavailable source or failed checks.");
|
|
10
|
+
process.exit(0);
|
|
11
|
+
}
|
|
12
|
+
assert.equal(process.argv.length, 2, "Expected no arguments; use --help");
|
|
13
|
+
await import("./compat-host.mjs");
|
|
14
|
+
const run = (command, args, options = {}) => {
|
|
15
|
+
const result = spawnSync(command, args, { stdio: "inherit", timeout: 1_200_000, ...options });
|
|
16
|
+
if (result.error) throw result.error;
|
|
17
|
+
assert.equal(result.status, 0, `${command} ${args.join(" ")} failed (${result.status ?? result.signal})`);
|
|
18
|
+
return result.stdout?.trim();
|
|
19
|
+
};
|
|
20
|
+
const git = (cwd, args) => run("git", args, { cwd, stdio: ["ignore", "pipe", "inherit"], encoding: "utf8" });
|
|
21
|
+
const root = mkdtempSync(join(tmpdir(), "ps-editor-"));
|
|
22
|
+
const editor = join(root, "source");
|
|
23
|
+
try {
|
|
24
|
+
const supplied = process.env.PI_EDITOR_RECEIPT_TEST_ROOT;
|
|
25
|
+
let expectedRef;
|
|
26
|
+
if (supplied) {
|
|
27
|
+
const source = resolve(supplied);
|
|
28
|
+
expectedRef = git(source, ["rev-parse", "HEAD"]);
|
|
29
|
+
assert.equal(git(source, ["status", "--porcelain", "--untracked-files=all"]), "", "Supplied editor source must be clean");
|
|
30
|
+
run("git", ["clone", "--no-hardlinks", "--quiet", source, editor]);
|
|
31
|
+
git(editor, ["checkout", "--quiet", "--detach", expectedRef]);
|
|
32
|
+
} else {
|
|
33
|
+
run("git", ["clone", "--depth", "1", "--branch", "main", "--single-branch", "--quiet", "https://github.com/fitchmultz/pi-apply-edits.git", editor]);
|
|
34
|
+
}
|
|
35
|
+
const ref = git(editor, ["rev-parse", "HEAD"]);
|
|
36
|
+
assert.match(ref, /^[a-f0-9]{40}$/);
|
|
37
|
+
if (expectedRef) assert.equal(ref, expectedRef);
|
|
38
|
+
assert.ok(existsSync(join(editor, "extensions", "apply-edits.ts")), "Editor source lacks its native extension");
|
|
39
|
+
// Only the editor's real third-party runtime dependencies; Pi supplies its own peers through the loader.
|
|
40
|
+
run("npm", ["ci", "--omit=dev", "--ignore-scripts", "--legacy-peer-deps", "--no-audit", "--no-fund"], { cwd: editor });
|
|
41
|
+
git(editor, ["diff", "--exit-code"]);
|
|
42
|
+
console.log(JSON.stringify({ nativeEditor: { ref, source: editor, supplied: supplied ? resolve(supplied) : null } }));
|
|
43
|
+
const env = { ...process.env, PI_EDITOR_RECEIPT_TEST_ROOT: editor };
|
|
44
|
+
run("npm", ["run", "ci"], { env });
|
|
45
|
+
run(process.execPath, ["scripts/native-package-smoke.mjs"], { env });
|
|
46
|
+
} finally {
|
|
47
|
+
rmSync(root, { recursive: true, force: true });
|
|
48
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Validate the graph prepared by the compatibility runner; never install a host here.
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
import { readFileSync, realpathSync } from "node:fs";
|
|
5
|
+
import { findPackageJSON } from "node:module";
|
|
6
|
+
import { dirname, join } from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
|
|
9
|
+
export const hostRoot = realpathSync(dirname(findPackageJSON("@earendil-works/pi-coding-agent", import.meta.url)));
|
|
10
|
+
export const hostIndex = join(hostRoot, "dist/index.js");
|
|
11
|
+
const manifest = JSON.parse(readFileSync(join(hostRoot, "package.json"), "utf8"));
|
|
12
|
+
export const hostCli = realpathSync(join(hostRoot, manifest.bin.pi));
|
|
13
|
+
for (const [name, actual] of [["PI_COMPAT_EXPECTED_PACKAGE_DIR", hostRoot], ["PI_HOST_INDEX", hostIndex], ["PI_HOST_CLI", hostCli]]) {
|
|
14
|
+
if (process.env[name]) assert.equal(realpathSync(process.env[name]), realpathSync(actual), `${name} must select the installed graph`);
|
|
15
|
+
}
|
|
16
|
+
if (process.env.PI_COMPAT_EXPECTED_VERSION) assert.equal(manifest.version, process.env.PI_COMPAT_EXPECTED_VERSION);
|
|
17
|
+
assert.equal(realpathSync(fileURLToPath(import.meta.resolve("@earendil-works/pi-coding-agent"))), realpathSync(hostIndex));
|
|
18
|
+
const hash = path => createHash("sha256").update(readFileSync(path)).digest("hex");
|
|
19
|
+
console.log(JSON.stringify({ host: process.env.PI_COMPAT_HOST ?? "local", version: manifest.version, node: process.version, hostRoot, hostIndex, hostCli, indexSha256: hash(hostIndex), cliSha256: hash(hostCli) }));
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
|
|
5
|
+
printf '%s\n' 'Usage: bash scripts/linux-smoke.sh
|
|
6
|
+
|
|
7
|
+
Validate committed HEAD in Docker against a credential-free, prebuilt Pi 1.0
|
|
8
|
+
Linux archive with a single top-level pi/ directory and built SDK/CLI output.
|
|
9
|
+
|
|
10
|
+
Example:
|
|
11
|
+
PI_LINUX_PI_ARCHIVE=/tmp/pi-linux.tar.gz bash scripts/linux-smoke.sh
|
|
12
|
+
|
|
13
|
+
Environment:
|
|
14
|
+
PI_LINUX_PI_ARCHIVE Absolute archive path (required)
|
|
15
|
+
PI_LINUX_IMAGE Node 24.21.0+ Linux image (default: node:24-bookworm)
|
|
16
|
+
|
|
17
|
+
Exit codes: 0 passed; 1 validation failed; 2 invalid arguments.'
|
|
18
|
+
exit 0
|
|
19
|
+
fi
|
|
20
|
+
if [[ $# -ne 0 ]]; then
|
|
21
|
+
printf '%s\n' 'No arguments accepted; use --help.' >&2
|
|
22
|
+
exit 2
|
|
23
|
+
fi
|
|
24
|
+
: "${PI_LINUX_PI_ARCHIVE:?Set PI_LINUX_PI_ARCHIVE to an absolute path to a credential-free prebuilt Linux Pi 1.0 .tar.gz (top-level pi/) with its Linux dependencies and built SDK/CLI.}"
|
|
25
|
+
|
|
26
|
+
# Validate committed HEAD without passing host credentials or mounting the checkout.
|
|
27
|
+
git -C "$(dirname "$0")/.." archive HEAD | docker run --rm --init -i \
|
|
28
|
+
--mount "type=bind,src=$PI_LINUX_PI_ARCHIVE,dst=/native-pi.tar.gz,readonly" \
|
|
29
|
+
"${PI_LINUX_IMAGE:-node:24-bookworm}" bash -c '
|
|
30
|
+
set -euo pipefail
|
|
31
|
+
mkdir /workspace /native-pi
|
|
32
|
+
tar -xf - -C /workspace
|
|
33
|
+
tar -xzf /native-pi.tar.gz -C /native-pi --strip-components=1
|
|
34
|
+
chown -R node:node /workspace /native-pi
|
|
35
|
+
runuser -u node -- bash -c '\''
|
|
36
|
+
set -euo pipefail
|
|
37
|
+
cd /workspace
|
|
38
|
+
node --version
|
|
39
|
+
npm --version
|
|
40
|
+
uname -sm
|
|
41
|
+
npm ci
|
|
42
|
+
ln -sf /native-pi/packages/coding-agent/dist/bundle/cli.js node_modules/.bin/pi
|
|
43
|
+
export PI_INTERCOM_TEST_SDK=/native-pi/packages/coding-agent
|
|
44
|
+
export PI_OWNERSHIP_TEST_PACKAGE_ROOT="$PI_INTERCOM_TEST_SDK"
|
|
45
|
+
export PI_CONTEXT_TEST_PACKAGE_ROOT="$PI_INTERCOM_TEST_SDK"
|
|
46
|
+
export PI_PACKAGE_DIR="$PI_INTERCOM_TEST_SDK"
|
|
47
|
+
export PATH="$PWD/node_modules/.bin:$PATH"
|
|
48
|
+
pi --version
|
|
49
|
+
npm run ci
|
|
50
|
+
'\''
|
|
51
|
+
'
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { mkdtempSync, rmSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join, resolve } from "node:path";
|
|
5
|
+
import { spawnSync } from "node:child_process";
|
|
6
|
+
import process from "node:process";
|
|
7
|
+
import { hostCli } from "./compat-host.mjs";
|
|
8
|
+
|
|
9
|
+
if (process.argv.includes("--help") || process.argv.includes("-h")) {
|
|
10
|
+
console.log(`Usage: node scripts/local-install-smoke.mjs\n\nInstalls this repository into an isolated temporary Pi home, then verifies pi list\ncan resolve the single package. User-level Pi settings are not modified.\n\nExit codes:\n 0 local path install/list smoke passed\n 1 pi install or pi list failed, or the installed package was not listed`);
|
|
11
|
+
process.exit(0);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function isolatedEnv(home) {
|
|
15
|
+
const env = { ...process.env };
|
|
16
|
+
for (const key of Object.keys(env)) if (key.startsWith("PI_SUBAGENT_")) delete env[key];
|
|
17
|
+
return {
|
|
18
|
+
...env,
|
|
19
|
+
HOME: home,
|
|
20
|
+
XDG_CONFIG_HOME: join(home, ".config"),
|
|
21
|
+
XDG_CACHE_HOME: join(home, ".cache"),
|
|
22
|
+
PI_CODING_AGENT_DIR: join(home, ".pi", "agent"),
|
|
23
|
+
PI_OFFLINE: "1",
|
|
24
|
+
PATH: process.env.PATH ?? "",
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function runPi(args, env) {
|
|
29
|
+
const result = spawnSync(process.execPath, [hostCli, ...args], {
|
|
30
|
+
cwd: process.cwd(),
|
|
31
|
+
env,
|
|
32
|
+
encoding: "utf-8",
|
|
33
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
34
|
+
});
|
|
35
|
+
return {
|
|
36
|
+
ok: !result.error && result.status === 0,
|
|
37
|
+
output: result.error ? result.error.message : `${result.stdout ?? ""}${result.stderr ?? ""}`,
|
|
38
|
+
status: result.status,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function requireSuccess(label, result) {
|
|
43
|
+
if (result.ok) return;
|
|
44
|
+
throw new Error(`${label} failed with ${result.status ?? "spawn error"}:\n${result.output}`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const repoRoot = resolve(process.cwd());
|
|
48
|
+
const home = mkdtempSync(join(tmpdir(), "pi-subagents-install-smoke-"));
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
const env = isolatedEnv(home);
|
|
52
|
+
requireSuccess("pi install pi-subagents", runPi(["install", repoRoot, "--approve"], env));
|
|
53
|
+
|
|
54
|
+
const list = runPi(["list", "--approve"], env);
|
|
55
|
+
requireSuccess("pi list", list);
|
|
56
|
+
if (!list.output.includes(repoRoot)) {
|
|
57
|
+
throw new Error(`pi list did not include installed local path ${repoRoot}:\n${list.output}`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
console.log("[local-install-smoke] installed and listed the single pi-subagents package with bundled intercom");
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.error(`[local-install-smoke] ${error instanceof Error ? error.message : String(error)}`);
|
|
63
|
+
process.exitCode = 1;
|
|
64
|
+
} finally {
|
|
65
|
+
rmSync(home, { recursive: true, force: true });
|
|
66
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// Full runtime-only package: native SDK + bundled CLI, one private Intercom broker, no model calls.
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
4
|
+
import { once } from "node:events";
|
|
5
|
+
import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
6
|
+
import { tmpdir } from "node:os";
|
|
7
|
+
import { join, resolve } from "node:path";
|
|
8
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
9
|
+
import { pathToFileURL } from "node:url";
|
|
10
|
+
import { hostCli, hostIndex, hostRoot } from "./compat-host.mjs";
|
|
11
|
+
|
|
12
|
+
const packageRoot = resolve(process.argv[2] ?? ".");
|
|
13
|
+
const root = mkdtempSync(join(tmpdir(), "ps-native-"));
|
|
14
|
+
const agentDir = join(root, "agent");
|
|
15
|
+
mkdirSync(agentDir);
|
|
16
|
+
Object.assign(process.env, { HOME: root, USERPROFILE: root, PI_CODING_AGENT_DIR: agentDir, PI_SUBAGENT_TEMP_ROOT: join(root, "pi-subagents-runs"), PI_OFFLINE: "1", PI_TELEMETRY: "0", PI_PACKAGE_DIR: hostRoot });
|
|
17
|
+
// A pruned extension cannot discover the embedding SDK via its own node_modules or PATH.
|
|
18
|
+
// PI_PACKAGE_DIR is the existing detached-runtime host contract, not a loader workaround.
|
|
19
|
+
const sdk = await import(pathToFileURL(hostIndex).href);
|
|
20
|
+
const broker = spawn(process.execPath, [join(packageRoot, "dist/pi-intercom/broker/broker.js")], { cwd: root, env: process.env, stdio: ["ignore", "pipe", "pipe"] });
|
|
21
|
+
let brokerLog = "";
|
|
22
|
+
broker.stdout.on("data", data => { brokerLog += data; });
|
|
23
|
+
broker.stderr.on("data", data => { brokerLog += data; });
|
|
24
|
+
let session;
|
|
25
|
+
try {
|
|
26
|
+
for (let i = 0; !brokerLog.includes("Intercom broker started"); i++) {
|
|
27
|
+
assert.ok(i < 100 && broker.exitCode === null, brokerLog || "Private broker did not start");
|
|
28
|
+
await delay(50);
|
|
29
|
+
}
|
|
30
|
+
const settingsManager = sdk.SettingsManager.inMemory({ packages: [packageRoot], compaction: { enabled: false }, retry: { enabled: false } });
|
|
31
|
+
const loader = new sdk.DefaultResourceLoader({ cwd: root, agentDir, settingsManager, noSkills: true, noContextFiles: true, noPromptTemplates: true, noThemes: true });
|
|
32
|
+
await loader.reload();
|
|
33
|
+
assert.deepEqual(loader.getExtensions().errors, []);
|
|
34
|
+
assert.deepEqual(loader.getExtensions().warnings ?? [], []);
|
|
35
|
+
assert.equal(loader.getExtensions().extensions.length, 2);
|
|
36
|
+
const modelRuntime = await sdk.ModelRuntime.create({ authPath: join(agentDir, "auth.json"), modelsPath: null, modelsStorePath: join(agentDir, "models-store.json"), allowModelNetwork: false });
|
|
37
|
+
({ session } = await sdk.createAgentSession({ cwd: root, agentDir, settingsManager, resourceLoader: loader, modelRuntime, sessionManager: sdk.SessionManager.create(root, join(root, "sessions")), noTools: "builtin" }));
|
|
38
|
+
const errors = [];
|
|
39
|
+
await session.bindExtensions({ mode: "print", onError: error => errors.push(error) });
|
|
40
|
+
for (const name of ["delegate", "load_subagent", "load_intercom"]) assert.ok(session.getActiveToolNames().includes(name), name);
|
|
41
|
+
for (const name of ["agent_runs", "subagent", "intercom"]) assert.ok(!session.getActiveToolNames().includes(name), `${name} starts lazy`);
|
|
42
|
+
await session.agent.state.tools.find(tool => tool.name === "load_subagent").execute("controls", { advanced: false }, new AbortController().signal);
|
|
43
|
+
assert.ok(session.getActiveToolNames().includes("agent_runs"));
|
|
44
|
+
assert.ok(!session.getActiveToolNames().includes("subagent"));
|
|
45
|
+
await session.agent.state.tools.find(tool => tool.name === "load_intercom").execute("peers", {}, new AbortController().signal);
|
|
46
|
+
const status = await session.agent.state.tools.find(tool => tool.name === "intercom").execute("status", { action: "status" }, new AbortController().signal);
|
|
47
|
+
assert.match(JSON.stringify(status.content), /Connected: Yes/);
|
|
48
|
+
const runs = await session.agent.state.tools.find(tool => tool.name === "agent_runs").execute("history-list", { action: "list" }, new AbortController().signal);
|
|
49
|
+
assert.notEqual(runs.isError, true, JSON.stringify(runs.content));
|
|
50
|
+
assert.equal(runs.details.runList.total, 0);
|
|
51
|
+
assert.deepEqual(errors, []);
|
|
52
|
+
const marker = join(root, "cli.json");
|
|
53
|
+
const observer = join(root, "observer.ts");
|
|
54
|
+
writeFileSync(observer, `import { writeFileSync } from "node:fs";
|
|
55
|
+
import { SubagentHistoryIndex } from ${JSON.stringify(join(packageRoot, "dist/history/index.js"))};
|
|
56
|
+
export default function(pi) { pi.on("session_start", async (_event, ctx) => {
|
|
57
|
+
const index = new SubagentHistoryIndex(${JSON.stringify(agentDir)});
|
|
58
|
+
try {
|
|
59
|
+
await index.setOwner({ ownerSessionId: ctx.sessionManager.getSessionId(), runs: [] });
|
|
60
|
+
const history = await index.listRuns();
|
|
61
|
+
writeFileSync(${JSON.stringify(marker)}, JSON.stringify({ tools: pi.getActiveTools(), commands: pi.getCommands().map(c => c.name), historyRuns: history.total }));
|
|
62
|
+
} finally { await index.close(); ctx.shutdown(); }
|
|
63
|
+
}); }`);
|
|
64
|
+
const cliEnv = { ...process.env, PATH: "/usr/bin:/bin" };
|
|
65
|
+
delete cliEnv.PI_PACKAGE_DIR; // Ordinary CLI consumers locate their host through argv, unlike detached SDK embeddings.
|
|
66
|
+
const child = spawnSync(process.execPath, [hostCli, "--mode", "rpc", "--no-session", "-ne", "-ns", "-np", "-nc", "--no-themes", "--approve", "-e", packageRoot, "-e", observer], { cwd: root, env: cliEnv, input: "", encoding: "utf8", timeout: 30_000 });
|
|
67
|
+
assert.equal(child.status, 0, `${child.error ?? ""}\n${child.stderr}`);
|
|
68
|
+
assert.doesNotMatch(child.stderr, /Failed to load extension|ERR_INTERNAL_ASSERTION|Extension error/);
|
|
69
|
+
const observed = JSON.parse(readFileSync(marker, "utf8"));
|
|
70
|
+
assert.equal(observed.historyRuns, 0, "CLI workers must inherit the selected host without Pi on PATH");
|
|
71
|
+
for (const name of ["delegate", "load_subagent", "load_intercom"]) assert.ok(observed.tools.includes(name), name);
|
|
72
|
+
for (const name of ["agent_runs", "subagent", "intercom"]) assert.ok(!observed.tools.includes(name), `${name} starts lazy in CLI`);
|
|
73
|
+
assert.ok(observed.commands.includes("subagents-doctor"));
|
|
74
|
+
console.log("[native-package-smoke] both compiled entries, history worker, broker registration/status, bundled RPC startup, and shutdown passed");
|
|
75
|
+
} finally {
|
|
76
|
+
try {
|
|
77
|
+
if (session) { await session.extensionRunner.emit({ type: "session_shutdown", reason: "quit" }); session.dispose(); }
|
|
78
|
+
} finally {
|
|
79
|
+
if (broker.exitCode === null) { const exited = once(broker, "exit"); broker.kill("SIGTERM"); await exited; }
|
|
80
|
+
rmSync(root, { recursive: true, force: true });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import { cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import { dirname, join } from "node:path";
|
|
7
|
+
import { pathToFileURL } from "node:url";
|
|
8
|
+
import process from "node:process";
|
|
9
|
+
import { hostCli, hostRoot } from "./compat-host.mjs";
|
|
10
|
+
|
|
11
|
+
const require = createRequire(import.meta.url);
|
|
12
|
+
const packageJson = require("../package.json");
|
|
13
|
+
process.env.PI_PACKAGE_DIR = hostRoot;
|
|
14
|
+
let productionRoot;
|
|
15
|
+
|
|
16
|
+
if (process.argv.includes("--help") || process.argv.includes("-h")) {
|
|
17
|
+
console.log(`Usage: node scripts/package-smoke.mjs\n\nVerifies the local pi-subagents package shape without publishing.\n\nChecks:\n - npm pack includes subagent and intercom runtime resources\n - package.json pi manifest points at both extensions and skills without registering example prompts\n - both compiled dist extension entrypoints load as native ES modules\n - a packed production install with dev dependencies omitted can load the detached runner and native broker\n\nExit codes:\n 0 smoke passed\n 1 package shape or runtime load check failed`);
|
|
18
|
+
process.exit(0);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function fail(message) {
|
|
22
|
+
console.error(`[package-smoke] ${message}`);
|
|
23
|
+
if (productionRoot) console.error(`[package-smoke] failure evidence: ${productionRoot}`);
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function run(command, args, cwd = process.cwd(), env = process.env) {
|
|
28
|
+
const result = spawnSync(command, args, {
|
|
29
|
+
cwd,
|
|
30
|
+
env,
|
|
31
|
+
encoding: "utf-8",
|
|
32
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
33
|
+
});
|
|
34
|
+
if (result.error) throw new Error(`failed to start ${command}: ${result.error.message}`);
|
|
35
|
+
if (result.status !== 0) {
|
|
36
|
+
process.stderr.write(result.stderr);
|
|
37
|
+
process.stdout.write(result.stdout);
|
|
38
|
+
throw new Error(`${command} ${args.join(" ")} exited with ${result.status}`);
|
|
39
|
+
}
|
|
40
|
+
return result.stdout;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function runOrFail(command, args, cwd = process.cwd()) {
|
|
44
|
+
try {
|
|
45
|
+
return run(command, args, cwd);
|
|
46
|
+
} catch (error) {
|
|
47
|
+
fail(error instanceof Error ? error.message : String(error));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function assertPackedFile(files, path) {
|
|
52
|
+
if (!files.some((file) => file.path === path)) fail(`npm pack output is missing ${path}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function assertNotPackedFile(files, path) {
|
|
56
|
+
if (files.some((file) => file.path === path)) fail(`npm pack output should not include ${path}`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
productionRoot = mkdtempSync(join(tmpdir(), "pi-subagents-package-smoke-"));
|
|
60
|
+
const packDir = join(productionRoot, "pack");
|
|
61
|
+
mkdirSync(packDir);
|
|
62
|
+
const packOutput = runOrFail("npm", ["pack", "--json", "--pack-destination", packDir]);
|
|
63
|
+
let packs;
|
|
64
|
+
try {
|
|
65
|
+
packs = JSON.parse(packOutput);
|
|
66
|
+
} catch (error) {
|
|
67
|
+
fail(`npm pack --json returned invalid JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
68
|
+
}
|
|
69
|
+
// npm 11 returns an array; npm 12 keys the same records by package name.
|
|
70
|
+
const pack = Object.values(packs)[0];
|
|
71
|
+
if (!pack || !Array.isArray(pack.files)) fail("npm pack --json did not report a file list");
|
|
72
|
+
|
|
73
|
+
for (const path of [
|
|
74
|
+
"package.json",
|
|
75
|
+
"LICENSE",
|
|
76
|
+
"README.md",
|
|
77
|
+
"dist/extension/index.js",
|
|
78
|
+
"dist/pi-intercom/index.js",
|
|
79
|
+
"dist/pi-intercom/broker/broker.js",
|
|
80
|
+
"dist/runs/background/subagent-runner-launcher.js",
|
|
81
|
+
"src/extension/index.ts",
|
|
82
|
+
"src/extension/schemas.ts",
|
|
83
|
+
"src/pi-intercom/index.ts",
|
|
84
|
+
"src/pi-intercom/broker/broker.ts",
|
|
85
|
+
"src/pi-intercom/ui/session-list.ts",
|
|
86
|
+
"src/shared/types.ts",
|
|
87
|
+
"src/runs/background/subagent-runner-launcher.ts",
|
|
88
|
+
"agents/reviewer.md",
|
|
89
|
+
"agents/reviewer-gpt.md",
|
|
90
|
+
"agents/watcher.md",
|
|
91
|
+
"skills/pi-subagents/SKILL.md",
|
|
92
|
+
"skills/pi-intercom/SKILL.md",
|
|
93
|
+
"docs/intercom.md",
|
|
94
|
+
"prompts/review-loop.md",
|
|
95
|
+
"scripts/real-pi-smoke.mjs",
|
|
96
|
+
]) {
|
|
97
|
+
assertPackedFile(pack.files, path);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
assertNotPackedFile(pack.files, "install.mjs");
|
|
101
|
+
|
|
102
|
+
if (packageJson.name !== "@fitchmultz/pi-subagents" || packageJson.private === true || packageJson.publishConfig?.access !== "public") fail("package.json must expose the owned public scoped npm identity");
|
|
103
|
+
if (packageJson.bin !== undefined) fail("package.json must not expose a legacy npx/bin installer");
|
|
104
|
+
if (!packageJson.pi?.extensions?.includes("./dist/extension/index.js")) fail("package.json pi.extensions must include ./dist/extension/index.js");
|
|
105
|
+
if (!packageJson.pi?.extensions?.includes("./dist/pi-intercom/index.js")) fail("package.json pi.extensions must include ./dist/pi-intercom/index.js");
|
|
106
|
+
if (!packageJson.pi?.skills?.includes("./skills")) fail("package.json pi.skills must include ./skills");
|
|
107
|
+
if (packageJson.pi?.prompts !== undefined) fail("package.json pi.prompts must stay unset so example prompts are not registered as slash commands");
|
|
108
|
+
|
|
109
|
+
for (const entrypoint of ["../dist/extension/index.js", "../dist/pi-intercom/index.js"]) {
|
|
110
|
+
const extensionModule = await import(new URL(entrypoint, import.meta.url));
|
|
111
|
+
if (typeof extensionModule.default !== "function") fail(`${entrypoint} did not load a default registration function`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
let productionImportError;
|
|
115
|
+
try {
|
|
116
|
+
const installDir = join(productionRoot, "install");
|
|
117
|
+
mkdirSync(installDir);
|
|
118
|
+
writeFileSync(join(installDir, "package.json"), JSON.stringify({ private: true, type: "module" }));
|
|
119
|
+
const filename = pack.filename;
|
|
120
|
+
if (typeof filename !== "string") throw new Error("npm pack did not report a tarball filename");
|
|
121
|
+
run("npm", ["install", "--ignore-scripts", "--omit=dev", "--legacy-peer-deps", join(packDir, filename)], installDir);
|
|
122
|
+
const installedRoot = join(installDir, "node_modules", packageJson.name);
|
|
123
|
+
if (!existsSync(installedRoot)) throw new Error(`production install is missing ${packageJson.name}`);
|
|
124
|
+
const gitPackageRoot = join(productionRoot, "git-package");
|
|
125
|
+
cpSync(installedRoot, gitPackageRoot, { recursive: true });
|
|
126
|
+
run("npm", ["install", "--ignore-scripts", "--omit=dev", "--legacy-peer-deps"], gitPackageRoot);
|
|
127
|
+
for (const name of ["typebox", "@earendil-works/pi-agent-core", "@earendil-works/pi-ai", "@earendil-works/pi-coding-agent", "@earendil-works/pi-tui"]) {
|
|
128
|
+
if (existsSync(join(gitPackageRoot, "node_modules", name))) throw new Error(`runtime-only package shadows host-provided ${name}`);
|
|
129
|
+
}
|
|
130
|
+
const hostTypebox = await import(pathToFileURL(createRequire(join(hostRoot, "dist/index.js")).resolve("typebox")).href);
|
|
131
|
+
const nativeTypebox = await import(pathToFileURL(join(gitPackageRoot, "dist/shared/native-typebox.js")).href);
|
|
132
|
+
if (nativeTypebox.Type !== hostTypebox.Type) throw new Error("packed runtime did not use the selected host's TypeBox");
|
|
133
|
+
await import(pathToFileURL(join(gitPackageRoot, "dist", "runs", "shared", "acceptance-contract.js")).href);
|
|
134
|
+
await import(pathToFileURL(join(gitPackageRoot, "dist", "runs", "shared", "supervisor-questions.js")).href);
|
|
135
|
+
const brokerSpawn = await import(pathToFileURL(join(gitPackageRoot, "dist", "pi-intercom", "broker", "spawn.js")).href);
|
|
136
|
+
const brokerCwd = brokerSpawn.getBrokerSpawnOptions().cwd;
|
|
137
|
+
if (realpathSync(brokerCwd) !== realpathSync(gitPackageRoot)) throw new Error(`packed broker resolved ${brokerCwd} instead of ${gitPackageRoot}`);
|
|
138
|
+
run(process.execPath, ["--check", join(gitPackageRoot, "dist", "pi-intercom", "broker", "broker.js")], gitPackageRoot);
|
|
139
|
+
const distPiArgs = await import(pathToFileURL(join(gitPackageRoot, "dist", "runs", "shared", "pi-args.js")).href);
|
|
140
|
+
const childArgs = distPiArgs.buildPiArgs({
|
|
141
|
+
baseArgs: [],
|
|
142
|
+
task: "package-smoke",
|
|
143
|
+
sessionEnabled: false,
|
|
144
|
+
inheritProjectContext: false,
|
|
145
|
+
inheritSkills: false,
|
|
146
|
+
allowSubagents: true,
|
|
147
|
+
}).args ?? [];
|
|
148
|
+
const childExtensionPaths = childArgs.flatMap((arg, index) => (arg === "--extension" ? [childArgs[index + 1]] : []));
|
|
149
|
+
if (childExtensionPaths.length === 0) throw new Error("dist buildPiArgs emitted no --extension paths");
|
|
150
|
+
for (const extensionPath of childExtensionPaths) {
|
|
151
|
+
if (!existsSync(extensionPath)) throw new Error(`dist buildPiArgs emitted a missing --extension path: ${extensionPath}`);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const home = join(productionRoot, "native-home");
|
|
155
|
+
const asyncDir = join(home, "run");
|
|
156
|
+
mkdirSync(asyncDir, { recursive: true });
|
|
157
|
+
const sessionFile = join(home, "child.jsonl");
|
|
158
|
+
writeFileSync(sessionFile, `${JSON.stringify({ type: "session", version: 3, id: "packed-native-probe", cwd: home, timestamp: new Date().toISOString() })}\n`);
|
|
159
|
+
const marker = join(home, "native-started.json");
|
|
160
|
+
const observer = join(home, "observe.ts");
|
|
161
|
+
writeFileSync(observer, `import { writeFileSync, writeSync } from "node:fs";
|
|
162
|
+
import { fauxAssistantMessage, fauxProvider } from "@earendil-works/pi-ai";
|
|
163
|
+
export default function (pi) {
|
|
164
|
+
const faux = fauxProvider();
|
|
165
|
+
pi.registerProvider("faux", { api: faux.api, baseUrl: faux.getModel().baseUrl, apiKey: "fixture-key", models: faux.models, streamSimple: faux.provider.streamSimple });
|
|
166
|
+
pi.on("session_start", (_event, ctx) => {
|
|
167
|
+
pi.appendEntry("package-probe", { nativeSession: true });
|
|
168
|
+
writeFileSync(${JSON.stringify(marker)}, JSON.stringify({ sessionFile: ctx.sessionManager.getSessionFile(), model: { provider: ctx.model?.provider, id: ctx.model?.id }, modelCalls: faux.state.callCount }));
|
|
169
|
+
const message = fauxAssistantMessage("PACKED_NATIVE_COMPLETE");
|
|
170
|
+
// Pi redirects extension stdout to stderr; this fixture emits controlled wire records.
|
|
171
|
+
writeSync(1, JSON.stringify({ type: "message_end", message }) + "\\n" + JSON.stringify({ type: "agent_settled" }) + "\\n");
|
|
172
|
+
process.exit(0);
|
|
173
|
+
});
|
|
174
|
+
}`);
|
|
175
|
+
const nativeEnv = { ...process.env, HOME: home, PI_CODING_AGENT_DIR: join(home, "agent"), PI_OFFLINE: "1" };
|
|
176
|
+
for (const key of Object.keys(nativeEnv)) if (key.startsWith("PI_SUBAGENT_") || /(?:API_KEY|AUTH_TOKEN|ACCESS_TOKEN)$/.test(key)) delete nativeEnv[key];
|
|
177
|
+
nativeEnv.PI_SUBAGENT_TEMP_ROOT = join(home, "pi-subagents-runtime");
|
|
178
|
+
const piPackageRoot = hostRoot;
|
|
179
|
+
const bin = join(home, "bin");
|
|
180
|
+
mkdirSync(bin);
|
|
181
|
+
writeFileSync(join(bin, "pi"), `#!/bin/sh\nexec "${process.execPath}" "${hostCli}" "$@"\n`, { mode: 0o755 });
|
|
182
|
+
// A wrapper-only PATH also covers managed installs whose shim is not a symlink.
|
|
183
|
+
nativeEnv.PATH = `${bin}:${dirname(process.execPath)}:/usr/bin:/bin`;
|
|
184
|
+
delete nativeEnv.PI_PACKAGE_DIR;
|
|
185
|
+
const configPath = join(home, "config.json");
|
|
186
|
+
const resultPath = join(home, "result.json");
|
|
187
|
+
writeFileSync(configPath, JSON.stringify({ id: "packed-native", cwd: home, asyncDir, resultPath, piPackageRoot, placeholder: "{previous}", resultMode: "single", sessionDir: home, steps: [{ agent: "probe", task: "Controlled package startup check; no model call", model: "faux/faux-1", sessionFile, extensions: [observer], inheritProjectContext: false, inheritSkills: false }] }));
|
|
188
|
+
run(process.execPath, [join(gitPackageRoot, "dist/runs/background/subagent-runner-launcher.js"), join(gitPackageRoot, "dist/runs/background/subagent-runner.js"), configPath], home, nativeEnv);
|
|
189
|
+
const result = JSON.parse(readFileSync(resultPath, "utf8"));
|
|
190
|
+
if (result.success !== true || result.results?.[0]?.output !== "PACKED_NATIVE_COMPLETE") throw new Error(`Packed detached runner did not complete its controlled native Pi child: ${JSON.stringify({ nativeStarted: existsSync(marker), children: result.results?.map(({ exitCode, error, output }) => ({ exitCode, error, output })) })}`);
|
|
191
|
+
const observed = JSON.parse(readFileSync(marker, "utf8"));
|
|
192
|
+
if (observed.sessionFile !== sessionFile) throw new Error("Packed child did not bind the requested native Pi session");
|
|
193
|
+
if (observed.model?.provider !== "faux" || observed.model?.id !== "faux-1" || observed.modelCalls !== 0) throw new Error("Packed child must select the native faux model without invoking it");
|
|
194
|
+
console.log("[package-smoke] packed detached Node runner completed a controlled native Pi startup (no model call)");
|
|
195
|
+
process.stdout.write(run(process.execPath, [join(process.cwd(), "scripts/native-package-smoke.mjs"), gitPackageRoot], home, { ...nativeEnv, PI_PACKAGE_DIR: hostRoot }));
|
|
196
|
+
} catch (error) {
|
|
197
|
+
productionImportError = error;
|
|
198
|
+
} finally {
|
|
199
|
+
if (!productionImportError) rmSync(productionRoot, { recursive: true, force: true });
|
|
200
|
+
}
|
|
201
|
+
if (productionImportError) {
|
|
202
|
+
fail(`packed production install could not load runtime paths: ${productionImportError instanceof Error ? productionImportError.message : String(productionImportError)}`);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
console.log(`[package-smoke] ${pack.name}@${pack.version}: ${pack.files.length} files packed; subagent, intercom, runner, and broker paths loaded`);
|