@daniel156161/prism 0.2.78 → 0.2.81

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.
Files changed (180) hide show
  1. package/dist/pi/pi-env.d.ts +1 -1
  2. package/dist/pi/pi-env.js +1 -1
  3. package/dist/pi/pi-patch-interactive.js +8 -5
  4. package/dist/pi/pi-patch-interactive.js.map +1 -1
  5. package/dist/prism-extensions/integrations/ai-memory-errors.d.ts +16 -0
  6. package/dist/prism-extensions/integrations/ai-memory-errors.js +52 -0
  7. package/dist/prism-extensions/integrations/ai-memory-errors.js.map +1 -0
  8. package/dist/prism-extensions/integrations/ai-memory-system.d.ts +3 -0
  9. package/dist/prism-extensions/integrations/ai-memory-system.js +175 -15
  10. package/dist/prism-extensions/integrations/ai-memory-system.js.map +1 -1
  11. package/dist/prism-extensions/integrations/research-tools.js +3 -3
  12. package/dist/prism-extensions/integrations/research-tools.js.map +1 -1
  13. package/dist/prism-extensions/integrations/youtube.d.ts +2 -2
  14. package/dist/prism-extensions/integrations/youtube.js +6 -7
  15. package/dist/prism-extensions/integrations/youtube.js.map +1 -1
  16. package/node_modules/@earendil-works/pi-agent-core/README.md +4 -4
  17. package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
  18. package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js +5 -1
  19. package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js.map +1 -1
  20. package/node_modules/@earendil-works/pi-agent-core/dist/agent.d.ts.map +1 -1
  21. package/node_modules/@earendil-works/pi-agent-core/dist/agent.js +3 -0
  22. package/node_modules/@earendil-works/pi-agent-core/dist/agent.js.map +1 -1
  23. package/node_modules/@earendil-works/pi-agent-core/dist/types.d.ts +6 -0
  24. package/node_modules/@earendil-works/pi-agent-core/dist/types.d.ts.map +1 -1
  25. package/node_modules/@earendil-works/pi-agent-core/dist/types.js.map +1 -1
  26. package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
  27. package/node_modules/@earendil-works/pi-ai/README.md +8 -1
  28. package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.d.ts.map +1 -1
  29. package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.js +1 -0
  30. package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.js.map +1 -1
  31. package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts +2 -0
  32. package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts.map +1 -1
  33. package/node_modules/@earendil-works/pi-ai/dist/models.generated.js +2 -0
  34. package/node_modules/@earendil-works/pi-ai/dist/models.generated.js.map +1 -1
  35. package/node_modules/@earendil-works/pi-ai/dist/providers/all.d.ts.map +1 -1
  36. package/node_modules/@earendil-works/pi-ai/dist/providers/all.js +2 -0
  37. package/node_modules/@earendil-works/pi-ai/dist/providers/all.js.map +1 -1
  38. package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
  39. package/node_modules/@earendil-works/pi-ai/dist/providers/data/github-copilot.json +1 -1
  40. package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode.json +1 -1
  41. package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
  42. package/node_modules/@earendil-works/pi-ai/dist/providers/data/qwen-token-plan-individual.json +1 -0
  43. package/node_modules/@earendil-works/pi-ai/dist/providers/data/vercel-ai-gateway.json +1 -1
  44. package/node_modules/@earendil-works/pi-ai/dist/providers/qwen-token-plan-individual.d.ts +3 -0
  45. package/node_modules/@earendil-works/pi-ai/dist/providers/qwen-token-plan-individual.d.ts.map +1 -0
  46. package/node_modules/@earendil-works/pi-ai/dist/providers/qwen-token-plan-individual.js +15 -0
  47. package/node_modules/@earendil-works/pi-ai/dist/providers/qwen-token-plan-individual.js.map +1 -0
  48. package/node_modules/@earendil-works/pi-ai/dist/providers/qwen-token-plan-individual.models.d.ts +4 -0
  49. package/node_modules/@earendil-works/pi-ai/dist/providers/qwen-token-plan-individual.models.d.ts.map +1 -0
  50. package/node_modules/@earendil-works/pi-ai/dist/providers/qwen-token-plan-individual.models.js +6 -0
  51. package/node_modules/@earendil-works/pi-ai/dist/providers/qwen-token-plan-individual.models.js.map +1 -0
  52. package/node_modules/@earendil-works/pi-ai/dist/types.d.ts +1 -1
  53. package/node_modules/@earendil-works/pi-ai/dist/types.d.ts.map +1 -1
  54. package/node_modules/@earendil-works/pi-ai/dist/types.js.map +1 -1
  55. package/node_modules/@earendil-works/pi-ai/package.json +2 -2
  56. package/node_modules/@earendil-works/pi-client/CHANGELOG.md +1 -1
  57. package/node_modules/@earendil-works/pi-client/package.json +2 -2
  58. package/node_modules/@earendil-works/pi-coding-agent/CHANGELOG.md +31 -2
  59. package/node_modules/@earendil-works/pi-coding-agent/dist/cli/args.d.ts.map +1 -1
  60. package/node_modules/@earendil-works/pi-coding-agent/dist/cli/args.js +3 -3
  61. package/node_modules/@earendil-works/pi-coding-agent/dist/cli/args.js.map +1 -1
  62. package/node_modules/@earendil-works/pi-coding-agent/dist/cli/auth-check.d.ts +19 -0
  63. package/node_modules/@earendil-works/pi-coding-agent/dist/cli/auth-check.d.ts.map +1 -0
  64. package/node_modules/@earendil-works/pi-coding-agent/dist/cli/auth-check.js +50 -0
  65. package/node_modules/@earendil-works/pi-coding-agent/dist/cli/auth-check.js.map +1 -0
  66. package/node_modules/@earendil-works/pi-coding-agent/dist/cli/auth-command.d.ts +24 -0
  67. package/node_modules/@earendil-works/pi-coding-agent/dist/cli/auth-command.d.ts.map +1 -0
  68. package/node_modules/@earendil-works/pi-coding-agent/dist/cli/auth-command.js +102 -0
  69. package/node_modules/@earendil-works/pi-coding-agent/dist/cli/auth-command.js.map +1 -0
  70. package/node_modules/@earendil-works/pi-coding-agent/dist/cli/credential-print.d.ts +4 -14
  71. package/node_modules/@earendil-works/pi-coding-agent/dist/cli/credential-print.d.ts.map +1 -1
  72. package/node_modules/@earendil-works/pi-coding-agent/dist/cli/credential-print.js +38 -79
  73. package/node_modules/@earendil-works/pi-coding-agent/dist/cli/credential-print.js.map +1 -1
  74. package/node_modules/@earendil-works/pi-coding-agent/dist/core/auth-storage.d.ts +10 -0
  75. package/node_modules/@earendil-works/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
  76. package/node_modules/@earendil-works/pi-coding-agent/dist/core/auth-storage.js +78 -1
  77. package/node_modules/@earendil-works/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
  78. package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/types.d.ts +5 -0
  79. package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  80. package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  81. package/node_modules/@earendil-works/pi-coding-agent/dist/core/keybindings.d.ts +8 -0
  82. package/node_modules/@earendil-works/pi-coding-agent/dist/core/keybindings.d.ts.map +1 -1
  83. package/node_modules/@earendil-works/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
  84. package/node_modules/@earendil-works/pi-coding-agent/dist/core/model-resolver.js +1 -0
  85. package/node_modules/@earendil-works/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
  86. package/node_modules/@earendil-works/pi-coding-agent/dist/core/model-runtime.d.ts +2 -0
  87. package/node_modules/@earendil-works/pi-coding-agent/dist/core/model-runtime.d.ts.map +1 -1
  88. package/node_modules/@earendil-works/pi-coding-agent/dist/core/model-runtime.js +3 -1
  89. package/node_modules/@earendil-works/pi-coding-agent/dist/core/model-runtime.js.map +1 -1
  90. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/bash.d.ts +4 -0
  91. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/bash.d.ts.map +1 -1
  92. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/bash.js +6 -4
  93. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/bash.js.map +1 -1
  94. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/edit.d.ts +4 -0
  95. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/edit.d.ts.map +1 -1
  96. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/edit.js +11 -7
  97. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/edit.js.map +1 -1
  98. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/find.d.ts +4 -0
  99. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/find.d.ts.map +1 -1
  100. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/find.js +5 -1
  101. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/find.js.map +1 -1
  102. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/grep.d.ts +4 -0
  103. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/grep.d.ts.map +1 -1
  104. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/grep.js +5 -1
  105. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/grep.js.map +1 -1
  106. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/ls.d.ts +4 -0
  107. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/ls.d.ts.map +1 -1
  108. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/ls.js +5 -1
  109. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/ls.js.map +1 -1
  110. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/read.d.ts +4 -0
  111. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/read.d.ts.map +1 -1
  112. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/read.js +6 -2
  113. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/read.js.map +1 -1
  114. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/write.d.ts +4 -0
  115. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/write.d.ts.map +1 -1
  116. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/write.js +6 -2
  117. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/write.js.map +1 -1
  118. package/node_modules/@earendil-works/pi-coding-agent/dist/main.d.ts.map +1 -1
  119. package/node_modules/@earendil-works/pi-coding-agent/dist/main.js +57 -21
  120. package/node_modules/@earendil-works/pi-coding-agent/dist/main.js.map +1 -1
  121. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +3 -0
  122. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  123. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/interactive-mode.js +234 -2561
  124. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  125. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  126. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/theme/theme.js +11 -3
  127. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/theme/theme.js.map +1 -1
  128. package/node_modules/@earendil-works/pi-coding-agent/dist/server/create-harness.d.ts +26 -0
  129. package/node_modules/@earendil-works/pi-coding-agent/dist/server/create-harness.d.ts.map +1 -0
  130. package/node_modules/@earendil-works/pi-coding-agent/dist/server/create-harness.js +104 -0
  131. package/node_modules/@earendil-works/pi-coding-agent/dist/server/create-harness.js.map +1 -0
  132. package/node_modules/@earendil-works/pi-coding-agent/docs/extensions.md +3 -2
  133. package/node_modules/@earendil-works/pi-coding-agent/docs/keybindings.md +9 -5
  134. package/node_modules/@earendil-works/pi-coding-agent/docs/providers.md +8 -1
  135. package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  136. package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/custom-provider-anthropic/package.json +1 -1
  137. package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  138. package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/gondolin/package-lock.json +2 -2
  139. package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/gondolin/package.json +1 -1
  140. package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/sandbox/package-lock.json +2 -2
  141. package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/sandbox/package.json +1 -1
  142. package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/with-deps/package-lock.json +2 -2
  143. package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/with-deps/package.json +1 -1
  144. package/node_modules/@earendil-works/pi-coding-agent/package.json +7 -7
  145. package/node_modules/@earendil-works/pi-protocol/package.json +1 -1
  146. package/node_modules/@earendil-works/pi-telemetry/package.json +1 -1
  147. package/node_modules/@earendil-works/pi-tui/dist/keybindings.d.ts +10 -0
  148. package/node_modules/@earendil-works/pi-tui/dist/keybindings.d.ts.map +1 -1
  149. package/node_modules/@earendil-works/pi-tui/dist/keybindings.js +8 -0
  150. package/node_modules/@earendil-works/pi-tui/dist/keybindings.js.map +1 -1
  151. package/node_modules/@earendil-works/pi-tui/dist/latex.d.ts.map +1 -1
  152. package/node_modules/@earendil-works/pi-tui/dist/latex.js +188 -48
  153. package/node_modules/@earendil-works/pi-tui/dist/latex.js.map +1 -1
  154. package/node_modules/@earendil-works/pi-tui/dist/tui-alt-screen.d.ts +12 -0
  155. package/node_modules/@earendil-works/pi-tui/dist/tui-alt-screen.d.ts.map +1 -1
  156. package/node_modules/@earendil-works/pi-tui/dist/tui-alt-screen.js +146 -12
  157. package/node_modules/@earendil-works/pi-tui/dist/tui-alt-screen.js.map +1 -1
  158. package/node_modules/@earendil-works/pi-tui/package.json +1 -1
  159. package/node_modules/@types/node/README.md +1 -1
  160. package/node_modules/@types/node/child_process.d.ts +0 -1
  161. package/node_modules/@types/node/crypto.d.ts +6 -6
  162. package/node_modules/@types/node/dns/promises.d.ts +5 -5
  163. package/node_modules/@types/node/dns.d.ts +5 -5
  164. package/node_modules/@types/node/fs.d.ts +7 -0
  165. package/node_modules/@types/node/http.d.ts +30 -0
  166. package/node_modules/@types/node/http2.d.ts +4 -4
  167. package/node_modules/@types/node/os.d.ts +30 -30
  168. package/node_modules/@types/node/package.json +2 -2
  169. package/node_modules/@types/node/perf_hooks.d.ts +2 -1
  170. package/node_modules/@types/node/process.d.ts +42 -43
  171. package/node_modules/@types/node/quic.d.ts +1049 -84
  172. package/node_modules/@types/node/sqlite.d.ts +14 -1
  173. package/node_modules/@types/node/stream/iter.d.ts +3 -2
  174. package/node_modules/@types/node/test.d.ts +61 -0
  175. package/node_modules/@types/node/v8.d.ts +20 -23
  176. package/package.json +6 -6
  177. package/src/prism-extensions/integrations/ai-memory-errors.ts +53 -0
  178. package/src/prism-extensions/integrations/ai-memory-system.ts +179 -15
  179. package/src/prism-extensions/integrations/research-tools.ts +3 -3
  180. package/src/prism-extensions/integrations/youtube.ts +6 -6
