@oh-my-pi/pi-coding-agent 15.5.15 → 15.7.0

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 (274) hide show
  1. package/CHANGELOG.md +81 -0
  2. package/dist/types/capability/rule-buckets.d.ts +30 -0
  3. package/dist/types/capability/rule.d.ts +7 -0
  4. package/dist/types/cli/classify-install-target.d.ts +0 -10
  5. package/dist/types/cli/completion-gen.d.ts +80 -0
  6. package/dist/types/cli/initial-message.d.ts +1 -1
  7. package/dist/types/cli/tiny-models-cli.d.ts +9 -0
  8. package/dist/types/commands/complete.d.ts +6 -0
  9. package/dist/types/commands/completions.d.ts +13 -0
  10. package/dist/types/commands/setup.d.ts +10 -1
  11. package/dist/types/commands/tiny-models.d.ts +22 -0
  12. package/dist/types/commit/analysis/conventional.d.ts +1 -1
  13. package/dist/types/commit/analysis/summary.d.ts +1 -1
  14. package/dist/types/commit/changelog/generate.d.ts +1 -1
  15. package/dist/types/commit/changelog/index.d.ts +2 -2
  16. package/dist/types/commit/map-reduce/map-phase.d.ts +1 -1
  17. package/dist/types/commit/map-reduce/reduce-phase.d.ts +1 -1
  18. package/dist/types/config/model-id-affixes.d.ts +10 -0
  19. package/dist/types/config/settings-schema.d.ts +402 -17
  20. package/dist/types/discovery/builtin-defaults.d.ts +1 -0
  21. package/dist/types/discovery/builtin-rules/index.d.ts +7 -0
  22. package/dist/types/discovery/helpers.d.ts +1 -1
  23. package/dist/types/discovery/index.d.ts +1 -0
  24. package/dist/types/discovery/substitute-plugin-root.d.ts +0 -4
  25. package/dist/types/edit/hashline/block-resolver.d.ts +9 -0
  26. package/dist/types/edit/hashline/index.d.ts +1 -0
  27. package/dist/types/eval/js/shared/rewrite-imports.d.ts +16 -1
  28. package/dist/types/eval/py/kernel.d.ts +3 -0
  29. package/dist/types/eval/py/runtime.d.ts +11 -1
  30. package/dist/types/export/html/template.generated.d.ts +1 -1
  31. package/dist/types/internal-urls/agent-protocol.d.ts +2 -1
  32. package/dist/types/internal-urls/artifact-protocol.d.ts +2 -1
  33. package/dist/types/internal-urls/local-protocol.d.ts +2 -1
  34. package/dist/types/internal-urls/memory-protocol.d.ts +2 -1
  35. package/dist/types/internal-urls/omp-protocol.d.ts +2 -1
  36. package/dist/types/internal-urls/router.d.ts +8 -1
  37. package/dist/types/internal-urls/rule-protocol.d.ts +2 -1
  38. package/dist/types/internal-urls/skill-protocol.d.ts +2 -1
  39. package/dist/types/internal-urls/types.d.ts +26 -0
  40. package/dist/types/main.d.ts +1 -0
  41. package/dist/types/memory-backend/index.d.ts +1 -0
  42. package/dist/types/memory-backend/resolve.d.ts +2 -1
  43. package/dist/types/memory-backend/types.d.ts +7 -1
  44. package/dist/types/mnemosyne/backend.d.ts +4 -0
  45. package/dist/types/mnemosyne/config.d.ts +29 -0
  46. package/dist/types/mnemosyne/index.d.ts +3 -0
  47. package/dist/types/mnemosyne/state.d.ts +72 -0
  48. package/dist/types/modes/components/custom-editor.d.ts +2 -3
  49. package/dist/types/modes/components/hook-selector.d.ts +27 -0
  50. package/dist/types/modes/components/index.d.ts +2 -0
  51. package/dist/types/modes/components/segment-track.d.ts +22 -0
  52. package/dist/types/modes/components/status-line/context-thresholds.d.ts +6 -0
  53. package/dist/types/modes/components/tiny-title-download-progress.d.ts +11 -0
  54. package/dist/types/modes/components/welcome.d.ts +22 -0
  55. package/dist/types/modes/controllers/extension-ui-controller.d.ts +4 -1
  56. package/dist/types/modes/gradient-highlight.d.ts +23 -0
  57. package/dist/types/modes/interactive-mode.d.ts +7 -4
  58. package/dist/types/modes/internal-url-autocomplete.d.ts +43 -0
  59. package/dist/types/modes/orchestrate.d.ts +10 -0
  60. package/dist/types/modes/setup-wizard/index.d.ts +16 -0
  61. package/dist/types/modes/setup-wizard/scenes/glyph.d.ts +2 -0
  62. package/dist/types/modes/setup-wizard/scenes/outro.d.ts +2 -0
  63. package/dist/types/modes/setup-wizard/scenes/providers.d.ts +2 -0
  64. package/dist/types/modes/setup-wizard/scenes/sign-in.d.ts +19 -0
  65. package/dist/types/modes/setup-wizard/scenes/splash.d.ts +11 -0
  66. package/dist/types/modes/setup-wizard/scenes/theme.d.ts +2 -0
  67. package/dist/types/modes/setup-wizard/scenes/types.d.ts +43 -0
  68. package/dist/types/modes/setup-wizard/scenes/web-search.d.ts +19 -0
  69. package/dist/types/modes/setup-wizard/wizard-overlay.d.ts +14 -0
  70. package/dist/types/modes/theme/defaults/index.d.ts +8406 -8406
  71. package/dist/types/modes/theme/shimmer.d.ts +2 -0
  72. package/dist/types/modes/theme/theme.d.ts +11 -0
  73. package/dist/types/modes/types.d.ts +5 -1
  74. package/dist/types/modes/ultrathink.d.ts +3 -3
  75. package/dist/types/modes/utils/keybinding-matchers.d.ts +5 -0
  76. package/dist/types/sdk.d.ts +3 -0
  77. package/dist/types/session/agent-session.d.ts +33 -0
  78. package/dist/types/system-prompt.d.ts +2 -0
  79. package/dist/types/task/executor.d.ts +2 -0
  80. package/dist/types/task/render.d.ts +5 -1
  81. package/dist/types/tiny/device.d.ts +78 -0
  82. package/dist/types/tiny/dtype.d.ts +85 -0
  83. package/dist/types/tiny/models.d.ts +185 -0
  84. package/dist/types/tiny/text.d.ts +19 -0
  85. package/dist/types/tiny/title-client.d.ts +32 -0
  86. package/dist/types/tiny/title-protocol.d.ts +74 -0
  87. package/dist/types/tiny/worker.d.ts +2 -0
  88. package/dist/types/tools/bash.d.ts +3 -2
  89. package/dist/types/tools/eval.d.ts +1 -1
  90. package/dist/types/tools/index.d.ts +7 -4
  91. package/dist/types/tools/memory-edit.d.ts +40 -0
  92. package/dist/types/tools/{hindsight-recall.d.ts → memory-recall.d.ts} +6 -6
  93. package/dist/types/tools/{hindsight-reflect.d.ts → memory-reflect.d.ts} +6 -6
  94. package/dist/types/tools/memory-render.d.ts +60 -0
  95. package/dist/types/tools/{hindsight-retain.d.ts → memory-retain.d.ts} +6 -6
  96. package/dist/types/tools/todo-write.d.ts +8 -0
  97. package/dist/types/tools/tool-result.d.ts +2 -0
  98. package/dist/types/tui/code-cell.d.ts +2 -0
  99. package/dist/types/tui/output-block.d.ts +17 -0
  100. package/dist/types/utils/title-generator.d.ts +3 -0
  101. package/package.json +18 -14
  102. package/scripts/build-binary.ts +1 -0
  103. package/src/capability/rule-buckets.ts +64 -0
  104. package/src/capability/rule.ts +8 -0
  105. package/src/cli/completion-gen.ts +550 -0
  106. package/src/cli/setup-cli.ts +5 -3
  107. package/src/cli/tiny-models-cli.ts +127 -0
  108. package/src/cli-commands.ts +3 -0
  109. package/src/cli.ts +9 -15
  110. package/src/commands/complete.ts +66 -0
  111. package/src/commands/completions.ts +60 -0
  112. package/src/commands/setup.ts +29 -4
  113. package/src/commands/tiny-models.ts +36 -0
  114. package/src/config/model-equivalence.ts +43 -2
  115. package/src/config/model-id-affixes.ts +64 -0
  116. package/src/config/model-registry.ts +84 -10
  117. package/src/config/settings-schema.ts +275 -15
  118. package/src/discovery/builtin-defaults.ts +39 -0
  119. package/src/discovery/builtin-rules/index.ts +48 -0
  120. package/src/discovery/builtin-rules/rs-box-leak.md +48 -0
  121. package/src/discovery/builtin-rules/rs-future-prelude.md +23 -0
  122. package/src/discovery/builtin-rules/rs-lazylock.md +51 -0
  123. package/src/discovery/builtin-rules/rs-match-ergonomics.md +67 -0
  124. package/src/discovery/builtin-rules/rs-parking-lot.md +44 -0
  125. package/src/discovery/builtin-rules/rs-result-type.md +19 -0
  126. package/src/discovery/builtin-rules/ts-bare-catch.md +38 -0
  127. package/src/discovery/builtin-rules/ts-import-type.md +42 -0
  128. package/src/discovery/builtin-rules/ts-no-any.md +56 -0
  129. package/src/discovery/builtin-rules/ts-no-dynamic-import.md +39 -0
  130. package/src/discovery/builtin-rules/ts-no-return-type.md +45 -0
  131. package/src/discovery/builtin-rules/ts-no-tiny-functions.md +50 -0
  132. package/src/discovery/builtin-rules/ts-promise-with-resolvers.md +65 -0
  133. package/src/discovery/builtin-rules/ts-set-map.md +28 -0
  134. package/src/discovery/index.ts +1 -0
  135. package/src/edit/hashline/block-resolver.ts +14 -0
  136. package/src/edit/hashline/diff.ts +9 -8
  137. package/src/edit/hashline/execute.ts +2 -1
  138. package/src/edit/hashline/index.ts +1 -0
  139. package/src/eval/__tests__/shared-executors.test.ts +36 -0
  140. package/src/eval/js/shared/local-module-loader.ts +13 -1
  141. package/src/eval/js/shared/rewrite-imports.ts +31 -26
  142. package/src/eval/py/kernel.ts +37 -15
  143. package/src/eval/py/runtime.ts +57 -28
  144. package/src/export/html/template.generated.ts +1 -1
  145. package/src/export/html/template.js +0 -12
  146. package/src/export/ttsr.ts +2 -0
  147. package/src/internal-urls/agent-protocol.ts +18 -1
  148. package/src/internal-urls/artifact-protocol.ts +19 -1
  149. package/src/internal-urls/docs-index.generated.ts +8 -7
  150. package/src/internal-urls/local-protocol.ts +14 -1
  151. package/src/internal-urls/memory-protocol.ts +6 -1
  152. package/src/internal-urls/omp-protocol.ts +5 -1
  153. package/src/internal-urls/router.ts +20 -1
  154. package/src/internal-urls/rule-protocol.ts +8 -1
  155. package/src/internal-urls/skill-protocol.ts +8 -1
  156. package/src/internal-urls/types.ts +27 -0
  157. package/src/lsp/render.ts +1 -1
  158. package/src/main.ts +18 -1
  159. package/src/mcp/oauth-flow.ts +2 -2
  160. package/src/memory-backend/index.ts +1 -0
  161. package/src/memory-backend/resolve.ts +4 -1
  162. package/src/memory-backend/types.ts +8 -1
  163. package/src/mnemosyne/backend.ts +374 -0
  164. package/src/mnemosyne/config.ts +160 -0
  165. package/src/mnemosyne/index.ts +3 -0
  166. package/src/mnemosyne/state.ts +548 -0
  167. package/src/modes/acp/acp-agent.ts +11 -6
  168. package/src/modes/components/agent-dashboard.ts +4 -4
  169. package/src/modes/components/custom-editor.ts +3 -2
  170. package/src/modes/components/diff.ts +2 -2
  171. package/src/modes/components/extensions/extension-list.ts +3 -2
  172. package/src/modes/components/footer.ts +5 -6
  173. package/src/modes/components/history-search.ts +3 -3
  174. package/src/modes/components/hook-selector.ts +92 -8
  175. package/src/modes/components/index.ts +2 -0
  176. package/src/modes/components/mcp-add-wizard.ts +3 -3
  177. package/src/modes/components/model-selector.ts +5 -4
  178. package/src/modes/components/oauth-selector.ts +3 -3
  179. package/src/modes/components/segment-track.ts +52 -0
  180. package/src/modes/components/session-observer-overlay.ts +19 -13
  181. package/src/modes/components/session-selector.ts +3 -3
  182. package/src/modes/components/settings-defs.ts +7 -0
  183. package/src/modes/components/status-line/context-thresholds.ts +11 -0
  184. package/src/modes/components/status-line/segments.ts +2 -2
  185. package/src/modes/components/tiny-title-download-progress.ts +90 -0
  186. package/src/modes/components/tips.txt +13 -0
  187. package/src/modes/components/tool-execution.ts +72 -4
  188. package/src/modes/components/tree-selector.ts +3 -3
  189. package/src/modes/components/user-message-selector.ts +3 -3
  190. package/src/modes/components/welcome.ts +102 -43
  191. package/src/modes/controllers/command-controller.ts +16 -1
  192. package/src/modes/controllers/extension-ui-controller.ts +3 -1
  193. package/src/modes/controllers/input-controller.ts +69 -21
  194. package/src/modes/gradient-highlight.ts +70 -0
  195. package/src/modes/interactive-mode.ts +75 -114
  196. package/src/modes/internal-url-autocomplete.ts +143 -0
  197. package/src/modes/orchestrate.ts +36 -0
  198. package/src/modes/prompt-action-autocomplete.ts +12 -0
  199. package/src/modes/setup-wizard/index.ts +88 -0
  200. package/src/modes/setup-wizard/scenes/glyph.ts +96 -0
  201. package/src/modes/setup-wizard/scenes/outro.ts +35 -0
  202. package/src/modes/setup-wizard/scenes/providers.ts +69 -0
  203. package/src/modes/setup-wizard/scenes/sign-in.ts +193 -0
  204. package/src/modes/setup-wizard/scenes/splash.ts +201 -0
  205. package/src/modes/setup-wizard/scenes/theme.ts +299 -0
  206. package/src/modes/setup-wizard/scenes/types.ts +48 -0
  207. package/src/modes/setup-wizard/scenes/web-search.ts +128 -0
  208. package/src/modes/setup-wizard/wizard-overlay.ts +275 -0
  209. package/src/modes/theme/shimmer.ts +5 -0
  210. package/src/modes/theme/theme.ts +44 -20
  211. package/src/modes/types.ts +6 -1
  212. package/src/modes/ultrathink.ts +9 -53
  213. package/src/modes/utils/keybinding-matchers.ts +11 -0
  214. package/src/prompts/system/memory-consolidation-system.md +8 -0
  215. package/src/prompts/system/memory-extraction-system.md +26 -0
  216. package/src/prompts/{commands/orchestrate.md → system/orchestrate-notice.md} +6 -17
  217. package/src/prompts/system/system-prompt.md +2 -0
  218. package/src/prompts/system/tiny-title-system.md +8 -0
  219. package/src/prompts/tools/memory-edit.md +8 -0
  220. package/src/prompts/tools/read.md +4 -0
  221. package/src/prompts/tools/task.md +4 -7
  222. package/src/sdk.ts +13 -21
  223. package/src/session/agent-session.ts +128 -44
  224. package/src/slash-commands/builtin-registry.ts +18 -1
  225. package/src/system-prompt.ts +4 -0
  226. package/src/task/commands.ts +1 -5
  227. package/src/task/executor.ts +8 -0
  228. package/src/task/index.ts +2 -0
  229. package/src/task/render.ts +69 -26
  230. package/src/tiny/device.ts +117 -0
  231. package/src/tiny/dtype.ts +101 -0
  232. package/src/tiny/models.ts +218 -0
  233. package/src/tiny/text.ts +54 -0
  234. package/src/tiny/title-client.ts +395 -0
  235. package/src/tiny/title-protocol.ts +51 -0
  236. package/src/tiny/worker.ts +587 -0
  237. package/src/tools/bash.ts +74 -29
  238. package/src/tools/browser/tab-worker.ts +1 -1
  239. package/src/tools/eval.ts +9 -4
  240. package/src/tools/index.ts +17 -22
  241. package/src/tools/memory-edit.ts +59 -0
  242. package/src/tools/memory-recall.ts +100 -0
  243. package/src/tools/memory-reflect.ts +88 -0
  244. package/src/tools/memory-render.ts +185 -0
  245. package/src/tools/memory-retain.ts +91 -0
  246. package/src/tools/read.ts +1 -0
  247. package/src/tools/renderers.ts +4 -2
  248. package/src/tools/todo-write.ts +128 -29
  249. package/src/tools/tool-result.ts +8 -0
  250. package/src/tui/code-cell.ts +6 -1
  251. package/src/tui/output-block.ts +199 -38
  252. package/src/utils/title-generator.ts +115 -13
  253. package/dist/types/tools/recipe/index.d.ts +0 -46
  254. package/dist/types/tools/recipe/render.d.ts +0 -36
  255. package/dist/types/tools/recipe/runner.d.ts +0 -60
  256. package/dist/types/tools/recipe/runners/cargo.d.ts +0 -16
  257. package/dist/types/tools/recipe/runners/index.d.ts +0 -2
  258. package/dist/types/tools/recipe/runners/just.d.ts +0 -2
  259. package/dist/types/tools/recipe/runners/make.d.ts +0 -2
  260. package/dist/types/tools/recipe/runners/pkg.d.ts +0 -2
  261. package/dist/types/tools/recipe/runners/task.d.ts +0 -2
  262. package/src/prompts/tools/recipe.md +0 -16
  263. package/src/tools/hindsight-recall.ts +0 -69
  264. package/src/tools/hindsight-reflect.ts +0 -58
  265. package/src/tools/hindsight-retain.ts +0 -57
  266. package/src/tools/recipe/index.ts +0 -81
  267. package/src/tools/recipe/render.ts +0 -19
  268. package/src/tools/recipe/runner.ts +0 -219
  269. package/src/tools/recipe/runners/cargo.ts +0 -131
  270. package/src/tools/recipe/runners/index.ts +0 -8
  271. package/src/tools/recipe/runners/just.ts +0 -73
  272. package/src/tools/recipe/runners/make.ts +0 -101
  273. package/src/tools/recipe/runners/pkg.ts +0 -167
  274. package/src/tools/recipe/runners/task.ts +0 -72
