@oh-my-pi/pi-coding-agent 15.5.13 → 15.6.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.
- package/CHANGELOG.md +77 -0
- package/dist/types/cli/classify-install-target.d.ts +0 -10
- package/dist/types/cli/initial-message.d.ts +1 -1
- package/dist/types/cli/tiny-models-cli.d.ts +9 -0
- package/dist/types/commands/tiny-models.d.ts +22 -0
- package/dist/types/commit/analysis/conventional.d.ts +1 -1
- package/dist/types/commit/analysis/summary.d.ts +1 -1
- package/dist/types/commit/changelog/generate.d.ts +1 -1
- package/dist/types/commit/changelog/index.d.ts +2 -2
- package/dist/types/commit/map-reduce/map-phase.d.ts +1 -1
- package/dist/types/commit/map-reduce/reduce-phase.d.ts +1 -1
- package/dist/types/config/model-id-affixes.d.ts +10 -0
- package/dist/types/config/model-registry.d.ts +1 -1
- package/dist/types/config/models-config-schema.d.ts +2 -0
- package/dist/types/config/settings-schema.d.ts +233 -17
- package/dist/types/discovery/helpers.d.ts +1 -1
- package/dist/types/discovery/substitute-plugin-root.d.ts +0 -4
- package/dist/types/eval/__tests__/llm-bridge.test.d.ts +1 -0
- package/dist/types/eval/js/shared/rewrite-imports.d.ts +16 -1
- package/dist/types/eval/llm-bridge.d.ts +25 -0
- package/dist/types/export/html/template.generated.d.ts +1 -1
- package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +15 -0
- package/dist/types/internal-urls/agent-protocol.d.ts +2 -1
- package/dist/types/internal-urls/artifact-protocol.d.ts +2 -1
- package/dist/types/internal-urls/local-protocol.d.ts +2 -1
- package/dist/types/internal-urls/memory-protocol.d.ts +2 -1
- package/dist/types/internal-urls/omp-protocol.d.ts +2 -1
- package/dist/types/internal-urls/router.d.ts +8 -1
- package/dist/types/internal-urls/rule-protocol.d.ts +2 -1
- package/dist/types/internal-urls/skill-protocol.d.ts +2 -1
- package/dist/types/internal-urls/types.d.ts +26 -0
- package/dist/types/memory-backend/index.d.ts +1 -0
- package/dist/types/memory-backend/resolve.d.ts +2 -1
- package/dist/types/memory-backend/types.d.ts +7 -1
- package/dist/types/mnemosyne/backend.d.ts +4 -0
- package/dist/types/mnemosyne/config.d.ts +29 -0
- package/dist/types/mnemosyne/index.d.ts +3 -0
- package/dist/types/mnemosyne/state.d.ts +72 -0
- package/dist/types/modes/components/custom-editor.d.ts +2 -3
- package/dist/types/modes/components/hook-selector.d.ts +27 -0
- package/dist/types/modes/components/index.d.ts +1 -0
- package/dist/types/modes/components/status-line/context-thresholds.d.ts +6 -0
- package/dist/types/modes/components/tiny-title-download-progress.d.ts +11 -0
- package/dist/types/modes/components/welcome.d.ts +1 -0
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +4 -1
- package/dist/types/modes/gradient-highlight.d.ts +23 -0
- package/dist/types/modes/interactive-mode.d.ts +4 -2
- package/dist/types/modes/internal-url-autocomplete.d.ts +43 -0
- package/dist/types/modes/orchestrate.d.ts +10 -0
- package/dist/types/modes/theme/defaults/index.d.ts +8406 -8406
- package/dist/types/modes/theme/theme.d.ts +2 -1
- package/dist/types/modes/ultrathink.d.ts +3 -3
- package/dist/types/modes/utils/keybinding-matchers.d.ts +5 -0
- package/dist/types/sdk.d.ts +3 -0
- package/dist/types/session/agent-session.d.ts +35 -0
- package/dist/types/system-prompt.d.ts +2 -0
- package/dist/types/task/executor.d.ts +2 -0
- package/dist/types/task/render.d.ts +5 -1
- package/dist/types/tiny/models.d.ts +185 -0
- package/dist/types/tiny/text.d.ts +4 -0
- package/dist/types/tiny/title-client.d.ts +24 -0
- package/dist/types/tiny/title-protocol.d.ts +74 -0
- package/dist/types/tiny/worker.d.ts +2 -0
- package/dist/types/tools/bash.d.ts +3 -1
- package/dist/types/tools/index.d.ts +7 -4
- package/dist/types/tools/memory-edit.d.ts +40 -0
- package/dist/types/tools/{hindsight-recall.d.ts → memory-recall.d.ts} +6 -6
- package/dist/types/tools/{hindsight-reflect.d.ts → memory-reflect.d.ts} +6 -6
- package/dist/types/tools/memory-render.d.ts +60 -0
- package/dist/types/tools/{hindsight-retain.d.ts → memory-retain.d.ts} +6 -6
- package/dist/types/tools/todo-write.d.ts +8 -0
- package/dist/types/tools/tool-result.d.ts +2 -0
- package/dist/types/utils/title-generator.d.ts +3 -0
- package/package.json +18 -14
- package/scripts/build-binary.ts +1 -0
- package/src/cli/tiny-models-cli.ts +127 -0
- package/src/cli-commands.ts +1 -0
- package/src/cli.ts +8 -8
- package/src/commands/tiny-models.ts +36 -0
- package/src/config/model-equivalence.ts +43 -2
- package/src/config/model-id-affixes.ts +64 -0
- package/src/config/model-registry.ts +166 -8
- package/src/config/models-config-schema.ts +1 -1
- package/src/config/settings-schema.ts +206 -14
- package/src/edit/hashline/diff.ts +5 -7
- package/src/eval/__tests__/llm-bridge.test.ts +297 -0
- package/src/eval/__tests__/shared-executors.test.ts +36 -0
- package/src/eval/js/shared/local-module-loader.ts +13 -1
- package/src/eval/js/shared/prelude.txt +8 -0
- package/src/eval/js/shared/rewrite-imports.ts +31 -26
- package/src/eval/js/tool-bridge.ts +4 -0
- package/src/eval/llm-bridge.ts +181 -0
- package/src/eval/py/prelude.py +52 -31
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +0 -13
- package/src/extensibility/plugins/legacy-pi-compat.ts +60 -23
- package/src/internal-urls/agent-protocol.ts +18 -1
- package/src/internal-urls/artifact-protocol.ts +19 -1
- package/src/internal-urls/docs-index.generated.ts +5 -4
- package/src/internal-urls/local-protocol.ts +14 -1
- package/src/internal-urls/memory-protocol.ts +6 -1
- package/src/internal-urls/omp-protocol.ts +5 -1
- package/src/internal-urls/router.ts +20 -1
- package/src/internal-urls/rule-protocol.ts +8 -1
- package/src/internal-urls/skill-protocol.ts +8 -1
- package/src/internal-urls/types.ts +27 -0
- package/src/lsp/render.ts +1 -1
- package/src/main.ts +4 -0
- package/src/mcp/oauth-flow.ts +2 -2
- package/src/memory-backend/index.ts +1 -0
- package/src/memory-backend/resolve.ts +4 -1
- package/src/memory-backend/types.ts +8 -1
- package/src/mnemosyne/backend.ts +374 -0
- package/src/mnemosyne/config.ts +160 -0
- package/src/mnemosyne/index.ts +3 -0
- package/src/mnemosyne/state.ts +548 -0
- package/src/modes/acp/acp-agent.ts +11 -6
- package/src/modes/components/agent-dashboard.ts +4 -4
- package/src/modes/components/custom-editor.ts +3 -2
- package/src/modes/components/diff.ts +2 -2
- package/src/modes/components/extensions/extension-list.ts +3 -2
- package/src/modes/components/footer.ts +5 -6
- package/src/modes/components/history-search.ts +3 -3
- package/src/modes/components/hook-selector.ts +94 -8
- package/src/modes/components/index.ts +1 -0
- package/src/modes/components/mcp-add-wizard.ts +3 -3
- package/src/modes/components/model-selector.ts +124 -26
- package/src/modes/components/oauth-selector.ts +3 -3
- package/src/modes/components/session-observer-overlay.ts +19 -13
- package/src/modes/components/session-selector.ts +3 -3
- package/src/modes/components/settings-defs.ts +7 -0
- package/src/modes/components/status-line/context-thresholds.ts +11 -0
- package/src/modes/components/status-line/presets.ts +1 -0
- package/src/modes/components/status-line/segments.ts +25 -2
- package/src/modes/components/tiny-title-download-progress.ts +90 -0
- package/src/modes/components/tips.txt +12 -0
- package/src/modes/components/tool-execution.ts +67 -3
- package/src/modes/components/tree-selector.ts +3 -3
- package/src/modes/components/user-message-selector.ts +3 -3
- package/src/modes/components/welcome.ts +55 -1
- package/src/modes/controllers/command-controller.ts +16 -1
- package/src/modes/controllers/extension-ui-controller.ts +3 -1
- package/src/modes/controllers/input-controller.ts +57 -0
- package/src/modes/gradient-highlight.ts +70 -0
- package/src/modes/interactive-mode.ts +80 -196
- package/src/modes/internal-url-autocomplete.ts +143 -0
- package/src/modes/orchestrate.ts +36 -0
- package/src/modes/prompt-action-autocomplete.ts +12 -0
- package/src/modes/theme/theme.ts +7 -0
- package/src/modes/ultrathink.ts +9 -53
- package/src/modes/utils/keybinding-matchers.ts +11 -0
- package/src/prompts/system/memory-consolidation-system.md +8 -0
- package/src/prompts/system/memory-extraction-system.md +26 -0
- package/src/prompts/{commands/orchestrate.md → system/orchestrate-notice.md} +5 -16
- package/src/prompts/system/system-prompt.md +2 -0
- package/src/prompts/system/tiny-title-system.md +8 -0
- package/src/prompts/tools/eval.md +2 -0
- package/src/prompts/tools/memory-edit.md +8 -0
- package/src/prompts/tools/task.md +4 -7
- package/src/sdk.ts +8 -6
- package/src/session/agent-session.ts +147 -44
- package/src/session/session-manager.ts +47 -0
- package/src/slash-commands/builtin-registry.ts +10 -1
- package/src/system-prompt.ts +4 -0
- package/src/task/commands.ts +1 -5
- package/src/task/executor.ts +8 -0
- package/src/task/index.ts +2 -0
- package/src/task/render.ts +69 -26
- package/src/tiny/models.ts +217 -0
- package/src/tiny/text.ts +19 -0
- package/src/tiny/title-client.ts +340 -0
- package/src/tiny/title-protocol.ts +51 -0
- package/src/tiny/worker.ts +523 -0
- package/src/tools/bash.ts +58 -16
- package/src/tools/browser/tab-worker.ts +1 -1
- package/src/tools/eval.ts +24 -48
- package/src/tools/index.ts +17 -15
- package/src/tools/memory-edit.ts +59 -0
- package/src/tools/memory-recall.ts +100 -0
- package/src/tools/memory-reflect.ts +88 -0
- package/src/tools/memory-render.ts +185 -0
- package/src/tools/memory-retain.ts +91 -0
- package/src/tools/renderers.ts +4 -2
- package/src/tools/todo-write.ts +128 -29
- package/src/tools/tool-result.ts +8 -0
- package/src/utils/title-generator.ts +115 -13
- package/dist/types/tools/calculator.d.ts +0 -77
- package/src/prompts/tools/calculator.md +0 -10
- package/src/tools/calculator.ts +0 -541
- package/src/tools/hindsight-recall.ts +0 -69
- package/src/tools/hindsight-reflect.ts +0 -58
- package/src/tools/hindsight-retain.ts +0 -57
package/src/tools/eval.ts
CHANGED
|
@@ -13,10 +13,19 @@ import { truncateToVisualLines } from "../modes/components/visual-truncate";
|
|
|
13
13
|
import { getMarkdownTheme, type Theme } from "../modes/theme/theme";
|
|
14
14
|
import evalDescription from "../prompts/tools/eval.md" with { type: "text" };
|
|
15
15
|
import { DEFAULT_MAX_BYTES, OutputSink, type OutputSummary, TailBuffer } from "../session/streaming-output";
|
|
16
|
-
import {
|
|
16
|
+
import { renderCodeCell } from "../tui";
|
|
17
17
|
import { formatDimensionNote, resizeImage } from "../utils/image-resize";
|
|
18
18
|
import { resolveEvalBackends, type ToolSession } from ".";
|
|
19
19
|
import { truncateForPrompt } from "./approval";
|
|
20
|
+
import {
|
|
21
|
+
JSON_TREE_MAX_DEPTH_COLLAPSED,
|
|
22
|
+
JSON_TREE_MAX_DEPTH_EXPANDED,
|
|
23
|
+
JSON_TREE_MAX_LINES_COLLAPSED,
|
|
24
|
+
JSON_TREE_MAX_LINES_EXPANDED,
|
|
25
|
+
JSON_TREE_SCALAR_LEN_COLLAPSED,
|
|
26
|
+
JSON_TREE_SCALAR_LEN_EXPANDED,
|
|
27
|
+
renderJsonTreeLines,
|
|
28
|
+
} from "./json-tree";
|
|
20
29
|
import {
|
|
21
30
|
formatStyledTruncationWarning,
|
|
22
31
|
resolveOutputMaxColumns,
|
|
@@ -61,15 +70,6 @@ export type EvalToolResult = {
|
|
|
61
70
|
|
|
62
71
|
export type EvalProxyExecutor = (params: EvalToolParams, signal?: AbortSignal) => Promise<EvalToolResult>;
|
|
63
72
|
|
|
64
|
-
function formatJsonScalar(value: unknown): string {
|
|
65
|
-
if (value === null) return "null";
|
|
66
|
-
if (value === undefined) return "undefined";
|
|
67
|
-
if (typeof value === "string") return JSON.stringify(value);
|
|
68
|
-
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") return String(value);
|
|
69
|
-
if (typeof value === "function") return "[function]";
|
|
70
|
-
return "[object]";
|
|
71
|
-
}
|
|
72
|
-
|
|
73
73
|
/** Cap per `display()` value sent back to the model. */
|
|
74
74
|
const MAX_DISPLAY_TEXT_BYTES = 8000;
|
|
75
75
|
|
|
@@ -102,41 +102,6 @@ function formatDisplayOutputsForText(outputs: EvalDisplayOutput[]): string {
|
|
|
102
102
|
return chunks.join("\n\n");
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
function renderJsonTree(value: unknown, theme: Theme, expanded: boolean, maxDepth = expanded ? 6 : 2): string[] {
|
|
106
|
-
const maxItems = expanded ? 20 : 5;
|
|
107
|
-
|
|
108
|
-
const renderNode = (node: unknown, prefix: string, depth: number, isLast: boolean, label?: string): string[] => {
|
|
109
|
-
const branch = getTreeBranch(isLast, theme);
|
|
110
|
-
const displayLabel = label ? `${label}: ` : "";
|
|
111
|
-
|
|
112
|
-
if (depth >= maxDepth || node === null || typeof node !== "object") {
|
|
113
|
-
return [`${prefix}${branch} ${displayLabel}${formatJsonScalar(node)}`];
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const isArray = Array.isArray(node);
|
|
117
|
-
const entries = isArray
|
|
118
|
-
? node.map((val, index) => [String(index), val] as const)
|
|
119
|
-
: Object.entries(node as object);
|
|
120
|
-
const header = `${prefix}${branch} ${displayLabel}${isArray ? `Array(${entries.length})` : `Object(${entries.length})`}`;
|
|
121
|
-
const lines = [header];
|
|
122
|
-
|
|
123
|
-
const childPrefix = prefix + getTreeContinuePrefix(isLast, theme);
|
|
124
|
-
const visible = entries.slice(0, maxItems);
|
|
125
|
-
for (let i = 0; i < visible.length; i++) {
|
|
126
|
-
const [key, val] = visible[i];
|
|
127
|
-
const childLast = i === visible.length - 1 && (expanded || entries.length <= maxItems);
|
|
128
|
-
lines.push(...renderNode(val, childPrefix, depth + 1, childLast, isArray ? `[${key}]` : key));
|
|
129
|
-
}
|
|
130
|
-
if (!expanded && entries.length > maxItems) {
|
|
131
|
-
const moreBranch = theme.tree.last;
|
|
132
|
-
lines.push(`${childPrefix}${moreBranch} ${entries.length - maxItems} more item(s)`);
|
|
133
|
-
}
|
|
134
|
-
return lines;
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
return renderNode(value, "", 0, true);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
105
|
export interface EvalToolDescriptionOptions {
|
|
141
106
|
py?: boolean;
|
|
142
107
|
js?: boolean;
|
|
@@ -669,6 +634,7 @@ function formatStatusEvent(event: EvalStatusEvent, theme: Theme): string {
|
|
|
669
634
|
sh: "icon.package",
|
|
670
635
|
env: "icon.package",
|
|
671
636
|
batch: "icon.package",
|
|
637
|
+
llm: "icon.package",
|
|
672
638
|
};
|
|
673
639
|
|
|
674
640
|
const iconKey = opIcons[op] ?? "icon.file";
|
|
@@ -735,6 +701,11 @@ function formatStatusEvent(event: EvalStatusEvent, theme: Theme): string {
|
|
|
735
701
|
case "batch":
|
|
736
702
|
parts.push(`${data.files} file${(data.files as number) !== 1 ? "s" : ""} processed`);
|
|
737
703
|
break;
|
|
704
|
+
case "llm":
|
|
705
|
+
if (data.model) parts.push(String(data.model));
|
|
706
|
+
if (data.tier && data.tier !== data.model) parts.push(`(${data.tier})`);
|
|
707
|
+
parts.push(`${data.chars ?? 0} chars`);
|
|
708
|
+
break;
|
|
738
709
|
case "wc":
|
|
739
710
|
parts.push(`${data.lines}L ${data.words}W ${data.chars}C`);
|
|
740
711
|
break;
|
|
@@ -950,10 +921,15 @@ export const evalToolRenderer = {
|
|
|
950
921
|
const output = stripOutputNotice(rawOutput, details?.meta).trimEnd();
|
|
951
922
|
|
|
952
923
|
const jsonOutputs = details?.jsonOutputs ?? [];
|
|
924
|
+
const treeExpanded = options.renderContext?.expanded ?? options.expanded;
|
|
925
|
+
const treeDepth = treeExpanded ? JSON_TREE_MAX_DEPTH_EXPANDED : JSON_TREE_MAX_DEPTH_COLLAPSED;
|
|
926
|
+
const treeLineCap = treeExpanded ? JSON_TREE_MAX_LINES_EXPANDED : JSON_TREE_MAX_LINES_COLLAPSED;
|
|
927
|
+
const treeScalarLen = treeExpanded ? JSON_TREE_SCALAR_LEN_EXPANDED : JSON_TREE_SCALAR_LEN_COLLAPSED;
|
|
928
|
+
const labelOutputs = jsonOutputs.length > 1;
|
|
953
929
|
const jsonLines = jsonOutputs.flatMap((value, index) => {
|
|
954
|
-
const
|
|
955
|
-
const
|
|
956
|
-
return [
|
|
930
|
+
const tree = renderJsonTreeLines(value, uiTheme, treeDepth, treeLineCap, treeScalarLen);
|
|
931
|
+
const body = tree.truncated ? [...tree.lines, uiTheme.fg("dim", "…")] : tree.lines;
|
|
932
|
+
return labelOutputs ? [uiTheme.fg("dim", `display[${index + 1}]`), ...body] : body;
|
|
957
933
|
});
|
|
958
934
|
|
|
959
935
|
const timeoutSeconds = options.renderContext?.timeout;
|
package/src/tools/index.ts
CHANGED
|
@@ -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";
|
|
@@ -28,18 +29,18 @@ import { AstEditTool } from "./ast-edit";
|
|
|
28
29
|
import { AstGrepTool } from "./ast-grep";
|
|
29
30
|
import { BashTool } from "./bash";
|
|
30
31
|
import { BrowserTool } from "./browser";
|
|
31
|
-
import { CalculatorTool } from "./calculator";
|
|
32
32
|
import { type CheckpointState, CheckpointTool, RewindTool } from "./checkpoint";
|
|
33
33
|
import { DebugTool } from "./debug";
|
|
34
34
|
import { EvalTool } from "./eval";
|
|
35
35
|
import { FindTool } from "./find";
|
|
36
36
|
import { GithubTool } from "./gh";
|
|
37
|
-
import { HindsightRecallTool } from "./hindsight-recall";
|
|
38
|
-
import { HindsightReflectTool } from "./hindsight-reflect";
|
|
39
|
-
import { HindsightRetainTool } from "./hindsight-retain";
|
|
40
37
|
import { InspectImageTool } from "./inspect-image";
|
|
41
38
|
import { IrcTool } from "./irc";
|
|
42
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";
|
|
43
44
|
import { wrapToolWithMetaNotice } from "./output-meta";
|
|
44
45
|
import { ReadTool } from "./read";
|
|
45
46
|
import { RecipeTool } from "./recipe";
|
|
@@ -69,19 +70,19 @@ export * from "./ast-edit";
|
|
|
69
70
|
export * from "./ast-grep";
|
|
70
71
|
export * from "./bash";
|
|
71
72
|
export * from "./browser";
|
|
72
|
-
export * from "./calculator";
|
|
73
73
|
export * from "./checkpoint";
|
|
74
74
|
export * from "./debug";
|
|
75
75
|
export * from "./eval";
|
|
76
76
|
export * from "./find";
|
|
77
77
|
export * from "./gh";
|
|
78
|
-
export * from "./hindsight-recall";
|
|
79
|
-
export * from "./hindsight-reflect";
|
|
80
|
-
export * from "./hindsight-retain";
|
|
81
78
|
export * from "./image-gen";
|
|
82
79
|
export * from "./inspect-image";
|
|
83
80
|
export * from "./irc";
|
|
84
81
|
export * from "./job";
|
|
82
|
+
export * from "./memory-edit";
|
|
83
|
+
export * from "./memory-recall";
|
|
84
|
+
export * from "./memory-reflect";
|
|
85
|
+
export * from "./memory-retain";
|
|
85
86
|
export * from "./read";
|
|
86
87
|
export * from "./recipe";
|
|
87
88
|
export * from "./render-mermaid";
|
|
@@ -154,6 +155,8 @@ export interface ToolSession {
|
|
|
154
155
|
getSessionId?: () => string | null;
|
|
155
156
|
/** Get Hindsight runtime state for this agent session. */
|
|
156
157
|
getHindsightSessionState?: () => HindsightSessionState | undefined;
|
|
158
|
+
/** Get Mnemosyne runtime state for this agent session. */
|
|
159
|
+
getMnemosyneSessionState?: () => MnemosyneSessionState | undefined;
|
|
157
160
|
/** Agent identity used for IRC routing. Returns the registry id (e.g. "0-Main", "0-AuthLoader"). */
|
|
158
161
|
getAgentId?: () => string | null;
|
|
159
162
|
/** Look up a registered tool by name (used by the eval js backend's tool bridge). */
|
|
@@ -286,7 +289,6 @@ export const BUILTIN_TOOLS: Record<string, ToolFactory> = {
|
|
|
286
289
|
ask: AskTool.createIf,
|
|
287
290
|
debug: DebugTool.createIf,
|
|
288
291
|
eval: s => new EvalTool(s),
|
|
289
|
-
calc: s => new CalculatorTool(s),
|
|
290
292
|
ssh: loadSshTool,
|
|
291
293
|
github: GithubTool.createIf,
|
|
292
294
|
find: s => new FindTool(s),
|
|
@@ -304,9 +306,10 @@ export const BUILTIN_TOOLS: Record<string, ToolFactory> = {
|
|
|
304
306
|
web_search: s => new WebSearchTool(s),
|
|
305
307
|
search_tool_bm25: SearchToolBm25Tool.createIf,
|
|
306
308
|
write: s => new WriteTool(s),
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
309
|
+
memory_edit: MemoryEditTool.createIf,
|
|
310
|
+
retain: MemoryRetainTool.createIf,
|
|
311
|
+
recall: MemoryRecallTool.createIf,
|
|
312
|
+
reflect: MemoryReflectTool.createIf,
|
|
310
313
|
};
|
|
311
314
|
|
|
312
315
|
export const HIDDEN_TOOLS: Record<string, ToolFactory> = {
|
|
@@ -420,7 +423,7 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
|
|
|
420
423
|
) {
|
|
421
424
|
requestedTools.push("recipe");
|
|
422
425
|
}
|
|
423
|
-
if (session.settings.get("memory.backend")
|
|
426
|
+
if (["hindsight", "mnemosyne"].includes(session.settings.get("memory.backend") ?? "")) {
|
|
424
427
|
for (const name of ["recall", "retain", "reflect"]) {
|
|
425
428
|
if (!requestedTools.includes(name)) requestedTools.push(name);
|
|
426
429
|
}
|
|
@@ -455,7 +458,6 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
|
|
|
455
458
|
if (name === "web_search") return session.settings.get("web_search.enabled");
|
|
456
459
|
// search_tool_bm25 is allowed when either legacy mcp.discoveryMode or new tools.discoveryMode is active.
|
|
457
460
|
if (name === "search_tool_bm25") return discoveryActive;
|
|
458
|
-
if (name === "calc") return session.settings.get("calc.enabled");
|
|
459
461
|
if (name === "browser") return session.settings.get("browser.enabled");
|
|
460
462
|
if (name === "checkpoint" || name === "rewind") return session.settings.get("checkpoint.enabled");
|
|
461
463
|
if (name === "irc") {
|
|
@@ -467,7 +469,7 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
|
|
|
467
469
|
}
|
|
468
470
|
if (name === "recipe") return session.settings.get("recipe.enabled");
|
|
469
471
|
if (name === "retain" || name === "recall" || name === "reflect") {
|
|
470
|
-
return session.settings.get("memory.backend")
|
|
472
|
+
return ["hindsight", "mnemosyne"].includes(session.settings.get("memory.backend") ?? "");
|
|
471
473
|
}
|
|
472
474
|
if (name === "task") {
|
|
473
475
|
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
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inline TUI renderers for the long-term memory tools (`retain`, `recall`,
|
|
3
|
+
* `reflect`).
|
|
4
|
+
*
|
|
5
|
+
* These keep the transcript terse — one status line plus, for `retain`, one
|
|
6
|
+
* `Remember: …` line per stored item — instead of the generic JSON arg tree,
|
|
7
|
+
* which exploded multi-line memory blobs into an unreadable wall.
|
|
8
|
+
*/
|
|
9
|
+
import type { Component } from "@oh-my-pi/pi-tui";
|
|
10
|
+
import { Text } from "@oh-my-pi/pi-tui";
|
|
11
|
+
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
12
|
+
import type { Theme } from "../modes/theme/theme";
|
|
13
|
+
import { Ellipsis, renderStatusLine, truncateToWidth } from "../tui";
|
|
14
|
+
import {
|
|
15
|
+
createCachedComponent,
|
|
16
|
+
formatErrorMessage,
|
|
17
|
+
formatExpandHint,
|
|
18
|
+
PREVIEW_LIMITS,
|
|
19
|
+
replaceTabs,
|
|
20
|
+
type ToolUIStatus,
|
|
21
|
+
} from "./render-utils";
|
|
22
|
+
|
|
23
|
+
// Each stored memory renders as `<bullet> <content>`; the bullet glyph comes
|
|
24
|
+
// from the active theme (`•` by default, a nerd-font dot under nerd themes).
|
|
25
|
+
|
|
26
|
+
interface RetainRenderArgs {
|
|
27
|
+
items?: Array<{ content?: string; context?: string }>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface QueryRenderArgs {
|
|
31
|
+
query?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function retainContents(args: RetainRenderArgs | undefined): string[] {
|
|
35
|
+
return (args?.items ?? []).map(item => replaceTabs((item?.content ?? "").trim())).filter(line => line.length > 0);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function resultText(result: { content?: Array<{ type: string; text?: string }> }): string {
|
|
39
|
+
return (result.content?.find(c => c.type === "text")?.text ?? "").trim();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Single-line query header used by `recall`/`reflect` calls and results. */
|
|
43
|
+
function queryHeader(
|
|
44
|
+
title: string,
|
|
45
|
+
query: string | undefined,
|
|
46
|
+
icon: ToolUIStatus,
|
|
47
|
+
theme: Theme,
|
|
48
|
+
meta?: string[],
|
|
49
|
+
): string {
|
|
50
|
+
const trimmed = replaceTabs((query ?? "").trim());
|
|
51
|
+
const description = trimmed ? truncateToWidth(trimmed, 80, Ellipsis.Unicode) : undefined;
|
|
52
|
+
return renderStatusLine({ icon, title, description, meta }, theme);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function retainComponent(contents: string[], header: string, getExpanded: () => boolean, theme: Theme): Component {
|
|
56
|
+
return createCachedComponent(getExpanded, (width, expanded) => {
|
|
57
|
+
const lines = [header];
|
|
58
|
+
const limit = expanded ? contents.length : PREVIEW_LIMITS.COLLAPSED_ITEMS;
|
|
59
|
+
const shown = contents.slice(0, limit);
|
|
60
|
+
const bullet = theme.format.bullet;
|
|
61
|
+
const contentWidth = Math.max(8, width - 2 - Bun.stringWidth(bullet) - 1);
|
|
62
|
+
for (const content of shown) {
|
|
63
|
+
const value = truncateToWidth(content, contentWidth, Ellipsis.Unicode);
|
|
64
|
+
lines.push(` ${theme.fg("muted", bullet)} ${theme.fg("toolOutput", value)}`);
|
|
65
|
+
}
|
|
66
|
+
const remaining = contents.length - shown.length;
|
|
67
|
+
if (remaining > 0) {
|
|
68
|
+
lines.push(` ${theme.fg("dim", `… ${remaining} more`)} ${formatExpandHint(theme, expanded, true)}`);
|
|
69
|
+
}
|
|
70
|
+
return lines.map(line => truncateToWidth(line, width, Ellipsis.Omit));
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export const retainToolRenderer = {
|
|
75
|
+
inline: true,
|
|
76
|
+
mergeCallAndResult: true,
|
|
77
|
+
renderCall(args: RetainRenderArgs, options: RenderResultOptions, theme: Theme): Component {
|
|
78
|
+
const contents = retainContents(args);
|
|
79
|
+
const header = renderStatusLine({ icon: "pending", title: "Retain" }, theme);
|
|
80
|
+
return retainComponent(contents, header, () => options.expanded, theme);
|
|
81
|
+
},
|
|
82
|
+
renderResult(
|
|
83
|
+
result: { content: Array<{ type: string; text?: string }>; details?: { count?: number }; isError?: boolean },
|
|
84
|
+
options: RenderResultOptions,
|
|
85
|
+
theme: Theme,
|
|
86
|
+
args?: RetainRenderArgs,
|
|
87
|
+
): Component {
|
|
88
|
+
if (result.isError) {
|
|
89
|
+
return new Text(formatErrorMessage(resultText(result) || "Retain failed", theme), 0, 0);
|
|
90
|
+
}
|
|
91
|
+
const contents = retainContents(args);
|
|
92
|
+
// `summary` is the tool's own "N memories stored/queued." line; drop the
|
|
93
|
+
// trailing period so it reads cleanly as a status meta segment.
|
|
94
|
+
const summary = resultText(result).replace(/\.$/, "");
|
|
95
|
+
const header = renderStatusLine(
|
|
96
|
+
{ icon: "success", title: "Retain", meta: summary ? [summary] : undefined },
|
|
97
|
+
theme,
|
|
98
|
+
);
|
|
99
|
+
return retainComponent(contents, header, () => options.expanded, theme);
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
export const recallToolRenderer = {
|
|
104
|
+
inline: true,
|
|
105
|
+
mergeCallAndResult: true,
|
|
106
|
+
renderCall(args: QueryRenderArgs, _options: RenderResultOptions, theme: Theme): Component {
|
|
107
|
+
return new Text(queryHeader("Recall", args.query, "pending", theme), 0, 0);
|
|
108
|
+
},
|
|
109
|
+
renderResult(
|
|
110
|
+
result: { content: Array<{ type: string; text?: string }>; isError?: boolean },
|
|
111
|
+
options: RenderResultOptions,
|
|
112
|
+
theme: Theme,
|
|
113
|
+
args?: QueryRenderArgs,
|
|
114
|
+
): Component {
|
|
115
|
+
if (result.isError) {
|
|
116
|
+
return new Text(formatErrorMessage(resultText(result) || "Recall failed", theme), 0, 0);
|
|
117
|
+
}
|
|
118
|
+
const text = resultText(result);
|
|
119
|
+
const match = text.match(/^Found (\d+) relevant/);
|
|
120
|
+
const found = match ? Number(match[1]) : 0;
|
|
121
|
+
const icon: ToolUIStatus = found > 0 ? "success" : "warning";
|
|
122
|
+
const meta = [found > 0 ? `${found} found` : "no matches"];
|
|
123
|
+
const header = queryHeader("Recall", args?.query, icon, theme, meta);
|
|
124
|
+
if (found === 0) {
|
|
125
|
+
return new Text(header, 0, 0);
|
|
126
|
+
}
|
|
127
|
+
// Collapsed view is the header alone; expand to inspect the recalled
|
|
128
|
+
// memories without dumping the whole block into the transcript.
|
|
129
|
+
const body = text.replace(/^[^\n]*\n+/, "");
|
|
130
|
+
return createCachedComponent(
|
|
131
|
+
() => options.expanded,
|
|
132
|
+
(width, expanded) => {
|
|
133
|
+
const lines = [header];
|
|
134
|
+
if (expanded) {
|
|
135
|
+
const bodyLines = body.split("\n").slice(0, PREVIEW_LIMITS.OUTPUT_EXPANDED);
|
|
136
|
+
for (const line of bodyLines) {
|
|
137
|
+
lines.push(` ${theme.fg("muted", replaceTabs(line))}`);
|
|
138
|
+
}
|
|
139
|
+
} else {
|
|
140
|
+
lines.push(` ${formatExpandHint(theme, false, true)}`);
|
|
141
|
+
}
|
|
142
|
+
return lines.map(line => truncateToWidth(line, width, Ellipsis.Omit));
|
|
143
|
+
},
|
|
144
|
+
);
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
export const reflectToolRenderer = {
|
|
149
|
+
inline: true,
|
|
150
|
+
mergeCallAndResult: true,
|
|
151
|
+
renderCall(args: QueryRenderArgs, _options: RenderResultOptions, theme: Theme): Component {
|
|
152
|
+
return new Text(queryHeader("Reflect", args.query, "pending", theme), 0, 0);
|
|
153
|
+
},
|
|
154
|
+
renderResult(
|
|
155
|
+
result: { content: Array<{ type: string; text?: string }>; isError?: boolean },
|
|
156
|
+
options: RenderResultOptions,
|
|
157
|
+
theme: Theme,
|
|
158
|
+
args?: QueryRenderArgs,
|
|
159
|
+
): Component {
|
|
160
|
+
if (result.isError) {
|
|
161
|
+
return new Text(formatErrorMessage(resultText(result) || "Reflect failed", theme), 0, 0);
|
|
162
|
+
}
|
|
163
|
+
const header = queryHeader("Reflect", args?.query, "success", theme);
|
|
164
|
+
const answer = resultText(result);
|
|
165
|
+
const answerLines = answer.split("\n").filter(line => line.trim().length > 0);
|
|
166
|
+
return createCachedComponent(
|
|
167
|
+
() => options.expanded,
|
|
168
|
+
(width, expanded) => {
|
|
169
|
+
const limit = expanded ? PREVIEW_LIMITS.OUTPUT_EXPANDED : PREVIEW_LIMITS.OUTPUT_COLLAPSED;
|
|
170
|
+
const shown = answerLines.slice(0, limit);
|
|
171
|
+
const lines = [header];
|
|
172
|
+
for (const line of shown) {
|
|
173
|
+
lines.push(` ${theme.fg("toolOutput", replaceTabs(line))}`);
|
|
174
|
+
}
|
|
175
|
+
const remaining = answerLines.length - shown.length;
|
|
176
|
+
if (remaining > 0) {
|
|
177
|
+
lines.push(
|
|
178
|
+
` ${theme.fg("dim", `… ${remaining} more lines`)} ${formatExpandHint(theme, expanded, true)}`,
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
return lines.map(line => truncateToWidth(line, width, Ellipsis.Omit));
|
|
182
|
+
},
|
|
183
|
+
);
|
|
184
|
+
},
|
|
185
|
+
};
|