@@ -298,10 +298,23 @@ declare module "node:sqlite" {
298
298
  * Loads a shared library into the database connection. This method is a wrapper
299
299
  * around [`sqlite3_load_extension()`](https://www.sqlite.org/c3ref/load_extension.html). It is required to enable the
300
300
  * `allowExtension` option when constructing the `DatabaseSync` instance.
301
+ *
302
+ * ```js
303
+ * import { DatabaseSync } from 'node:sqlite';
304
+ * const database = new DatabaseSync(':memory:', { allowExtension: true });
305
+ *
306
+ * // Load using the entry point derived from the filename.
307
+ * database.loadExtension('./decimal.dylib');
308
+ *
309
+ * // Override the entry point when the derived name does not match.
310
+ * database.loadExtension('./base64.dylib', 'sqlite3_base64_init');
301
311
  * @since v22.13.0
302
312
  * @param path The path to the shared library to load.
313
+ * @param entryPoint The name of the extension's entry-point function. When
314
+ * omitted, SQLite derives the entry point from the shared library's filename;
315
+ * pass this argument explicitly when the derived name does not match.
303
316
  */
304
- loadExtension(path: string): void;
317
+ loadExtension(path: string, entryPoint?: string): void;
305
318
  /**
306
319
  * Enables or disables the `loadExtension` SQL function, and the `loadExtension()`
307
320
  * method. When `allowExtension` is `false` when constructing, you cannot enable
@@ -311,8 +311,9 @@ declare module "node:stream/iter" {
311
311
  * return `false` or `-1`, deferring to the async path. The per-write
312
312
  * `options.signal` parameter from the Writer interface is also ignored.
313
313
  *
314
- * The result is cached per instance -- calling `fromWritable()` twice with the
315
- * same stream returns the same Writer.
314
+ * The result is cached per instance and backpressure policy -- calling
315
+ * `fromWritable()` twice with the same stream and `backpressure` option returns
316
+ * the same Writer.
316
317
  *
317
318
  * For duck-typed streams that do not expose `writableHighWaterMark`,
318
319
  * `writableLength`, or similar properties, sensible defaults are used.
@@ -264,6 +264,15 @@ declare module "node:test" {
264
264
  * @since v22.1.0
265
265
  */
266
266
  testSkipPatterns?: string | RegExp | ReadonlyArray<string | RegExp> | undefined;
267
+ /**
268
+ * A tag name, or an array of tag names,
269
+ * used to filter tests by their declared tags. Tests must contain every
270
+ * listed tag to run. Equivalent to passing `--experimental-test-tag-filter`
271
+ * on the command line. See [Test tags](https://nodejs.org/docs/latest-v26.x/api/test.html#test-tags).
272
+ * @default undefined
273
+ * @since v26.2.0
274
+ */
275
+ testTagFilters?: string | readonly string[] | undefined;
267
276
  /**
268
277
  * The number of milliseconds after which the test execution will fail.
269
278
  * If unspecified, subtests inherit this value from their parent.
@@ -668,6 +677,12 @@ declare module "node:test" {
668
677
  * The nesting level of the test.
669
678
  */
670
679
  nesting: number;
680
+ /**
681
+ * The flattened lowercased tags declared on the test
682
+ * and its ancestor suites, in declaration order. Empty for untagged tests.
683
+ * See [Test tags](https://nodejs.org/docs/latest-v26.x/api/test.html#test-tags).
684
+ */
685
+ tags: string[];
671
686
  /**
672
687
  * A numeric identifier for this test instance, unique
673
688
  * within the test file's process. Consistent across all events for the same
@@ -696,6 +711,12 @@ declare module "node:test" {
696
711
  * The nesting level of the test.
697
712
  */
698
713
  nesting: number;
714
+ /**
715
+ * The flattened lowercased tags declared on the test
716
+ * and its ancestor suites, in declaration order. Empty for untagged tests.
717
+ * See [Test tags](https://nodejs.org/docs/latest-v26.x/api/test.html#test-tags).
718
+ */
719
+ tags: string[];
699
720
  /**
700
721
  * A numeric identifier for this test instance, unique
701
722
  * within the test file's process. Consistent across all events for the same
@@ -717,6 +738,12 @@ declare module "node:test" {
717
738
  * The nesting level of the test.
718
739
  */
719
740
  nesting: number;
741
+ /**
742
+ * The flattened lowercased tags declared on the test
743
+ * and its ancestor suites, in declaration order. Empty for untagged tests.
744
+ * See [Test tags](https://nodejs.org/docs/latest-v26.x/api/test.html#test-tags).
745
+ */
746
+ tags: string[];
720
747
  /**
721
748
  * A numeric identifier for this test instance, unique
722
749
  * within the test file's process. Consistent across all events for the same
@@ -762,6 +789,12 @@ declare module "node:test" {
762
789
  * The nesting level of the test.
763
790
  */
764
791
  nesting: number;
792
+ /**
793
+ * The flattened lowercased tags declared on the test
794
+ * and its ancestor suites, in declaration order. Empty for untagged tests.
795
+ * See [Test tags](https://nodejs.org/docs/latest-v26.x/api/test.html#test-tags).
796
+ */
797
+ tags: string[];
765
798
  /**
766
799
  * A numeric identifier for this test instance, unique
767
800
  * within the test file's process. Consistent across all events for the same
@@ -823,6 +856,12 @@ declare module "node:test" {
823
856
  * The nesting level of the test.
824
857
  */
825
858
  nesting: number;
859
+ /**
860
+ * The flattened lowercased tags declared on the test
861
+ * and its ancestor suites, in declaration order. Empty for untagged tests.
862
+ * See [Test tags](https://nodejs.org/docs/latest-v26.x/api/test.html#test-tags).
863
+ */
864
+ tags: string[];
826
865
  /**
827
866
  * A numeric identifier for this test instance, unique
828
867
  * within the test file's process. Consistent across all events for the same
@@ -861,6 +900,12 @@ declare module "node:test" {
861
900
  * The nesting level of the test.
862
901
  */
863
902
  nesting: number;
903
+ /**
904
+ * The flattened lowercased tags declared on the test
905
+ * and its ancestor suites, in declaration order. Empty for untagged tests.
906
+ * See [Test tags](https://nodejs.org/docs/latest-v26.x/api/test.html#test-tags).
907
+ */
908
+ tags: string[];
864
909
  /**
865
910
  * A numeric identifier for this test instance, unique
866
911
  * within the test file's process. Consistent across all events for the same
@@ -1082,6 +1127,13 @@ declare module "node:test" {
1082
1127
  * @since v21.7.0, v20.12.0
1083
1128
  */
1084
1129
  readonly attempt: number;
1130
+ /**
1131
+ * A frozen array of the test's flattened lowercased tags, in declaration
1132
+ * order, including any tags inherited from ancestor suites. Empty when the
1133
+ * test has no tags. See [Test tags](https://nodejs.org/docs/latest-v26.x/api/test.html#test-tags).
1134
+ * @since v26.2.0
1135
+ */
1136
+ readonly tags: readonly string[];
1085
1137
  /**
1086
1138
  * The unique identifier of the worker running the current test file. This value is
1087
1139
  * derived from the `NODE_TEST_WORKER_ID` environment variable. When running tests
@@ -1443,6 +1495,15 @@ declare module "node:test" {
1443
1495
  * @default false
1444
1496
  */
1445
1497
  skip?: boolean | string | undefined;
1498
+ /**
1499
+ * An array of string labels associated with the test.
1500
+ * Used together with `--experimental-test-tag-filter` to filter which
1501
+ * tests run. Tags inherit from suites to nested tests by union. See
1502
+ * [Test tags](https://nodejs.org/docs/latest-v26.x/api/test.html#test-tags).
1503
+ * @default []
1504
+ * @since v26.2.0
1505
+ */
1506
+ tags?: readonly string[] | undefined;
1446
1507
  /**
1447
1508
  * A number of milliseconds the test will fail after. If unspecified, subtests inherit this
1448
1509
  * value from their parent.
@@ -89,23 +89,22 @@ declare module "node:v8" {
89
89
  * `total_allocated_bytes` The value of total allocated bytes since the Isolate
90
90
  * creation.
91
91
  *
92
- * ```js
92
+ * ```json
93
93
  * {
94
- * total_heap_size: 7326976,
95
- * total_heap_size_executable: 4194304,
96
- * total_physical_size: 7326976,
97
- * total_available_size: 1152656,
98
- * used_heap_size: 3476208,
99
- * heap_size_limit: 1535115264,
100
- * malloced_memory: 16384,
101
- * peak_malloced_memory: 1127496,
102
- * does_zap_garbage: 0,
103
- * number_of_native_contexts: 1,
104
- * number_of_detached_contexts: 0,
105
- * total_global_handles_size: 8192,
106
- * used_global_handles_size: 3296,
107
- * external_memory: 318824,
108
- * total_allocated_bytes: 45224088
94
+ * "total_heap_size": 7326976,
95
+ * "total_heap_size_executable": 4194304,
96
+ * "total_physical_size": 7326976,
97
+ * "total_available_size": 1152656,
98
+ * "used_heap_size": 3476208,
99
+ * "heap_size_limit": 1535115264,
100
+ * "malloced_memory": 16384,
101
+ * "peak_malloced_memory": 1127496,
102
+ * "does_zap_garbage": 0,
103
+ * "number_of_native_contexts": 1,
104
+ * "number_of_detached_contexts": 0,
105
+ * "total_global_handles_size": 8192,
106
+ * "used_global_handles_size": 3296,
107
+ * "external_memory": 318824
109
108
  * }
110
109
  * ```
111
110
  * @since v1.0.0
@@ -386,12 +385,12 @@ declare module "node:v8" {
386
385
  * V8 [`GetHeapCodeAndMetadataStatistics`](https://v8docs.nodesource.com/node-13.2/d5/dda/classv8_1_1_isolate.html#a6079122af17612ef54ef3348ce170866) API. Returns an object with the
387
386
  * following properties:
388
387
  *
389
- * ```js
388
+ * ```json
390
389
  * {
391
- * code_and_metadata_size: 212208,
392
- * bytecode_and_metadata_size: 161368,
393
- * external_script_source_size: 1410794,
394
- * cpu_profiler_metadata_size: 0,
390
+ * "code_and_metadata_size": 212208,
391
+ * "bytecode_and_metadata_size": 161368,
392
+ * "external_script_source_size": 1410794,
393
+ * "cpu_profiler_metadata_size": 0
395
394
  * }
396
395
  * ```
397
396
  * @since v12.8.0
@@ -963,8 +962,6 @@ declare module "node:v8" {
963
962
  * For example, if the `entry.js` contains the following script:
964
963
  *
965
964
  * ```js
966
- * 'use strict';
967
- *
968
965
  * import fs from 'node:fs';
969
966
  * import zlib from 'node:zlib';
970
967
  * import path from 'node:path';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@daniel156161/prism",
3
- "version": "0.2.78",
3
+ "version": "0.2.81",
4
4
  "description": "Prism-branded wrapper around pi that stores config in ~/.prism",
5
5
  "type": "module",
6
6
  "engines": {
@@ -25,9 +25,9 @@
25
25
  "package:publish:npm": "node scripts/publish-npm-package.mjs"
26
26
  },
27
27
  "dependencies": {
28
- "@earendil-works/pi-ai": "^0.84.0",
29
- "@earendil-works/pi-coding-agent": "^0.84.0",
30
- "pi-mcp-adapter": "^2.20.1",
28
+ "@earendil-works/pi-ai": "^0.84.1",
29
+ "@earendil-works/pi-coding-agent": "^0.84.1",
30
+ "pi-mcp-adapter": "^2.21.1",
31
31
  "pkce-challenge": "^6.0.0",
32
32
  "typebox": "^1.3.11"
33
33
  },
@@ -35,8 +35,8 @@
35
35
  "@earendil-works/pi-coding-agent"
36
36
  ],
37
37
  "devDependencies": {
38
- "@types/node": "^26.1.2",
39
- "tsx": "^4.23.9",
38
+ "@types/node": "^26.2.0",
39
+ "tsx": "^4.23.11",
40
40
  "typescript": "^7.0.2"
41
41
  },
42
42
  "files": [
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Error and diagnostics formatting for the AI Memory System integration.
3
+ *
4
+ * The API returns a structured error body (`{"error": {...}, "request_id": ...}`).
5
+ * Collapsing that into "HTTP 500 <raw json>" made intermittent failures
6
+ * impossible to trace, so the request id, the failing route and the query are
7
+ * kept in the message the model and the user actually see.
8
+ */
9
+
10
+ export type AiMemoryRequestContext = {
11
+ method: string
12
+ path: string
13
+ query?: string
14
+ filters?: Record<string, unknown>
15
+ }
16
+
17
+ function parseBody(text: string): any {
18
+ try {
19
+ return JSON.parse(text)
20
+ } catch {
21
+ return undefined
22
+ }
23
+ }
24
+
25
+ function errorDetail(body: any, fallback: string): string {
26
+ if (!body || typeof body !== "object") return fallback.slice(0, 500)
27
+ const error = body.error ?? body
28
+ const detail = error?.message ?? error?.detail ?? body?.detail
29
+ if (typeof detail === "string" && detail.trim()) return detail.trim()
30
+ if (Array.isArray(detail)) return detail.map((item: any) => item?.msg ?? JSON.stringify(item)).join("; ")
31
+ return fallback.slice(0, 500)
32
+ }
33
+
34
+ export function formatApiFailure(status: number, text: string, context: AiMemoryRequestContext): string {
35
+ const body = parseBody(text)
36
+ const requestId = body?.request_id ?? body?.error?.request_id
37
+ const code = body?.error?.code ?? body?.code
38
+ const parts = [`AI Memory ${context.method} ${context.path} failed: HTTP ${status}`]
39
+ parts.push(errorDetail(body, text))
40
+ if (code) parts.push(`code=${code}`)
41
+ if (requestId) parts.push(`request_id=${requestId}`)
42
+ if (context.query) parts.push(`query=${JSON.stringify(context.query)}`)
43
+ if (context.filters && Object.keys(context.filters).length > 0) parts.push(`filters=${JSON.stringify(context.filters)}`)
44
+ return parts.join(" | ")
45
+ }
46
+
47
+ export function formatDegradedNotice(degraded: any): string {
48
+ const stages: string[] = Array.isArray(degraded?.stages) ? degraded.stages : []
49
+ if (stages.length === 0) return ""
50
+ const errors = degraded?.errors ?? {}
51
+ const details = stages.map((stage) => (errors?.[stage] ? `${stage} (${errors[stage]})` : stage)).join(", ")
52
+ return `Warnung: Teilausfall im Retrieval — degradierte Stufen: ${details}. Ergebnisse können unvollständig sein.`
53
+ }
@@ -2,8 +2,10 @@ type ExtensionAPI = any
2
2
 
3
3
  import { Type } from "typebox"
4
4
  import { readSettings } from "../core/shared-config.js"
5
+ import { formatApiFailure, formatDegradedNotice } from "./ai-memory-errors.js"
5
6
  import { renderCollapsibleTextResult } from "../ui/collapsed-text-rendering.js"
6
- import { formatBracketedToolCall, renderSingleLineToolCall, type ThemeLike } from "../ui/tool-call-rendering.js"
7
+ import { obsidianOpenUrl } from "./obsidian-memory.js"
8
+ import { ansiHyperlink, formatBracketedToolCall, renderSingleLineToolCall, type ThemeLike } from "../ui/tool-call-rendering.js"
7
9
 
8
10
  const DEFAULT_BASE_URL = "http://127.0.0.1:8765"
9
11
  const DEFAULT_LIMIT = 8
@@ -108,7 +110,15 @@ async function postJson<T>(path: string, body: unknown, timeoutMs = aiMemoryRequ
108
110
  signal: AbortSignal.timeout(timeoutMs),
109
111
  })
110
112
  const text = await response.text()
111
- if (!response.ok) throw new Error(`AI Memory request failed: HTTP ${response.status} ${text.slice(0, 500)}`)
113
+ if (!response.ok) {
114
+ const payload = body as any
115
+ throw new Error(formatApiFailure(response.status, text, {
116
+ method: "POST",
117
+ path,
118
+ query: typeof payload?.query === "string" ? payload.query : undefined,
119
+ filters: payload?.filters,
120
+ }))
121
+ }
112
122
  return JSON.parse(text) as T
113
123
  }
114
124
 
@@ -121,7 +131,7 @@ async function putJson<T>(path: string, body: unknown, timeoutMs = aiMemoryReque
121
131
  signal: AbortSignal.timeout(timeoutMs),
122
132
  })
123
133
  const text = await response.text()
124
- if (!response.ok) throw new Error(`AI Memory request failed: HTTP ${response.status} ${text.slice(0, 500)}`)
134
+ if (!response.ok) throw new Error(formatApiFailure(response.status, text, { method: "PUT", path }))
125
135
  return JSON.parse(text) as T
126
136
  }