package/src/tools/bash.ts CHANGED
@@ -7,7 +7,7 @@ import type {
7
7
  ToolApprovalDecision,
8
8
  } from "@oh-my-pi/pi-agent-core";
9
9
  import type { Component } from "@oh-my-pi/pi-tui";
10
- import { ImageProtocol, TERMINAL, Text } from "@oh-my-pi/pi-tui";
10
+ import { ImageProtocol, TERMINAL } from "@oh-my-pi/pi-tui";
11
11
  import { getProjectDir, isEnoent, logger, prompt } from "@oh-my-pi/pi-utils";
12
12
  import * as z from "zod/v4";
13
13
  import { AsyncJobManager } from "../async";
@@ -15,6 +15,7 @@ import { type BashResult, executeBash } from "../exec/bash-executor";
15
15
  import type { RenderResultOptions } from "../extensibility/custom-tools/types";
16
16
  import { InternalUrlRouter } from "../internal-urls";
17
17
  import { truncateToVisualLines } from "../modes/components/visual-truncate";
18
+ import { shimmerEnabled } from "../modes/theme/shimmer";
18
19
  import { highlightCode, type Theme } from "../modes/theme/theme";
19
20
  import bashDescription from "../prompts/tools/bash.md" with { type: "text" };
20
21
  import type { ClientBridgeTerminalExitStatus, ClientBridgeTerminalOutput } from "../session/client-bridge";
