@companion-ai/feynman 0.5.21 → 0.5.23
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/.feynman/SYSTEM.md +1 -0
- package/RELEASES.md +11 -0
- package/dist/pi/launch.js +11 -8
- package/dist/setup/doctor.js +1 -1
- package/extensions/research-tools/launch-notice.ts +12 -0
- package/extensions/research-tools/science-database-arxiv.ts +26 -12
- package/extensions/research-tools/science-database-pubmed.ts +25 -16
- package/extensions/research-tools.ts +2 -0
- package/package.json +1 -1
- package/prompts/deepresearch.md +1 -1
- package/prompts/summarize.md +1 -1
package/.feynman/SYSTEM.md
CHANGED
|
@@ -17,6 +17,7 @@ Tools:
|
|
|
17
17
|
|
|
18
18
|
Delegation:
|
|
19
19
|
- Use the `researcher`, `writer`, `verifier`, and `reviewer` subagents when decomposition reduces context pressure or parallelizes evidence gathering. Keep delegation internal; do not make the user manage it.
|
|
20
|
+
- To run several subagents in one call, write the workflow script to `outputs/.plans/<slug>-workflow.js`, and only after that write has succeeded, in a later tool call, call `subagent` with `workflow` set to that path. Do not use `workflow: true`: it needs a fenced block in the visible reply and fails when the block is only in your reasoning.
|
|
20
21
|
- A workflow is finished only when its final artifact is on disk. Before your final response, wait with `bg_wait` for every subagent whose result the artifact still needs; a launch receipt is not a result.
|
|
21
22
|
- For long workflows, write the plan to disk early and keep its task ledger and verification log current. If `CHANGELOG.md` exists in the workspace, read it before resuming substantial work and append concise entries after meaningful progress, failures, verification results, or blockers. Do not create it for one-shot tasks.
|
|
22
23
|
- Prefer the smallest investigation or experiment that can reduce uncertainty. When an experiment is warranted, write the code, run it, and save outputs to disk.
|
package/RELEASES.md
CHANGED
|
@@ -6,6 +6,17 @@ GitHub release notes are generated from the matching `## vX.Y.Z` section in this
|
|
|
6
6
|
|
|
7
7
|
## Unreleased
|
|
8
8
|
|
|
9
|
+
## v0.5.23 - 2026-10-02
|
|
10
|
+
|
|
11
|
+
- **arXiv and PubMed rate limits.** arXiv asks for one request every three seconds, and parallel lookups got `429`; arXiv requests now run at that pace and a 429 is retried once. A PubMed `429`, which a subagent in another process or a shared IP can still trigger, is retried once after a pause.
|
|
12
|
+
- Running several subagents at once from any workflow no longer fails with "workflow: true requires exactly one ```js workflow fenced block": Feynman now always writes the workflow script to a file and runs it by path, as `/deepresearch` already did.
|
|
13
|
+
- Pi 1.0 is not in this release: pi-subagents 0.74.0 cannot start background subagents on it. The fix is merged upstream (nicobailon/pi-subagents#2634) and Feynman will move to Pi 1.0 once it is released.
|
|
14
|
+
|
|
15
|
+
## v0.5.22 - 2026-10-01
|
|
16
|
+
|
|
17
|
+
- **Launch warnings and the first-run telemetry notice show inside the session.** Feynman printed them to the terminal just before starting Pi, where Pi 1.0's fullscreen interface (coming in a later Feynman release) hides them for the whole session. They now appear in Pi's own interface: the Windows "No Bash found" warning, the small-local-model warning for research workflows, and the one-time telemetry disclosure. A one-shot `--prompt` run still prints them to stderr.
|
|
18
|
+
- `feynman doctor` reports Pi's new `quietStartup: "header"` setting as "header only" instead of "disabled".
|
|
19
|
+
|
|
9
20
|
## v0.5.21 - 2026-10-01
|
|
10
21
|
|
|
11
22
|
- **LiteLLM and custom providers that read their key from an environment variable work again.** Since Pi 0.99, `models.json` resolves an environment variable only when written as `$NAME`; a bare name is sent as the key itself. Feynman's LiteLLM setup wrote `LITELLM_MASTER_KEY`, so requests went out as `Authorization: Bearer LITELLM_MASTER_KEY` and the proxy rejected them, and custom providers set up with a variable name broke the same way. Feynman now writes `$NAME`, and on start rewrites any `apiKey` in `models.json` that is a bare all-caps variable name, such as `LITELLM_MASTER_KEY`, to `$LITELLM_MASTER_KEY`. Literal keys, placeholders such as `local`, and `!command` values are left alone.
|
package/dist/pi/launch.js
CHANGED
|
@@ -41,15 +41,10 @@ export async function runPi(options, args, env, stdin = "inherit") {
|
|
|
41
41
|
});
|
|
42
42
|
}
|
|
43
43
|
export async function launchPiChat(options) {
|
|
44
|
+
let telemetryNotice;
|
|
44
45
|
if (process.stdout.isTTY && options.mode !== "rpc") {
|
|
45
46
|
process.stdout.write("\x1b[2J\x1b[3J\x1b[H");
|
|
46
|
-
|
|
47
|
-
const telemetryNotice = telemetryFirstRunNotice();
|
|
48
|
-
if (telemetryNotice)
|
|
49
|
-
process.stderr.write(`${telemetryNotice}\n`);
|
|
50
|
-
}
|
|
51
|
-
if (options.preLaunchNotice) {
|
|
52
|
-
process.stdout.write(`${options.preLaunchNotice}\n`);
|
|
47
|
+
telemetryNotice = telemetryFirstRunNotice();
|
|
53
48
|
}
|
|
54
49
|
const executables = await resolveAllExecutables();
|
|
55
50
|
ensureFeynmanCommandShim(options.appRoot, options.feynmanAgentDir);
|
|
@@ -59,5 +54,13 @@ export async function launchPiChat(options) {
|
|
|
59
54
|
// explicit prompt forever.
|
|
60
55
|
const explicitPrompt = Boolean(options.oneShotPrompt || options.initialPrompt);
|
|
61
56
|
const stdin = explicitPrompt && options.mode !== "rpc" && !process.stdin.isTTY ? "ignore" : "inherit";
|
|
62
|
-
|
|
57
|
+
// Pi's fullscreen UI hides anything printed before it starts, so the TUI
|
|
58
|
+
// session shows these itself; one-shot and JSON runs print them as before.
|
|
59
|
+
const notice = [telemetryNotice, options.preLaunchNotice].filter(Boolean).join("\n");
|
|
60
|
+
const tui = options.mode !== "rpc" && options.mode !== "json" && !options.oneShotPrompt;
|
|
61
|
+
if (notice && !tui)
|
|
62
|
+
process.stderr.write(`${notice}\n`);
|
|
63
|
+
const env = buildPiEnv(options, executables);
|
|
64
|
+
env.FEYNMAN_LAUNCH_NOTICE = notice && tui ? notice : undefined;
|
|
65
|
+
process.exitCode = await runPi(options, buildPiArgs(options), env, stdin);
|
|
63
66
|
}
|
package/dist/setup/doctor.js
CHANGED
|
@@ -171,7 +171,7 @@ export async function runDoctor(options) {
|
|
|
171
171
|
for (const line of formatPiWebAccessDoctorLines()) {
|
|
172
172
|
console.log(line);
|
|
173
173
|
}
|
|
174
|
-
console.log(`quiet startup: ${settings.quietStartup === true ? "enabled" : "disabled"}`);
|
|
174
|
+
console.log(`quiet startup: ${settings.quietStartup === "header" ? "header only" : settings.quietStartup === true ? "enabled" : "disabled"}`);
|
|
175
175
|
console.log(`theme: ${typeof settings.theme === "string" ? settings.theme : "not set"}`);
|
|
176
176
|
if (missingPiBits.length > 0) {
|
|
177
177
|
console.log("pi runtime: missing files");
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
|
|
3
|
+
export function registerLaunchNotice(pi: ExtensionAPI): void {
|
|
4
|
+
pi.on("session_start", (_event, ctx) => {
|
|
5
|
+
const notice = process.env.FEYNMAN_LAUNCH_NOTICE;
|
|
6
|
+
delete process.env.FEYNMAN_LAUNCH_NOTICE;
|
|
7
|
+
if (notice && ctx.mode === "tui" && ctx.hasUI) {
|
|
8
|
+
// Pi labels warnings itself.
|
|
9
|
+
ctx.ui.notify(notice.replace(/^Warning: /gm, ""), "warning");
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { XMLParser } from "fast-xml-parser";
|
|
2
2
|
|
|
3
|
+
import { createRequestPacer } from "./request-pacer.js";
|
|
4
|
+
|
|
3
5
|
type SearchParams = {
|
|
4
6
|
query: string;
|
|
5
7
|
};
|
|
@@ -27,20 +29,32 @@ function cleanQuery(query: string): string {
|
|
|
27
29
|
return clean;
|
|
28
30
|
}
|
|
29
31
|
|
|
32
|
+
// arXiv's API terms ask for no more than one request every three seconds, and
|
|
33
|
+
// parallel lookups got 429. Requests run one after another at that pace, and a
|
|
34
|
+
// 429 is retried once after the same wait.
|
|
35
|
+
const arxivPacer = createRequestPacer();
|
|
36
|
+
export const ARXIV_MIN_GAP_MS = 3000;
|
|
37
|
+
|
|
30
38
|
async function fetchText(url: URL, accept: string): Promise<string> {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
39
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
40
|
+
const result = await arxivPacer(ARXIV_MIN_GAP_MS, async () => {
|
|
41
|
+
const controller = new AbortController();
|
|
42
|
+
const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
43
|
+
try {
|
|
44
|
+
const response = await fetch(url, {
|
|
45
|
+
headers: { accept },
|
|
46
|
+
signal: controller.signal,
|
|
47
|
+
});
|
|
48
|
+
if (response.status === 429 && attempt === 0) return undefined;
|
|
49
|
+
if (!response.ok) {
|
|
50
|
+
throw new Error(`${url.hostname} request failed: ${response.status} ${response.statusText}`);
|
|
51
|
+
}
|
|
52
|
+
return await response.text();
|
|
53
|
+
} finally {
|
|
54
|
+
clearTimeout(timeout);
|
|
55
|
+
}
|
|
37
56
|
});
|
|
38
|
-
if (
|
|
39
|
-
throw new Error(`${url.hostname} request failed: ${response.status} ${response.statusText}`);
|
|
40
|
-
}
|
|
41
|
-
return response.text();
|
|
42
|
-
} finally {
|
|
43
|
-
clearTimeout(timeout);
|
|
57
|
+
if (result !== undefined) return result;
|
|
44
58
|
}
|
|
45
59
|
}
|
|
46
60
|
|
|
@@ -98,23 +98,32 @@ function prune<T extends Record<string, unknown>>(record: T): Record<string, unk
|
|
|
98
98
|
|
|
99
99
|
// The abort timer stays armed across the body read: fetch resolves on headers,
|
|
100
100
|
// so clearing it earlier would leave a stalled body hanging forever.
|
|
101
|
+
// Pacing keeps one process under NCBI's limit, but subagents run in their own
|
|
102
|
+
// processes and an IP can be shared, so a 429 is retried once after a pause.
|
|
103
|
+
const NCBI_RETRY_MS = 1500;
|
|
104
|
+
|
|
101
105
|
async function send<T>(url: URL, accept: string, read: (response: Response) => Promise<T>): Promise<T> {
|
|
102
|
-
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
106
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
107
|
+
const result = await withNcbiRateLimit(url, async (): Promise<{ retry: true } | { value: T }> => {
|
|
108
|
+
const controller = new AbortController();
|
|
109
|
+
const timeout = setTimeout(() => controller.abort(), requestTimeoutMs);
|
|
110
|
+
try {
|
|
111
|
+
const response = await fetch(url, {
|
|
112
|
+
headers: {
|
|
113
|
+
accept,
|
|
114
|
+
"user-agent": "feynman-pubmed-tools/1.0 (https://github.com/Companion-Inc/feynman)",
|
|
115
|
+
},
|
|
116
|
+
signal: controller.signal,
|
|
117
|
+
});
|
|
118
|
+
if (response.status === 429 && attempt === 0) return { retry: true };
|
|
119
|
+
return { value: await read(response) };
|
|
120
|
+
} finally {
|
|
121
|
+
clearTimeout(timeout);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
if ("value" in result) return result.value;
|
|
125
|
+
await new Promise((resolve) => setTimeout(resolve, NCBI_RETRY_MS));
|
|
126
|
+
}
|
|
118
127
|
}
|
|
119
128
|
|
|
120
129
|
function assertOk(response: Response): void {
|
|
@@ -7,6 +7,7 @@ import { registerDocxFallback } from "./research-tools/docx-fallback.js";
|
|
|
7
7
|
import { installFeynmanHeader } from "./research-tools/header.js";
|
|
8
8
|
import { registerHelpCommand } from "./research-tools/help.js";
|
|
9
9
|
import { registerHuggingFaceTools } from "./research-tools/huggingface.js";
|
|
10
|
+
import { registerLaunchNotice } from "./research-tools/launch-notice.js";
|
|
10
11
|
import { registerInitCommand, registerOutputsCommand } from "./research-tools/project.js";
|
|
11
12
|
import { registerServiceTierControls } from "./research-tools/service-tier.js";
|
|
12
13
|
import { registerScienceDatabaseTools } from "./research-tools/science-databases.js";
|
|
@@ -27,6 +28,7 @@ export default function researchTools(pi: ExtensionAPI): void {
|
|
|
27
28
|
registerDocxFallback(pi);
|
|
28
29
|
registerHelpCommand(pi);
|
|
29
30
|
registerInitCommand(pi);
|
|
31
|
+
registerLaunchNotice(pi);
|
|
30
32
|
registerOutputsCommand(pi);
|
|
31
33
|
registerServiceTierControls(pi);
|
|
32
34
|
registerScienceDatabaseTools(pi);
|
package/package.json
CHANGED
package/prompts/deepresearch.md
CHANGED
|
@@ -68,7 +68,7 @@ If subagents were chosen:
|
|
|
68
68
|
- Keep `subagent` tool-call JSON small and valid.
|
|
69
69
|
- Do not place multi-paragraph instructions inside the `subagent` JSON.
|
|
70
70
|
- Use only supported `subagent` keys. Do not add extra keys such as `artifacts` unless the tool schema explicitly exposes them.
|
|
71
|
-
- For parallel evidence gathering, write a workflow script that returns `await runs.all(...)` to `outputs/.plans/<slug>-workflow.js` with the write tool. After the write succeeds, call `subagent` with `workflow: "./outputs/.plans/<slug>-workflow.js"`, `async: true`, and `globalConcurrencyLimit: 4`. Each item needs a unique stable `key`, plus its agent, short task, and output path. Do not pass the script text inside the tool call.
|
|
71
|
+
- For parallel evidence gathering, write a workflow script that returns `await runs.all(...)` to `outputs/.plans/<slug>-workflow.js` with the write tool. After the write succeeds, in a later tool call (not in parallel with the write), call `subagent` with `workflow: "./outputs/.plans/<slug>-workflow.js"`, `async: true`, and `globalConcurrencyLimit: 4`. Each item needs a unique stable `key`, plus its agent, short task, and output path. Do not pass the script text inside the tool call.
|
|
72
72
|
- Read the ordered result array and record each child's `ok`, error, and returned output/artifact paths. Ordinary child failures are collected by `runs.all`; validation or infrastructure failure can still fail the workflow. Do not assume every output exists.
|
|
73
73
|
- Prefer broad guidance such as "use paper search and web search"; if a PDF parser or paper fetch fails, the researcher must continue from metadata, abstracts, and web sources and mark PDF parsing as blocked.
|
|
74
74
|
|
package/prompts/summarize.md
CHANGED
|
@@ -116,7 +116,7 @@ Briefly summarize: "Source is ~<chars> chars -> <N> chunks -> <N> researcher sub
|
|
|
116
116
|
|
|
117
117
|
### 3c. Dispatch researcher subagents
|
|
118
118
|
|
|
119
|
-
Use one async workflow with a stable key for each chunk and a concurrency limit of four: write the script to `outputs/.plans/<slug>-workflow.js` with the write tool, then, after the write succeeds, call `subagent` with `{ "workflow": "./outputs/.plans/<slug>-workflow.js", "async": true, "globalConcurrencyLimit": 4 }`. Expand the example item below for the actual chunk inventory; retain the single-source restriction and explicit output declaration for every child.
|
|
119
|
+
Use one async workflow with a stable key for each chunk and a concurrency limit of four: write the script to `outputs/.plans/<slug>-workflow.js` with the write tool, then, after the write succeeds, in a later tool call, call `subagent` with `{ "workflow": "./outputs/.plans/<slug>-workflow.js", "async": true, "globalConcurrencyLimit": 4 }`. Expand the example item below for the actual chunk inventory; retain the single-source restriction and explicit output declaration for every child.
|
|
120
120
|
|
|
121
121
|
```js
|
|
122
122
|
return await runs.all([
|