@companion-ai/feynman 0.5.10 → 0.5.12
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/RELEASES.md
CHANGED
|
@@ -6,6 +6,15 @@ GitHub release notes are generated from the matching `## vX.Y.Z` section in this
|
|
|
6
6
|
|
|
7
7
|
## Unreleased
|
|
8
8
|
|
|
9
|
+
## v0.5.12 - 2026-09-28
|
|
10
|
+
|
|
11
|
+
- Crossref pacing leaves a margin over the pool's limit and retries a rate-limited request once after the server's `Retry-After`. A stress test of three bursts of 10 parallel searches on the public pool now succeeds on all 30; 0.5.11 still lost about one in six to network jitter.
|
|
12
|
+
|
|
13
|
+
## v0.5.11 - 2026-09-27
|
|
14
|
+
|
|
15
|
+
- **Crossref lookups no longer fail in bursts.** Crossref's public pool allows one request at a time, one per second, and parallel DOI checks (the verifier cross-checks every citation) got `429 Too Many Requests` on most calls. Crossref requests now run one after another at the pool's pace. Set `CROSSREF_MAILTO` to your email for Crossref's faster polite pool; a 429 now says so instead of a generic "Science database request failed".
|
|
16
|
+
- Telemetry's "wrote output" flag no longer misses a report written in the first second of a run on filesystems that round file times (HFS+, FAT, ext3).
|
|
17
|
+
|
|
9
18
|
## v0.5.10 - 2026-09-27
|
|
10
19
|
|
|
11
20
|
- **Semantic Scholar understands the documented query syntax.** A `semantic:` prefix and `year_from=`/`year_to=` flags, which the model reuses from the OpenAlex examples, were sent to Semantic Scholar as search text and returned unrelated papers or nothing. They now become Semantic Scholar's own year filter.
|
|
@@ -137,6 +137,27 @@ function crossrefAuthors(value: unknown): string[] {
|
|
|
137
137
|
.slice(0, 8);
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
+
// Crossref's public pool allows one request at a time, one per second
|
|
141
|
+
// (x-concurrency-limit: 1); the polite pool, used when a mailto is sent, allows
|
|
142
|
+
// three per second. Parallel DOI checks got 429 on most calls, so run Crossref
|
|
143
|
+
// requests one after another at the pool's pace.
|
|
144
|
+
let crossrefQueue: Promise<unknown> = Promise.resolve();
|
|
145
|
+
let crossrefLastStartedAt = Number.NEGATIVE_INFINITY;
|
|
146
|
+
|
|
147
|
+
export function withCrossrefPacing<T>(polite: boolean, run: () => Promise<T>): Promise<T> {
|
|
148
|
+
// A little over the pool's limit: network jitter can land two requests paced
|
|
149
|
+
// exactly one second apart inside the same window.
|
|
150
|
+
const minGapMs = polite ? 400 : 1200;
|
|
151
|
+
const next = crossrefQueue.then(async () => {
|
|
152
|
+
const wait = crossrefLastStartedAt + minGapMs - Date.now();
|
|
153
|
+
if (wait > 0) await new Promise((resolve) => setTimeout(resolve, wait));
|
|
154
|
+
crossrefLastStartedAt = Date.now();
|
|
155
|
+
return run();
|
|
156
|
+
});
|
|
157
|
+
crossrefQueue = next.catch(() => {});
|
|
158
|
+
return next;
|
|
159
|
+
}
|
|
160
|
+
|
|
140
161
|
async function searchCrossref(params: ScienceDatabaseSearchParams): Promise<Record<string, unknown>> {
|
|
141
162
|
const query = cleanQuery(params.query);
|
|
142
163
|
const limit = safeLimit(params.limit);
|
|
@@ -148,7 +169,24 @@ async function searchCrossref(params: ScienceDatabaseSearchParams): Promise<Reco
|
|
|
148
169
|
select: "DOI,title,published-print,published-online,issued,container-title,author,is-referenced-by-count,URL,type",
|
|
149
170
|
...(mailto ? { mailto } : {}),
|
|
150
171
|
}).toString();
|
|
151
|
-
const
|
|
172
|
+
const request = () => withCrossrefPacing(Boolean(mailto), () => fetchJson(url));
|
|
173
|
+
let payload: Record<string, unknown>;
|
|
174
|
+
try {
|
|
175
|
+
try {
|
|
176
|
+
payload = recordValue(await request());
|
|
177
|
+
} catch (error) {
|
|
178
|
+
if (!(error instanceof ScienceDatabaseRequestError) || error.status !== 429) throw error;
|
|
179
|
+
await new Promise((resolve) => setTimeout(resolve, Math.min(error.retryAfterMs ?? 1500, 5000)));
|
|
180
|
+
payload = recordValue(await request());
|
|
181
|
+
}
|
|
182
|
+
} catch (error) {
|
|
183
|
+
if (!(error instanceof ScienceDatabaseRequestError) || error.status !== 429) throw error;
|
|
184
|
+
throw new Error(
|
|
185
|
+
mailto
|
|
186
|
+
? "Crossref rate-limited this request (HTTP 429). Wait a few seconds and retry, or look the DOI up with source openalex."
|
|
187
|
+
: "Crossref's public pool rate-limited this request (HTTP 429). Set CROSSREF_MAILTO to your email for Crossref's faster polite pool, wait and retry, or look the DOI up with source openalex.",
|
|
188
|
+
);
|
|
189
|
+
}
|
|
152
190
|
const message = recordValue(payload.message);
|
|
153
191
|
const results = arrayValue(message.items).flatMap((item) => {
|
|
154
192
|
const record = recordValue(item);
|
|
@@ -95,6 +95,11 @@ export function workflowName(text: string, workflows: ReadonlySet<string>): stri
|
|
|
95
95
|
return name && workflows.has(name) ? name : "chat";
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
// Filesystems stamp modification times from a coarse clock, and some (HFS+,
|
|
99
|
+
// FAT, ext3) round them to whole seconds, so a file written just after the run
|
|
100
|
+
// started can carry an earlier mtime. Allow for that rounding.
|
|
101
|
+
const MTIME_TOLERANCE_MS = 2000;
|
|
102
|
+
|
|
98
103
|
export function wroteResearchOutput(cwd: string, since: number): boolean {
|
|
99
104
|
let scanned = 0;
|
|
100
105
|
for (const dir of OUTPUT_DIRS) {
|
|
@@ -108,7 +113,7 @@ export function wroteResearchOutput(cwd: string, since: number): boolean {
|
|
|
108
113
|
if (++scanned > OUTPUT_SCAN_LIMIT) return false;
|
|
109
114
|
try {
|
|
110
115
|
const stats = statSync(join(cwd, dir, entry));
|
|
111
|
-
if (stats.isFile() && stats.mtimeMs >= since) return true;
|
|
116
|
+
if (stats.isFile() && stats.mtimeMs >= since - MTIME_TOLERANCE_MS) return true;
|
|
112
117
|
} catch {}
|
|
113
118
|
}
|
|
114
119
|
}
|