127
137
 
@@ -134,7 +144,7 @@ async function deleteJson<T>(path: string, body: unknown, timeoutMs = aiMemoryRe
134
144
  signal: AbortSignal.timeout(timeoutMs),
135
145
  })
136
146
  const text = await response.text()
137
- if (!response.ok) throw new Error(`AI Memory request failed: HTTP ${response.status} ${text.slice(0, 500)}`)
147
+ if (!response.ok) throw new Error(formatApiFailure(response.status, text, { method: "DELETE", path }))
138
148
  return JSON.parse(text) as T
139
149
  }
140
150
 
@@ -152,7 +162,7 @@ async function methodJson<T>(
152
162
  signal: AbortSignal.timeout(timeoutMs),
153
163
  })
154
164
  const text = await response.text()
155
- if (!response.ok) throw new Error(`AI Memory request failed: HTTP ${response.status} ${text.slice(0, 500)}`)
165
+ if (!response.ok) throw new Error(formatApiFailure(response.status, text, { method, path }))
156
166
  return JSON.parse(text) as T
157
167
  }
158
168
 
@@ -163,7 +173,7 @@ async function getJson<T>(path: string, timeoutMs = aiMemoryStatusTimeoutMs()):
163
173
  signal: AbortSignal.timeout(timeoutMs),
