@companion-ai/feynman 0.5.27 → 0.5.29

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,19 @@ GitHub release notes are generated from the matching `## vX.Y.Z` section in this
6
6
 
7
7
  ## Unreleased
8
8
 
9
+ ## v0.5.29 - 2026-10-07
10
+
11
+ - **PubMed titles and abstracts keep their italic terms.** Text inside inline tags such as `<i>BRCA1</i>` was dropped, so a title read "patient with germline mutation" instead of "germline BRCA1 mutation", and gene names went missing from abstracts. Thanks to @KennyMcSimpson (#328).
12
+ - **PubMed authors keep all their affiliations.** An author listed with two or more affiliations came back with none. Thanks to @KennyMcSimpson (#327).
13
+ - Security: GHSA-6qxp-vccf-f47h (MCP SDK OAuth client). Feynman's own copies of `@modelcontextprotocol/sdk` move to 1.32.1. pi-web-access still pins 1.27.1 for its separate `npx pi-web-access` MCP server, which Feynman does not run and which does not use the affected OAuth client; it is updated in nicobailon/pi-web-access#522.
14
+
15
+ ## v0.5.28 - 2026-10-07
16
+
17
+ - **Pi 1.0.4, pi-subagents 0.76.1, pi-web-access 0.37.0.**
18
+ - Pi: syntax highlighting keeps its colors across multiline strings and comments, MCP sign-in works on servers using OpenID Connect registration, and stalled Bedrock streams are retried.
19
+ - pi-subagents: when a subagent's result wakes the main session, that turn keeps its normal system prompt (subagent list, MCP servers, other extensions) and the provider's prompt cache. A per-tool timeout now names the tool instead of a run deadline that never fired.
20
+ - pi-web-access: a `web_search` or `fetch_content` call where nothing succeeded is now reported as a tool error, so the model knows to try something else; Perplexity results include page snippets and cost less.
21
+
9
22
  ## v0.5.27 - 2026-10-06
10
23
 
11
24
  - **Pi 1.0.3.** Sign in with ChatGPT no longer fails with `refresh_token_invalidated` after a request is cancelled during a token refresh, and closing the terminal no longer reports a `read EIO` / `setRawMode EIO` crash. In fullscreen, `Home`/`End` now move within the input line; jump to the top or bottom of the transcript with `Ctrl+Home`/`Ctrl+End`.
@@ -8,6 +8,14 @@ const xmlParser = new XMLParser({
8
8
  removeNSPrefix: true,
9
9
  });
10
10
 
11
+ // Object-mode parsing groups tags and loses the order of mixed inline text.
12
+ const orderedTextParser = new XMLParser({
13
+ preserveOrder: true,
14
+ removeNSPrefix: true,
15
+ trimValues: false,
16
+ parseTagValue: false,
17
+ });
18
+
11
19
  function recordValue(value: unknown): Record<string, unknown> {
12
20
  return value && typeof value === "object" && !Array.isArray(value) ? value as Record<string, unknown> : {};
13
21
  }
@@ -49,6 +57,31 @@ function textValue(value: unknown): string | undefined {
49
57
  return parts.length ? parts.join(" ").replace(/\s+/g, " ").trim() : undefined;
50
58
  }
51
59
 
60
+ function orderedChildren(value: unknown, tag: string): unknown[] {
61
+ return listValue(value).flatMap((node) => {
62
+ const children = recordValue(node)[tag];
63
+ return children === undefined ? [] : [children];
64
+ });
65
+ }
66
+
67
+ function orderedText(value: unknown): string {
68
+ if (typeof value === "string" || typeof value === "number") return String(value);
69
+ if (Array.isArray(value)) return value.map((node) => orderedText(node)).join("");
70
+ return Object.entries(recordValue(value))
71
+ .filter(([key]) => key !== ":@" && !key.startsWith("?"))
72
+ .map(([, children]) => orderedText(children))
73
+ .join("");
74
+ }
75
+
76
+ function orderedTextValue(value: unknown): string | undefined {
77
+ return orderedText(value).replace(/\s+/g, " ").trim() || undefined;
78
+ }
79
+
80
+ function orderedPubmedArticles(xml: string): unknown[] {
81
+ const set = orderedChildren(orderedTextParser.parse(xml), "PubmedArticleSet")[0];
82
+ return orderedChildren(set, "PubmedArticle");
83
+ }
84
+
52
85
  function dateObject(value: unknown): Record<string, string> | undefined {
53
86
  const record = recordValue(value);
54
87
  const monthMap: Record<string, string> = {
@@ -101,7 +134,8 @@ function parseAuthors(value: unknown): Array<Record<string, unknown>> {
101
134
  const author = recordValue(item);
102
135
  const collective = textValue(author.CollectiveName);
103
136
  if (collective) return { collectiveName: collective, affiliations: [] };
104
- const affiliations = listValue(recordValue(author.AffiliationInfo).Affiliation)
137
+ const affiliations = listValue(author.AffiliationInfo)
138
+ .flatMap((info) => listValue(recordValue(info).Affiliation))
105
139
  .map((affiliation) => textValue(affiliation))
106
140
  .filter((affiliation): affiliation is string => Boolean(affiliation));
107
141
  return prune({
@@ -113,15 +147,17 @@ function parseAuthors(value: unknown): Array<Record<string, unknown>> {
113
147
  });
114
148
  }
115
149
 
116
- function parsePubmedArticle(value: unknown): Record<string, unknown> {
150
+ function parsePubmedArticle(value: unknown, orderedArticle: unknown): Record<string, unknown> {
117
151
  const articleRoot = recordValue(value);
118
152
  const citation = recordValue(articleRoot.MedlineCitation);
119
153
  const article = recordValue(citation.Article);
120
154
  const journal = recordValue(article.Journal);
121
155
  const issue = recordValue(journal.JournalIssue);
122
- const abstract = recordValue(article.Abstract);
123
- const abstractParts = listValue(abstract.AbstractText)
124
- .map((part) => textValue(part))
156
+ const orderedCitation = orderedChildren(orderedArticle, "MedlineCitation")[0];
157
+ const orderedContent = orderedChildren(orderedCitation, "Article")[0];
158
+ const orderedAbstract = orderedChildren(orderedContent, "Abstract")[0];
159
+ const abstractParts = orderedChildren(orderedAbstract, "AbstractText")
160
+ .map((part) => orderedTextValue(part))
125
161
  .filter((part): part is string => Boolean(part));
126
162
  const publicationTypes = listValue(recordValue(article.PublicationTypeList).PublicationType)
127
163
  .map((item) => textValue(item))
@@ -140,7 +176,7 @@ function parsePubmedArticle(value: unknown): Record<string, unknown> {
140
176
  pmid: identifiers.pmid,
141
177
  pmcid: identifiers.pmc,
142
178
  doi: identifiers.doi,
143
- title: textValue(article.ArticleTitle),
179
+ title: orderedTextValue(orderedChildren(orderedContent, "ArticleTitle")[0]),
144
180
  abstract: abstractParts.length ? abstractParts.join("\n") : undefined,
145
181
  journal: prune({
146
182
  title: textValue(journal.Title),
@@ -160,7 +196,8 @@ function parsePubmedArticle(value: unknown): Record<string, unknown> {
160
196
  export function parsePubmedArticles(xml: string): Record<string, unknown>[] {
161
197
  const parsed = recordValue(xmlParser.parse(xml));
162
198
  const set = recordValue(parsed.PubmedArticleSet);
163
- return listValue(set.PubmedArticle).map((article) => parsePubmedArticle(article));
199
+ const orderedArticles = orderedPubmedArticles(xml);
200
+ return listValue(set.PubmedArticle).map((article, index) => parsePubmedArticle(article, orderedArticles[index]));
164
201
  }
165
202
 
166
203
  function jatsArticleId(meta: Record<string, unknown>, type: string): string | undefined {
@@ -238,8 +275,9 @@ export function copyrightFromPubmedXml(xml: string): Map<string, Record<string,
238
275
  const map = new Map<string, Record<string, unknown>>();
239
276
  const parsed = recordValue(xmlParser.parse(xml));
240
277
  const set = recordValue(parsed.PubmedArticleSet);
241
- for (const rawArticle of listValue(set.PubmedArticle)) {
242
- const article = parsePubmedArticle(rawArticle);
278
+ const orderedArticles = orderedPubmedArticles(xml);
279
+ for (const [index, rawArticle] of listValue(set.PubmedArticle).entries()) {
280
+ const article = parsePubmedArticle(rawArticle, orderedArticles[index]);
243
281
  const pmid = stringValue(article.pmid);
244
282
  if (!pmid) continue;
245
283
  const pubmedArticle = recordValue(rawArticle);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@companion-ai/feynman",
3
- "version": "0.5.27",
3
+ "version": "0.5.29",
4
4
  "description": "Research-first CLI agent built on Pi and alphaXiv",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -82,13 +82,13 @@
82
82
  "fast-xml-parser": "5.11.1",
83
83
  "pi-btw": "0.7.1",
84
84
  "pi-docparser": "4.0.0",
85
- "pi-subagents": "0.76.0",
86
- "pi-web-access": "0.36.0",
85
+ "pi-subagents": "0.76.1",
86
+ "pi-web-access": "0.37.0",
87
87
  "posthog-node": "^5.53.0"
88
88
  },
89
89
  "peerDependencies": {
90
- "@earendil-works/pi-ai": "1.0.3",
91
- "@earendil-works/pi-coding-agent": "1.0.3",
90
+ "@earendil-works/pi-ai": "1.0.4",
91
+ "@earendil-works/pi-coding-agent": "1.0.4",
92
92
  "@earendil-works/pi-tui": "*",
93
93
  "typebox": "*"
94
94
  },