@oh-my-pi/pi-coding-agent 15.7.2 → 15.7.3

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 (160) hide show
  1. package/CHANGELOG.md +75 -6
  2. package/dist/types/cli/args.d.ts +1 -1
  3. package/dist/types/cli/extension-flags.d.ts +36 -0
  4. package/dist/types/config/config-file.d.ts +4 -0
  5. package/dist/types/config/file-lock.d.ts +23 -0
  6. package/dist/types/config/keybindings.d.ts +2 -1
  7. package/dist/types/config/model-registry.d.ts +6 -0
  8. package/dist/types/config/settings-schema.d.ts +88 -65
  9. package/dist/types/edit/hashline/diff.d.ts +3 -3
  10. package/dist/types/eval/__tests__/agent-bridge.test.d.ts +1 -0
  11. package/dist/types/eval/__tests__/budget-bridge.test.d.ts +1 -0
  12. package/dist/types/eval/__tests__/idle-timeout.test.d.ts +1 -0
  13. package/dist/types/eval/agent-bridge.d.ts +25 -0
  14. package/dist/types/eval/backend.d.ts +17 -2
  15. package/dist/types/eval/budget-bridge.d.ts +29 -0
  16. package/dist/types/eval/idle-timeout.d.ts +28 -0
  17. package/dist/types/eval/js/executor.d.ts +8 -0
  18. package/dist/types/eval/js/tool-bridge.d.ts +2 -1
  19. package/dist/types/eval/py/executor.d.ts +13 -0
  20. package/dist/types/exec/bash-executor.d.ts +1 -0
  21. package/dist/types/extensibility/custom-tools/types.d.ts +2 -2
  22. package/dist/types/extensibility/extensions/runner.d.ts +7 -0
  23. package/dist/types/extensibility/plugins/git-url.d.ts +11 -1
  24. package/dist/types/extensibility/plugins/manager.d.ts +12 -1
  25. package/dist/types/extensibility/shared-events.d.ts +2 -2
  26. package/dist/types/memory-backend/index.d.ts +1 -1
  27. package/dist/types/memory-backend/resolve.d.ts +1 -1
  28. package/dist/types/memory-backend/types.d.ts +3 -3
  29. package/dist/types/mnemopi/backend.d.ts +4 -0
  30. package/dist/types/mnemopi/config.d.ts +29 -0
  31. package/dist/types/mnemopi/state.d.ts +72 -0
  32. package/dist/types/modes/components/custom-editor.d.ts +2 -2
  33. package/dist/types/modes/components/omfg-panel.d.ts +19 -0
  34. package/dist/types/modes/controllers/command-controller.d.ts +7 -0
  35. package/dist/types/modes/controllers/input-controller.d.ts +1 -3
  36. package/dist/types/modes/controllers/omfg-controller.d.ts +10 -0
  37. package/dist/types/modes/controllers/omfg-rule.d.ts +26 -0
  38. package/dist/types/modes/gradient-highlight.d.ts +5 -1
  39. package/dist/types/modes/interactive-mode.d.ts +7 -3
  40. package/dist/types/modes/magic-keywords.d.ts +14 -0
  41. package/dist/types/modes/markdown-prose.d.ts +27 -0
  42. package/dist/types/modes/orchestrate.d.ts +7 -2
  43. package/dist/types/modes/shared.d.ts +1 -1
  44. package/dist/types/modes/turn-budget.d.ts +18 -0
  45. package/dist/types/modes/types.d.ts +7 -3
  46. package/dist/types/modes/ultrathink.d.ts +7 -2
  47. package/dist/types/modes/workflow.d.ts +15 -0
  48. package/dist/types/sdk.d.ts +13 -3
  49. package/dist/types/session/agent-session.d.ts +40 -17
  50. package/dist/types/session/session-manager.d.ts +18 -0
  51. package/dist/types/session/session-storage.d.ts +6 -0
  52. package/dist/types/session/shake-types.d.ts +24 -0
  53. package/dist/types/task/executor.d.ts +2 -2
  54. package/dist/types/tiny/models.d.ts +15 -1
  55. package/dist/types/tiny/title-protocol.d.ts +4 -0
  56. package/dist/types/tools/index.d.ts +19 -3
  57. package/dist/types/tools/memory-edit.d.ts +1 -1
  58. package/examples/sdk/09-api-keys-and-oauth.ts +2 -2
  59. package/package.json +10 -10
  60. package/src/autoresearch/tools/run-experiment.ts +45 -113
  61. package/src/cli/args.ts +39 -16
  62. package/src/cli/extension-flags.ts +48 -0
  63. package/src/cli/plugin-cli.ts +11 -2
  64. package/src/config/config-file.ts +98 -13
  65. package/src/config/file-lock.ts +60 -17
  66. package/src/config/keybindings.ts +78 -27
  67. package/src/config/model-registry.ts +7 -1
  68. package/src/config/settings-schema.ts +94 -67
  69. package/src/config/settings.ts +12 -0
  70. package/src/edit/hashline/diff.ts +81 -24
  71. package/src/edit/renderer.ts +16 -12
  72. package/src/eval/__tests__/agent-bridge.test.ts +433 -0
  73. package/src/eval/__tests__/budget-bridge.test.ts +69 -0
  74. package/src/eval/__tests__/idle-timeout.test.ts +66 -0
  75. package/src/eval/__tests__/shared-executors.test.ts +21 -0
  76. package/src/eval/agent-bridge.ts +295 -0
  77. package/src/eval/backend.ts +17 -2
  78. package/src/eval/budget-bridge.ts +48 -0
  79. package/src/eval/idle-timeout.ts +80 -0
  80. package/src/eval/js/executor.ts +35 -7
  81. package/src/eval/js/index.ts +2 -1
  82. package/src/eval/js/shared/prelude.txt +85 -1
  83. package/src/eval/js/tool-bridge.ts +9 -0
  84. package/src/eval/py/executor.ts +41 -14
  85. package/src/eval/py/index.ts +2 -1
  86. package/src/eval/py/prelude.py +132 -1
  87. package/src/exec/bash-executor.ts +2 -3
  88. package/src/extensibility/custom-tools/types.ts +2 -2
  89. package/src/extensibility/extensions/runner.ts +12 -2
  90. package/src/extensibility/plugins/git-url.ts +90 -4
  91. package/src/extensibility/plugins/manager.ts +103 -7
  92. package/src/extensibility/shared-events.ts +2 -2
  93. package/src/internal-urls/docs-index.generated.ts +88 -88
  94. package/src/main.ts +44 -55
  95. package/src/memory-backend/index.ts +1 -1
  96. package/src/memory-backend/resolve.ts +3 -3
  97. package/src/memory-backend/types.ts +3 -3
  98. package/src/{mnemosyne → mnemopi}/backend.ts +70 -70
  99. package/src/{mnemosyne → mnemopi}/config.ts +36 -36
  100. package/src/{mnemosyne → mnemopi}/state.ts +67 -67
  101. package/src/modes/components/agent-dashboard.ts +6 -6
  102. package/src/modes/components/custom-editor.ts +4 -11
  103. package/src/modes/components/extensions/state-manager.ts +3 -4
  104. package/src/modes/components/footer.ts +8 -9
  105. package/src/modes/components/hook-selector.ts +86 -20
  106. package/src/modes/components/oauth-selector.ts +93 -21
  107. package/src/modes/components/omfg-panel.ts +141 -0
  108. package/src/modes/components/settings-defs.ts +2 -2
  109. package/src/modes/components/tips.txt +2 -1
  110. package/src/modes/components/tool-execution.ts +38 -19
  111. package/src/modes/components/tree-selector.ts +4 -3
  112. package/src/modes/components/user-message-selector.ts +94 -19
  113. package/src/modes/components/user-message.ts +8 -1
  114. package/src/modes/controllers/command-controller.ts +57 -0
  115. package/src/modes/controllers/event-controller.ts +60 -2
  116. package/src/modes/controllers/input-controller.ts +14 -11
  117. package/src/modes/controllers/omfg-controller.ts +283 -0
  118. package/src/modes/controllers/omfg-rule.ts +647 -0
  119. package/src/modes/controllers/selector-controller.ts +1 -0
  120. package/src/modes/gradient-highlight.ts +23 -6
  121. package/src/modes/interactive-mode.ts +41 -7
  122. package/src/modes/magic-keywords.ts +20 -0
  123. package/src/modes/markdown-prose.ts +247 -0
  124. package/src/modes/orchestrate.ts +17 -11
  125. package/src/modes/shared.ts +3 -11
  126. package/src/modes/turn-budget.ts +31 -0
  127. package/src/modes/types.ts +7 -1
  128. package/src/modes/ultrathink.ts +16 -10
  129. package/src/modes/utils/hotkeys-markdown.ts +1 -1
  130. package/src/modes/workflow.ts +42 -0
  131. package/src/prompts/system/omfg-user.md +51 -0
  132. package/src/prompts/system/system-prompt.md +1 -0
  133. package/src/prompts/system/workflow-notice.md +70 -0
  134. package/src/prompts/tools/eval.md +13 -1
  135. package/src/prompts/tools/memory-edit.md +1 -1
  136. package/src/sdk.ts +63 -33
  137. package/src/session/agent-session.ts +373 -56
  138. package/src/session/session-manager.ts +32 -0
  139. package/src/session/session-storage.ts +68 -8
  140. package/src/session/shake-types.ts +44 -0
  141. package/src/slash-commands/builtin-registry.ts +41 -16
  142. package/src/task/executor.ts +3 -3
  143. package/src/task/index.ts +6 -6
  144. package/src/tiny/models.ts +30 -2
  145. package/src/tiny/title-protocol.ts +11 -1
  146. package/src/tiny/worker.ts +19 -7
  147. package/src/tools/eval.ts +202 -26
  148. package/src/tools/grouped-file-output.ts +9 -2
  149. package/src/tools/index.ts +17 -5
  150. package/src/tools/memory-edit.ts +4 -4
  151. package/src/tools/memory-recall.ts +5 -5
  152. package/src/tools/memory-reflect.ts +5 -5
  153. package/src/tools/memory-retain.ts +4 -4
  154. package/src/tools/render-utils.ts +2 -1
  155. package/src/tools/search.ts +480 -76
  156. package/dist/types/mnemosyne/backend.d.ts +0 -4
  157. package/dist/types/mnemosyne/config.d.ts +0 -29
  158. package/dist/types/mnemosyne/state.d.ts +0 -72
  159. /package/dist/types/{mnemosyne → mnemopi}/index.d.ts +0 -0
  160. /package/src/{mnemosyne → mnemopi}/index.ts +0 -0