@@ -129,6 +130,8 @@ export interface BashToolDetails {
129
130
  timeoutSeconds?: number;
130
131
  requestedTimeoutSeconds?: number;
131
132
  wallTimeMs?: number;
133
+ /** Exit code of a command that ran to completion but failed (non-zero). */
134
+ exitCode?: number;
132
135
  terminalId?: string;
133
136
  async?: {
134
137
  state: "running" | "completed" | "failed";
@@ -281,17 +284,19 @@ function formatWallTimeNotice(wallTimeMs: number): string {
281
284
  return `Wall time: ${formatWallTimeSeconds(wallTimeMs)} seconds`;
282
285
  }
283
286
 
287
+ function formatExitCodeNotice(exitCode: number): string {
288
+ return `Command exited with code ${exitCode}`;
289
+ }
290
+
284
291
  /**
285
- * Strip the trailing `Wall time: <secs> seconds` notice from text so the TUI
286
- * can render the wall time via its styled `[Wall: …]` label without echoing
287
- * the same value verbatim in the output pane.
292
+ * Strip the trailing occurrence of `notice` (plus a single surrounding newline
293
+ * on each side) so the TUI can echo the value via a styled footer label
294
+ * instead of repeating it verbatim in the output pane. The notice is
295
+ * reconstructed from the same value the result was tagged with, so a literal
296
+ * sub-string match never strips a coincidental in-output token — only the
297
+ * exact line we appended in #buildCompletedResult.
288
298
  */
289
- function stripWallTimeNotice(text: string, wallTimeMs: number | undefined): string {
290
- if (wallTimeMs === undefined) return text;
291
- // Reconstruct the notice from the same value the result was tagged with so
292
- // a literal sub-string match never strips a coincidental in-output token —
293
- // only the exact line we appended in #buildCompletedResult.
294
- const notice = formatWallTimeNotice(wallTimeMs);
299
+ function stripTrailingNotice(text: string, notice: string): string {
295
300
  const idx = text.lastIndexOf(notice);
296
301
  if (idx === -1) return text;
297
302
  let start = idx;
@@ -301,6 +306,16 @@ function stripWallTimeNotice(text: string, wallTimeMs: number | undefined): stri
301
306
  return (text.slice(0, start) + text.slice(end)).trimEnd();
302
307
  }
303
308
 
309
+ function stripWallTimeNotice(text: string, wallTimeMs: number | undefined): string {
310
+ if (wallTimeMs === undefined) return text;
311
+ return stripTrailingNotice(text, formatWallTimeNotice(wallTimeMs));
312
+ }
313
+
314
+ function stripExitCodeNotice(text: string, exitCode: number | undefined): string {
315
+ if (exitCode === undefined) return text;
316
+ return stripTrailingNotice(text, formatExitCodeNotice(exitCode));
317
+ }
318
+
304
319
  /**
305
320
  * Bash tool implementation.
306
321
  *
@@ -357,7 +372,15 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
357
372
  return outputText || "(no output)";
358
373
  }
359
374
 
360
- #buildResultText(result: BashResult | BashInteractiveResult, timeoutSec: number, outputText: string): string {
375
+ /**
376
+ * Throw for outcomes that are *not* a completed command: user/timeout
377
+ * aborts and a missing exit status. The foreground and bridge callers plus
378
+ * the async job manager rely on these throwing so cancellations surface as
379
+ * aborts and jobs are recorded as failed. A definite non-zero exit is a
380
+ * completed command that failed; #buildCompletedResult surfaces it as an
381
+ * error *result* (carrying execution details) rather than a throw.
382
+ */
383
+ #throwIfUnfinished(result: BashResult | BashInteractiveResult, timeoutSec: number, outputText: string): void {
361
384
  if (result.cancelled) {
362
385
  throw new ToolError(normalizeResultOutput(result) || "Command aborted");
363
386
  }
@@ -367,10 +390,6 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
367
390
  if (result.exitCode === undefined) {
368
391
  throw new ToolError(`${outputText}\n\nCommand failed: missing exit status`);
369
392
  }
370
- if (result.exitCode !== 0) {
371
- throw new ToolError(`${outputText}\n\nCommand exited with code ${result.exitCode}`);
372
- }
373
- return outputText;
374
393
  }
375
394
 
376
395
  #buildCompletedResult(
@@ -383,6 +402,9 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
383
402
  wallTimeMs?: number;
384
403
  } = {},
385
404
  ): AgentToolResult<BashToolDetails> {
405
+ const exitCode = result.exitCode;
406
+ const failedExit = exitCode !== undefined && exitCode !== 0;
407
+
386
408
  const outputLines = [this.#formatResultOutput(result)];
387
409
  const notices: string[] = [];
388
410
  if (options.wallTimeMs !== undefined) {
@@ -394,7 +416,12 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
394
416
  }
395
417
  }
396
418
  if (notices.length > 0) outputLines.push("", ...notices);
419
+ if (failedExit) outputLines.push("", formatExitCodeNotice(exitCode));
397
420
  const outputText = outputLines.join("\n");
421
+
422
+ // Aborts / timeouts / missing-status still propagate as thrown errors.
423
+ this.#throwIfUnfinished(result, timeoutSec, outputText);
424
+
398
425
  const details: BashToolDetails = { timeoutSeconds: timeoutSec };
399
426
  if (options.requestedTimeoutSec !== undefined && options.requestedTimeoutSec !== timeoutSec) {
400
427
  details.requestedTimeoutSeconds = options.requestedTimeoutSec;
@@ -405,8 +432,11 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
405
432
  if (options.wallTimeMs !== undefined) {
406
433
  details.wallTimeMs = options.wallTimeMs;
407
434
  }
435
+ if (failedExit) {
436
+ details.exitCode = exitCode;
437
+ }
408
438
  const resultBuilder = toolResult(details).text(outputText).truncationFromSummary(result, { direction: "tail" });
409
- this.#buildResultText(result, timeoutSec, outputText);
439
+ if (failedExit) resultBuilder.error();
410
440
  return resultBuilder.done();
411
441
  }
412
442
 
@@ -500,7 +530,16 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
500
530
  });
