Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1b53e3b7f1 |
@@ -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.29`.
|
||||
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.30`.
|
||||
|
||||
The installer downloads a standalone native bundle with its own Node.js runtime.
|
||||
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@companion-ai/feynman",
|
||||
"version": "0.2.29",
|
||||
"version": "0.2.30",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@companion-ai/feynman",
|
||||
"version": "0.2.29",
|
||||
"version": "0.2.30",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@companion-ai/feynman",
|
||||
"version": "0.2.29",
|
||||
"version": "0.2.30",
|
||||
"description": "Research-first CLI agent built on Pi and alphaXiv",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
|
||||
@@ -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.29
|
||||
& ([scriptblock]::Create((irm https://feynman.is/install.ps1))) -Version 0.2.30
|
||||
"@
|
||||
}
|
||||
|
||||
|
||||
@@ -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.29
|
||||
curl -fsSL https://feynman.is/install | bash -s -- 0.2.30
|
||||
EOF
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -5,6 +5,8 @@ export const PI_SUBAGENTS_PATCH_TARGETS = [
|
||||
"run-history.ts",
|
||||
"skills.ts",
|
||||
"chain-clarify.ts",
|
||||
"subagent-executor.ts",
|
||||
"schemas.ts",
|
||||
];
|
||||
|
||||
const RESOLVE_PI_AGENT_DIR_HELPER = [
|
||||
@@ -94,6 +96,11 @@ export function patchPiSubagentsSource(relativePath, source) {
|
||||
'const configPath = path.join(os.homedir(), ".pi", "agent", "extensions", "subagent", "config.json");',
|
||||
'const configPath = path.join(resolvePiAgentDir(), "extensions", "subagent", "config.json");',
|
||||
);
|
||||
patched = replaceAll(
|
||||
patched,
|
||||
"• PARALLEL: { tasks: [{agent,task,count?}, ...], concurrency?: number, worktree?: true } - concurrent execution (worktree: isolate each task in a git worktree)",
|
||||
"• PARALLEL: { tasks: [{agent,task,count?,output?}, ...], concurrency?: number, worktree?: true } - concurrent execution (output: per-task file target, worktree: isolate each task in a git worktree)",
|
||||
);
|
||||
break;
|
||||
case "agents.ts":
|
||||
patched = replaceAll(
|
||||
@@ -190,6 +197,69 @@ export function patchPiSubagentsSource(relativePath, source) {
|
||||
'const dir = path.join(resolvePiAgentDir(), "agents");',
|
||||
);
|
||||
break;
|
||||
case "subagent-executor.ts":
|
||||
patched = replaceAll(
|
||||
patched,
|
||||
[
|
||||
"\tcwd?: string;",
|
||||
"\tcount?: number;",
|
||||
"\tmodel?: string;",
|
||||
"\tskill?: string | string[] | boolean;",
|
||||
].join("\n"),
|
||||
[
|
||||
"\tcwd?: string;",
|
||||
"\tcount?: number;",
|
||||
"\tmodel?: string;",
|
||||
"\tskill?: string | string[] | boolean;",
|
||||
"\toutput?: string | false;",
|
||||
].join("\n"),
|
||||
);
|
||||
patched = replaceAll(
|
||||
patched,
|
||||
[
|
||||
"\t\t\tcwd: task.cwd,",
|
||||
"\t\t\t...(modelOverrides[index] ? { model: modelOverrides[index] } : {}),",
|
||||
].join("\n"),
|
||||
[
|
||||
"\t\t\tcwd: task.cwd,",
|
||||
"\t\t\toutput: task.output,",
|
||||
"\t\t\t...(modelOverrides[index] ? { model: modelOverrides[index] } : {}),",
|
||||
].join("\n"),
|
||||
);
|
||||
patched = replaceAll(
|
||||
patched,
|
||||
[
|
||||
"\t\tcwd: task.cwd,",
|
||||
"\t\t...(modelOverrides[index] ? { model: modelOverrides[index] } : {}),",
|
||||
].join("\n"),
|
||||
[
|
||||
"\t\tcwd: task.cwd,",
|
||||
"\t\toutput: task.output,",
|
||||
"\t\t...(modelOverrides[index] ? { model: modelOverrides[index] } : {}),",
|
||||
].join("\n"),
|
||||
);
|
||||
break;
|
||||
case "schemas.ts":
|
||||
patched = replaceAll(
|
||||
patched,
|
||||
[
|
||||
"\tcwd: Type.Optional(Type.String()),",
|
||||
'\tcount: Type.Optional(Type.Integer({ minimum: 1, description: "Repeat this parallel task N times with the same settings." })),',
|
||||
'\tmodel: Type.Optional(Type.String({ description: "Override model for this task (e.g. \'google/gemini-3-pro\')" })),',
|
||||
].join("\n"),
|
||||
[
|
||||
"\tcwd: Type.Optional(Type.String()),",
|
||||
'\tcount: Type.Optional(Type.Integer({ minimum: 1, description: "Repeat this parallel task N times with the same settings." })),',
|
||||
'\toutput: Type.Optional(Type.Any({ description: "Output file for this parallel task (string), or false to disable. Relative paths resolve against cwd." })),',
|
||||
'\tmodel: Type.Optional(Type.String({ description: "Override model for this task (e.g. \'google/gemini-3-pro\')" })),',
|
||||
].join("\n"),
|
||||
);
|
||||
patched = replaceAll(
|
||||
patched,
|
||||
'tasks: Type.Optional(Type.Array(TaskItem, { description: "PARALLEL mode: [{agent, task, count?}, ...]" })),',
|
||||
'tasks: Type.Optional(Type.Array(TaskItem, { description: "PARALLEL mode: [{agent, task, count?, output?}, ...]" })),',
|
||||
);
|
||||
break;
|
||||
default:
|
||||
return source;
|
||||
}
|
||||
@@ -198,5 +268,5 @@ export function patchPiSubagentsSource(relativePath, source) {
|
||||
return source;
|
||||
}
|
||||
|
||||
return injectResolvePiAgentDirHelper(patched);
|
||||
return patched.includes("resolvePiAgentDir()") ? injectResolvePiAgentDirHelper(patched) : patched;
|
||||
}
|
||||
|
||||
@@ -141,6 +141,74 @@ test("patchPiSubagentsSource rewrites modern agents.ts discovery paths", () => {
|
||||
assert.ok(!patched.includes('fs.existsSync(userDirNew) ? userDirNew : userDirOld'));
|
||||
});
|
||||
|
||||
test("patchPiSubagentsSource preserves output on top-level parallel tasks", () => {
|
||||
const input = [
|
||||
"interface TaskParam {",
|
||||
"\tagent: string;",
|
||||
"\ttask: string;",
|
||||
"\tcwd?: string;",
|
||||
"\tcount?: number;",
|
||||
"\tmodel?: string;",
|
||||
"\tskill?: string | string[] | boolean;",
|
||||
"}",
|
||||
"function run(params: { tasks: TaskParam[] }) {",
|
||||
"\tconst modelOverrides = params.tasks.map(() => undefined);",
|
||||
"\tconst skillOverrides = params.tasks.map(() => undefined);",
|
||||
"\tconst parallelTasks = params.tasks.map((task, index) => ({",
|
||||
"\t\tagent: task.agent,",
|
||||
"\t\ttask: params.context === \"fork\" ? wrapForkTask(task.task) : task.task,",
|
||||
"\t\tcwd: task.cwd,",
|
||||
"\t\t...(modelOverrides[index] ? { model: modelOverrides[index] } : {}),",
|
||||
"\t\t...(skillOverrides[index] !== undefined ? { skill: skillOverrides[index] } : {}),",
|
||||
"\t}));",
|
||||
"}",
|
||||
].join("\n");
|
||||
|
||||
const patched = patchPiSubagentsSource("subagent-executor.ts", input);
|
||||
|
||||
assert.match(patched, /output\?: string \| false;/);
|
||||
assert.match(patched, /\n\t\toutput: task\.output,/);
|
||||
assert.doesNotMatch(patched, /resolvePiAgentDir/);
|
||||
});
|
||||
|
||||
test("patchPiSubagentsSource documents output in top-level task schema", () => {
|
||||
const input = [
|
||||
"export const TaskItem = Type.Object({ ",
|
||||
"\tagent: Type.String(), ",
|
||||
"\ttask: Type.String(), ",
|
||||
"\tcwd: Type.Optional(Type.String()),",
|
||||
"\tcount: Type.Optional(Type.Integer({ minimum: 1, description: \"Repeat this parallel task N times with the same settings.\" })),",
|
||||
"\tmodel: Type.Optional(Type.String({ description: \"Override model for this task (e.g. 'google/gemini-3-pro')\" })),",
|
||||
"\tskill: Type.Optional(SkillOverride),",
|
||||
"});",
|
||||
"export const SubagentParams = Type.Object({",
|
||||
"\ttasks: Type.Optional(Type.Array(TaskItem, { description: \"PARALLEL mode: [{agent, task, count?}, ...]\" })),",
|
||||
"});",
|
||||
].join("\n");
|
||||
|
||||
const patched = patchPiSubagentsSource("schemas.ts", input);
|
||||
|
||||
assert.match(patched, /output: Type\.Optional\(Type\.Any/);
|
||||
assert.match(patched, /count\?, output\?/);
|
||||
assert.doesNotMatch(patched, /resolvePiAgentDir/);
|
||||
});
|
||||
|
||||
test("patchPiSubagentsSource documents output in top-level parallel help", () => {
|
||||
const input = [
|
||||
'import * as os from "node:os";',
|
||||
'import * as path from "node:path";',
|
||||
"const help = `",
|
||||
"• PARALLEL: { tasks: [{agent,task,count?}, ...], concurrency?: number, worktree?: true } - concurrent execution (worktree: isolate each task in a git worktree)",
|
||||
"`;",
|
||||
].join("\n");
|
||||
|
||||
const patched = patchPiSubagentsSource("index.ts", input);
|
||||
|
||||
assert.match(patched, /output\?/);
|
||||
assert.match(patched, /per-task file target/);
|
||||
assert.doesNotMatch(patched, /function resolvePiAgentDir/);
|
||||
});
|
||||
|
||||
test("stripPiSubagentBuiltinModelSource removes built-in model pins", () => {
|
||||
const input = [
|
||||
"---",
|
||||
|
||||
@@ -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.29
|
||||
curl -fsSL https://feynman.is/install | bash -s -- 0.2.30
|
||||
EOF
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -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.29
|
||||
& ([scriptblock]::Create((irm https://feynman.is/install.ps1))) -Version 0.2.30
|
||||
"@
|
||||
}
|
||||
|
||||
|
||||
@@ -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.29
|
||||
curl -fsSL https://feynman.is/install | bash -s -- 0.2.30
|
||||
```
|
||||
|
||||
On Windows:
|
||||
|
||||
```powershell
|
||||
& ([scriptblock]::Create((irm https://feynman.is/install.ps1))) -Version 0.2.29
|
||||
& ([scriptblock]::Create((irm https://feynman.is/install.ps1))) -Version 0.2.30
|
||||
```
|
||||
|
||||
## Post-install setup
|
||||
|
||||
Reference in New Issue
Block a user