164
174
  })
165
175
  const text = await response.text()
166
- if (!response.ok) throw new Error(`AI Memory request failed: HTTP ${response.status} ${text.slice(0, 500)}`)
176
+ if (!response.ok) throw new Error(formatApiFailure(response.status, text, { method: "GET", path }))
167
177
  return JSON.parse(text) as T
168
178
  }
169
179
 
@@ -176,11 +186,19 @@ function formatSearchResults(results: any[]): string {
176
186
  const lexical = Number(row.lexical_score ?? 0).toFixed(4)
177
187
  const graph = Number(row.graph_score ?? 0).toFixed(1)
178
188
  const fact = Number(row.fact_score ?? 0).toFixed(1)
189
+ const feedback = Number(row.feedback_score ?? 0).toFixed(2)
190
+ const stale = Number(row.stale_score ?? 0).toFixed(2)
179
191
  const excerpt = String(row.text ?? "").replace(/\s+/g, " ").trim().slice(0, 700)
180
- return `${index + 1}. score=${score} vector=${vector} keyword=${keyword} lexical=${lexical} graph=${graph} fact=${fact}\nsource=${row.source}:${row.path} > ${row.heading}\n${excerpt}`
192
+ return `${index + 1}. score=${score} vector=${vector} keyword=${keyword} lexical=${lexical} graph=${graph} fact=${fact} feedback=${feedback} stale=${stale}\nsource=${row.source}:${row.path} > ${row.heading}\n${excerpt}`
181
193
  }).join("\n\n")
