@agentmemory/agentmemory 0.9.27 → 0.9.29
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/.env.example +15 -6
- package/AGENTS.md +7 -7
- package/README.md +292 -164
- package/dist/.env.example +15 -6
- package/dist/cli.d.mts.map +1 -1
- package/dist/cli.mjs +427 -177
- package/dist/cli.mjs.map +1 -1
- package/dist/{connect-bmZ5eqYN.mjs → connect-Qaeq15TQ.mjs} +717 -65
- package/dist/connect-Qaeq15TQ.mjs.map +1 -0
- package/dist/docker-compose.yml +2 -2
- package/dist/hooks/antigravity-bridge.d.mts +8 -0
- package/dist/hooks/antigravity-bridge.d.mts.map +1 -0
- package/dist/hooks/antigravity-bridge.mjs +114 -0
- package/dist/hooks/antigravity-bridge.mjs.map +1 -0
- package/dist/hooks/notification.mjs +21 -5
- package/dist/hooks/notification.mjs.map +1 -1
- package/dist/hooks/post-commit.mjs +15 -2
- package/dist/hooks/post-commit.mjs.map +1 -1
- package/dist/hooks/post-tool-failure.mjs +16 -4
- package/dist/hooks/post-tool-failure.mjs.map +1 -1
- package/dist/hooks/post-tool-use.mjs +16 -4
- package/dist/hooks/post-tool-use.mjs.map +1 -1
- package/dist/hooks/pre-compact.mjs +14 -3
- package/dist/hooks/pre-compact.mjs.map +1 -1
- package/dist/hooks/pre-tool-use.mjs +3 -2
- package/dist/hooks/pre-tool-use.mjs.map +1 -1
- package/dist/hooks/prompt-submit.mjs +16 -4
- package/dist/hooks/prompt-submit.mjs.map +1 -1
- package/dist/hooks/session-end.mjs +84 -19
- package/dist/hooks/session-end.mjs.map +1 -1
- package/dist/hooks/session-start.mjs +24 -5
- package/dist/hooks/session-start.mjs.map +1 -1
- package/dist/hooks/stop.mjs +3 -8
- package/dist/hooks/stop.mjs.map +1 -1
- package/dist/hooks/subagent-start.mjs +16 -4
- package/dist/hooks/subagent-start.mjs.map +1 -1
- package/dist/hooks/subagent-stop.mjs +16 -4
- package/dist/hooks/subagent-stop.mjs.map +1 -1
- package/dist/hooks/task-completed.mjs +16 -4
- package/dist/hooks/task-completed.mjs.map +1 -1
- package/dist/{image-refs-B5Zf_Djo.mjs → image-refs-BKLmp_sD.mjs} +4 -22
- package/dist/image-refs-BKLmp_sD.mjs.map +1 -0
- package/dist/image-refs-CJgZmb9X.mjs +34 -0
- package/dist/image-refs-CJgZmb9X.mjs.map +1 -0
- package/dist/index.mjs +1681 -911
- package/dist/index.mjs.map +1 -1
- package/dist/{image-store-Gpo2mgM9.mjs → keyed-mutex-DTHtcZrt.mjs} +14 -11
- package/dist/keyed-mutex-DTHtcZrt.mjs.map +1 -0
- package/dist/{logger-yHTcEBAI.mjs → logger-BEB7pCI9.mjs} +1 -1
- package/dist/{logger-yHTcEBAI.mjs.map → logger-BEB7pCI9.mjs.map} +1 -1
- package/dist/schema-BFZhNTF7.mjs +197 -0
- package/dist/schema-BFZhNTF7.mjs.map +1 -0
- package/dist/{src-MOg2zKJs.mjs → src-G7yt8gGm.mjs} +1521 -854
- package/dist/src-G7yt8gGm.mjs.map +1 -0
- package/dist/{standalone-BORXC71-.mjs → standalone-BqNJDJ04.mjs} +30 -16
- package/dist/standalone-BqNJDJ04.mjs.map +1 -0
- package/dist/standalone.d.mts.map +1 -1
- package/dist/standalone.mjs +55 -15
- package/dist/standalone.mjs.map +1 -1
- package/dist/{tools-registry-D8XVVPbJ.mjs → tools-registry-BV_2bu7z.mjs} +55 -17
- package/dist/tools-registry-BV_2bu7z.mjs.map +1 -0
- package/dist/viewer/favicon.svg +35 -1
- package/dist/viewer/index.html +473 -83
- package/docker-compose.yml +2 -2
- package/integrations/pi/README.md +77 -0
- package/integrations/pi/index.ts +403 -0
- package/integrations/pi/package.json +32 -0
- package/integrations/pi/security.ts +35 -0
- package/package.json +6 -4
- package/plugin/.claude-plugin/plugin.json +2 -2
- package/plugin/.codex-plugin/plugin.json +2 -2
- package/plugin/.devin-plugin/plugin.json +32 -0
- package/plugin/cursor/hooks.json +41 -0
- package/plugin/cursor/mcp.json +12 -0
- package/plugin/hooks/hooks.antigravity.json +43 -0
- package/plugin/hooks/hooks.devin.json +67 -0
- package/plugin/hooks/hooks.droid.json +55 -0
- package/plugin/opencode/README.md +1 -1
- package/plugin/opencode/agentmemory-capture.ts +73 -13
- package/plugin/opencode/plugin.json +1 -1
- package/plugin/plugin.json +2 -2
- package/plugin/scripts/antigravity-bridge.d.mts +8 -0
- package/plugin/scripts/antigravity-bridge.d.mts.map +1 -0
- package/plugin/scripts/antigravity-bridge.mjs +114 -0
- package/plugin/scripts/antigravity-bridge.mjs.map +1 -0
- package/plugin/scripts/notification.mjs +21 -5
- package/plugin/scripts/notification.mjs.map +1 -1
- package/plugin/scripts/post-commit.mjs +15 -2
- package/plugin/scripts/post-commit.mjs.map +1 -1
- package/plugin/scripts/post-tool-failure.mjs +16 -4
- package/plugin/scripts/post-tool-failure.mjs.map +1 -1
- package/plugin/scripts/post-tool-use.mjs +16 -4
- package/plugin/scripts/post-tool-use.mjs.map +1 -1
- package/plugin/scripts/pre-compact.mjs +14 -3
- package/plugin/scripts/pre-compact.mjs.map +1 -1
- package/plugin/scripts/pre-tool-use.mjs +3 -2
- package/plugin/scripts/pre-tool-use.mjs.map +1 -1
- package/plugin/scripts/prompt-submit.mjs +16 -4
- package/plugin/scripts/prompt-submit.mjs.map +1 -1
- package/plugin/scripts/session-end.mjs +84 -19
- package/plugin/scripts/session-end.mjs.map +1 -1
- package/plugin/scripts/session-start.mjs +24 -5
- package/plugin/scripts/session-start.mjs.map +1 -1
- package/plugin/scripts/stop.mjs +3 -8
- package/plugin/scripts/stop.mjs.map +1 -1
- package/plugin/scripts/subagent-start.mjs +16 -4
- package/plugin/scripts/subagent-start.mjs.map +1 -1
- package/plugin/scripts/subagent-stop.mjs +16 -4
- package/plugin/scripts/subagent-stop.mjs.map +1 -1
- package/plugin/scripts/task-completed.mjs +16 -4
- package/plugin/scripts/task-completed.mjs.map +1 -1
- package/plugin/skills/_shared/TROUBLESHOOTING.md +38 -0
- package/plugin/skills/agentmemory-agents/REFERENCE.md +31 -0
- package/plugin/skills/agentmemory-agents/SKILL.md +34 -0
- package/plugin/skills/agentmemory-architecture/SKILL.md +33 -0
- package/plugin/skills/agentmemory-config/REFERENCE.md +45 -0
- package/plugin/skills/agentmemory-config/SKILL.md +37 -0
- package/plugin/skills/agentmemory-hooks/REFERENCE.md +20 -0
- package/plugin/skills/agentmemory-hooks/SKILL.md +39 -0
- package/plugin/skills/agentmemory-mcp-tools/REFERENCE.md +66 -0
- package/plugin/skills/agentmemory-mcp-tools/SKILL.md +39 -0
- package/plugin/skills/agentmemory-rest-api/REFERENCE.md +142 -0
- package/plugin/skills/agentmemory-rest-api/SKILL.md +43 -0
- package/plugin/skills/commit-context/EXAMPLES.md +71 -0
- package/plugin/skills/commit-context/SKILL.md +53 -8
- package/plugin/skills/commit-history/EXAMPLES.md +71 -0
- package/plugin/skills/commit-history/SKILL.md +51 -9
- package/plugin/skills/forget/EXAMPLES.md +75 -0
- package/plugin/skills/forget/SKILL.md +57 -14
- package/plugin/skills/handoff/EXAMPLES.md +56 -0
- package/plugin/skills/handoff/SKILL.md +56 -9
- package/plugin/skills/lesson/SKILL.md +64 -0
- package/plugin/skills/memory-discipline/SKILL.md +57 -0
- package/plugin/skills/recall/EXAMPLES.md +74 -0
- package/plugin/skills/recall/SKILL.md +53 -11
- package/plugin/skills/recap/EXAMPLES.md +60 -0
- package/plugin/skills/recap/SKILL.md +51 -13
- package/plugin/skills/remember/EXAMPLES.md +67 -0
- package/plugin/skills/remember/SKILL.md +58 -17
- package/plugin/skills/session-history/EXAMPLES.md +59 -0
- package/plugin/skills/session-history/SKILL.md +54 -11
- package/plugin/skills/write-agentmemory-skill/SKILL.md +40 -0
- package/dist/connect-bmZ5eqYN.mjs.map +0 -1
- package/dist/image-refs-B5Zf_Djo.mjs.map +0 -1
- package/dist/image-store-Gpo2mgM9.mjs.map +0 -1
- package/dist/rolldown-runtime-twds-ZHy.mjs +0 -14
- package/dist/schema-CNiCWzV9.mjs +0 -78
- package/dist/schema-CNiCWzV9.mjs.map +0 -1
- package/dist/src-MOg2zKJs.mjs.map +0 -1
- package/dist/standalone-BORXC71-.mjs.map +0 -1
- package/dist/tools-registry-D8XVVPbJ.mjs.map +0 -1
- package/dist/version-9DNrRyph.mjs +0 -6
- package/dist/version-9DNrRyph.mjs.map +0 -1
- package/plugin/scripts/diagnostics.d.mts +0 -17
- package/plugin/scripts/diagnostics.d.mts.map +0 -1
- package/plugin/scripts/diagnostics.mjs.map +0 -1
package/docker-compose.yml
CHANGED
|
@@ -9,7 +9,7 @@ services:
|
|
|
9
9
|
image: busybox:1.36
|
|
10
10
|
user: "0:0"
|
|
11
11
|
volumes:
|
|
12
|
-
- iii-data:/data
|
|
12
|
+
- "${AGENTMEMORY_DATA_DIR:-iii-data}:/data"
|
|
13
13
|
entrypoint: ["sh", "-c", "chown -R 65532:65532 /data && chmod 755 /data"]
|
|
14
14
|
restart: "no"
|
|
15
15
|
|
|
@@ -34,7 +34,7 @@ services:
|
|
|
34
34
|
- "127.0.0.1:3112:3112"
|
|
35
35
|
- "127.0.0.1:9464:9464"
|
|
36
36
|
volumes:
|
|
37
|
-
- iii-data:/data
|
|
37
|
+
- "${AGENTMEMORY_DATA_DIR:-iii-data}:/data"
|
|
38
38
|
- ./iii-config.docker.yaml:/app/config.yaml:ro
|
|
39
39
|
restart: unless-stopped
|
|
40
40
|
logging:
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="../../assets/banner.png" alt="agentmemory" width="640" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<h1 align="center">
|
|
6
|
+
agentmemory for pi
|
|
7
|
+
</h1>
|
|
8
|
+
|
|
9
|
+
<p align="center">
|
|
10
|
+
<strong>Your pi sessions remember everything. No more re-explaining.</strong><br/>
|
|
11
|
+
<sub>Persistent cross-session memory via <a href="https://github.com/rohitg00/agentmemory">agentmemory</a> — shared with Claude Code, Codex CLI, Gemini CLI, Hermes, OpenClaw, and more.</sub>
|
|
12
|
+
</p>
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Quick setup
|
|
17
|
+
|
|
18
|
+
Start the agentmemory server in a separate terminal:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npx @agentmemory/agentmemory
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Copy this folder into pi's global extensions directory:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
mkdir -p ~/.pi/agent/extensions/agentmemory
|
|
28
|
+
cp integrations/pi/index.ts ~/.pi/agent/extensions/agentmemory/index.ts
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Then enable it in `~/.pi/agent/settings.json` if you prefer explicit loading:
|
|
32
|
+
|
|
33
|
+
```json
|
|
34
|
+
{
|
|
35
|
+
"extensions": ["~/.pi/agent/extensions/agentmemory"]
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
If you place it under `~/.pi/agent/extensions/agentmemory/`, pi will also auto-discover it and `/reload` can hot-reload it.
|
|
40
|
+
|
|
41
|
+
## What it adds
|
|
42
|
+
|
|
43
|
+
- `memory_health` — confirm the shared memory server is reachable
|
|
44
|
+
- `memory_search` — search prior decisions, bugs, workflows, and preferences
|
|
45
|
+
- `memory_save` — write durable facts back to long-term memory
|
|
46
|
+
- `/agentmemory-status` — check health from inside pi
|
|
47
|
+
- `before_agent_start` recall — injects relevant memories into the prompt
|
|
48
|
+
- `agent_end` capture — saves completed conversation turns back to agentmemory
|
|
49
|
+
|
|
50
|
+
## Environment variables
|
|
51
|
+
|
|
52
|
+
| Variable | Default | Description |
|
|
53
|
+
|---|---|---|
|
|
54
|
+
| `AGENTMEMORY_URL` | `http://localhost:3111` | agentmemory server URL |
|
|
55
|
+
| `AGENTMEMORY_SECRET` | (none) | Bearer token for protected instances |
|
|
56
|
+
| `AGENTMEMORY_REQUIRE_HTTPS` | (off) | When set to `1`, refuse to send a bearer token over plaintext HTTP to a non-loopback host. Sends the token only when `AGENTMEMORY_URL` is `https://...` or points at `localhost`/`127.0.0.1`/`::1`. With this off, the plugin warns once but still sends. |
|
|
57
|
+
|
|
58
|
+
## Smoke test
|
|
59
|
+
|
|
60
|
+
Run pi and ask it to use the `memory_health` tool, or call the command directly:
|
|
61
|
+
|
|
62
|
+
```text
|
|
63
|
+
/agentmemory-status
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
You should see `agentmemory healthy` and a footer status like `🧠 agentmemory`.
|
|
67
|
+
|
|
68
|
+
## Notes
|
|
69
|
+
|
|
70
|
+
- This extension uses pi's extension API, not MCP, so it can hook directly into the agent lifecycle.
|
|
71
|
+
- One local agentmemory server can be shared across pi, pi2, Hermes, OpenClaw, Claude Code, Codex CLI, and Gemini CLI.
|
|
72
|
+
|
|
73
|
+
## See also
|
|
74
|
+
|
|
75
|
+
- [agentmemory main README](../../README.md)
|
|
76
|
+
- [Hermes integration](../hermes/README.md)
|
|
77
|
+
- [OpenClaw integration](../openclaw/README.md)
|
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { Type } from "typebox";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import crypto from "node:crypto";
|
|
5
|
+
import { execFileSync } from "node:child_process";
|
|
6
|
+
import { createPlaintextBearerAuthGuard } from "./security.js";
|
|
7
|
+
|
|
8
|
+
type TextBlock = { type?: string; text?: string };
|
|
9
|
+
type AssistantMessage = { role?: string; content?: unknown };
|
|
10
|
+
type SmartSearchResult = {
|
|
11
|
+
title?: string;
|
|
12
|
+
narrative?: string;
|
|
13
|
+
type?: string;
|
|
14
|
+
combinedScore?: number;
|
|
15
|
+
score?: number;
|
|
16
|
+
observation?: {
|
|
17
|
+
title?: string;
|
|
18
|
+
narrative?: string;
|
|
19
|
+
type?: string;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
type HealthResponse = {
|
|
24
|
+
status?: string;
|
|
25
|
+
service?: string;
|
|
26
|
+
version?: string;
|
|
27
|
+
health?: {
|
|
28
|
+
status?: string;
|
|
29
|
+
notes?: string[];
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const DEFAULT_URL = process.env.AGENTMEMORY_URL || "http://localhost:3111";
|
|
34
|
+
const guardPlaintextBearerAuth = createPlaintextBearerAuthGuard();
|
|
35
|
+
const TOOL_GUIDANCE = [
|
|
36
|
+
"agentmemory is available for cross-session memory.",
|
|
37
|
+
"Use memory_search to recall prior decisions, preferences, bugs, and workflows.",
|
|
38
|
+
"Use memory_save when you discover durable facts worth remembering beyond this session.",
|
|
39
|
+
].join(" ");
|
|
40
|
+
|
|
41
|
+
function normalizeBaseUrl(url: string): string {
|
|
42
|
+
return url.replace(/\/+$/, "");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function getText(content: unknown): string {
|
|
46
|
+
if (typeof content === "string") return content;
|
|
47
|
+
if (!Array.isArray(content)) return "";
|
|
48
|
+
return content
|
|
49
|
+
.flatMap((part) => {
|
|
50
|
+
if (!part || typeof part !== "object") return [] as string[];
|
|
51
|
+
const block = part as TextBlock;
|
|
52
|
+
if (block.type === "text" && typeof block.text === "string") return [block.text];
|
|
53
|
+
return [] as string[];
|
|
54
|
+
})
|
|
55
|
+
.join("\n")
|
|
56
|
+
.trim();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function getLastAssistantText(messages: unknown[]): string {
|
|
60
|
+
for (const msg of [...messages].reverse()) {
|
|
61
|
+
if (!msg || typeof msg !== "object") continue;
|
|
62
|
+
const assistant = msg as AssistantMessage;
|
|
63
|
+
if (assistant.role !== "assistant") continue;
|
|
64
|
+
const text = getText(assistant.content);
|
|
65
|
+
if (text) return text;
|
|
66
|
+
}
|
|
67
|
+
return "";
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function formatSearchResults(results: SmartSearchResult[]): string {
|
|
71
|
+
if (!results.length) return "No relevant memories found.";
|
|
72
|
+
return results
|
|
73
|
+
.slice(0, 5)
|
|
74
|
+
.map((result, index) => {
|
|
75
|
+
const obs = result.observation ?? result;
|
|
76
|
+
const title = obs.title?.trim() || `Memory ${index + 1}`;
|
|
77
|
+
const narrative = obs.narrative?.trim() || "";
|
|
78
|
+
const type = obs.type?.trim() || "memory";
|
|
79
|
+
const score = result.combinedScore ?? result.score;
|
|
80
|
+
const scoreText = typeof score === "number" ? ` [score=${score.toFixed(3)}]` : "";
|
|
81
|
+
return `- ${title} (${type})${scoreText}${narrative ? `: ${narrative}` : ""}`;
|
|
82
|
+
})
|
|
83
|
+
.join("\n");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function callAgentMemory<T>(
|
|
87
|
+
pathname: string,
|
|
88
|
+
options?: {
|
|
89
|
+
method?: "GET" | "POST";
|
|
90
|
+
body?: unknown;
|
|
91
|
+
baseUrl?: string;
|
|
92
|
+
timeoutMs?: number;
|
|
93
|
+
},
|
|
94
|
+
): Promise<T | null> {
|
|
95
|
+
const baseUrl = normalizeBaseUrl(options?.baseUrl || process.env.AGENTMEMORY_URL || DEFAULT_URL);
|
|
96
|
+
const method = options?.method || "POST";
|
|
97
|
+
const url = `${baseUrl}/agentmemory/${pathname.replace(/^\/+/, "")}`;
|
|
98
|
+
const headers: Record<string, string> = {};
|
|
99
|
+
const secret = process.env.AGENTMEMORY_SECRET;
|
|
100
|
+
guardPlaintextBearerAuth(baseUrl, secret);
|
|
101
|
+
if (options?.body !== undefined) headers["Content-Type"] = "application/json";
|
|
102
|
+
if (secret) headers.Authorization = `Bearer ${secret}`;
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
const response = await fetch(url, {
|
|
106
|
+
method,
|
|
107
|
+
headers,
|
|
108
|
+
body: options?.body !== undefined ? JSON.stringify(options.body) : undefined,
|
|
109
|
+
signal: options?.timeoutMs ? AbortSignal.timeout(options.timeoutMs) : undefined,
|
|
110
|
+
});
|
|
111
|
+
if (!response.ok) return null;
|
|
112
|
+
return (await response.json()) as T;
|
|
113
|
+
} catch {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export default function agentmemoryExtension(pi: ExtensionAPI) {
|
|
119
|
+
if (process.env.AGENTMEMORY_REQUIRE_HTTPS === "1") {
|
|
120
|
+
guardPlaintextBearerAuth(
|
|
121
|
+
normalizeBaseUrl(process.env.AGENTMEMORY_URL || DEFAULT_URL),
|
|
122
|
+
process.env.AGENTMEMORY_SECRET,
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
let sessionId = `ephemeral-${crypto.randomUUID().slice(0, 8)}`;
|
|
126
|
+
// Canonical project scope, matching the hooks' resolveProject order (env
|
|
127
|
+
// override, git toplevel basename, cwd basename) so Pi sessions share a
|
|
128
|
+
// project bucket with every other agent instead of scoping on a raw path.
|
|
129
|
+
const projectCache = new Map<string, string>();
|
|
130
|
+
function resolveProjectName(dir: string): string {
|
|
131
|
+
const explicit = process.env["AGENTMEMORY_PROJECT_NAME"]?.trim();
|
|
132
|
+
if (explicit) return explicit;
|
|
133
|
+
const cached = projectCache.get(dir);
|
|
134
|
+
if (cached) return cached;
|
|
135
|
+
let name = path.basename(dir) || dir;
|
|
136
|
+
try {
|
|
137
|
+
const top = execFileSync("git", ["rev-parse", "--show-toplevel"], {
|
|
138
|
+
cwd: dir,
|
|
139
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
140
|
+
encoding: "utf8",
|
|
141
|
+
}).trim();
|
|
142
|
+
if (top) name = path.basename(top);
|
|
143
|
+
} catch {
|
|
144
|
+
// not a git repo
|
|
145
|
+
}
|
|
146
|
+
projectCache.set(dir, name);
|
|
147
|
+
return name;
|
|
148
|
+
}
|
|
149
|
+
let currentCwd = process.cwd();
|
|
150
|
+
let currentProject = resolveProjectName(currentCwd);
|
|
151
|
+
let lastPrompt = "";
|
|
152
|
+
let lastHealthOk = false;
|
|
153
|
+
|
|
154
|
+
const toolObserveEnabled = process.env.AGENTMEMORY_TOOL_OBSERVE !== "0";
|
|
155
|
+
|
|
156
|
+
// Skips the round-trip when an auto-retry re-submits an identical prompt.
|
|
157
|
+
const DEDUP_WINDOW_MS = 5 * 60 * 1000;
|
|
158
|
+
const recentHashes = new Map<string, number>();
|
|
159
|
+
function isDuplicate(data: string): boolean {
|
|
160
|
+
const hash = crypto.createHash("sha256").update(data).digest("hex");
|
|
161
|
+
const now = Date.now();
|
|
162
|
+
const prev = recentHashes.get(hash);
|
|
163
|
+
if (prev !== undefined && now - prev < DEDUP_WINDOW_MS) return true;
|
|
164
|
+
if (recentHashes.size > 500) {
|
|
165
|
+
for (const [key, ts] of recentHashes) {
|
|
166
|
+
if (now - ts >= DEDUP_WINDOW_MS) recentHashes.delete(key);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
recentHashes.set(hash, now);
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async function getHealth() {
|
|
174
|
+
return await callAgentMemory<HealthResponse>("health", { method: "GET" });
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async function refreshStatus(ctx: { ui: { setStatus: (key: string, text: string) => void } }) {
|
|
178
|
+
// Bind before the await: ctx goes stale if the session is replaced.
|
|
179
|
+
let setStatus: (key: string, text: string) => void;
|
|
180
|
+
try {
|
|
181
|
+
const ui = ctx.ui;
|
|
182
|
+
setStatus = ui.setStatus.bind(ui);
|
|
183
|
+
} catch {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
const health = await getHealth();
|
|
187
|
+
lastHealthOk =
|
|
188
|
+
!!health &&
|
|
189
|
+
(health.status === "ok" ||
|
|
190
|
+
health.status === "healthy" ||
|
|
191
|
+
health.health?.status === "healthy");
|
|
192
|
+
try {
|
|
193
|
+
setStatus("agentmemory", lastHealthOk ? "🧠 agentmemory" : "🧠 agentmemory off");
|
|
194
|
+
} catch {
|
|
195
|
+
// status is best-effort
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
pi.registerCommand("agentmemory-status", {
|
|
200
|
+
description: "Check local agentmemory server health",
|
|
201
|
+
handler: async (_args, ctx) => {
|
|
202
|
+
const health = await getHealth();
|
|
203
|
+
if (!health) {
|
|
204
|
+
ctx.ui.notify("agentmemory is unreachable at http://localhost:3111", "warning");
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
ctx.ui.notify(
|
|
208
|
+
`agentmemory ${health.status || health.health?.status || "unknown"}${health.version ? ` v${health.version}` : ""}`,
|
|
209
|
+
"info",
|
|
210
|
+
);
|
|
211
|
+
},
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
pi.registerTool({
|
|
215
|
+
name: "memory_health",
|
|
216
|
+
label: "Memory Health",
|
|
217
|
+
description: "Check whether the local agentmemory server is reachable and healthy",
|
|
218
|
+
parameters: Type.Object({}),
|
|
219
|
+
async execute() {
|
|
220
|
+
const health = await getHealth();
|
|
221
|
+
if (!health) {
|
|
222
|
+
return {
|
|
223
|
+
content: [{ type: "text", text: "agentmemory is unreachable at http://localhost:3111" }],
|
|
224
|
+
details: { ok: false },
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
return {
|
|
228
|
+
content: [
|
|
229
|
+
{
|
|
230
|
+
type: "text",
|
|
231
|
+
text: `agentmemory status: ${health.status || health.health?.status || "unknown"}${health.version ? ` (v${health.version})` : ""}`,
|
|
232
|
+
},
|
|
233
|
+
],
|
|
234
|
+
details: health,
|
|
235
|
+
};
|
|
236
|
+
},
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
pi.registerTool({
|
|
240
|
+
name: "memory_search",
|
|
241
|
+
label: "Memory Search",
|
|
242
|
+
description: "Search agentmemory for cross-session project memory, prior decisions, bugs, and user preferences",
|
|
243
|
+
parameters: Type.Object({
|
|
244
|
+
query: Type.String({ description: "What to search for in memory" }),
|
|
245
|
+
limit: Type.Optional(Type.Integer({ minimum: 1, maximum: 10, default: 5, description: "Maximum results" })),
|
|
246
|
+
}),
|
|
247
|
+
async execute(_toolCallId, params) {
|
|
248
|
+
const result = await callAgentMemory<{ results?: SmartSearchResult[] }>("smart-search", {
|
|
249
|
+
body: { query: params.query, limit: params.limit ?? 5, project: currentProject },
|
|
250
|
+
});
|
|
251
|
+
const results = result?.results || [];
|
|
252
|
+
return {
|
|
253
|
+
content: [{ type: "text", text: formatSearchResults(results) }],
|
|
254
|
+
details: { query: params.query, results },
|
|
255
|
+
};
|
|
256
|
+
},
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
pi.registerTool({
|
|
260
|
+
name: "memory_save",
|
|
261
|
+
label: "Memory Save",
|
|
262
|
+
description: "Save a durable fact, convention, workflow, preference, or bug fix into agentmemory",
|
|
263
|
+
parameters: Type.Object({
|
|
264
|
+
content: Type.String({ description: "What should be remembered" }),
|
|
265
|
+
type: Type.Optional(
|
|
266
|
+
Type.String({
|
|
267
|
+
description: "Memory type",
|
|
268
|
+
default: "fact",
|
|
269
|
+
}),
|
|
270
|
+
),
|
|
271
|
+
}),
|
|
272
|
+
async execute(_toolCallId, params) {
|
|
273
|
+
const result = await callAgentMemory<Record<string, unknown>>("remember", {
|
|
274
|
+
body: { content: params.content, type: params.type || "fact", project: currentProject },
|
|
275
|
+
});
|
|
276
|
+
if (!result) {
|
|
277
|
+
return {
|
|
278
|
+
content: [{ type: "text", text: "Failed to save memory to agentmemory." }],
|
|
279
|
+
details: { ok: false },
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
return {
|
|
283
|
+
content: [{ type: "text", text: `Saved memory (${params.type || "fact"}): ${params.content}` }],
|
|
284
|
+
details: result,
|
|
285
|
+
};
|
|
286
|
+
},
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
290
|
+
const sessionFile = ctx.sessionManager.getSessionFile();
|
|
291
|
+
sessionId = sessionFile ? path.basename(sessionFile).replace(/\.[^.]+$/, "") : `ephemeral-${crypto.randomUUID().slice(0, 8)}`;
|
|
292
|
+
currentCwd = process.cwd();
|
|
293
|
+
currentProject = resolveProjectName(currentCwd);
|
|
294
|
+
await refreshStatus(ctx);
|
|
295
|
+
// After refreshStatus: that is where lastHealthOk is first populated.
|
|
296
|
+
if (lastHealthOk) {
|
|
297
|
+
await callAgentMemory("session/start", {
|
|
298
|
+
body: { sessionId, project: currentProject, cwd: currentCwd },
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
pi.on("before_agent_start", async (event, ctx) => {
|
|
304
|
+
currentCwd = event.systemPromptOptions.cwd || process.cwd();
|
|
305
|
+
currentProject = resolveProjectName(currentCwd);
|
|
306
|
+
lastPrompt = event.prompt?.trim() || "";
|
|
307
|
+
if (!lastPrompt) return;
|
|
308
|
+
|
|
309
|
+
if (lastHealthOk && !isDuplicate(`prompt_submit:${sessionId}:${lastPrompt}`)) {
|
|
310
|
+
void callAgentMemory("observe", {
|
|
311
|
+
body: {
|
|
312
|
+
hookType: "prompt_submit",
|
|
313
|
+
sessionId,
|
|
314
|
+
project: currentProject,
|
|
315
|
+
cwd: currentCwd,
|
|
316
|
+
timestamp: new Date().toISOString(),
|
|
317
|
+
data: { prompt: lastPrompt },
|
|
318
|
+
},
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const result = await callAgentMemory<{ results?: SmartSearchResult[] }>("smart-search", {
|
|
323
|
+
body: { query: lastPrompt, limit: 5, project: currentProject },
|
|
324
|
+
});
|
|
325
|
+
const results = result?.results || [];
|
|
326
|
+
const recallBlock = results.length
|
|
327
|
+
? [
|
|
328
|
+
"Relevant long-term memory from agentmemory:",
|
|
329
|
+
formatSearchResults(results),
|
|
330
|
+
].join("\n")
|
|
331
|
+
: "";
|
|
332
|
+
|
|
333
|
+
await refreshStatus(ctx);
|
|
334
|
+
return {
|
|
335
|
+
systemPrompt: [event.systemPrompt, TOOL_GUIDANCE, recallBlock].filter(Boolean).join("\n\n"),
|
|
336
|
+
};
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
pi.on("tool_result", (event) => {
|
|
340
|
+
if (!toolObserveEnabled || !lastHealthOk || !sessionId) return;
|
|
341
|
+
const toolName = event.toolName;
|
|
342
|
+
if (!toolName) return;
|
|
343
|
+
let input = "";
|
|
344
|
+
try {
|
|
345
|
+
input = typeof event.input === "string" ? event.input : JSON.stringify(event.input ?? {});
|
|
346
|
+
} catch {
|
|
347
|
+
// non-serializable
|
|
348
|
+
}
|
|
349
|
+
let output = "";
|
|
350
|
+
try {
|
|
351
|
+
output = typeof event.content === "string" ? event.content : JSON.stringify(event.content ?? "");
|
|
352
|
+
} catch {
|
|
353
|
+
// non-serializable
|
|
354
|
+
}
|
|
355
|
+
void callAgentMemory("observe", {
|
|
356
|
+
body: {
|
|
357
|
+
hookType: "post_tool_use",
|
|
358
|
+
sessionId,
|
|
359
|
+
project: currentProject,
|
|
360
|
+
cwd: currentCwd,
|
|
361
|
+
timestamp: new Date().toISOString(),
|
|
362
|
+
data: {
|
|
363
|
+
tool_name: toolName,
|
|
364
|
+
tool_input: input.slice(0, 8000),
|
|
365
|
+
tool_output: output.slice(0, 8000),
|
|
366
|
+
...(event.isError ? { tool_error: true } : {}),
|
|
367
|
+
},
|
|
368
|
+
},
|
|
369
|
+
});
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
pi.on("agent_end", async (event) => {
|
|
373
|
+
if (!lastHealthOk || !lastPrompt) return;
|
|
374
|
+
const assistantText = getLastAssistantText(event.messages as unknown[]);
|
|
375
|
+
if (!assistantText) return;
|
|
376
|
+
void callAgentMemory("observe", {
|
|
377
|
+
body: {
|
|
378
|
+
hookType: "post_tool_use",
|
|
379
|
+
sessionId,
|
|
380
|
+
project: currentProject,
|
|
381
|
+
cwd: currentCwd,
|
|
382
|
+
timestamp: new Date().toISOString(),
|
|
383
|
+
data: {
|
|
384
|
+
tool_name: "conversation",
|
|
385
|
+
tool_input: lastPrompt.slice(0, 8000),
|
|
386
|
+
tool_output: assistantText.slice(0, 8000),
|
|
387
|
+
},
|
|
388
|
+
},
|
|
389
|
+
});
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
pi.on("session_shutdown", async (event) => {
|
|
393
|
+
// /new, /resume, /fork and reloads fire this too; only quit ends the session.
|
|
394
|
+
if (event.reason !== "quit") return;
|
|
395
|
+
if (!lastHealthOk || !sessionId) return;
|
|
396
|
+
// session/end already fans out the summary server-side (#1203).
|
|
397
|
+
await callAgentMemory("session/end", {
|
|
398
|
+
body: { sessionId },
|
|
399
|
+
timeoutMs: 5_000,
|
|
400
|
+
});
|
|
401
|
+
void callAgentMemory("consolidate", { body: {} });
|
|
402
|
+
});
|
|
403
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "agentmemory-pi-extension",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"description": "agentmemory extension for the pi coding agent: memory recall on agent start, capture on agent end, memory_search / memory_save / memory_health tools, /agentmemory-status command",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"license": "Apache-2.0",
|
|
8
|
+
"keywords": [
|
|
9
|
+
"pi-package",
|
|
10
|
+
"agentmemory",
|
|
11
|
+
"memory"
|
|
12
|
+
],
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "https://github.com/rohitg00/agentmemory.git",
|
|
16
|
+
"directory": "integrations/pi"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"index.ts",
|
|
20
|
+
"security.ts",
|
|
21
|
+
"README.md"
|
|
22
|
+
],
|
|
23
|
+
"pi": {
|
|
24
|
+
"extensions": [
|
|
25
|
+
"./index.ts"
|
|
26
|
+
]
|
|
27
|
+
},
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
30
|
+
"typebox": "*"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const LOOPBACK_HOSTS = new Set(["localhost", "127.0.0.1", "::1"]);
|
|
2
|
+
|
|
3
|
+
function normalizedHostname(hostname: string): string {
|
|
4
|
+
return hostname.replace(/^\[|\]$/g, "").toLowerCase();
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function usesPlaintextBearerAuth(baseUrl: string, secret?: string): boolean {
|
|
8
|
+
if (!secret) return false;
|
|
9
|
+
try {
|
|
10
|
+
const parsed = new URL(baseUrl);
|
|
11
|
+
return parsed.protocol === "http:" && !LOOPBACK_HOSTS.has(normalizedHostname(parsed.hostname));
|
|
12
|
+
} catch {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function plaintextBearerAuthMessage(baseUrl: string): string {
|
|
18
|
+
return `agentmemory: AGENTMEMORY_SECRET is configured for plaintext HTTP to ${baseUrl}. Bearer tokens and memory payloads can be observed on the network; use HTTPS or an SSH tunnel.`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function createPlaintextBearerAuthGuard(
|
|
22
|
+
warn: (message: string) => void = (message) => console.warn(message),
|
|
23
|
+
env?: { AGENTMEMORY_REQUIRE_HTTPS?: string },
|
|
24
|
+
): (baseUrl: string, secret?: string) => void {
|
|
25
|
+
let warned = false;
|
|
26
|
+
return (baseUrl, secret) => {
|
|
27
|
+
if (!usesPlaintextBearerAuth(baseUrl, secret)) return;
|
|
28
|
+
const message = plaintextBearerAuthMessage(baseUrl);
|
|
29
|
+
if ((env || process.env).AGENTMEMORY_REQUIRE_HTTPS === "1") throw new Error(message);
|
|
30
|
+
if (!warned) {
|
|
31
|
+
warned = true;
|
|
32
|
+
warn(message);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentmemory/agentmemory",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.29",
|
|
4
4
|
"description": "Persistent memory for AI coding agents, powered by iii-engine's three primitives",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.mjs",
|
|
@@ -25,6 +25,8 @@
|
|
|
25
25
|
"test:watch": "vitest --exclude test/integration.test.ts",
|
|
26
26
|
"test:integration": "vitest run test/integration.test.ts",
|
|
27
27
|
"test:all": "vitest run",
|
|
28
|
+
"skills:gen": "tsx scripts/skills/generate.ts",
|
|
29
|
+
"skills:check": "tsx scripts/skills/generate.ts --check && tsx scripts/skills/check.ts",
|
|
28
30
|
"bench:load": "node --import tsx benchmark/load-100k.ts",
|
|
29
31
|
"eval:longmemeval": "tsx eval/runner/longmemeval.ts",
|
|
30
32
|
"eval:coding-life": "tsx eval/runner/coding-life.ts"
|
|
@@ -43,6 +45,7 @@
|
|
|
43
45
|
"files": [
|
|
44
46
|
"dist/",
|
|
45
47
|
"plugin/",
|
|
48
|
+
"integrations/pi/",
|
|
46
49
|
"iii-config.yaml",
|
|
47
50
|
"iii-config.docker.yaml",
|
|
48
51
|
"docker-compose.yml",
|
|
@@ -63,13 +66,12 @@
|
|
|
63
66
|
"@clack/prompts": "^1.2.0",
|
|
64
67
|
"dotenv": "^17.4.2",
|
|
65
68
|
"iii-sdk": "0.11.2",
|
|
69
|
+
"picocolors": "^1.1.1",
|
|
66
70
|
"zod": "^4.0.0"
|
|
67
71
|
},
|
|
68
72
|
"optionalDependencies": {
|
|
73
|
+
"@huggingface/transformers": "^4.2.0",
|
|
69
74
|
"@node-rs/jieba": "^2.0.1",
|
|
70
|
-
"@xenova/transformers": "^2.17.2",
|
|
71
|
-
"onnxruntime-node": "^1.14.0",
|
|
72
|
-
"onnxruntime-web": "^1.14.0",
|
|
73
75
|
"tiny-segmenter": "^0.2.0"
|
|
74
76
|
},
|
|
75
77
|
"devDependencies": {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentmemory",
|
|
3
|
-
"version": "0.9.
|
|
4
|
-
"description": "Persistent memory for AI coding agents -- captures tool usage, compresses via LLM, injects context into future sessions. 12 hooks,
|
|
3
|
+
"version": "0.9.29",
|
|
4
|
+
"description": "Persistent memory for AI coding agents -- captures tool usage, compresses via LLM, injects context into future sessions. 12 hooks, 54 MCP tools, 8 skills, real-time viewer.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Rohit Ghumare",
|
|
7
7
|
"url": "https://github.com/rohitg00"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentmemory",
|
|
3
|
-
"version": "0.9.
|
|
4
|
-
"description": "Persistent memory for AI coding agents -- captures tool usage, compresses via LLM, injects context into future sessions. 6 hooks,
|
|
3
|
+
"version": "0.9.29",
|
|
4
|
+
"description": "Persistent memory for AI coding agents -- captures tool usage, compresses via LLM, injects context into future sessions. 6 hooks, 54 MCP tools, 8 skills, real-time viewer.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Rohit Ghumare",
|
|
7
7
|
"url": "https://github.com/rohitg00"
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "agentmemory",
|
|
3
|
+
"version": "0.9.29",
|
|
4
|
+
"description": "Persistent memory for AI coding agents. Captures sessions, recalls with hybrid BM25 + vector + graph search, and exposes 54 memory tools over MCP. Keyless by default, runs on your machine.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Rohit Ghumare",
|
|
7
|
+
"email": "ghumare64@gmail.com"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://agent-memory.dev",
|
|
10
|
+
"repository": "https://github.com/rohitg00/agentmemory",
|
|
11
|
+
"license": "Apache-2.0",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"memory",
|
|
14
|
+
"mcp",
|
|
15
|
+
"recall",
|
|
16
|
+
"knowledge-graph",
|
|
17
|
+
"agent-memory"
|
|
18
|
+
],
|
|
19
|
+
"mcpServers": {
|
|
20
|
+
"agentmemory": {
|
|
21
|
+
"command": "npx",
|
|
22
|
+
"args": [
|
|
23
|
+
"-y",
|
|
24
|
+
"@agentmemory/mcp"
|
|
25
|
+
],
|
|
26
|
+
"env": {
|
|
27
|
+
"AGENTMEMORY_URL": "${AGENTMEMORY_URL:-http://localhost:3111}",
|
|
28
|
+
"AGENTMEMORY_SECRET": "${AGENTMEMORY_SECRET:-}"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|