501
531
  const finalText = this.#extractTextResult(finalResult);
502
532
  latestText = finalText;
533
+ // Hand the detailed result to the foreground auto-background
534
+ // waiter (which renders it, footer included) before deciding
535
+ // the job's terminal state.
503
536
  completion.resolve({ kind: "completed", result: finalResult });
537
+ if (finalResult.isError === true) {
538
+ // A non-zero exit is a completed command that failed. Re-enter
539
+ // the failure path so the job manager records it as failed and
540
+ // delivers the error text, matching prior throw-based behavior.
541
+ throw new ToolError(finalText);
542
+ }
504
543
  await reportProgress(finalText, { async: { state: "completed", jobId, type: "bash" } });
505
544
  return finalText;
506
545
  } catch (error) {
@@ -1007,15 +1046,6 @@ export function getBashEnvForDisplay(args: BashRenderArgs): Record<string, strin
1007
1046
  return args.env ?? partialEnv;
1008
1047
  }
1009
1048
 
1010
- export function formatBashCommand(args: BashRenderArgs): string {
1011
- const command = replaceTabs(args.command || "…");
1012
- const prompt = "$";
1013
- const cwd = getProjectDir();
1014
- const displayWorkdir = formatToolWorkingDirectory(args.cwd, cwd);
1015
- const renderedCommand = [formatBashEnvAssignments(getBashEnvForDisplay(args)), command].filter(Boolean).join(" ");
1016
- return displayWorkdir ? `${prompt} cd ${displayWorkdir} && ${renderedCommand}` : `${prompt} ${renderedCommand}`;
1017
- }
1018
-
1019
1049
  /**
1020
1050
  * Returns the bash command formatted for the result body: the dim `$ cd … &&`
1021
1051
  * prefix joined with syntax-highlighted command lines. The prefix is applied
@@ -1050,10 +1080,20 @@ export function createShellRenderer<TArgs>(config: ShellRendererConfig<TArgs>) {
1050
1080
  return {
1051
1081
  renderCall(args: TArgs, options: RenderResultOptions, uiTheme: Theme): Component {
1052
1082
  const renderArgs = toBashRenderArgs(args, config);
1053
- const cmdText = formatBashCommand(renderArgs);
1054
1083
  const title = config.resolveTitle(args, options);
1055
- const text = renderStatusLine({ icon: "pending", title, description: cmdText }, uiTheme);
1056
- return new Text(text, 0, 0);
1084
+ const cmdLines = formatBashCommandLines(renderArgs, uiTheme);
1085
+ const header = renderStatusLine({ icon: "pending", title }, uiTheme);
1086
+ const outputBlock = new CachedOutputBlock();
1087
+ return {
1088
+ render: (width: number): string[] =>
1089
+ outputBlock.render(
1090
+ { header, state: "pending", sections: [{ lines: cmdLines }], width, animate: true },
1091
+ uiTheme,
1092
+ ),
1093
+ invalidate: () => {
1094
+ outputBlock.invalidate();
1095
+ },
1096
+ };
1057
1097
  },
1058
1098
 
1059
1099
  renderResult(
@@ -1087,7 +1127,8 @@ export function createShellRenderer<TArgs>(config: ShellRendererConfig<TArgs>) {
1087
1127
  // double-print it alongside the styled warning line below.
1088
1128
  const rawOutput = renderContext?.output ?? result.content?.find(c => c.type === "text")?.text ?? "";
1089
1129
  const strippedOutput = stripOutputNotice(rawOutput, details?.meta);
1090
- const output = stripWallTimeNotice(strippedOutput, details?.wallTimeMs);
1130
+ const withoutExit = stripExitCodeNotice(strippedOutput, details?.exitCode);
1131
+ const output = stripWallTimeNotice(withoutExit, details?.wallTimeMs);
1091
1132
  const displayOutput = output.trimEnd();
1092
1133
  const showingFullOutput = expanded && renderContext?.isFullOutput === true;
1093
1134
 
@@ -1106,6 +1147,9 @@ export function createShellRenderer<TArgs>(config: ShellRendererConfig<TArgs>) {
1106
1147
  : `Timeout: ${timeoutSeconds}s`,
1107
1148
  );
1108
1149
  }
1150
+ if (isError && typeof details?.exitCode === "number") {
1151
+ statsParts.push(`Exit: ${details.exitCode}`);
1152
+ }
1109
1153
  const timeoutLine =
1110
1154
  statsParts.length > 0
1111
1155
  ? uiTheme.fg(
@@ -1162,6 +1206,7 @@ export function createShellRenderer<TArgs>(config: ShellRendererConfig<TArgs>) {
1162
1206
  { label: uiTheme.fg("toolTitle", "Output"), lines: outputLines },
1163
1207
  ],
1164
1208
  width,
1209
+ animate: options.isPartial && shimmerEnabled(),
1165
1210
  },
1166
1211
  uiTheme,
1167
1212
  );
@@ -917,7 +917,7 @@ export class WorkerCore {
917
917
  dispatchEvent: (event: unknown) => boolean;
918
918
  }
919
919
  const select = el as unknown as SelectLike;
920
- if (!select || select.tagName !== "SELECT") throw new Error("tab.select() requires a <select> element");
920
+ if (select?.tagName !== "SELECT") throw new Error("tab.select() requires a <select> element");
921
921
  const EventCtor = (
922
922
  globalThis as unknown as { Event: new (type: string, init?: { bubbles: boolean }) => unknown }
923
923
  ).Event;
package/src/tools/eval.ts CHANGED
@@ -10,10 +10,11 @@ import { defaultEvalSessionId } from "../eval/session-id";
10
10
  import type { EvalCellResult, EvalDisplayOutput, EvalLanguage, EvalStatusEvent, EvalToolDetails } from "../eval/types";
11
11
  import type { RenderResultOptions } from "../extensibility/custom-tools/types";
12
12
  import { truncateToVisualLines } from "../modes/components/visual-truncate";
13
+ import { shimmerEnabled } from "../modes/theme/shimmer";
13
14
  import { getMarkdownTheme, type Theme } from "../modes/theme/theme";
14
15
  import evalDescription from "../prompts/tools/eval.md" with { type: "text" };
15
16
  import { DEFAULT_MAX_BYTES, OutputSink, type OutputSummary, TailBuffer } from "../session/streaming-output";
16
- import { renderCodeCell } from "../tui";
17
+ import { borderShimmerTick, renderCodeCell } from "../tui";
17
18
  import { formatDimensionNote, resizeImage } from "../utils/image-resize";
18
19
  import { resolveEvalBackends, type ToolSession } from ".";
19
20
  import { truncateForPrompt } from "./approval";
@@ -857,7 +858,7 @@ function formatCellOutputLines(
857
858
  }
858
859
 
859
860
  export const evalToolRenderer = {
860
- renderCall(args: EvalRenderArgs, _options: RenderResultOptions, uiTheme: Theme): Component {
861
+ renderCall(args: EvalRenderArgs, options: RenderResultOptions, uiTheme: Theme): Component {
861
862
  const cells = getRenderCells(args);
862
863
 
863
864
  if (cells.length === 0) {
@@ -870,7 +871,8 @@ export const evalToolRenderer = {
870
871
 
871
872
  return {
872
873
  render: (width: number): string[] => {
873
- const key = cells.map(c => `${c.language}:${c.title ?? ""}:${c.code.length}`).join("|");
874
+ const animate = options.isPartial && shimmerEnabled();
875
+ const key = `${animate ? borderShimmerTick() : 0}|${cells.map(c => `${c.language}:${c.title ?? ""}:${c.code.length}`).join("|")}`;
874
876
  if (cached && cached.key === key && cached.width === width) {
875
877
  return cached.result;
876
878
  }
@@ -889,6 +891,7 @@ export const evalToolRenderer = {
889
891
  width,
890
892
  codeMaxLines: EVAL_DEFAULT_PREVIEW_LINES,
891
893
  expanded: true,
894
+ animate,
892
895
  },
893
896
  uiTheme,
894
897
  );
@@ -951,7 +954,8 @@ export const evalToolRenderer = {
951
954
  render: (width: number): string[] => {
952
955
  const expanded = options.renderContext?.expanded ?? options.expanded;
953
956
  const previewLines = options.renderContext?.previewLines ?? EVAL_DEFAULT_PREVIEW_LINES;
954
- const key = `${expanded}|${previewLines}|${options.spinnerFrame}`;
957
+ const animate = options.isPartial && shimmerEnabled();
958
+ const key = `${expanded}|${previewLines}|${options.spinnerFrame}|${animate ? borderShimmerTick() : 0}`;
955
959
  if (cached && cached.key === key && cached.width === width) {
956
960
  return cached.result;
957
961
  }
@@ -988,6 +992,7 @@ export const evalToolRenderer = {
988
992
  codeMaxLines: expanded ? Number.POSITIVE_INFINITY : EVAL_DEFAULT_PREVIEW_LINES,
989
993
  expanded,
990
994
  width,
995
+ animate,
991
996
  },
992
997
  uiTheme,
993
998
  );
@@ -11,6 +11,7 @@ import type { GoalModeState, GoalRuntime } from "../goals";
11
11
  import { GoalTool } from "../goals/tools/goal-tool";
12
12
  import type { HindsightSessionState } from "../hindsight/state";
13
13
  import { LspTool } from "../lsp";
14
+ import type { MnemosyneSessionState } from "../mnemosyne/state";
14
15
  import type { PlanModeState } from "../plan-mode/state";
15
16
  import { type AgentRegistry, MAIN_AGENT_ID } from "../registry/agent-registry";
16
17
  import type { ArtifactManager } from "../session/artifacts";
@@ -33,15 +34,15 @@ import { DebugTool } from "./debug";
33
34
  import { EvalTool } from "./eval";
34
35
  import { FindTool } from "./find";
35
36
  import { GithubTool } from "./gh";
36
- import { HindsightRecallTool } from "./hindsight-recall";
37
- import { HindsightReflectTool } from "./hindsight-reflect";
38
- import { HindsightRetainTool } from "./hindsight-retain";
39
37
  import { InspectImageTool } from "./inspect-image";
40
38
  import { IrcTool } from "./irc";
41
39
  import { JobTool } from "./job";
40
+ import { MemoryEditTool } from "./memory-edit";
41
+ import { MemoryRecallTool } from "./memory-recall";
42
+ import { MemoryReflectTool } from "./memory-reflect";
43
+ import { MemoryRetainTool } from "./memory-retain";
42
44
  import { wrapToolWithMetaNotice } from "./output-meta";
43
45
  import { ReadTool } from "./read";
44
- import { RecipeTool } from "./recipe";
45
46
  import { RenderMermaidTool } from "./render-mermaid";
46
47
  import { createReportToolIssueTool, isAutoQaEnabled } from "./report-tool-issue";
47
48
  import { ResolveTool } from "./resolve";
@@ -73,15 +74,15 @@ export * from "./debug";
73
74
  export * from "./eval";
74
75
  export * from "./find";
75
76
  export * from "./gh";
76
- export * from "./hindsight-recall";
77
- export * from "./hindsight-reflect";
78
- export * from "./hindsight-retain";
79
77
  export * from "./image-gen";
80
78
  export * from "./inspect-image";
81
79
  export * from "./irc";
82
80
  export * from "./job";
81
+ export * from "./memory-edit";
82
+ export * from "./memory-recall";
83
+ export * from "./memory-reflect";
84
+ export * from "./memory-retain";
83
85
  export * from "./read";
84
- export * from "./recipe";
85
86
  export * from "./render-mermaid";
86
87
  export * from "./report-tool-issue";
87
88
  export * from "./resolve";
@@ -152,6 +153,8 @@ export interface ToolSession {
152
153
  getSessionId?: () => string | null;
153
154
  /** Get Hindsight runtime state for this agent session. */