182
194
  }
183
195
 
196
+ export function formatSearchResponse(data: any): string {
197
+ const results = Array.isArray(data?.results) ? data.results : []
198
+ const notices = [formatDegradedNotice(data?.degraded), String(data?.hint ?? "").trim()].filter(Boolean)
199
+ return [...notices, formatSearchResults(results)].join("\n\n")
200
+ }
201
+
184
202
  function formatReadResult(data: any): string {
185
203
  const source = String(data?.source ?? "unknown")
186
204
  const path = String(data?.path ?? "unknown")
@@ -277,9 +295,25 @@ function renderAiMemoryResult(result: any, options: { expanded?: boolean; isPart
277
295
  })
278
296
  }
279
297
 
280
- function formatAiMemoryToolCall(toolName: string, args: any, theme: ThemeLike): string {
298
+ export function formatAiMemoryToolCall(toolName: string, args: any, theme: ThemeLike): string {
281
299
  const label = toolName.replace(/^ai_memory_/, "ai memory ").replace(/_/g, " ")
282
- return formatBracketedToolCall(label, args?.query || args?.id || args?.path || args?.session || "", theme)
300
+ const rawValue = args?.query || args?.id || args?.path || args?.session || ""
301
+ const value = typeof rawValue === "string" && rawValue.trim() ? rawValue.trim() : "..."
302
+ const linksToVaultNote = ["ai_memory_vault_write", "ai_memory_vault_edit", "ai_memory_vault_delete"].includes(toolName)
303
+ if (linksToVaultNote && value !== "...") {
304
+ return `${theme.fg("toolTitle", theme.bold(label))} ${theme.fg("accent", ansiHyperlink(obsidianOpenUrl(value), value))}`
305
+ }
306
+ return formatBracketedToolCall(label, value, theme)
307
+ }
308
+
309
+ function formatVaultWriteResult(action: "Wrote" | "Edited", params: any, responsePath: unknown): string {
310
+ const path = String(responsePath ?? params.path ?? "")
311
+ const frontmatter = params.frontmatter === undefined ? "" : String(params.frontmatter).trim()
312
+ const heading = action === "Edited" ? `# ${String(params.heading ?? "").trim()}` : ""
313
+ const body = String(params.body ?? "")
314
+ const writtenContent = [frontmatter, heading, body].filter(Boolean).join("\n\n") || "(empty)"
315
+ const status = action === "Wrote" ? `Wrote: ${path}\n\n` : ""
316
+ return `${status}Write content:\n${writtenContent}`
283
317
  }
284
318
 
285
319
  export default function aiMemorySystemExtension(pi: ExtensionAPI): void {
@@ -305,12 +339,12 @@ export default function aiMemorySystemExtension(pi: ExtensionAPI): void {
305
339
  path: Type.Optional(Type.String({ description: "Path substring filter" })),
306
340
  }),
307
341
  async execute(_id: string, params: any) {
308
- const data = await postJson<{ results: any[] }>("/search", {
342
+ const data = await postJson<{ results: any[]; degraded?: any; hint?: string }>("/search", {
309
343
  query: String(params.query ?? ""),
310
344
  limit: clampLimit(params.limit),
311
345
  filters: cleanFilters(params),
312
346
  })
313
- return { content: [{ type: "text", text: formatSearchResults(data.results) }], details: { count: data.results?.length ?? 0, results: data.results } }
347
+ return { content: [{ type: "text", text: formatSearchResponse(data) }], details: { count: data.results?.length ?? 0, results: data.results, degraded: data.degraded, hint: data.hint } }
314
348
  },
315
349
  renderCall(args: any, theme: ThemeLike, context: any) {
316
350
  return renderSingleLineToolCall(formatAiMemoryToolCall("ai_memory_search", args, theme), context)
@@ -369,6 +403,130 @@ export default function aiMemorySystemExtension(pi: ExtensionAPI): void {
369
403
  },
370
404
  })
371
405
 
406
+ pi.registerTool({
407
+ name: "ai_memory_rate_result",
408
+ label: "AI Memory Rate Result",
409
+ description: "Rate one retrieved memory result so future ranking learns from it.",
410
+ promptSnippet: "Use ai_memory_rate_result when a retrieved memory was clearly helpful, irrelevant, outdated, or wrong.",
411
+ promptGuidelines: [
412
+ "Rate the path exactly as returned by ai_memory_search.",
413
+ "Use 'outdated' or 'wrong' when the user corrects information that came from memory.",
414
+ "This changes ranking only; it never writes or deletes a note.",
415
+ ],
416
+ parameters: Type.Object({
417
+ path: Type.String({ description: "Path of the rated result, e.g. 02 Projekte/Memory.md" }),
418
+ rating: Type.String({ description: "helpful, irrelevant, outdated, or wrong" }),
419
+ query: Type.Optional(Type.String({ description: "Search query that produced the result" })),
420
+ reason: Type.Optional(Type.String({ description: "Short reason in German" })),
421
+ source: Type.Optional(Type.String({ description: "obsidian or prism_session" })),
422
+ chunk_id: Type.Optional(Type.String({ description: "Exact chunk id if known" })),
423
+ session_id: Type.Optional(Type.String({ description: "Originating session id" })),
424
+ }),
425
+ async execute(_id: string, params: any) {
426
+ const data = await postJson<any>("/retrieval-feedback", {
427
+ path: String(params.path ?? ""),
428
+ rating: String(params.rating ?? ""),
429
+ query: String(params.query ?? ""),
430
+ reason: String(params.reason ?? ""),
431
+ source: String(params.source ?? ""),
432
+ chunk_id: String(params.chunk_id ?? ""),
433
+ session_id: String(params.session_id ?? ""),
434
+ })
435
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }], details: data }
436
+ },
437
+ renderCall(args: any, theme: ThemeLike, context: any) {
438
+ return renderSingleLineToolCall(formatAiMemoryToolCall("ai_memory_rate_result", args, theme), context)
439
+ },
440
+ })
441
+
442
+ pi.registerTool({
443
+ name: "ai_memory_session_review",
444
+ label: "AI Memory Session Review",
445
+ description: "Review a Prism session for outcome evidence and human-approved durable lesson proposals.",
446
+ promptSnippet: "Review a completed session and return pending lessons; this does not approve them.",
447
+ parameters: Type.Object({
448
+ session: Type.Optional(Type.String({ description: "Session path/id or latest" })),
449
+ force: Type.Optional(Type.Boolean({ description: "Review an unchanged session again" })),
450
+ }),
451
+ async execute(_id: string, params: any) {
452
+ const data = await postJson<any>("/session-review", {
453
+ session: typeof params.session === "string" ? params.session : "latest",
454
+ force: params.force === true,
455
+ })
456
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }], details: data }
457
+ },
458
+ renderCall(args: any, theme: ThemeLike, context: any) {
459
+ return renderSingleLineToolCall(formatAiMemoryToolCall("ai_memory_session_review", args, theme), context)
460
+ },
461
+ })
462
+
463
+ pi.registerTool({
464
+ name: "ai_memory_propose_lesson",
465
+ label: "AI Memory Propose Lesson",
466
+ description: "Create a pending durable lesson proposal. User approval is still required before writing memory.",
467
+ promptSnippet: "Propose a lesson from explicit user feedback without promoting it automatically.",
468
+ parameters: Type.Object({
469
+ category: Type.String({ description: "correction, decision, preference, workflow, or quality" }),
470
+ title: Type.String({ description: "Short German title" }),
471
+ rule: Type.String({ description: "Proposed durable rule in German" }),
472
+ rationale: Type.Optional(Type.String({ description: "Why the rule matters" })),
473
+ evidence: Type.String({ description: "Exact user feedback supporting the proposal" }),
474
+ session_id: Type.Optional(Type.String({ description: "Originating session id" })),
475
+ confidence: Type.Optional(Type.Number({ description: "Confidence from 0 to 1" })),
476
+ }),
477
+ async execute(_id: string, params: any) {
478
+ const data = await postJson<any>("/lessons", {
479
+ category: String(params.category ?? ""),
480
+ title: String(params.title ?? ""),
481
+ rule: String(params.rule ?? ""),
482
+ rationale: String(params.rationale ?? ""),
483
+ evidence: String(params.evidence ?? ""),
484
+ session_id: String(params.session_id ?? "manual"),
485
+ confidence: params.confidence === undefined ? 0.8 : Number(params.confidence),
486
+ })
487
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }], details: data }
488
+ },
489
+ renderCall(args: any, theme: ThemeLike, context: any) {
490
+ return renderSingleLineToolCall(formatAiMemoryToolCall("ai_memory_propose_lesson", args, theme), context)
491
+ },
492
+ })
493
+
494
+ pi.registerTool({
495
+ name: "ai_memory_feedback",
496
+ label: "AI Memory Feedback",
497
+ description: "List pending lessons or approve/reject one proposal. Approval writes durable memory.",
498
+ promptSnippet: "Use only after explicit user approval to approve a proposed durable lesson.",
499
+ promptGuidelines: ["Never approve a lesson without explicit user approval in the current conversation."],
500
+ parameters: Type.Object({
501
+ action: Type.Optional(Type.String({ description: "list (default), approve, or reject" })),
502
+ proposal_id: Type.Optional(Type.String({ description: "Required for approve/reject" })),
503
+ status: Type.Optional(Type.String({ description: "List filter: pending, approved, or rejected" })),
504
+ session_id: Type.Optional(Type.String({ description: "Optional session filter for listing" })),
505
+ }),
506
+ async execute(_id: string, params: any) {
507
+ const action = String(params.action ?? "list")
508
+ let data: any
509
+ if (action === "list") {
510
+ const query = new URLSearchParams()
511
+ if (params.status) query.set("status", String(params.status))
512
+ if (params.session_id) query.set("session_id", String(params.session_id))
513
+ data = await getJson<any>(`/lessons${query.size ? `?${query}` : ""}`, aiMemoryRequestTimeoutMs())
514
+ } else {
515
+ if (!["approve", "reject"].includes(action) || !params.proposal_id) {
516
+ throw new Error("action must be list, approve, or reject; proposal_id is required for decisions")
517
+ }
518
+ data = await methodJson<any>("DECISION", "/lessons", aiMemoryRequestTimeoutMs(), {
519
+ proposal_id: String(params.proposal_id),
520
+ decision: action,
521
+ })
522
+ }
523
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }], details: data }
524
+ },
525
+ renderCall(args: any, theme: ThemeLike, context: any) {
526
+ return renderSingleLineToolCall(formatAiMemoryToolCall("ai_memory_feedback", args, theme), context)
527
+ },
528
+ })
529
+
372
530
  // ── File-backed vault ops (write/edit/list/delete) ──