package/src/tools/eval.ts CHANGED
@@ -2,13 +2,16 @@ import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallb
2
2
  import type { ImageContent } from "@oh-my-pi/pi-ai";
3
3
  import type { Component } from "@oh-my-pi/pi-tui";
4
4
  import { Markdown, Text } from "@oh-my-pi/pi-tui";
5
- import { prompt } from "@oh-my-pi/pi-utils";
5
+ import { formatNumber, prompt } from "@oh-my-pi/pi-utils";
6
6
  import * as z from "zod/v4";
7
+ import { settings } from "../config/settings";
7
8
  import { jsBackend, pythonBackend } from "../eval";
8
- import type { ExecutorBackend } from "../eval/backend";
9
+ import type { ExecutorBackend, ExecutorBackendResult } from "../eval/backend";
10
+ import { IdleTimeout } from "../eval/idle-timeout";
9
11
  import { defaultEvalSessionId } from "../eval/session-id";
10
12
  import type { EvalCellResult, EvalDisplayOutput, EvalLanguage, EvalStatusEvent, EvalToolDetails } from "../eval/types";
11
13
  import type { RenderResultOptions } from "../extensibility/custom-tools/types";
14
+ import { formatContextUsage } from "../modes/components/status-line/context-thresholds";
12
15
  import { truncateToVisualLines } from "../modes/components/visual-truncate";
