@companion-ai/feynman 0.5.43 → 0.5.45

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,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.45 - 2026-10-13
10
+
11
+ - **Paper lookups recover instead of failing.**
12
+ - A topic sent to the arXiv source, which only looks papers up by ID, now gets OpenAlex semantic search results with a note saying so, instead of an error the model had to recover from.
13
+ - When arXiv times out or is overloaded, arXiv ID lookups come from Semantic Scholar, with a note saying so.
14
+
15
+ ## v0.5.44 - 2026-10-13
16
+
17
+ - **`feynman search set` works with every search provider pi-web-access supports.** It accepted only `auto`, `exa`, `perplexity` and `gemini`; it now takes all 39, including Brave, Tavily, Kagi, Serper, You.com, SearXNG and DuckDuckGo, and saves each key where pi-web-access reads it. `feynman search status` and `feynman doctor` name the provider you chose and list every saved key, and no longer describe an outdated automatic search order.
18
+ - **pi-web-access 0.40.0.** Asking about a YouTube or local video works again when a thumbnail comes back with the transcript, excerpts no longer cut an emoji in half, and a new opt-in setting (`fetchContent.redirects: "same-origin"`) refuses redirects to another site.
19
+
9
20
  ## v0.5.43 - 2026-10-13
10
21
 
11
22
  - **Feynman reads and writes Pi's configuration only through Pi.** Settings now go through Pi's settings manager, which locks the file and changes only the keys Feynman sets, so a change Pi or another Feynman window makes at the same moment is no longer overwritten. Model checks, local-model detection and `feynman doctor` read your models through Pi, so comments in `models.json`, a `baseUrl` set on one model, and a key saved with `feynman model login` count the same way they do when Feynman runs.