154
155
  getHindsightSessionState?: () => HindsightSessionState | undefined;
156
+ /** Get Mnemosyne runtime state for this agent session. */
157
+ getMnemosyneSessionState?: () => MnemosyneSessionState | undefined;
155
158
  /** Agent identity used for IRC routing. Returns the registry id (e.g. "0-Main", "0-AuthLoader"). */
156
159
  getAgentId?: () => string | null;
157
160
  /** Look up a registered tool by name (used by the eval js backend's tool bridge). */
@@ -295,15 +298,15 @@ export const BUILTIN_TOOLS: Record<string, ToolFactory> = {
295
298
  rewind: RewindTool.createIf,
296
299
  task: s => TaskTool.create(s),
297
300
  job: JobTool.createIf,
298
- recipe: RecipeTool.createIf,
299
301
  irc: IrcTool.createIf,
300
302
  todo_write: s => new TodoWriteTool(s),
301
303
  web_search: s => new WebSearchTool(s),
302
304
  search_tool_bm25: SearchToolBm25Tool.createIf,
303
305
  write: s => new WriteTool(s),
304
- retain: HindsightRetainTool.createIf,
305
- recall: HindsightRecallTool.createIf,
306
- reflect: HindsightReflectTool.createIf,
306
+ memory_edit: MemoryEditTool.createIf,
307
+ retain: MemoryRetainTool.createIf,
308
+ recall: MemoryRecallTool.createIf,
309
+ reflect: MemoryReflectTool.createIf,
307
310
  };