373
531
 
374
532
  pi.registerTool({
@@ -396,7 +554,7 @@ export default function aiMemorySystemExtension(pi: ExtensionAPI): void {
396
554
  name: "ai_memory_vault_write",
397
555
  label: "AI Memory Vault Write",
398
556
  description: "Create/overwrite a Markdown file in the configured Obsidian vault.",
399
- promptSnippet: "Write vault file [path + body/frontmatter]",
557
+ promptSnippet: "Write vault file [show path and written body/frontmatter]",
400
558
  parameters: Type.Object({
401
559
  path: Type.String({ description: "Vault-relative path, e.g. 02 Projekte/Test.md" }),
402
560
  frontmatter: Type.Optional(Type.String({ description: "Optional YAML frontmatter including leading/trailing --- lines" })),
@@ -408,18 +566,21 @@ export default function aiMemorySystemExtension(pi: ExtensionAPI): void {
408
566
  frontmatter: params.frontmatter !== undefined ? String(params.frontmatter) : null,
409
567
  body: String(params.body ?? ""),
410
568
  })
411
- return { content: [{ type: "text", text: `Wrote: ${data.path ?? params.path}` }], details: data }
569
+ return { content: [{ type: "text", text: formatVaultWriteResult("Wrote", params, data.path) }], details: data }
412
570
  },
413
571
  renderCall(args: any, theme: ThemeLike, context: any) {
414
572
  return renderSingleLineToolCall(formatAiMemoryToolCall("ai_memory_vault_write", args, theme), context)
415
573
  },
574
+ renderResult(result: any, options: any, theme: ThemeLike) {
575
+ return renderAiMemoryResult(result, options, theme)
576
+ },
416
577
  })
417
578
 
418
579
  pi.registerTool({
419
580
  name: "ai_memory_vault_edit",
420
581
  label: "AI Memory Vault Edit",
421
582
  description: "Replace the content block under a given heading in a Markdown file (or append if missing).",
422
- promptSnippet: "Edit vault file section by heading",
583
+ promptSnippet: "Edit vault file section [show path, heading, and written body/frontmatter]",
423
584
  parameters: Type.Object({
424
585
  path: Type.String({ description: "Vault-relative path" }),
425
586
  heading: Type.String({ description: "Heading text without leading #, e.g. 'Update 2026-01-01 — Thema'" }),
@@ -433,11 +594,14 @@ export default function aiMemorySystemExtension(pi: ExtensionAPI): void {
433
594
  frontmatter: params.frontmatter !== undefined ? String(params.frontmatter) : null,
434
595
  body: String(params.body ?? ""),
435
596
  })
436
- return { content: [{ type: "text", text: `Edited: ${data.path ?? params.path}` }], details: data }
597
+ return { content: [{ type: "text", text: formatVaultWriteResult("Edited", params, data.path) }], details: data }
437
598
  },
438
599
  renderCall(args: any, theme: ThemeLike, context: any) {
439
600
  return renderSingleLineToolCall(formatAiMemoryToolCall("ai_memory_vault_edit", args, theme), context)
440
601
  },
602
+ renderResult(result: any, options: any, theme: ThemeLike) {
603
+ return renderAiMemoryResult(result, options, theme)
604
+ },
441
605
  })
442
606
 
443
607
  pi.registerTool({
@@ -332,7 +332,7 @@ export async function webSearch(query: string, limit = 10): Promise<string> {
332
332
  export async function webFetch(url: string, maxChars = 20_000): Promise<string> {
333
333
  const safeUrl = validateHttpUrl(url)
334
334
  const safeMaxChars = clampLimit(maxChars, 20_000, 1_000, 100_000)
335
- if (isYouTubeUrl(safeUrl)) return fetchYouTubeContent(safeUrl, safeMaxChars)
335
+ if (isYouTubeUrl(safeUrl)) return fetchYouTubeContent(safeUrl)
336
336
  const response = await fetch(safeUrl, { headers: { "User-Agent": "Mozilla/5.0 (compatible; prism-research/1.0)" }, signal: AbortSignal.timeout(15_000) })
337
337
  const text = await response.text()
338
338
  if (!response.ok) throw new Error(`Fetch failed for ${safeUrl}: HTTP ${response.status}`)
@@ -392,8 +392,8 @@ export default function researchToolsExtension(pi: ExtensionAPI): void {
392
392
  pi.registerTool({
393
393
  name: "web_fetch",
394
394
  label: "web fetch",
395
- description: "Fetch readable text from a web page for citation-backed research. For YouTube URLs, extracts structured video metadata (title, channel, description, duration, views, publish date) and transcript when available.",
396
- promptSnippet: "Fetch readable text from a web page. For YouTube URLs, extracts structured video metadata (title, channel, description, duration, views, publish date) and transcript when available.",
395
+ description: "Fetch readable text from a web page for citation-backed research. For YouTube URLs, extracts structured video metadata and the complete transcript without applying maxChars truncation.",
396
+ promptSnippet: "Fetch readable text from a web page. For YouTube URLs, extracts structured video metadata and the complete transcript without truncation.",
397
397
  parameters: Type.Object({ url: Type.String(), maxChars: Type.Optional(Type.Number()) }),
398
398
  async execute(_id: string, params: any) {
399
399
  return { content: [{ type: "text", text: await webFetch(params.url, params.maxChars || 20_000) }], details: { url: params.url } }