13
16
  import { shimmerEnabled } from "../modes/theme/shimmer";
14
17
  import { getMarkdownTheme, type Theme } from "../modes/theme/theme";
@@ -33,7 +36,16 @@ import {
33
36
  resolveOutputSinkHeadBytes,
34
37
  stripOutputNotice,
35
38
  } from "./output-meta";
36
- import { formatTitle, replaceTabs, shortenPath, truncateToWidth, wrapBrackets } from "./render-utils";
39
+ import {
40
+ formatBadge,
41
+ formatDuration,
42
+ formatStatusIcon,
43
+ formatTitle,
44
+ replaceTabs,
45
+ shortenPath,
46
+ truncateToWidth,
47
+ wrapBrackets,
48
+ } from "./render-utils";
37
49
  import { ToolAbortError, ToolError } from "./tool-errors";
38
50
  import { toolResult } from "./tool-result";
39
51
  import { clampTimeout } from "./tool-timeouts";
@@ -335,12 +347,20 @@ export class EvalTool implements AgentTool<typeof evalSchema> {
335
347
  for (let i = 0; i < cells.length; i++) {
336
348
  const cell = cells[i];
337
349
  const backend = cell.resolved.backend;
338
- const timeoutSec = timeoutSecondsFromMs(cell.timeoutMs);
339
- const deadlineMs = Date.now() + timeoutSec * 1000;
340
- const timeoutSignal = AbortSignal.timeout(Math.max(0, deadlineMs - Date.now()));
350
+ // The per-cell `timeout` is an *inactivity* budget, not a hard
351
+ // wall-clock cap: it bounds the gap between progress signals
352
+ // (status events — agent() updates, log()/phase(), tool-bridge
353
+ // activity), so a long fanout that keeps reporting progress runs to
354
+ // completion while a genuinely stalled cell (no progress for the
355
+ // whole window) is still interrupted. Raw stdout deliberately does
356
+ // NOT re-arm it, so pure-compute runaway loops stay bounded. The
357
+ // watchdog drives `combinedSignal`; we pass no wall-clock deadline
358
+ // downstream so the backends never arm a competing fixed timer.
359
+ const idleTimeoutMs = timeoutSecondsFromMs(cell.timeoutMs) * 1000;
360
+ const idle = new IdleTimeout(idleTimeoutMs);
341
361
  const combinedSignal = signal
342
- ? AbortSignal.any([signal, timeoutSignal, sessionAbortController.signal])
343
- : AbortSignal.any([timeoutSignal, sessionAbortController.signal]);
362
+ ? AbortSignal.any([signal, idle.signal, sessionAbortController.signal])
363
+ : AbortSignal.any([idle.signal, sessionAbortController.signal]);
344
364
 
345
365
  const cellResult = cellResults[i];
346
366
  cellResult.status = "running";
@@ -351,21 +371,32 @@ export class EvalTool implements AgentTool<typeof evalSchema> {
351
371
  pushUpdate();
352
372
 
353
373
  const startTime = Date.now();
354
- const result = await backend.execute(cell.code, {
355
- cwd: session.cwd,
356
- sessionId,
357
- sessionFile: sessionFile ?? undefined,
358
- kernelOwnerId,
359
- signal: combinedSignal,
360
- session,
361
- deadlineMs,
362
- reset: cell.reset,
363
- artifactPath,
364
- artifactId,
365
- onChunk: chunk => {
366
- outputSink!.push(chunk);
367
- },
368
- });
374
+ let result: ExecutorBackendResult;
375
+ try {
376
+ result = await backend.execute(cell.code, {
377
+ cwd: session.cwd,
378
+ sessionId,
379
+ sessionFile: sessionFile ?? undefined,
380
+ kernelOwnerId,
381
+ signal: combinedSignal,
382
+ session,
383
+ idleTimeoutMs,
384
+ reset: cell.reset,
385
+ artifactPath,
386
+ artifactId,
387
+ onChunk: chunk => {
388
+ outputSink!.push(chunk);
389
+ },
390
+ onStatus: event => {
391
+ idle.bump();
392
+ cellResult.statusEvents ??= [];
393
+ upsertStatusEvent(cellResult.statusEvents, event);
394
+ pushUpdate();
395
+ },
396
+ });
397
+ } finally {
398
+ idle.dispose();
399
+ }
369
400
  const durationMs = Date.now() - startTime;
370
401
 
371
402
  const cellStatusEvents: EvalStatusEvent[] = [];
@@ -400,8 +431,8 @@ export class EvalTool implements AgentTool<typeof evalSchema> {
400
431
  }
401
432
  }
402
433
  if (output.type === "status") {
403
- statusEvents.push(output.event);
404
- cellStatusEvents.push(output.event);
434
+ upsertStatusEvent(statusEvents, output.event);
435
+ upsertStatusEvent(cellStatusEvents, output.event);
405
436
  }
406
437
  if (output.type === "markdown") {
407
438
  cellHasMarkdown = true;
@@ -608,6 +639,137 @@ function getRenderCells(args: EvalRenderArgs | undefined): EvalRenderCell[] {
608
639
  return out;
609
640
  }
610
641
 
642
+ type AgentEventStatus = "pending" | "running" | "completed" | "failed" | "aborted";
643
+
644
+ /**
645
+ * Append or replace a status event. `agent` events are progress snapshots keyed
646
+ * by `id`, so they coalesce in place (preserving first-seen order); every other
647
+ * op is a discrete action and simply appends. Keeps the persisted event list
648
+ * bounded even when a subagent emits hundreds of throttled progress ticks.
649
+ */
650
+ function upsertStatusEvent(events: EvalStatusEvent[], event: EvalStatusEvent): void {
651
+ if (event.op === "agent" && typeof event.id === "string") {
652
+ const id = event.id;
653
+ const idx = events.findIndex(e => e.op === "agent" && e.id === id);
654
+ if (idx >= 0) {
655
+ events[idx] = event;
656
+ return;
657
+ }
658
+ }
659
+ events.push(event);
660
+ }
661
+
662
+ function eventString(value: unknown): string | undefined {
663
+ return typeof value === "string" && value.length > 0 ? value : undefined;
664
+ }
665
+
666
+ function eventNumber(value: unknown): number {
667
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
668
+ }
669
+
670
+ function agentEventStatus(value: unknown): AgentEventStatus {
671
+ switch (value) {
672
+ case "pending":
673
+ case "running":
674
+ case "completed":
675
+ case "failed":
676
+ case "aborted":
677
+ return value;
678
+ default:
679
+ return "running";
680
+ }
681
+ }
682
+
683
+ /** Append the toolCount · context · cost · model stat run, mirroring the task tool. */
684
+ function formatAgentStats(event: EvalStatusEvent, theme: Theme): string {
685
+ let line = "";
686
+ const toolCount = eventNumber(event.toolCount);
687
+ if (toolCount > 0) {
688
+ line += `${theme.sep.dot}${theme.fg("dim", `${formatNumber(toolCount)} ${theme.icon.extensionTool}`)}`;
689
+ }
690
+ const contextTokens = eventNumber(event.contextTokens);
691
+ if (contextTokens > 0) {
692
+ const contextWindow = eventNumber(event.contextWindow);
693
+ const ctx =
694
+ contextWindow > 0
695
+ ? formatContextUsage((contextTokens / contextWindow) * 100, contextWindow)
696
+ : formatNumber(contextTokens);
697
+ line += `${theme.sep.dot}${theme.fg("dim", ctx)}`;
698
+ }
699
+ const cost = eventNumber(event.cost);
700
+ if (cost > 0) {
701
+ line += `${theme.sep.dot}${theme.fg("statusLineCost", `$${cost.toFixed(2)}`)}`;
702
+ }
703
+ const model = eventString(event.model);
704
+ if (model && settings.get("task.showResolvedModelBadge")) {
705
+ line += `${theme.sep.dot}${theme.fg("dim", truncateToWidth(replaceTabs(model), 30))}`;
706
+ }
707
+ return line;
708
+ }
709
+
710
+ /**
711
+ * Render coalesced `agent()` progress as a Task-tool-style tree, one entry per
712
+ * subagent: a status line (icon · id · stats) plus, while running, the current
713
+ * tool/intent. Drawn below the cell box so progress streams live.
714
+ */
715
+ function renderAgentProgressEvents(events: EvalStatusEvent[], theme: Theme, spinnerFrame?: number): string[] {
716
+ const lines: string[] = [];
717
+ for (let i = 0; i < events.length; i++) {
718
+ const event = events[i];
719
+ const isLast = i === events.length - 1;
720
+ const prefix = theme.fg("dim", isLast ? theme.tree.last : theme.tree.branch);
721
+ const cont = isLast ? " " : `${theme.fg("dim", theme.tree.vertical)} `;
722
+
723
+ const status = agentEventStatus(event.status);
724
+ const iconStatus =
725
+ status === "completed"
726
+ ? "success"
727
+ : status === "failed"
728
+ ? "error"
729
+ : status === "aborted"
730
+ ? "aborted"
731
+ : status === "pending"
732
+ ? "pending"
733
+ : "running";
734
+ const iconColor =
735
+ status === "completed" ? "success" : status === "failed" || status === "aborted" ? "error" : "accent";
736
+ const icon = formatStatusIcon(iconStatus, theme, status === "running" ? spinnerFrame : undefined);
737
+
738
+ const id = eventString(event.id) ?? "agent";
739
+ let line = `${prefix} ${theme.fg(iconColor, icon)} ${theme.fg("accent", theme.bold(id))}`;
740
+
741
+ if (status === "failed" || status === "aborted") {
742
+ line += ` ${formatBadge(status, iconColor, theme)}`;
743
+ }
744
+
745
+ const currentTool = eventString(event.currentTool);
746
+ const lastIntent = eventString(event.lastIntent);
747
+ if (status === "running" && !currentTool && !lastIntent) {
748
+ const preview = eventString(event.taskPreview);
749
+ if (preview) line += ` ${theme.fg("muted", truncateToWidth(replaceTabs(preview), 48))}`;
750
+ }
751
+
752
+ line += formatAgentStats(event, theme);
753
+ if (status === "completed" || status === "failed" || status === "aborted") {
754
+ const durationMs = eventNumber(event.durationMs);
755
+ if (durationMs > 0) line += `${theme.sep.dot}${theme.fg("dim", formatDuration(durationMs))}`;
756
+ }
757
+ lines.push(line);
758
+
759
+ if (status === "running") {
760
+ if (currentTool) {
761
+ let toolLine = `${cont}${theme.tree.hook} ${theme.fg("muted", currentTool)}`;
762
+ const detail = lastIntent ?? eventString(event.currentToolArgs);
763
+ if (detail) toolLine += `: ${theme.fg("dim", truncateToWidth(replaceTabs(detail), 48))}`;
764
+ lines.push(toolLine);
765
+ } else if (lastIntent) {
766
+ lines.push(`${cont}${theme.tree.hook} ${theme.fg("dim", truncateToWidth(replaceTabs(lastIntent), 48))}`);
767
+ }
768
+ }
769
+ }
770
+ return lines;
771
+ }
772
+
611
773
  /** Format a status event as a single line for display. */
612
774
  function formatStatusEvent(event: EvalStatusEvent, theme: Theme): string {
613
775
  const { op, ...data } = event;
@@ -636,6 +798,8 @@ function formatStatusEvent(event: EvalStatusEvent, theme: Theme): string {
636
798
  env: "icon.package",
637
799
  batch: "icon.package",
638
800
  llm: "icon.package",
801
+ log: "icon.package",
802
+ phase: "icon.package",
639
803
  };
640
804
 
641
805
  const iconKey = opIcons[op] ?? "icon.file";
@@ -716,6 +880,12 @@ function formatStatusEvent(event: EvalStatusEvent, theme: Theme): string {
716
880
  case "touch":
717
881
  if (data.path) parts.push(shortenPath(String(data.path)));
718
882
  break;
883
+ case "log":
884
+ parts.push(String(data.message ?? ""));
885
+ break;
886
+ case "phase":
887
+ parts.push(String(data.title ?? ""));
888
+ break;
719
889
  default:
720
890
  if (data.count !== undefined) {
721
891
  parts.push(String(data.count));
@@ -963,7 +1133,10 @@ export const evalToolRenderer = {
963
1133
  const lines: string[] = [];
964
1134
  for (let i = 0; i < cellResults.length; i++) {
965
1135
  const cell = cellResults[i];
966
- const statusLines = renderStatusEvents(cell.statusEvents ?? [], uiTheme, expanded);
1136
+ const allEvents = cell.statusEvents ?? [];
1137
+ const agentEvents = allEvents.filter(e => e.op === "agent");
1138
+ const otherEvents = agentEvents.length > 0 ? allEvents.filter(e => e.op !== "agent") : allEvents;
1139
+ const statusLines = renderStatusEvents(otherEvents, uiTheme, expanded);
967
1140
  const outputContent = formatCellOutputLines(cell, expanded, previewLines, uiTheme, width);
968
1141
  const outputLines = [...outputContent.lines];
969
1142
  if (!expanded && outputContent.hiddenCount > 0) {
@@ -997,6 +1170,9 @@ export const evalToolRenderer = {
997
1170
  uiTheme,
998
1171
  );
999
1172
  lines.push(...cellLines);
1173
+ if (agentEvents.length > 0) {
1174
+ lines.push(...renderAgentProgressEvents(agentEvents, uiTheme, options.spinnerFrame));
1175
+ }
1000
1176
  if (i < cellResults.length - 1) {
1001
1177
  lines.push("");
1002
1178
  }
@@ -1,5 +1,11 @@
1
1
  import path from "node:path";
2
2
 
3
+ const URL_LIKE_PATH_RE = /^[a-z][a-z0-9+.-]*:\/\//i;
4
+
5
+ function isUrlLikePath(filePath: string): boolean {
6
+ return URL_LIKE_PATH_RE.test(filePath);
7
+ }
8
+
3
9
  /**
4
10
  * One file's contribution to a grouped file output. The header itself is generated
5
11
  * by `formatGroupedFiles` (single `#` for root files, `##` for files inside a dir);
@@ -43,7 +49,7 @@ export function formatGroupedFiles(
43
49
  ): GroupedFilesOutput {
44
50
  const filesByDirectory = new Map<string, string[]>();
45
51
  for (const filePath of files) {
46
- const directory = path.dirname(filePath).replace(/\\/g, "/");
52
+ const directory = isUrlLikePath(filePath) ? "." : path.dirname(filePath).replace(/\\/g, "/");
47
53
  if (!filesByDirectory.has(directory)) {
48
54
  filesByDirectory.set(directory, []);
49
55
  }
@@ -66,7 +72,8 @@ export function formatGroupedFiles(
66
72
  const section = renderFile(filePath);
67
73
  if (section.skip) continue;
68
74
  pushSeparatorIfNeeded();
69
- const header = `# ${path.basename(filePath)}${section.headerSuffix ?? ""}`;
75
+ const headerName = isUrlLikePath(filePath) ? filePath : path.basename(filePath);
76
+ const header = `# ${headerName}${section.headerSuffix ?? ""}`;
70
77
  model.push(header, ...section.modelLines);
71
78
  display.push(header, ...(section.displayLines ?? section.modelLines));
72
79
  }
@@ -10,8 +10,10 @@ import type { Skill } from "../extensibility/skills";
10
10
  import type { GoalModeState, GoalRuntime } from "../goals";
11
11
  import { GoalTool } from "../goals/tools/goal-tool";
12
12
  import type { HindsightSessionState } from "../hindsight/state";
13
+ import type { LocalProtocolOptions } from "../internal-urls";
13
14
  import { LspTool } from "../lsp";
14
- import type { MnemosyneSessionState } from "../mnemosyne/state";
15
+ import type { MCPManager } from "../mcp";
16
+ import type { MnemopiSessionState } from "../mnemopi/state";
15
17
  import type { PlanModeState } from "../plan-mode/state";
16
18
  import { type AgentRegistry, MAIN_AGENT_ID } from "../registry/agent-registry";
17
19
  import type { ArtifactManager } from "../session/artifacts";
@@ -153,8 +155,8 @@ export interface ToolSession {
153
155
  getSessionId?: () => string | null;
154
156
  /** Get Hindsight runtime state for this agent session. */
155
157
  getHindsightSessionState?: () => HindsightSessionState | undefined;
156
- /** Get Mnemosyne runtime state for this agent session. */
157
- getMnemosyneSessionState?: () => MnemosyneSessionState | undefined;
158
+ /** Get Mnemopi runtime state for this agent session. */
159
+ getMnemopiSessionState?: () => MnemopiSessionState | undefined;
158
160
  /** Agent identity used for IRC routing. Returns the registry id (e.g. "0-Main", "0-AuthLoader"). */
159
161
  getAgentId?: () => string | null;
160
162
  /** Look up a registered tool by name (used by the eval js backend's tool bridge). */
@@ -179,6 +181,10 @@ export interface ToolSession {
179
181
  modelRegistry?: import("../config/model-registry").ModelRegistry;
180
182
  /** Agent output manager for unique agent:// IDs across task invocations */
181
183
  agentOutputManager?: AgentOutputManager;
184
+ /** MCP manager visible to subagents without relying on the process-global singleton. */
185
+ mcpManager?: MCPManager;
186
+ /** Local protocol root to propagate to nested subagents and eval-created agents. */
187
+ localProtocolOptions?: LocalProtocolOptions;
182
188
  /** Settings instance for passing to subagents */
183
189
  settings: Settings;
184
190
  /** Plan mode state (if active) */
@@ -187,6 +193,12 @@ export interface ToolSession {
187
193
  getGoalModeState?: () => GoalModeState | undefined;
188
194
  /** Goal runtime for the active agent session. */
189
195
  getGoalRuntime?: () => GoalRuntime | undefined;
196
+ /** Get cumulative session usage statistics (input/output tokens, cost). */
197
+ getUsageStatistics?: () => import("../session/session-manager").UsageStatistics;
198
+ /** Current per-turn token budget {total, spent, hard} for the eval `budget` helper. */
199
+ getTurnBudget?: () => { total: number | null; spent: number; hard: boolean };
200
+ /** Record output tokens consumed by an eval-spawned subagent toward the current turn budget. */
201
+ recordEvalSubagentUsage?: (output: number) => void;
190
202
  /** Bridge to the connected client (e.g. ACP editor host). Tools should route fs/terminal/permission requests through this when available. */
191
203
  getClientBridge?: () => ClientBridge | undefined;
192
204
  /** Get compact conversation context for subagents (excludes tool results, system prompts) */
@@ -413,7 +425,7 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
413
425
  ) {
414
426
  requestedTools.push("ast_edit");
415
427
  }
416
- if (["hindsight", "mnemosyne"].includes(session.settings.get("memory.backend") ?? "")) {
428
+ if (["hindsight", "mnemopi"].includes(session.settings.get("memory.backend") ?? "")) {
417
429
  for (const name of ["recall", "retain", "reflect"]) {
418
430
  if (!requestedTools.includes(name)) requestedTools.push(name);
419
431
  }
@@ -458,7 +470,7 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
458
470
  return true;
459
471
  }
460
472
  if (name === "retain" || name === "recall" || name === "reflect") {
461
- return ["hindsight", "mnemosyne"].includes(session.settings.get("memory.backend") ?? "");
473
+ return ["hindsight", "mnemopi"].includes(session.settings.get("memory.backend") ?? "");
462
474
  }
463
475
  if (name === "task") {
464
476
  const maxDepth = session.settings.get("task.maxRecursionDepth") ?? 2;
@@ -21,20 +21,20 @@ export class MemoryEditTool implements AgentTool<typeof memoryEditSchema> {
21
21
  readonly parameters = memoryEditSchema;
22
22
  readonly strict = true;
23
23
  readonly loadMode = "discoverable";
24
- readonly summary = "Update, forget, or invalidate Mnemosyne memories";
24
+ readonly summary = "Update, forget, or invalidate Mnemopi memories";
25
25
 
26
26
  constructor(private readonly session: ToolSession) {}
27
27
 
28
28
  static createIf(session: ToolSession): MemoryEditTool | null {
29
29
  const backend = session.settings.get("memory.backend");
30
- if (backend !== "mnemosyne") return null;
30
+ if (backend !== "mnemopi") return null;
31
31
  return new MemoryEditTool(session);
32
32
  }
33
33
 
34
34
  async execute(_id: string, params: MemoryEditParams): Promise<AgentToolResult> {
35
- const state = this.session.getMnemosyneSessionState?.();
35
+ const state = this.session.getMnemopiSessionState?.();
36
36
  if (!state) {
37
- throw new Error("Mnemosyne backend is not initialised for this session.");
37
+ throw new Error("Mnemopi backend is not initialised for this session.");
38
38
  }
39
39
  if (params.op === "update" && params.content === undefined && params.importance === undefined) {
40
40
  throw new Error("memory_edit update requires content or importance.");
@@ -25,17 +25,17 @@ export class MemoryRecallTool implements AgentTool<typeof memoryRecallSchema> {
25
25
 
26
26
  static createIf(session: ToolSession): MemoryRecallTool | null {
27
27
  const backend = session.settings.get("memory.backend");
28
- if (backend !== "hindsight" && backend !== "mnemosyne") return null;
28
+ if (backend !== "hindsight" && backend !== "mnemopi") return null;
29
29
  return new MemoryRecallTool(session);
30
30
  }
31
31
 
32
32
  async execute(_id: string, params: MemoryRecallParams, signal?: AbortSignal): Promise<AgentToolResult> {
33
33
  return untilAborted(signal, async () => {
34
34
  const backend = this.session.settings.get("memory.backend");
35
- if (backend === "mnemosyne") {
36
- const state = this.session.getMnemosyneSessionState?.();
35
+ if (backend === "mnemopi") {
36
+ const state = this.session.getMnemopiSessionState?.();
37
37
  if (!state) {
38
- throw new Error("Mnemosyne backend is not initialised for this session.");
38
+ throw new Error("Mnemopi backend is not initialised for this session.");
39
39
  }
40
40
  try {
41
41
  const results = state.recallResultsScoped(params.query);
@@ -56,7 +56,7 @@ export class MemoryRecallTool implements AgentTool<typeof memoryRecallSchema> {
56
56
  details: {},
57
57
  };
58
58
  } catch (err) {
59
- logger.warn("recall failed", { backend: "mnemosyne", bank: state.config.bank, error: String(err) });
59
+ logger.warn("recall failed", { backend: "mnemopi", bank: state.config.bank, error: String(err) });
60
60
  throw err instanceof Error ? err : new Error(String(err));
61
61
  }
62
62
  }
@@ -26,17 +26,17 @@ export class MemoryReflectTool implements AgentTool<typeof memoryReflectSchema>
26
26
 
27
27
  static createIf(session: ToolSession): MemoryReflectTool | null {
28
28
  const backend = session.settings.get("memory.backend");
29
- if (backend !== "hindsight" && backend !== "mnemosyne") return null;
29
+ if (backend !== "hindsight" && backend !== "mnemopi") return null;
30
30
  return new MemoryReflectTool(session);
31
31
  }
32
32
 
33
33
  async execute(_id: string, params: MemoryReflectParams, signal?: AbortSignal): Promise<AgentToolResult> {
34
34
  return untilAborted(signal, async () => {
35
35
  const backend = this.session.settings.get("memory.backend");
36
- if (backend === "mnemosyne") {
37
- const state = this.session.getMnemosyneSessionState?.();
36
+ if (backend === "mnemopi") {
37
+ const state = this.session.getMnemopiSessionState?.();
38
38
  if (!state) {
39
- throw new Error("Mnemosyne backend is not initialised for this session.");
39
+ throw new Error("Mnemopi backend is not initialised for this session.");
40
40
  }
41
41
 
42
42
  try {
@@ -56,7 +56,7 @@ export class MemoryReflectTool implements AgentTool<typeof memoryReflectSchema>
56
56
  details: {},
57
57
  };
58
58
  } catch (err) {
59
- logger.warn("reflect failed", { backend: "mnemosyne", bank: state.config.bank, error: String(err) });
59
+ logger.warn("reflect failed", { backend: "mnemopi", bank: state.config.bank, error: String(err) });
60
60
  throw err instanceof Error ? err : new Error(String(err));
61
61
  }
62
62
  }
@@ -30,16 +30,16 @@ export class MemoryRetainTool implements AgentTool<typeof memoryRetainSchema> {
30
30
 
31
31
  static createIf(session: ToolSession): MemoryRetainTool | null {
32
32
  const backend = session.settings.get("memory.backend");
33
- if (backend !== "hindsight" && backend !== "mnemosyne") return null;
33
+ if (backend !== "hindsight" && backend !== "mnemopi") return null;
34
34
  return new MemoryRetainTool(session);
35
35
  }
36
36
 
37
37
  async execute(_id: string, params: MemoryRetainParams): Promise<AgentToolResult> {
38
38
  const backend = this.session.settings.get("memory.backend");
39
- if (backend === "mnemosyne") {
40
- const state = this.session.getMnemosyneSessionState?.();
39
+ if (backend === "mnemopi") {
40
+ const state = this.session.getMnemopiSessionState?.();
41
41
  if (!state) {
42
- throw new Error("Mnemosyne backend is not initialised for this session.");
42
+ throw new Error("Mnemopi backend is not initialised for this session.");
43
43
  }
44
44
 
45
45
  for (const item of params.items) {
@@ -177,7 +177,8 @@ export function formatMeta(meta: string[], theme: Theme): string {
177
177
 
178
178
  export function formatErrorMessage(message: string | undefined, theme: Theme): string {
179
179
  const clean = (message ?? "").replace(/^Error:\s*/, "").trim();
180
- return `${theme.styledSymbol("status.error", "error")} ${theme.fg("error", `Error: ${clean || "Unknown error"}`)}`;
180
+ const safe = clean ? replaceTabs(truncateToWidth(clean, TRUNCATE_LENGTHS.LINE)) : "Unknown error";
181
+ return `${theme.styledSymbol("status.error", "error")} ${theme.fg("error", `Error: ${safe}`)}`;
181
182
  }
182
183
 
183
184
  export function formatEmptyMessage(message: string, theme: Theme): string {