308
311
 
309
312
  export const HIDDEN_TOOLS: Record<string, ToolFactory> = {
@@ -410,14 +413,7 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
410
413
  ) {
411
414
  requestedTools.push("ast_edit");
412
415
  }
413
- if (
414
- requestedTools.includes("bash") &&
415
- !requestedTools.includes("recipe") &&
416
- session.settings.get("recipe.enabled")
417
- ) {
418
- requestedTools.push("recipe");
419
- }
420
- if (session.settings.get("memory.backend") === "hindsight") {
416
+ if (["hindsight", "mnemosyne"].includes(session.settings.get("memory.backend") ?? "")) {
421
417
  for (const name of ["recall", "retain", "reflect"]) {
422
418
  if (!requestedTools.includes(name)) requestedTools.push(name);
423
419
  }
@@ -461,9 +457,8 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
461
457
  if (!session.settings.get("async.enabled") && session.getAgentId?.() === MAIN_AGENT_ID) return false;
462
458
  return true;
463
459
  }
464
- if (name === "recipe") return session.settings.get("recipe.enabled");
465
460
  if (name === "retain" || name === "recall" || name === "reflect") {
466
- return session.settings.get("memory.backend") === "hindsight";
461
+ return ["hindsight", "mnemosyne"].includes(session.settings.get("memory.backend") ?? "");
467
462
  }
468
463
  if (name === "task") {
469
464
  const maxDepth = session.settings.get("task.maxRecursionDepth") ?? 2;
@@ -0,0 +1,59 @@
1
+ import type { AgentTool, AgentToolResult } from "@oh-my-pi/pi-agent-core";
2
+ import * as z from "zod/v4";
3
+ import memoryEditDescription from "../prompts/tools/memory-edit.md" with { type: "text" };
4
+ import type { ToolSession } from ".";
5
+
6
+ const memoryEditSchema = z.object({
7
+ op: z.enum(["update", "forget", "invalidate"]).describe("memory edit operation"),
8
+ id: z.string().describe("memory id from recall output"),
9
+ content: z.string().optional().describe("replacement content for update"),
10
+ importance: z.number().optional().describe("replacement importance for update, clamped to [0, 1]"),
11
+ replacement_id: z.string().optional().describe("replacement memory id for invalidate"),
12
+ });
13
+
14
+ export type MemoryEditParams = z.infer<typeof memoryEditSchema>;
15
+
16
+ export class MemoryEditTool implements AgentTool<typeof memoryEditSchema> {
17
+ readonly name = "memory_edit";
18
+ readonly approval = "read" as const;
19
+ readonly label = "Memory Edit";
20
+ readonly description = memoryEditDescription;
21
+ readonly parameters = memoryEditSchema;
22
+ readonly strict = true;
23
+ readonly loadMode = "discoverable";
24
+ readonly summary = "Update, forget, or invalidate Mnemosyne memories";
25
+
26
+ constructor(private readonly session: ToolSession) {}
27
+
28
+ static createIf(session: ToolSession): MemoryEditTool | null {
29
+ const backend = session.settings.get("memory.backend");
30
+ if (backend !== "mnemosyne") return null;
31
+ return new MemoryEditTool(session);
32
+ }
33
+
34
+ async execute(_id: string, params: MemoryEditParams): Promise<AgentToolResult> {
35
+ const state = this.session.getMnemosyneSessionState?.();
36
+ if (!state) {
37
+ throw new Error("Mnemosyne backend is not initialised for this session.");
38
+ }
39
+ if (params.op === "update" && params.content === undefined && params.importance === undefined) {
40
+ throw new Error("memory_edit update requires content or importance.");
41
+ }
42
+
43
+ const importance = params.importance === undefined ? undefined : Math.max(0, Math.min(1, params.importance));
44
+ const result = state.editScopedMemory(params.op, params.id, {
45
+ content: params.content,
46
+ importance,
47
+ replacementId: params.replacement_id,
48
+ });
49
+ const location = result.bank ? ` in bank ${result.bank}${result.store ? ` (${result.store})` : ""}` : "";
50
+ const text =
51
+ result.status === "not_found"
52
+ ? `Memory ${params.id} was not found${location}.`
53
+ : `Memory ${params.id} ${result.status}${location}.`;
54
+ return {
55
+ content: [{ type: "text", text }],
56
+ details: result,
57
+ };
58
+ }
59
+ }
@@ -0,0 +1,100 @@
1
+ import type { AgentTool, AgentToolResult } from "@oh-my-pi/pi-agent-core";
2
+ import { logger, untilAborted } from "@oh-my-pi/pi-utils";
3
+ import * as z from "zod/v4";
4
+ import { formatCurrentTime, formatMemories } from "../hindsight/content";
5
+ import recallDescription from "../prompts/tools/recall.md" with { type: "text" };
6
+ import type { ToolSession } from ".";
7
+
8
+ const memoryRecallSchema = z.object({
9
+ query: z.string().describe("natural language search query"),
10
+ });
11
+
12
+ export type MemoryRecallParams = z.infer<typeof memoryRecallSchema>;
13
+
14
+ export class MemoryRecallTool implements AgentTool<typeof memoryRecallSchema> {
15
+ readonly name = "recall";
16
+ readonly approval = "read" as const;
17
+ readonly label = "Recall";
18
+ readonly description = recallDescription;
19
+ readonly parameters = memoryRecallSchema;
20
+ readonly strict = true;
21
+ readonly loadMode = "discoverable";
22
+ readonly summary = "Search memory for relevant prior context";
23
+
24
+ constructor(private readonly session: ToolSession) {}
25
+
26
+ static createIf(session: ToolSession): MemoryRecallTool | null {
27
+ const backend = session.settings.get("memory.backend");
28
+ if (backend !== "hindsight" && backend !== "mnemosyne") return null;
29
+ return new MemoryRecallTool(session);
30
+ }
31
+
32
+ async execute(_id: string, params: MemoryRecallParams, signal?: AbortSignal): Promise<AgentToolResult> {
33
+ return untilAborted(signal, async () => {
34
+ const backend = this.session.settings.get("memory.backend");
35
+ if (backend === "mnemosyne") {
36
+ const state = this.session.getMnemosyneSessionState?.();
37
+ if (!state) {
38
+ throw new Error("Mnemosyne backend is not initialised for this session.");
39
+ }
40
+ try {
41
+ const results = state.recallResultsScoped(params.query);
42
+ if (results.length === 0) {
43
+ return {
44
+ content: [{ type: "text", text: "No relevant memories found." }],
45
+ details: {},
46
+ };
47
+ }
48
+ const formatted = state.formatScopedRecallWithIds(results);
49
+ return {
50
+ content: [
51
+ {
52
+ type: "text",
53
+ text: `Found ${results.length} relevant ${results.length === 1 ? "memory" : "memories"} (as of ${formatCurrentTime()} UTC):\n\n${formatted}`,
54
+ },
55
+ ],
56
+ details: {},
57
+ };
58
+ } catch (err) {
59
+ logger.warn("recall failed", { backend: "mnemosyne", bank: state.config.bank, error: String(err) });
60
+ throw err instanceof Error ? err : new Error(String(err));
61
+ }
62
+ }
63
+
64
+ const state = this.session.getHindsightSessionState?.();
65
+ if (!state) {
66
+ throw new Error("Hindsight backend is not initialised for this session.");
67
+ }
68
+
69
+ try {
70
+ const response = await state.client.recall(state.bankId, params.query, {
71
+ budget: state.config.recallBudget,
72
+ maxTokens: state.config.recallMaxTokens,
73
+ types: state.config.recallTypes.length > 0 ? state.config.recallTypes : undefined,
74
+ tags: state.recallTags,
75
+ tagsMatch: state.recallTagsMatch,
76
+ });
77
+ const results = response.results ?? [];
78
+ if (results.length === 0) {
79
+ return {
80
+ content: [{ type: "text", text: "No relevant memories found." }],
81
+ details: {},
82
+ };
83
+ }
84
+ const formatted = formatMemories(results);
85
+ return {
86
+ content: [
87
+ {
88
+ type: "text",
89
+ text: `Found ${results.length} relevant ${results.length === 1 ? "memory" : "memories"} (as of ${formatCurrentTime()} UTC):\n\n${formatted}`,
90
+ },
91
+ ],
92
+ details: {},
93
+ };
94
+ } catch (err) {
95
+ logger.warn("recall failed", { bankId: state.bankId, error: String(err) });
96
+ throw err instanceof Error ? err : new Error(String(err));
97
+ }
98
+ });
99
+ }
100
+ }
@@ -0,0 +1,88 @@
1
+ import type { AgentTool, AgentToolResult } from "@oh-my-pi/pi-agent-core";
2
+ import { logger, untilAborted } from "@oh-my-pi/pi-utils";
3
+ import * as z from "zod/v4";
4
+ import { ensureBankMission } from "../hindsight/bank";
5
+ import reflectDescription from "../prompts/tools/reflect.md" with { type: "text" };
6
+ import type { ToolSession } from ".";
7
+
8
+ const memoryReflectSchema = z.object({
9
+ query: z.string().describe("question to answer"),
10
+ context: z.string().describe("optional context").optional(),
11
+ });
12
+
13
+ export type MemoryReflectParams = z.infer<typeof memoryReflectSchema>;
14
+
15
+ export class MemoryReflectTool implements AgentTool<typeof memoryReflectSchema> {
16
+ readonly name = "reflect";
17
+ readonly approval = "read" as const;
18
+ readonly label = "Reflect";
19
+ readonly description = reflectDescription;
20
+ readonly parameters = memoryReflectSchema;
21
+ readonly strict = true;
22
+ readonly loadMode = "discoverable";
23
+ readonly summary = "Synthesize an answer from long-term memory";
24
+
25
+ constructor(private readonly session: ToolSession) {}
26
+
27
+ static createIf(session: ToolSession): MemoryReflectTool | null {
28
+ const backend = session.settings.get("memory.backend");
29
+ if (backend !== "hindsight" && backend !== "mnemosyne") return null;
30
+ return new MemoryReflectTool(session);
31
+ }
32
+
33
+ async execute(_id: string, params: MemoryReflectParams, signal?: AbortSignal): Promise<AgentToolResult> {
34
+ return untilAborted(signal, async () => {
35
+ const backend = this.session.settings.get("memory.backend");
36
+ if (backend === "mnemosyne") {
37
+ const state = this.session.getMnemosyneSessionState?.();
38
+ if (!state) {
39
+ throw new Error("Mnemosyne backend is not initialised for this session.");
40
+ }
41
+
42
+ try {
43
+ const query = params.context?.trim()
44
+ ? `${params.query.trim()}\n\nAdditional context:\n${params.context.trim()}`
45
+ : params.query;
46
+ const results = state.recallResultsScoped(query);
47
+ if (results.length === 0) {
48
+ return {
49
+ content: [{ type: "text", text: "No relevant information found to reflect on." }],
50
+ details: {},
51
+ };
52
+ }
53
+ const summary = state.formatContextScoped(results);
54
+ return {
55
+ content: [{ type: "text", text: `Based on recalled memories:\n\n${summary}` }],
56
+ details: {},
57
+ };
58
+ } catch (err) {
59
+ logger.warn("reflect failed", { backend: "mnemosyne", bank: state.config.bank, error: String(err) });
60
+ throw err instanceof Error ? err : new Error(String(err));
61
+ }
62
+ }
63
+
64
+ const state = this.session.getHindsightSessionState?.();
65
+ if (!state) {
66
+ throw new Error("Hindsight backend is not initialised for this session.");
67
+ }
68
+
69
+ try {
70
+ await ensureBankMission(state.client, state.bankId, state.config, state.missionsSet);
71
+ const response = await state.client.reflect(state.bankId, params.query, {
72
+ context: params.context,
73
+ budget: state.config.recallBudget,
74
+ tags: state.recallTags,
75
+ tagsMatch: state.recallTagsMatch,
76
+ });
77
+ const text = response.text?.trim() || "No relevant information found to reflect on.";
78
+ return {
79
+ content: [{ type: "text", text }],
80
+ details: {},
81
+ };
82
+ } catch (err) {
83
+ logger.warn("reflect failed", { bankId: state.bankId, error: String(err) });
84
+ throw err instanceof Error ? err : new Error(String(err));
85
+ }
86
+ });
87
+ }
88
+ }