4 Commits

Author SHA1 Message Date
Advait Paliwal
ec4cbfb57e Update Pi runtime packages 2026-04-17 13:45:16 -07:00
Advait Paliwal
1cd1a147f2 Remove runtime hygiene extension bloat 2026-04-17 11:47:18 -07:00
Advait Paliwal
92914acff7 Add Pi event guards for workflow state 2026-04-17 11:13:57 -07:00
Advait Paliwal
f0bbb25910 Use Pi runtime hooks for research context hygiene 2026-04-17 10:38:42 -07:00
17 changed files with 656 additions and 615 deletions

View File

@@ -25,7 +25,7 @@ curl -fsSL https://feynman.is/install | bash
irm https://feynman.is/install.ps1 | iex
```
The one-line installer fetches the latest tagged release. To pin a version, pass it explicitly, for example `curl -fsSL https://feynman.is/install | bash -s -- 0.2.25`.
The one-line installer fetches the latest tagged release. To pin a version, pass it explicitly, for example `curl -fsSL https://feynman.is/install | bash -s -- 0.2.29`.
The installer downloads a standalone native bundle with its own Node.js runtime.

1105
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "@companion-ai/feynman",
"version": "0.2.25",
"version": "0.2.29",
"description": "Research-first CLI agent built on Pi and alphaXiv",
"license": "MIT",
"type": "module",
@@ -61,16 +61,16 @@
"dependencies": {
"@clack/prompts": "^1.2.0",
"@companion-ai/alpha-hub": "^0.1.3",
"@mariozechner/pi-ai": "^0.66.1",
"@mariozechner/pi-coding-agent": "^0.66.1",
"@sinclair/typebox": "^0.34.48",
"dotenv": "^17.3.1"
"@mariozechner/pi-ai": "^0.67.6",
"@mariozechner/pi-coding-agent": "^0.67.6",
"@sinclair/typebox": "^0.34.49",
"dotenv": "^17.4.2"
},
"overrides": {
"basic-ftp": "5.2.2",
"basic-ftp": "5.3.0",
"@modelcontextprotocol/sdk": {
"@hono/node-server": "1.19.13",
"hono": "4.12.12"
"@hono/node-server": "1.19.14",
"hono": "4.12.14"
},
"express": {
"router": {
@@ -80,16 +80,17 @@
"proxy-agent": {
"pac-proxy-agent": {
"get-uri": {
"basic-ftp": "5.2.2"
"basic-ftp": "5.3.0"
}
}
},
"protobufjs": "7.5.5",
"minimatch": {
"brace-expansion": "5.0.5"
}
},
"devDependencies": {
"@types/node": "^25.5.0",
"@types/node": "^25.6.0",
"tsx": "^4.21.0",
"typescript": "^5.9.3"
},

View File

@@ -6,6 +6,8 @@ topLevelCli: true
---
Run a deep research workflow for: $@
This is an execution request, not a request to explain or implement the workflow instructions. Carry out the workflow with tools and durable files. Do not answer by describing the protocol, converting it into programming steps, or saying how someone could implement it.
You are the Lead Researcher. You plan, delegate, evaluate, verify, write, and cite. Internal orchestration is invisible to the user unless they ask.
## 1. Plan

View File

@@ -110,7 +110,7 @@ This usually means the release exists, but not all platform bundles were uploade
Workarounds:
- try again after the release finishes publishing
- pass the latest published version explicitly, e.g.:
& ([scriptblock]::Create((irm https://feynman.is/install.ps1))) -Version 0.2.25
& ([scriptblock]::Create((irm https://feynman.is/install.ps1))) -Version 0.2.29
"@
}

View File

@@ -261,7 +261,7 @@ This usually means the release exists, but not all platform bundles were uploade
Workarounds:
- try again after the release finishes publishing
- pass the latest published version explicitly, e.g.:
curl -fsSL https://feynman.is/install | bash -s -- 0.2.25
curl -fsSL https://feynman.is/install | bash -s -- 0.2.29
EOF
exit 1
fi

View File

@@ -1,2 +1,3 @@
export const PI_SUBAGENTS_PATCH_TARGETS: string[];
export function patchPiSubagentsSource(relativePath: string, source: string): string;
export function stripPiSubagentBuiltinModelSource(source: string): string;

View File

@@ -1,4 +1,5 @@
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
import { createHash } from "node:crypto";
import { resolve } from "node:path";
import { spawnSync } from "node:child_process";
@@ -6,6 +7,8 @@ import { stripPiSubagentBuiltinModelSource } from "./lib/pi-subagents-patch.mjs"
const appRoot = resolve(import.meta.dirname, "..");
const settingsPath = resolve(appRoot, ".feynman", "settings.json");
const packageJsonPath = resolve(appRoot, "package.json");
const packageLockPath = resolve(appRoot, "package-lock.json");
const feynmanDir = resolve(appRoot, ".feynman");
const workspaceDir = resolve(appRoot, ".feynman", "npm");
const workspaceNodeModulesDir = resolve(workspaceDir, "node_modules");
@@ -13,16 +16,29 @@ const manifestPath = resolve(workspaceDir, ".runtime-manifest.json");
const workspacePackageJsonPath = resolve(workspaceDir, "package.json");
const workspaceArchivePath = resolve(feynmanDir, "runtime-workspace.tgz");
const PRUNE_VERSION = 4;
const PINNED_RUNTIME_PACKAGES = [
"@mariozechner/pi-agent-core",
"@mariozechner/pi-ai",
"@mariozechner/pi-coding-agent",
"@mariozechner/pi-tui",
];
function readPackageSpecs() {
const settings = JSON.parse(readFileSync(settingsPath, "utf8"));
if (!Array.isArray(settings.packages)) {
return [];
const packageSpecs = Array.isArray(settings.packages)
? settings.packages
.filter((value) => typeof value === "string" && value.startsWith("npm:"))
.map((value) => value.slice(4))
: [];
for (const packageName of PINNED_RUNTIME_PACKAGES) {
const version = readLockedPackageVersion(packageName);
if (version) {
packageSpecs.push(`${packageName}@${version}`);
}
}
return settings.packages
.filter((value) => typeof value === "string" && value.startsWith("npm:"))
.map((value) => value.slice(4));
return Array.from(new Set(packageSpecs));
}
function parsePackageName(spec) {
@@ -30,10 +46,41 @@ function parsePackageName(spec) {
return match?.[1] ?? spec;
}
function readLockedPackageVersion(packageName) {
if (!existsSync(packageLockPath)) {
return undefined;
}
try {
const lockfile = JSON.parse(readFileSync(packageLockPath, "utf8"));
const entry = lockfile.packages?.[`node_modules/${packageName}`];
return typeof entry?.version === "string" ? entry.version : undefined;
} catch {
return undefined;
}
}
function arraysMatch(left, right) {
return left.length === right.length && left.every((value, index) => value === right[index]);
}
function hashFile(path) {
if (!existsSync(path)) {
return null;
}
return createHash("sha256").update(readFileSync(path)).digest("hex");
}
function getRuntimeInputHash() {
const hash = createHash("sha256");
for (const path of [packageJsonPath, packageLockPath, settingsPath]) {
hash.update(path);
hash.update("\0");
hash.update(hashFile(path) ?? "missing");
hash.update("\0");
}
return hash.digest("hex");
}
function workspaceIsCurrent(packageSpecs) {
if (!existsSync(manifestPath) || !existsSync(workspaceNodeModulesDir)) {
return false;
@@ -44,6 +91,9 @@ function workspaceIsCurrent(packageSpecs) {
if (!Array.isArray(manifest.packageSpecs) || !arraysMatch(manifest.packageSpecs, packageSpecs)) {
return false;
}
if (manifest.runtimeInputHash !== getRuntimeInputHash()) {
return false;
}
if (
manifest.nodeAbi !== process.versions.modules ||
manifest.platform !== process.platform ||
@@ -97,8 +147,8 @@ function prepareWorkspace(packageSpecs) {
const result = spawnSync(
process.env.npm_execpath ? process.execPath : "npm",
process.env.npm_execpath
? [process.env.npm_execpath, "install", "--prefer-offline", "--no-audit", "--no-fund", "--no-dry-run", "--loglevel", "error", "--prefix", workspaceDir, ...packageSpecs]
: ["install", "--prefer-offline", "--no-audit", "--no-fund", "--no-dry-run", "--loglevel", "error", "--prefix", workspaceDir, ...packageSpecs],
? [process.env.npm_execpath, "install", "--prefer-offline", "--no-audit", "--no-fund", "--no-dry-run", "--legacy-peer-deps", "--loglevel", "error", "--prefix", workspaceDir, ...packageSpecs]
: ["install", "--prefer-offline", "--no-audit", "--no-fund", "--no-dry-run", "--legacy-peer-deps", "--loglevel", "error", "--prefix", workspaceDir, ...packageSpecs],
{ stdio: "inherit", env: childNpmInstallEnv() },
);
if (result.status !== 0) {
@@ -112,6 +162,7 @@ function writeManifest(packageSpecs) {
JSON.stringify(
{
packageSpecs,
runtimeInputHash: getRuntimeInputHash(),
generatedAt: new Date().toISOString(),
nodeAbi: process.versions.modules,
nodeVersion: process.version,

View File

@@ -558,6 +558,7 @@ export async function main(): Promise<void> {
normalizeFeynmanSettings(feynmanSettingsPath, bundledSettingsPath, thinkingLevel, feynmanAuthPath);
}
const workflowCommandNames = new Set(readPromptSpecs(appRoot).filter((s) => s.topLevelCli).map((s) => s.name));
await launchPiChat({
appRoot,
workingDir,
@@ -568,6 +569,6 @@ export async function main(): Promise<void> {
thinkingLevel,
explicitModelSpec,
oneShotPrompt: values.prompt,
initialPrompt: resolveInitialPrompt(command, rest, values.prompt, new Set(readPromptSpecs(appRoot).filter((s) => s.topLevelCli).map((s) => s.name))),
initialPrompt: resolveInitialPrompt(command, rest, values.prompt, workflowCommandNames),
});
}

View File

@@ -65,6 +65,8 @@ test("deepresearch workflow requires durable artifacts even when blocked", () =>
assert.match(systemPrompt, /Do not claim you are only a static model/i);
assert.match(systemPrompt, /write the requested durable artifact/i);
assert.match(deepResearchPrompt, /Do not stop after planning/i);
assert.match(deepResearchPrompt, /not a request to explain or implement/i);
assert.match(deepResearchPrompt, /Do not answer by describing the protocol/i);
assert.match(deepResearchPrompt, /degraded mode/i);
assert.match(deepResearchPrompt, /Verification: BLOCKED/i);
assert.match(deepResearchPrompt, /Never end with only an explanation in chat/i);

View File

@@ -243,6 +243,10 @@ test("updateConfiguredPackages batches multiple npm updates into a single instal
` console.log(resolve(${JSON.stringify(root)}, "npm-global", "lib", "node_modules"));`,
` process.exit(0);`,
`}`,
`if (args.length >= 4 && args[0] === "view" && args[2] === "version" && args[3] === "--json") {`,
` console.log(JSON.stringify("2.0.0"));`,
` process.exit(0);`,
`}`,
`appendFileSync(${JSON.stringify(logPath)}, JSON.stringify(args) + "\\n", "utf8");`,
"process.exit(0);",
].join("\n"));
@@ -258,7 +262,7 @@ test("updateConfiguredPackages batches multiple npm updates into a single instal
globalThis.fetch = (async () => ({
ok: true,
json: async () => ({ version: "2.0.0" }),
})) as typeof fetch;
})) as unknown as typeof fetch;
try {
const result = await updateConfiguredPackages(workingDir, agentDir);
@@ -290,6 +294,10 @@ test("updateConfiguredPackages skips native package updates on unsupported Node
` console.log(resolve(${JSON.stringify(root)}, "npm-global", "lib", "node_modules"));`,
` process.exit(0);`,
`}`,
`if (args.length >= 4 && args[0] === "view" && args[2] === "version" && args[3] === "--json") {`,
` console.log(JSON.stringify("2.0.0"));`,
` process.exit(0);`,
`}`,
`appendFileSync(${JSON.stringify(logPath)}, JSON.stringify(args) + "\\n", "utf8");`,
"process.exit(0);",
].join("\n"));
@@ -306,7 +314,7 @@ test("updateConfiguredPackages skips native package updates on unsupported Node
globalThis.fetch = (async () => ({
ok: true,
json: async () => ({ version: "2.0.0" }),
})) as typeof fetch;
})) as unknown as typeof fetch;
Object.defineProperty(process.versions, "node", { value: "25.0.0", configurable: true });
try {

View File

@@ -1544,9 +1544,9 @@
}
},
"node_modules/@hono/node-server": {
"version": "1.19.13",
"resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.13.tgz",
"integrity": "sha512-TsQLe4i2gvoTtrHje625ngThGBySOgSK3Xo2XRYOdqGN1teR8+I7vchQC46uLJi8OF62YTYA3AhSpumtkhsaKQ==",
"version": "1.19.14",
"resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz",
"integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==",
"license": "MIT",
"engines": {
"node": ">=18.14.1"
@@ -7998,9 +7998,9 @@
}
},
"node_modules/hono": {
"version": "4.12.12",
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.12.tgz",
"integrity": "sha512-p1JfQMKaceuCbpJKAPKVqyqviZdS0eUxH9v82oWo1kb9xjQ5wA6iP3FNVAPDFlz5/p7d45lO+BpSk1tuSZMF4Q==",
"version": "4.12.14",
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.14.tgz",
"integrity": "sha512-am5zfg3yu6sqn5yjKBNqhnTX7Cv+m00ox+7jbaKkrLMRJ4rAdldd1xPd/JzbBWspqaQv6RSTrgFN95EsfhC+7w==",
"license": "MIT",
"engines": {
"node": ">=16.9.0"

View File

@@ -36,8 +36,8 @@
},
"overrides": {
"@modelcontextprotocol/sdk": {
"@hono/node-server": "1.19.13",
"hono": "4.12.12"
"@hono/node-server": "1.19.14",
"hono": "4.12.14"
},
"router": {
"path-to-regexp": "8.4.2"

View File

@@ -261,7 +261,7 @@ This usually means the release exists, but not all platform bundles were uploade
Workarounds:
- try again after the release finishes publishing
- pass the latest published version explicitly, e.g.:
curl -fsSL https://feynman.is/install | bash -s -- 0.2.25
curl -fsSL https://feynman.is/install | bash -s -- 0.2.29
EOF
exit 1
fi

View File

@@ -110,7 +110,7 @@ This usually means the release exists, but not all platform bundles were uploade
Workarounds:
- try again after the release finishes publishing
- pass the latest published version explicitly, e.g.:
& ([scriptblock]::Create((irm https://feynman.is/install.ps1))) -Version 0.2.25
& ([scriptblock]::Create((irm https://feynman.is/install.ps1))) -Version 0.2.29
"@
}

View File

@@ -117,13 +117,13 @@ These installers download the bundled `skills/` and `prompts/` trees plus the re
The one-line installer already targets the latest tagged release. To pin an exact version, pass it explicitly:
```bash
curl -fsSL https://feynman.is/install | bash -s -- 0.2.25
curl -fsSL https://feynman.is/install | bash -s -- 0.2.29
```
On Windows:
```powershell
& ([scriptblock]::Create((irm https://feynman.is/install.ps1))) -Version 0.2.25
& ([scriptblock]::Create((irm https://feynman.is/install.ps1))) -Version 0.2.29
```
## Post-install setup

View File

@@ -22,7 +22,9 @@ These are installed by default with every Feynman installation. They provide the
| `pi-mermaid` | Render Mermaid diagrams in the terminal UI |
| `@aliou/pi-processes` | Manage long-running experiments, background tasks, and log tailing |
| `pi-zotero` | Integration with Zotero for citation library management |
| `@kaiserlich-dev/pi-session-search` | Indexed session recall with summarize and resume UI. Powers session lookup |
| `pi-schedule-prompt` | Schedule recurring and deferred research jobs. Powers the `/watch` workflow |
| `@samfp/pi-memory` | Pi-managed preference and correction memory across sessions |
| `@tmustier/pi-ralph-wiggum` | Long-running agent loops for iterative development. Powers `/autoresearch` |
These packages are updated together when you run `feynman update`. You do not need to install them individually.
@@ -34,8 +36,6 @@ Install on demand with `feynman packages install <preset>`. These extend Feynman
| Package | Preset | Purpose |
| --- | --- | --- |
| `pi-generative-ui` | `generative-ui` | Interactive HTML-style widgets for rich output |
| `@kaiserlich-dev/pi-session-search` | `session-search` | Indexed session recall with summarize and resume UI. Powers `/search` |
| `@samfp/pi-memory` | `memory` | Automatic preference and correction memory across sessions |
## Installing and managing packages
@@ -48,17 +48,9 @@ feynman packages list
Install a specific optional preset:
```bash
feynman packages install session-search
feynman packages install memory
feynman packages install generative-ui
```
Install all optional packages at once:
```bash
feynman packages install all-extras
```
## Updating packages
Update all installed packages to their latest versions: