@oh-my-pi/pi-coding-agent 15.5.15 → 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 +46 -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/settings-schema.d.ts +232 -7
- package/dist/types/discovery/helpers.d.ts +1 -1
- package/dist/types/discovery/substitute-plugin-root.d.ts +0 -4
- package/dist/types/eval/js/shared/rewrite-imports.d.ts +16 -1
- 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/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 +33 -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 -3
- 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 +84 -10
- package/src/config/settings-schema.ts +205 -4
- package/src/edit/hashline/diff.ts +5 -7
- 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/rewrite-imports.ts +31 -26
- 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 +3 -1
- 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/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 +5 -4
- 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/segments.ts +2 -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 +58 -109
- 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/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/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 +128 -44
- 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/index.ts +17 -11
- 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 -0
- 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/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/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";
|
|
@@ -33,12 +34,13 @@ 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
46
|
import { RecipeTool } from "./recipe";
|
|
@@ -73,13 +75,14 @@ export * from "./debug";
|
|
|
73
75
|
export * from "./eval";
|
|
74
76
|
export * from "./find";
|
|
75
77
|
export * from "./gh";
|
|
76
|
-
export * from "./hindsight-recall";
|
|
77
|
-
export * from "./hindsight-reflect";
|
|
78
|
-
export * from "./hindsight-retain";
|
|
79
78
|
export * from "./image-gen";
|
|
80
79
|
export * from "./inspect-image";
|
|
81
80
|
export * from "./irc";
|
|
82
81
|
export * from "./job";
|
|
82
|
+
export * from "./memory-edit";
|
|
83
|
+
export * from "./memory-recall";
|
|
84
|
+
export * from "./memory-reflect";
|
|
85
|
+
export * from "./memory-retain";
|
|
83
86
|
export * from "./read";
|
|
84
87
|
export * from "./recipe";
|
|
85
88
|
export * from "./render-mermaid";
|
|
@@ -152,6 +155,8 @@ export interface ToolSession {
|
|
|
152
155
|
getSessionId?: () => string | null;
|
|
153
156
|
/** Get Hindsight runtime state for this agent session. */
|
|
154
157
|
getHindsightSessionState?: () => HindsightSessionState | undefined;
|
|
158
|
+
/** Get Mnemosyne runtime state for this agent session. */
|
|
159
|
+
getMnemosyneSessionState?: () => MnemosyneSessionState | undefined;
|
|
155
160
|
/** Agent identity used for IRC routing. Returns the registry id (e.g. "0-Main", "0-AuthLoader"). */
|
|
156
161
|
getAgentId?: () => string | null;
|
|
157
162
|
/** Look up a registered tool by name (used by the eval js backend's tool bridge). */
|
|
@@ -301,9 +306,10 @@ export const BUILTIN_TOOLS: Record<string, ToolFactory> = {
|
|
|
301
306
|
web_search: s => new WebSearchTool(s),
|
|
302
307
|
search_tool_bm25: SearchToolBm25Tool.createIf,
|
|
303
308
|
write: s => new WriteTool(s),
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
309
|
+
memory_edit: MemoryEditTool.createIf,
|
|
310
|
+
retain: MemoryRetainTool.createIf,
|
|
311
|
+
recall: MemoryRecallTool.createIf,
|
|
312
|
+
reflect: MemoryReflectTool.createIf,
|
|
307
313
|
};
|
|
308
314
|
|
|
309
315
|
export const HIDDEN_TOOLS: Record<string, ToolFactory> = {
|
|
@@ -417,7 +423,7 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
|
|
|
417
423
|
) {
|
|
418
424
|
requestedTools.push("recipe");
|
|
419
425
|
}
|
|
420
|
-
if (session.settings.get("memory.backend")
|
|
426
|
+
if (["hindsight", "mnemosyne"].includes(session.settings.get("memory.backend") ?? "")) {
|
|
421
427
|
for (const name of ["recall", "retain", "reflect"]) {
|
|
422
428
|
if (!requestedTools.includes(name)) requestedTools.push(name);
|
|
423
429
|
}
|
|
@@ -463,7 +469,7 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
|
|
|
463
469
|
}
|
|
464
470
|
if (name === "recipe") return session.settings.get("recipe.enabled");
|
|
465
471
|
if (name === "retain" || name === "recall" || name === "reflect") {
|
|
466
|
-
return session.settings.get("memory.backend")
|
|
472
|
+
return ["hindsight", "mnemosyne"].includes(session.settings.get("memory.backend") ?? "");
|
|
467
473
|
}
|
|
468
474
|
if (name === "task") {
|
|
469
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
|
+
};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { AgentTool, AgentToolResult } from "@oh-my-pi/pi-agent-core";
|
|
2
|
+
import * as z from "zod/v4";
|
|
3
|
+
import retainDescription from "../prompts/tools/retain.md" with { type: "text" };
|
|
4
|
+
import type { ToolSession } from ".";
|
|
5
|
+
|
|
6
|
+
const memoryRetainSchema = z.object({
|
|
7
|
+
items: z
|
|
8
|
+
.array(
|
|
9
|
+
z.object({
|
|
10
|
+
content: z.string().describe("information to remember"),
|
|
11
|
+
context: z.string().describe("source context").optional(),
|
|
12
|
+
}),
|
|
13
|
+
)
|
|
14
|
+
.min(1)
|
|
15
|
+
.describe("memories to retain"),
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
export type MemoryRetainParams = z.infer<typeof memoryRetainSchema>;
|
|
19
|
+
export class MemoryRetainTool implements AgentTool<typeof memoryRetainSchema> {
|
|
20
|
+
readonly name = "retain";
|
|
21
|
+
readonly approval = "read" as const;
|
|
22
|
+
readonly label = "Retain";
|
|
23
|
+
readonly description = retainDescription;
|
|
24
|
+
readonly parameters = memoryRetainSchema;
|
|
25
|
+
readonly strict = true;
|
|
26
|
+
readonly loadMode = "discoverable";
|
|
27
|
+
readonly summary = "Store important facts in long-term memory";
|
|
28
|
+
|
|
29
|
+
constructor(private readonly session: ToolSession) {}
|
|
30
|
+
|
|
31
|
+
static createIf(session: ToolSession): MemoryRetainTool | null {
|
|
32
|
+
const backend = session.settings.get("memory.backend");
|
|
33
|
+
if (backend !== "hindsight" && backend !== "mnemosyne") return null;
|
|
34
|
+
return new MemoryRetainTool(session);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async execute(_id: string, params: MemoryRetainParams): Promise<AgentToolResult> {
|
|
38
|
+
const backend = this.session.settings.get("memory.backend");
|
|
39
|
+
if (backend === "mnemosyne") {
|
|
40
|
+
const state = this.session.getMnemosyneSessionState?.();
|
|
41
|
+
if (!state) {
|
|
42
|
+
throw new Error("Mnemosyne backend is not initialised for this session.");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
for (const item of params.items) {
|
|
46
|
+
state.rememberScoped(item.content, {
|
|
47
|
+
source: "coding-agent-retain",
|
|
48
|
+
importance: 0.75,
|
|
49
|
+
metadata: {
|
|
50
|
+
session_id: state.sessionId,
|
|
51
|
+
cwd: state.session.sessionManager.getCwd(),
|
|
52
|
+
context: item.context ?? null,
|
|
53
|
+
tool: "retain",
|
|
54
|
+
},
|
|
55
|
+
scope: "bank",
|
|
56
|
+
extract: true,
|
|
57
|
+
extractEntities: true,
|
|
58
|
+
veracity: "tool",
|
|
59
|
+
memoryType: "fact",
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const count = params.items.length;
|
|
64
|
+
const noun = count === 1 ? "memory" : "memories";
|
|
65
|
+
return {
|
|
66
|
+
content: [{ type: "text", text: `${count} ${noun} stored.` }],
|
|
67
|
+
details: { count },
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const state = this.session.getHindsightSessionState?.();
|
|
72
|
+
if (!state) {
|
|
73
|
+
throw new Error("Hindsight backend is not initialised for this session.");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Push every item onto the session-owned queue and return immediately.
|
|
77
|
+
// The queue flushes either when it reaches its batch threshold or when
|
|
78
|
+
// its debounce timer fires. If the eventual batch fails, the queue
|
|
79
|
+
// surfaces a UI-only warning notice — the LLM is not informed.
|
|
80
|
+
for (const item of params.items) {
|
|
81
|
+
state.enqueueRetain(item.content, item.context);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const count = params.items.length;
|
|
85
|
+
const noun = count === 1 ? "memory" : "memories";
|
|
86
|
+
return {
|
|
87
|
+
content: [{ type: "text", text: `${count} ${noun} queued.` }],
|
|
88
|
+
details: { count },
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|
package/src/tools/renderers.ts
CHANGED
|
@@ -22,6 +22,7 @@ import { findToolRenderer } from "./find";
|
|
|
22
22
|
import { githubToolRenderer } from "./gh-renderer";
|
|
23
23
|
import { inspectImageToolRenderer } from "./inspect-image-renderer";
|
|
24
24
|
import { jobToolRenderer } from "./job";
|
|
25
|
+
import { recallToolRenderer, reflectToolRenderer, retainToolRenderer } from "./memory-render";
|
|
25
26
|
import { readToolRenderer } from "./read";
|
|
26
27
|
import { recipeToolRenderer } from "./recipe/render";
|
|
27
28
|
import { resolveToolRenderer } from "./resolve";
|
|
@@ -62,6 +63,9 @@ export const toolRenderers: Record<string, ToolRenderer> = {
|
|
|
62
63
|
read: readToolRenderer as ToolRenderer,
|
|
63
64
|
job: jobToolRenderer as ToolRenderer,
|
|
64
65
|
resolve: resolveToolRenderer as ToolRenderer,
|
|
66
|
+
retain: retainToolRenderer as ToolRenderer,
|
|
67
|
+
recall: recallToolRenderer as ToolRenderer,
|
|
68
|
+
reflect: reflectToolRenderer as ToolRenderer,
|
|
65
69
|
search_tool_bm25: searchToolBm25Renderer as ToolRenderer,
|
|
66
70
|
ssh: sshToolRenderer as ToolRenderer,
|
|
67
71
|
task: taskToolRenderer as ToolRenderer,
|