package/dist/cli.js CHANGED
@@ -24,7 +24,7 @@ import { BUNDLED_PI_PACKAGES, buildPiEnv, findWindowsBash, WINDOWS_BASH_MISSING_
24
24
  import { getConfiguredServiceTier, normalizeServiceTier, setConfiguredServiceTier } from "./model/service-tier.js";
25
25
  import { authenticateModelProvider, getCurrentModelSpec, isLocalModelProvider, loginModelProvider, logoutModelProvider, printModelList, selectDefaultModel, setDefaultModelSpec, } from "./model/commands.js";
26
26
  import { buildModelStatusSnapshotFromRecords, getAuthenticatedModelRecords, isProClassModelSpec, getSupportedModelRecords, } from "./model/catalog.js";
27
- import { clearSearchConfig, printSearchStatus, setSearchProvider } from "./search/commands.js";
27
+ import { clearSearchConfig, printSearchStatus, searchSetUsage, setSearchProvider } from "./search/commands.js";
28
28
  import { fetchLatestFeynmanVersion, getFeynmanUpgradeLines, isNewerVersion } from "./system/self-update.js";
29
29
  import { runDoctor, runStatus } from "./setup/doctor.js";
30
30
  import { setupPreviewDependencies } from "./setup/preview.js";
@@ -307,12 +307,9 @@ function handleSearchCommand(subcommand, args) {
307
307
  return;
308
308
  }
309
309
  if (subcommand === "set") {
310
- const provider = args[0];
311
- const validProviders = ["auto", "perplexity", "exa", "gemini"];
312
- if (!provider || !validProviders.includes(provider)) {
313
- throw new Error("Usage: feynman search set <auto|perplexity|exa|gemini> [api-key]");
314
- }
315
- setSearchProvider(provider, args[1]);
310
+ if (!args[0])
311
+ throw new Error(searchSetUsage());
312
+ setSearchProvider(args[0], args[1]);
316
313
  return;
317
314
  }
318
315
  if (subcommand === "clear") {
@@ -2,13 +2,33 @@ import { chmodSync, existsSync, mkdirSync, writeFileSync } from "node:fs";
2
2
  import { dirname, resolve } from "node:path";
3
3
  import { readJsonFile } from "../config/json-file.js";
4
4
  import { getFeynmanAgentDir, getFeynmanHome } from "../config/paths.js";
5
+ // pi-web-access's search providers (SEARCH_PROVIDERS in its gemini-search.ts;
6
+ // a test keeps this list in step with the installed package).
7
+ export const PI_WEB_SEARCH_PROVIDERS = [
8
+ "auto", "all", "openai", "brave", "parallel", "parallel-mcp", "tinyfish", "search1api", "searchinfinity", "querit",
9
+ "tavily", "you", "firecrawl", "jina", "searxng", "degoog", "duckduckgo", "perplexity", "gemini", "kimi", "exa",
10
+ "serpdive", "kagi", "ollama", "anysearch", "xai", "mistral", "brightdata", "serpbase", "serpapi", "serper", "serply",
11
+ "valyu", "bocha", "xcrawl", "baizhi", "zai", "keenable", "ceramic",
12
+ ];
13
+ // The web-search.json field holding a provider's key (searxng takes its base URL);
14
+ // undefined for providers with nothing to set.
15
+ export function searchProviderKeyField(provider) {
16
+ if (provider === "auto" || provider === "all" || provider === "duckduckgo" || provider === "kimi")
17
+ return undefined;
18
+ if (provider === "parallel-mcp")
19
+ return "parallelApiKey";
20
+ if (provider === "searxng")
21
+ return "searxngBaseUrl";
22
+ return `${provider}ApiKey`;
23
+ }
24
+ const ROUTE_LABELS = { auto: "Auto", perplexity: "Perplexity", exa: "Exa", gemini: "Gemini" };
5
25
  // pi-web-access reads web-search.json from PI_CODING_AGENT_DIR.
6
26
  export function getPiWebSearchConfigPath(home) {
7
27
  const feynmanHome = home ? resolve(home, ".feynman") : getFeynmanHome();
8
28
  return resolve(getFeynmanAgentDir(feynmanHome), "web-search.json");
9
29
  }
10
30
  function normalizeProvider(value) {
11
- return value === "auto" || value === "perplexity" || value === "exa" || value === "gemini" ? value : undefined;
31
+ return PI_WEB_SEARCH_PROVIDERS.includes(value) ? value : undefined;
12
32
  }
13
33
  function normalizeWorkflow(value) {
14
34
  return value === "none" || value === "summary-review" ? value : undefined;
@@ -48,29 +68,12 @@ export function savePiWebAccessConfig(updates, configPath = getPiWebSearchConfig
48
68
  // ignore permission errors (best-effort)
49
69
  }
50
70
  }
51
- function formatRouteLabel(provider) {
52
- switch (provider) {
53
- case "perplexity":
54
- return "Perplexity";
55
- case "exa":
56
- return "Exa";
57
- case "gemini":
58
- return "Gemini";
59
- default:
60
- return "Auto";
61
- }
62
- }
63
71
  function formatRouteNote(provider) {
64
- switch (provider) {
65
- case "perplexity":
66
- return "Pi web-access will use Perplexity for search.";
67
- case "exa":
68
- return "Pi web-access will use Exa for search.";
69
- case "gemini":
70
- return "Pi web-access will use Gemini API. Browser-cookie fallback is opt-in.";
71
- default:
72
- return "Pi web-access will try Exa, then Perplexity, then Gemini API. Browser-cookie fallback is opt-in.";
73
- }
72
+ if (provider === "auto")
73
+ return "Pi web-access picks from the search providers you have keys or sign-ins for. Browser-cookie fallback is opt-in.";
74
+ if (provider === "all")
75
+ return "Pi web-access searches every eligible provider at once.";
76
+ return `Pi web-access will use ${ROUTE_LABELS[provider] ?? provider} for search.`;
74
77
  }
75
78
  export function getPiWebAccessStatus(config = loadPiWebAccessConfig(), configPath = getPiWebSearchConfigPath()) {
76
79
  const searchProvider = normalizeProvider(config.searchProvider) ?? normalizeProvider(config.route) ?? normalizeProvider(config.provider) ?? "auto";
@@ -82,6 +85,10 @@ export function getPiWebAccessStatus(config = loadPiWebAccessConfig(), configPat
82
85
  const chromeProfile = normalizeNonEmptyString(config.browserCookies?.profile);
83
86
  const geminiBrowserEnabled = config.allowBrowserCookies === true;
84
87
  const effectiveProvider = searchProvider;
88
+ const configuredKeys = PI_WEB_SEARCH_PROVIDERS.filter((provider) => {
89
+ const field = searchProviderKeyField(provider);
90
+ return field !== undefined && field.endsWith("ApiKey") && Boolean(normalizeNonEmptyString(config[field]));
91
+ }).filter((provider) => provider !== "parallel-mcp");
85
92
  return {
86
93
  configPath,
87
94
  configExists: existsSync(configPath),
@@ -93,7 +100,8 @@ export function getPiWebAccessStatus(config = loadPiWebAccessConfig(), configPat
93
100
  geminiApiConfigured,
94
101
  chromeProfile,
95
102
  geminiBrowserEnabled,
96
- routeLabel: formatRouteLabel(effectiveProvider),
103
+ configuredKeys,
104
+ routeLabel: ROUTE_LABELS[effectiveProvider] ?? effectiveProvider,
97
105
  note: formatRouteNote(effectiveProvider),
98
106
  };
99
107
  }
@@ -104,18 +112,16 @@ export function formatPiWebAccessDoctorLines(status = getPiWebAccessStatus()) {
104
112
  ` search route: ${status.routeLabel}`,
105
113
  ` request route: ${status.requestProvider}`,
106
114
  ` search workflow: ${status.workflow}`,
107
- ` perplexity api: ${status.perplexityConfigured ? "configured" : "not configured"}`,
108
- ` exa api: ${status.exaConfigured ? "configured" : "not configured"}`,
109
- ` gemini api: ${status.geminiApiConfigured ? "configured" : "not configured"}`,
115
+ ` saved api keys: ${status.configuredKeys.join(", ") || "none"}`,
110
116
  ` gemini browser fallback: ${status.geminiBrowserEnabled ? "enabled" : "disabled"}`,
111
117
  ` config path: ${status.configPath}${configPathSuffix}`,
112
118
  ` note: ${status.note}`,
113
119
  ];
114
120
  if (status.geminiBrowserEnabled && status.chromeProfile) {
115
- lines.splice(8, 0, ` gemini browser profile: ${status.chromeProfile}`);
121
+ lines.splice(6, 0, ` gemini browser profile: ${status.chromeProfile}`);
116
122
  }
117
123
  if (!status.configExists) {
118
- lines.push(" hint: run `feynman search set <auto|perplexity|exa|gemini> [api-key]` to configure web search");
124
+ lines.push(" hint: run `feynman search set <provider> [api-key]` to configure web search (`feynman search set` lists providers)");
119
125
  }
120
126
  return lines;
121
127
  }
@@ -1,20 +1,15 @@
1
- import { getPiWebAccessStatus, savePiWebAccessConfig, } from "../pi/web-access.js";
1
+ import { getPiWebAccessStatus, PI_WEB_SEARCH_PROVIDERS, savePiWebAccessConfig, searchProviderKeyField, } from "../pi/web-access.js";
2
2
  import { printInfo } from "../ui/terminal.js";
3
- const SEARCH_PROVIDERS = ["auto", "perplexity", "exa", "gemini"];
4
- const PROVIDER_API_KEY_FIELDS = {
5
- perplexity: "perplexityApiKey",
6
- exa: "exaApiKey",
7
- gemini: "geminiApiKey",
8
- };
3
+ export function searchSetUsage() {
4
+ return `Usage: feynman search set <provider> [api-key]\nProviders: ${PI_WEB_SEARCH_PROVIDERS.join(", ")}`;
5
+ }
9
6
  export function printSearchStatus(status = getPiWebAccessStatus()) {
10
7
  const configPathSuffix = status.configExists ? "" : " (not created yet)";
11
8
  printInfo("Managed by: pi-web-access");
12
9
  printInfo(`Search route: ${status.routeLabel}`);
13
10
  printInfo(`Request route: ${status.requestProvider}`);
14
11
  printInfo(`Search workflow: ${status.workflow}`);
15
- printInfo(`Perplexity API configured: ${status.perplexityConfigured ? "yes" : "no"}`);
16
- printInfo(`Exa API configured: ${status.exaConfigured ? "yes" : "no"}`);
17
- printInfo(`Gemini API configured: ${status.geminiApiConfigured ? "yes" : "no"}`);
12
+ printInfo(`Saved API keys: ${status.configuredKeys.join(", ") || "none"}`);
18
13
  printInfo(`Gemini browser fallback: ${status.geminiBrowserEnabled ? "enabled" : "disabled"}`);
19
14
  if (status.geminiBrowserEnabled && status.chromeProfile) {
20
15
  printInfo(`Gemini browser profile: ${status.chromeProfile}`);
@@ -23,17 +18,16 @@ export function printSearchStatus(status = getPiWebAccessStatus()) {
23
18
  if (!status.configExists) {
24
19
  printInfo("Not configured yet. Run one of:");
25
20
  printInfo(" feynman search set auto");
26
- printInfo(" feynman search set perplexity <api-key>");
27
- printInfo(" feynman search set exa <api-key>");
28
- printInfo(" feynman search set gemini <api-key>");
21
+ printInfo(" feynman search set <provider> <api-key> (for example exa, brave, tavily, perplexity)");
29
22
  }
30
23
  }
31
24
  export function setSearchProvider(provider, apiKey) {
32
- if (!SEARCH_PROVIDERS.includes(provider)) {
33
- throw new Error(`Usage: feynman search set <${SEARCH_PROVIDERS.join("|")}> [api-key]`);
25
+ if (!PI_WEB_SEARCH_PROVIDERS.includes(provider)) {
26
+ throw new Error(searchSetUsage());
34
27
  }
35
- if (apiKey !== undefined && provider === "auto") {
36
- throw new Error("The auto provider does not use an API key. Usage: feynman search set auto");
28
+ const keyField = searchProviderKeyField(provider);
29
+ if (apiKey !== undefined && !keyField) {
30
+ throw new Error(`${provider} takes no API key. Usage: feynman search set ${provider}`);
37
31
  }
38
32
  const updates = {
39
33
  provider,
@@ -42,9 +36,8 @@ export function setSearchProvider(provider, apiKey) {
42
36
  allowBrowserCookies: false,
43
37
  route: undefined,
44
38
  };
45
- const apiKeyField = PROVIDER_API_KEY_FIELDS[provider];
46
- if (apiKeyField && apiKey !== undefined) {
47
- updates[apiKeyField] = apiKey;
39
+ if (keyField && apiKey !== undefined) {
40
+ updates[keyField] = apiKey;
48
41
  }
49
42
  savePiWebAccessConfig(updates);
50
43
  const status = getPiWebAccessStatus();
@@ -35,6 +35,20 @@ function cleanQuery(query: string): string {
35
35
  const arxivPacer = createRequestPacer();
36
36
  export const ARXIV_MIN_GAP_MS = 3000;
37
37
 
38
+ // The query held no arXiv ID; the caller searches another index for `topic`.
39
+ export class ArxivTopicQueryError extends Error {
40
+ constructor(message: string, readonly topic: string) {
41
+ super(message);
42
+ }
43
+ }
44
+
45
+ // arXiv timed out or answered 429/5xx; the caller can look `ids` up elsewhere.
46
+ export class ArxivUnavailableError extends Error {
47
+ constructor(message: string, readonly ids: string[]) {
48
+ super(message);
49
+ }
50
+ }
51
+
38
52
  async function fetchText(url: URL, accept: string): Promise<string> {
39
53
  for (let attempt = 0; ; attempt += 1) {
40
54
  const result = await arxivPacer(ARXIV_MIN_GAP_MS, async () => {
@@ -157,13 +171,25 @@ async function getArxivPapers(query: string): Promise<Record<string, unknown>> {
157
171
  else notFound.push(raw);
158
172
  }
159
173
  if (!ids.length) {
160
- throw new Error(`No arXiv IDs found in "${query}". The arxiv source only looks up papers by ID; search topics with source semanticscholar or openalex.`);
174
+ throw new ArxivTopicQueryError(
175
+ `No arXiv IDs found in "${query}". The arxiv source only looks up papers by ID; search topics with source semanticscholar or openalex.`,
176
+ parsed.text || stripGetPapersCommand(query),
177
+ );
161
178
  }
162
179
  const rows: Record<string, unknown>[] = [];
163
180
  const duplicates: Array<Record<string, unknown>> = [];
164
181
  const url = new URL(ARXIV_QUERY_URL);
165
182
  url.search = new URLSearchParams({ id_list: ids.join(","), max_results: String(ids.length) }).toString();
166
- const xml = await fetchText(url, "application/atom+xml, application/xml;q=0.9, text/xml;q=0.8");
183
+ let xml: string;
184
+ try {
185
+ xml = await fetchText(url, "application/atom+xml, application/xml;q=0.9, text/xml;q=0.8");
186
+ } catch (error) {
187
+ const reason = error instanceof Error && error.name === "AbortError" ? `no answer within ${REQUEST_TIMEOUT_MS / 1000} s` : error instanceof Error ? error.message : String(error);
188
+ if (error instanceof Error && (error.name === "AbortError" || /request failed: (?:429|5\d\d)\b/.test(error.message))) {
189
+ throw new ArxivUnavailableError(reason, ids);
190
+ }
191
+ throw error;
192
+ }
167
193
  const feed = recordValue(xmlParser.parse(xml).feed);
168
194
  const byId = new Map<string, Record<string, unknown>>();
169
195
  for (const row of arxivFeedEntries(feed).map(parseArxivEntry).filter((item): item is Record<string, unknown> => Boolean(item))) {
@@ -1,6 +1,6 @@
1
1
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import { Type } from "typebox";
3
- import { searchArxiv } from "./science-database-arxiv.js";
3
+ import { ArxivTopicQueryError, ArxivUnavailableError, searchArxiv } from "./science-database-arxiv.js";
4
4
  import { searchOpenAlex } from "./science-database-openalex.js";
5
5
  import { searchPreprints } from "./science-database-preprints.js";
6
6
  import { searchPubMed } from "./science-database-pubmed.js";
@@ -322,6 +322,29 @@ function semanticScholarSearchUrl(query: string, limit: number, sort: string, ye
322
322
 
323
323
  // Bulk search sorted by citation count surfaces seminal papers that relevance
324
324
  // ranking misses; it returns up to 1,000 rows, so only the top `limit` are kept.
325
+ function semanticScholarRecord(item: unknown): Record<string, unknown>[] {
326
+ const record = recordValue(item);
327
+ const paperId = stringValue(record.paperId);
328
+ if (!paperId) return [];
329
+ const externalIds = recordValue(record.externalIds);
330
+ const doi = stringValue(externalIds.DOI);
331
+ return [{
332
+ paperId,
333
+ title: stringValue(record.title),
334
+ year: numberValue(record.year),
335
+ authors: arrayValue(record.authors).map((author) => stringValue(recordValue(author).name)).filter(Boolean).slice(0, 8),
336
+ venue: stringValue(record.venue),
337
+ citationCount: numberValue(record.citationCount),
338
+ doi,
339
+ arxivId: stringValue(externalIds.ArXiv),
340
+ pmid: stringValue(externalIds.PubMed),
341
+ openAccessPdf: stringValue(recordValue(record.openAccessPdf).url),
342
+ abstract: truncateText(stringValue(record.abstract), SEMANTIC_SCHOLAR_ABSTRACT_CHARS),
343
+ url: `https://www.semanticscholar.org/paper/${paperId}`,
344
+ ...(doiUrl(doi) ? { doiUrl: doiUrl(doi) } : {}),
345
+ }];
346
+ }
347
+
325
348
  async function searchSemanticScholar(params: ScienceDatabaseSearchParams): Promise<Record<string, unknown>> {
326
349
  const { query, year } = parseSemanticScholarQuery(cleanQuery(params.query));
327
350
  const limit = safeLimit(params.limit);
@@ -340,28 +363,7 @@ async function searchSemanticScholar(params: ScienceDatabaseSearchParams): Promi
340
363
  note = `Relevance search was rate-limited (HTTP 429) on Semantic Scholar's shared anonymous pool, so these are citation-sorted bulk results. A free SEMANTIC_SCHOLAR_API_KEY avoids this: ${SEMANTIC_SCHOLAR_KEY_URL}`;
341
364
  payload = recordValue(await fetchSemanticScholar(endpoints[1]!));
342
365
  }
343
- const results = arrayValue(payload.data).slice(0, limit).flatMap((item) => {
344
- const record = recordValue(item);
345
- const paperId = stringValue(record.paperId);
346
- if (!paperId) return [];
347
- const externalIds = recordValue(record.externalIds);
348
- const doi = stringValue(externalIds.DOI);
349
- return [{
350
- paperId,
351
- title: stringValue(record.title),
352
- year: numberValue(record.year),
353
- authors: arrayValue(record.authors).map((author) => stringValue(recordValue(author).name)).filter(Boolean).slice(0, 8),
354
- venue: stringValue(record.venue),
355
- citationCount: numberValue(record.citationCount),
356
- doi,
357
- arxivId: stringValue(externalIds.ArXiv),
358
- pmid: stringValue(externalIds.PubMed),
359
- openAccessPdf: stringValue(recordValue(record.openAccessPdf).url),
360
- abstract: truncateText(stringValue(record.abstract), SEMANTIC_SCHOLAR_ABSTRACT_CHARS),
361
- url: `https://www.semanticscholar.org/paper/${paperId}`,
362
- ...(doiUrl(doi) ? { doiUrl: doiUrl(doi) } : {}),
363
- }];
364
- });
366
+ const results = arrayValue(payload.data).slice(0, limit).flatMap(semanticScholarRecord);
365
367
  return {
366
368
  schema: "feynman.scienceDatabaseSearch.v1",
367
369
  source: "semanticscholar",
@@ -418,8 +420,49 @@ async function withIndexFallback(
418
420
  }
419
421
  }
420
422
 
423
+ // arXiv IDs looked up on Semantic Scholar, one request per ID (at most 10).
424
+ async function semanticScholarByArxivIds(ids: string[]): Promise<Record<string, unknown>> {
425
+ const endpoints: string[] = [];
426
+ const results: Record<string, unknown>[] = [];
427
+ const notFound: string[] = [];
428
+ for (const id of ids.slice(0, 10)) {
429
+ const bare = id.replace(/v\d+$/, "");
430
+ const url = new URL(`${SEMANTIC_SCHOLAR_BASE}/paper/ARXIV:${bare}`);
431
+ url.searchParams.set("fields", SEMANTIC_SCHOLAR_FIELDS);
432
+ endpoints.push(url.toString());
433
+ try {
434
+ results.push(...semanticScholarRecord(await fetchSemanticScholar(url)));
435
+ } catch (error) {
436
+ if (!(error instanceof ScienceDatabaseRequestError) || error.status !== 404) throw error;
437
+ notFound.push(id);
438
+ }
439
+ }
440
+ return { schema: "feynman.scienceDatabaseSearch.v1", source: "semanticscholar", query: ids.join(","), not_found: notFound, results, provenance: { endpoints } };
441
+ }
442
+
443
+ async function searchArxivWithFallback(params: ScienceDatabaseSearchParams): Promise<Record<string, unknown>> {
444
+ try {
445
+ return await searchArxiv(params);
446
+ } catch (error) {
447
+ if (error instanceof ArxivTopicQueryError) {
448
+ const result = await searchOpenAlex({ limit: params.limit, query: `semantic: ${error.topic}`, source: "openalex" });
449
+ return { fallbackNote: "The arxiv source only looks up papers by ID, so this topic was searched with OpenAlex semantic search instead.", ...result };
450
+ }
451
+ if (error instanceof ArxivUnavailableError) {
452
+ let result: Record<string, unknown>;
453
+ try {
454
+ result = await semanticScholarByArxivIds(error.ids);
455
+ } catch {
456
+ throw error;
457
+ }
458
+ return { fallbackNote: `arXiv was unavailable (${error.message}); these records are from Semantic Scholar.`, ...result };
459
+ }
460
+ throw error;
461
+ }
462
+ }
463
+
421
464
  async function scienceDatabaseSearch(params: ScienceDatabaseSearchParams): Promise<Record<string, unknown>> {
422
- if (params.source === "arxiv") return searchArxiv(params);
465
+ if (params.source === "arxiv") return searchArxivWithFallback(params);
423
466
  if (params.source === "biorxiv") return searchPreprints(params, "biorxiv");
424
467
  if (params.source === "medrxiv") return searchPreprints(params, "medrxiv");
425
468
  if (params.source === "pubmed") return searchPubMed(params);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@companion-ai/feynman",
3
- "version": "0.5.43",
3
+ "version": "0.5.45",
4
4
  "description": "The open source AI research agent.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -82,11 +82,11 @@
82
82
  "@earendil-works/pi-codemode": "1.2.0",
83
83
  "@earendil-works/pi-mcp": "1.2.0",
84
84
  "@earendil-works/pi-telemetry": "1.2.0",
85
- "fast-xml-parser": "5.11.1",
85
+ "fast-xml-parser": "5.11.2",
86
86
  "pi-btw": "0.8.0",
87
87
  "pi-docparser": "4.0.0",
88
88
  "pi-subagents": "0.78.0",
89
- "pi-web-access": "0.39.0",
89
+ "pi-web-access": "0.40.0",
90
90
  "posthog-node": "^5.53.0"
91
91
  },
92
92
  "peerDependencies": {