agent-working-memory 0.14.5 → 0.15.4
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/.claude-plugin/marketplace.json +17 -0
- package/README.md +131 -122
- package/dist/adapters/claude-code.d.ts +9 -0
- package/dist/adapters/claude-code.d.ts.map +1 -1
- package/dist/adapters/claude-code.js +235 -132
- package/dist/adapters/claude-code.js.map +1 -1
- package/dist/adapters/common.d.ts +47 -5
- package/dist/adapters/common.d.ts.map +1 -1
- package/dist/adapters/common.js +111 -41
- package/dist/adapters/common.js.map +1 -1
- package/dist/adapters/hook-scripts.d.ts +54 -0
- package/dist/adapters/hook-scripts.d.ts.map +1 -0
- package/dist/adapters/hook-scripts.js +340 -0
- package/dist/adapters/hook-scripts.js.map +1 -0
- package/dist/adapters/types.d.ts +13 -3
- package/dist/adapters/types.d.ts.map +1 -1
- package/dist/cli.js +74 -6
- package/dist/cli.js.map +1 -1
- package/dist/core/agent-id.d.ts +19 -0
- package/dist/core/agent-id.d.ts.map +1 -0
- package/dist/core/agent-id.js +28 -0
- package/dist/core/agent-id.js.map +1 -0
- package/dist/core/write-pipeline.js +17 -0
- package/dist/core/write-pipeline.js.map +1 -1
- package/dist/hooks/prime.d.ts +10 -4
- package/dist/hooks/prime.d.ts.map +1 -1
- package/dist/hooks/prime.js +16 -6
- package/dist/hooks/prime.js.map +1 -1
- package/dist/mcp.js +5 -12
- package/dist/mcp.js.map +1 -1
- package/dist/storage/factory.d.ts.map +1 -1
- package/dist/storage/factory.js +20 -8
- package/dist/storage/factory.js.map +1 -1
- package/package.json +100 -90
- package/plugin/.claude-plugin/plugin.json +34 -0
- package/plugin/README.md +36 -0
- package/plugin/bin/awm-mcp-launcher.cjs +109 -0
- package/plugin/hooks/awm-checkpoint.cjs +55 -0
- package/plugin/hooks/awm-db-mutation-reminder.cjs +25 -0
- package/plugin/hooks/awm-find-sidecar.cjs +145 -0
- package/plugin/hooks/awm-prime.cjs +97 -0
- package/plugin/hooks/hooks.json +66 -0
- package/plugin/skills/awm-memory/SKILL.md +445 -0
- package/src/adapters/claude-code.ts +224 -126
- package/src/adapters/common.ts +119 -42
- package/src/adapters/hook-scripts.ts +370 -0
- package/src/adapters/types.ts +14 -3
- package/src/cli.ts +76 -5
- package/src/core/agent-id.ts +28 -0
- package/src/core/write-pipeline.ts +18 -0
- package/src/hooks/prime.ts +22 -7
- package/src/mcp.ts +5 -11
- package/src/plugin/awm-mcp-launcher.cjs +109 -0
- package/src/storage/factory.ts +19 -7
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
// Copyright 2026 Robert Winter / Complete Ideas
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* Claude Code hook scripts shipped by `awm setup` (0.14.6).
|
|
5
|
+
*
|
|
6
|
+
* WHY SCRIPTS AND NOT INLINE CURL
|
|
7
|
+
* -------------------------------
|
|
8
|
+
* Until 0.14.5 `awm setup` wrote PreCompact/SessionEnd hooks as one-line curl
|
|
9
|
+
* commands with the sidecar port and the bearer secret baked into
|
|
10
|
+
* settings.json. Three things broke, each silently:
|
|
11
|
+
*
|
|
12
|
+
* 1. The port was fixed (8401, falling back to 8402). Since 0.14.2 every MCP
|
|
13
|
+
* process binds the first free port in 8401..8410, so a session that lands
|
|
14
|
+
* on 8403 had no hooks at all — and with two pools open, the fallback
|
|
15
|
+
* posted a work session's checkpoint into the personal pool's process.
|
|
16
|
+
* 2. The secret was a literal in settings.json. When the secret file was
|
|
17
|
+
* regenerated the hooks got 401s, and `curl -sf` hid them. Observed twice.
|
|
18
|
+
* 3. There was no prime hook. The only mechanism that injects memory without
|
|
19
|
+
* the model choosing to call recall was a documentation page.
|
|
20
|
+
*
|
|
21
|
+
* These scripts resolve everything at run time: which agent this session is
|
|
22
|
+
* (from the MCP config that governs its cwd), which sidecar belongs to that
|
|
23
|
+
* agent (by probing the port range and reading /health), and the secret (from
|
|
24
|
+
* the same config, or the secret file next to the database). Nothing here is
|
|
25
|
+
* baked in at setup time except a fallback record in ~/.claude/hooks/awm-hooks.json.
|
|
26
|
+
*
|
|
27
|
+
* Every script fails OPEN: any problem prints nothing and exits 0. A hook that
|
|
28
|
+
* blocks a prompt or a compaction is worse than no hook.
|
|
29
|
+
*
|
|
30
|
+
* The JavaScript bodies deliberately avoid template literals so they can live
|
|
31
|
+
* inside these TypeScript template strings without escaping.
|
|
32
|
+
*/
|
|
33
|
+
/** Bumped when any shipped script changes; written into awm-hooks.json and the
|
|
34
|
+
* script headers so `awm doctor` can tell a stale install from a current one. */
|
|
35
|
+
export const AWM_HOOKS_VERSION = '0.14.6';
|
|
36
|
+
/** File whose presence turns the prime hook off without uninstalling it. */
|
|
37
|
+
export const PRIME_DISABLE_FILE = 'awm-prime.disabled';
|
|
38
|
+
const HEADER = (name, purpose) => `#!/usr/bin/env node
|
|
39
|
+
// ${name} — installed by \`awm setup\` (hooks v${AWM_HOOKS_VERSION}). ${purpose}
|
|
40
|
+
// Re-running \`awm setup\` rewrites this file; put local changes elsewhere.
|
|
41
|
+
// Fails open: on any problem it prints nothing and exits 0.
|
|
42
|
+
`;
|
|
43
|
+
// ─── awm-find-sidecar.cjs ────────────────────────────────────────────────────
|
|
44
|
+
export const FIND_SIDECAR_SCRIPT = HEADER('awm-find-sidecar.cjs', 'Shared resolver: which AWM sidecar belongs to THIS session.') + `
|
|
45
|
+
// Since AWM 0.14.2 each MCP process binds the first free port in
|
|
46
|
+
// [AWM_HOOK_PORT, +AWM_HOOK_PORT_RANGE), so "the sidecar is on 8401" is not a
|
|
47
|
+
// safe assumption: with N sessions open there are N sidecars and the one on
|
|
48
|
+
// 8401 may serve a different agent pool. This module (1) works out which agent
|
|
49
|
+
// a session started in \`cwd\` belongs to, from the MCP config that governs that
|
|
50
|
+
// directory, (2) probes the whole range in parallel (local, ~10 ms), and
|
|
51
|
+
// (3) returns the responder whose agentId matches — newest version first, then
|
|
52
|
+
// lowest port. No match → null, and callers fail open.
|
|
53
|
+
'use strict';
|
|
54
|
+
const fs = require('fs');
|
|
55
|
+
const os = require('os');
|
|
56
|
+
const path = require('path');
|
|
57
|
+
const http = require('http');
|
|
58
|
+
|
|
59
|
+
const SERVER_NAME = 'agent-working-memory';
|
|
60
|
+
const PROBE_TIMEOUT_MS = 400;
|
|
61
|
+
|
|
62
|
+
function norm(p) { return String(p || '').replace(/\\\\/g, '/').replace(/\\/+$/, '').toLowerCase(); }
|
|
63
|
+
function readJson(p) { try { return JSON.parse(fs.readFileSync(p, 'utf8')); } catch (e) { return null; } }
|
|
64
|
+
function readText(p) { try { return fs.readFileSync(p, 'utf8').trim(); } catch (e) { return ''; } }
|
|
65
|
+
|
|
66
|
+
// MIRROR of deriveAgentFromDir() in src/core/agent-id.ts — the server's own
|
|
67
|
+
// fallback when AWM_AGENT_ID is unset. Kept in sync by tests/adapters/hook-scripts.test.ts.
|
|
68
|
+
function deriveAgentFromDir(dir) {
|
|
69
|
+
const d = String(dir || '').replace(/\\\\/g, '/');
|
|
70
|
+
if (/\\/AgentSynapse\\//i.test(d)) return 'work';
|
|
71
|
+
return /\\/Personal-Projects(\\/|$)/i.test(d) ? 'personal' : 'work';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// The AWM entry inside one MCP config file, if any. ~/.claude.json keeps
|
|
75
|
+
// project-scoped servers under projects[<path>].mcpServers.
|
|
76
|
+
function awmEntry(json, cwd) {
|
|
77
|
+
if (!json) return null;
|
|
78
|
+
if (json.mcpServers && json.mcpServers[SERVER_NAME]) return json.mcpServers[SERVER_NAME];
|
|
79
|
+
if (json.projects && cwd) {
|
|
80
|
+
for (const k of Object.keys(json.projects)) {
|
|
81
|
+
if (norm(k) !== norm(cwd)) continue;
|
|
82
|
+
const s = json.projects[k] && json.projects[k].mcpServers;
|
|
83
|
+
if (s && s[SERVER_NAME]) return s[SERVER_NAME];
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Resolve the configuration that governs a session started in \`cwd\`:
|
|
91
|
+
* { agentId, secret, port, range, dbPath, source }.
|
|
92
|
+
*
|
|
93
|
+
* Order: the nearest .mcp.json / .claude/mcp.json walking up from cwd, then
|
|
94
|
+
* ~/.mcp.json, then ~/.claude.json, then the record \`awm setup\` left in
|
|
95
|
+
* ~/.claude/hooks/awm-hooks.json. Process env always wins for a given key.
|
|
96
|
+
*/
|
|
97
|
+
function resolveConfig(cwd) {
|
|
98
|
+
cwd = cwd || process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
99
|
+
const home = os.homedir();
|
|
100
|
+
const candidates = [];
|
|
101
|
+
let dir = path.resolve(cwd);
|
|
102
|
+
for (let i = 0; i < 16; i++) {
|
|
103
|
+
candidates.push(path.join(dir, '.mcp.json'), path.join(dir, '.claude', 'mcp.json'));
|
|
104
|
+
const parent = path.dirname(dir);
|
|
105
|
+
if (parent === dir) break;
|
|
106
|
+
dir = parent;
|
|
107
|
+
}
|
|
108
|
+
candidates.push(path.join(home, '.mcp.json'), path.join(home, '.claude.json'));
|
|
109
|
+
|
|
110
|
+
let entry = null, source = null;
|
|
111
|
+
for (const c of candidates) {
|
|
112
|
+
const e = awmEntry(readJson(c), cwd);
|
|
113
|
+
if (e) { entry = e; source = c; break; }
|
|
114
|
+
}
|
|
115
|
+
const env = (entry && entry.env) || {};
|
|
116
|
+
const fallback = readJson(path.join(home, '.claude', 'hooks', 'awm-hooks.json')) || {};
|
|
117
|
+
|
|
118
|
+
const agentId = process.env.AWM_AGENT_ID || env.AWM_AGENT_ID
|
|
119
|
+
|| (entry ? deriveAgentFromDir(cwd) : (fallback.agentId || deriveAgentFromDir(cwd)));
|
|
120
|
+
const dbPath = env.AWM_DB_PATH || fallback.dbPath || null;
|
|
121
|
+
let secret = process.env.AWM_HOOK_SECRET || env.AWM_HOOK_SECRET || '';
|
|
122
|
+
if (!secret && dbPath) secret = readText(path.join(path.dirname(dbPath), '.awm-hook-secret'));
|
|
123
|
+
if (!secret && fallback.secretPath) secret = readText(fallback.secretPath);
|
|
124
|
+
const port = parseInt(process.env.AWM_HOOK_PORT || env.AWM_HOOK_PORT || fallback.hookPort || '8401', 10) || 8401;
|
|
125
|
+
const range = Math.max(1, parseInt(process.env.AWM_HOOK_PORT_RANGE || env.AWM_HOOK_PORT_RANGE || fallback.hookPortRange || '10', 10) || 10);
|
|
126
|
+
return { agentId: agentId, secret: secret, port: port, range: range, dbPath: dbPath, source: source };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function probe(port) {
|
|
130
|
+
return new Promise(function (resolve) {
|
|
131
|
+
const req = http.get({ host: '127.0.0.1', port: port, path: '/health', timeout: PROBE_TIMEOUT_MS }, function (res) {
|
|
132
|
+
let data = '';
|
|
133
|
+
res.on('data', function (c) { data += c; });
|
|
134
|
+
res.on('end', function () {
|
|
135
|
+
try {
|
|
136
|
+
const j = JSON.parse(data);
|
|
137
|
+
resolve(j && j.sidecar ? { port: port, agentId: j.agentId || null, version: j.version || '0.0.0', pid: j.pid || null } : null);
|
|
138
|
+
} catch (e) { resolve(null); }
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
req.on('error', function () { resolve(null); });
|
|
142
|
+
req.on('timeout', function () { req.destroy(); resolve(null); });
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function versionKey(v) { return String(v || '0').split('.').map(function (n) { return parseInt(n, 10) || 0; }); }
|
|
147
|
+
function newer(a, b) {
|
|
148
|
+
const ka = versionKey(a), kb = versionKey(b);
|
|
149
|
+
for (let i = 0; i < Math.max(ka.length, kb.length); i++) {
|
|
150
|
+
const d = (ka[i] || 0) - (kb[i] || 0);
|
|
151
|
+
if (d) return d > 0;
|
|
152
|
+
}
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** Every live sidecar in the configured range, regardless of agent. */
|
|
157
|
+
async function listSidecars(cfg) {
|
|
158
|
+
const ports = [];
|
|
159
|
+
for (let i = 0; i < cfg.range; i++) ports.push(cfg.port + i);
|
|
160
|
+
return (await Promise.all(ports.map(probe))).filter(Boolean);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* The sidecar for this session, or null. Resolves to
|
|
165
|
+
* { sidecar: {port, agentId, version, pid} | null, config, all: [...] }.
|
|
166
|
+
*/
|
|
167
|
+
async function findSidecar(cwd) {
|
|
168
|
+
const cfg = resolveConfig(cwd);
|
|
169
|
+
const all = await listSidecars(cfg);
|
|
170
|
+
const mine = all.filter(function (s) { return s.agentId === cfg.agentId; });
|
|
171
|
+
mine.sort(function (a, b) { return newer(a.version, b.version) ? -1 : newer(b.version, a.version) ? 1 : a.port - b.port; });
|
|
172
|
+
return { sidecar: mine[0] || null, config: cfg, all: all };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** Optional trace for troubleshooting: AWM_HOOK_DEBUG=1 appends to ~/.claude/hooks/awm-hooks.log. */
|
|
176
|
+
function debugLog(hook, fields) {
|
|
177
|
+
if (!process.env.AWM_HOOK_DEBUG) return;
|
|
178
|
+
try {
|
|
179
|
+
const line = Object.assign({ ts: new Date().toISOString(), hook: hook }, fields);
|
|
180
|
+
fs.appendFileSync(path.join(os.homedir(), '.claude', 'hooks', 'awm-hooks.log'), JSON.stringify(line) + '\\n');
|
|
181
|
+
} catch (e) { /* never matters */ }
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
module.exports = { resolveConfig, findSidecar, listSidecars, deriveAgentFromDir, debugLog, PROBE_TIMEOUT_MS };
|
|
185
|
+
`;
|
|
186
|
+
// ─── awm-checkpoint.cjs ──────────────────────────────────────────────────────
|
|
187
|
+
export const CHECKPOINT_HOOK_SCRIPT = HEADER('awm-checkpoint.cjs', 'PreCompact / SessionEnd: checkpoint this session into ITS agent\'s AWM.') + `
|
|
188
|
+
// Forwards the whole hook payload (hook_event_name, transcript_path, cwd,
|
|
189
|
+
// session_id) to POST /hooks/checkpoint on the sidecar that belongs to this
|
|
190
|
+
// session's agent. transcript_path matters: the sidecar parses it for the
|
|
191
|
+
// current task and active files, so the checkpoint is a real one, not a stub.
|
|
192
|
+
'use strict';
|
|
193
|
+
const http = require('http');
|
|
194
|
+
|
|
195
|
+
let finder = null;
|
|
196
|
+
try { finder = require('./awm-find-sidecar.cjs'); } catch (e) { process.exit(0); }
|
|
197
|
+
|
|
198
|
+
const TIMEOUT_MS = 5000;
|
|
199
|
+
|
|
200
|
+
let raw = '';
|
|
201
|
+
process.stdin.on('data', function (d) { raw += d; });
|
|
202
|
+
process.stdin.on('end', function () {
|
|
203
|
+
let payload = {};
|
|
204
|
+
try { payload = JSON.parse(raw || '{}'); } catch (e) { payload = {}; }
|
|
205
|
+
if (!payload.hook_event_name) payload.hook_event_name = 'PreCompact';
|
|
206
|
+
main(payload).catch(function () { process.exit(0); });
|
|
207
|
+
});
|
|
208
|
+
// A hook with nothing on stdin must still exit.
|
|
209
|
+
process.stdin.on('error', function () { process.exit(0); });
|
|
210
|
+
|
|
211
|
+
async function main(payload) {
|
|
212
|
+
const found = await finder.findSidecar(payload.cwd);
|
|
213
|
+
const sc = found.sidecar;
|
|
214
|
+
if (!sc) {
|
|
215
|
+
finder.debugLog('checkpoint', { event: payload.hook_event_name, agent: found.config.agentId, sidecar: null, live: found.all.length });
|
|
216
|
+
process.exit(0);
|
|
217
|
+
}
|
|
218
|
+
const body = JSON.stringify(payload);
|
|
219
|
+
const req = http.request({
|
|
220
|
+
host: '127.0.0.1', port: sc.port, path: '/hooks/checkpoint', method: 'POST',
|
|
221
|
+
headers: {
|
|
222
|
+
'Content-Type': 'application/json',
|
|
223
|
+
'Content-Length': Buffer.byteLength(body),
|
|
224
|
+
'Authorization': 'Bearer ' + found.config.secret,
|
|
225
|
+
},
|
|
226
|
+
timeout: TIMEOUT_MS,
|
|
227
|
+
}, function (res) {
|
|
228
|
+
res.resume();
|
|
229
|
+
res.on('end', function () {
|
|
230
|
+
finder.debugLog('checkpoint', { event: payload.hook_event_name, agent: found.config.agentId, port: sc.port, status: res.statusCode });
|
|
231
|
+
process.exit(0);
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
req.on('error', function () { process.exit(0); });
|
|
235
|
+
req.on('timeout', function () { req.destroy(); process.exit(0); });
|
|
236
|
+
req.end(body);
|
|
237
|
+
}
|
|
238
|
+
`;
|
|
239
|
+
// ─── awm-prime.cjs ───────────────────────────────────────────────────────────
|
|
240
|
+
export const PRIME_HOOK_SCRIPT = HEADER('awm-prime.cjs', 'UserPromptSubmit: prime the turn with relevant memories, or stay silent.') + `
|
|
241
|
+
// The PRIME phase of the AWM-native harness (docs/patterns/awm-native-harness.md).
|
|
242
|
+
// Posts the prompt to POST /hooks/prime on this session's sidecar and injects the
|
|
243
|
+
// returned text as additionalContext. The sidecar decides what to inject, formats
|
|
244
|
+
// it, abstains when confidence is low and caps the token budget — see
|
|
245
|
+
// src/hooks/prime.ts — so this file stays thin.
|
|
246
|
+
//
|
|
247
|
+
// Turn it off without uninstalling: create ~/.claude/hooks/${PRIME_DISABLE_FILE}
|
|
248
|
+
// (an empty file). Remove the file to turn it back on. \`awm doctor\` reports which.
|
|
249
|
+
'use strict';
|
|
250
|
+
const fs = require('fs');
|
|
251
|
+
const os = require('os');
|
|
252
|
+
const path = require('path');
|
|
253
|
+
const http = require('http');
|
|
254
|
+
|
|
255
|
+
let finder = null;
|
|
256
|
+
try { finder = require('./awm-find-sidecar.cjs'); } catch (e) { process.exit(0); }
|
|
257
|
+
|
|
258
|
+
// A slow AWM must never delay a prompt. Warm recall with rerank measures 1.4–1.8 s
|
|
259
|
+
// on a 30k-memory store; 4 s leaves margin. Connection refused returns in ~100 ms.
|
|
260
|
+
const TIMEOUT_MS = 4000;
|
|
261
|
+
const MIN_PROMPT_LEN = 15; // skip "yes" / "ok" / "go on" turns
|
|
262
|
+
// Set-level confidence below which the sidecar injects nothing. 0.10 rather than
|
|
263
|
+
// the 0.25 once documented as "balanced": measured 2026-09-11, 0.25 silenced
|
|
264
|
+
// specific prompts with one clear winner (top score 0.26–0.38) while passing a
|
|
265
|
+
// vague one at 0.92. The gate is on the shape of the score distribution, not on
|
|
266
|
+
// relevance, so specific questions are exactly the ones a high threshold loses.
|
|
267
|
+
const MIN_CONFIDENCE = 0.10;
|
|
268
|
+
const DISABLE_FILE = path.join(os.homedir(), '.claude', 'hooks', '${PRIME_DISABLE_FILE}');
|
|
269
|
+
|
|
270
|
+
let raw = '';
|
|
271
|
+
process.stdin.on('data', function (d) { raw += d; });
|
|
272
|
+
process.stdin.on('end', function () {
|
|
273
|
+
let payload = {};
|
|
274
|
+
try { payload = JSON.parse(raw || '{}'); } catch (e) { process.exit(0); }
|
|
275
|
+
main(payload).catch(function () { process.exit(0); });
|
|
276
|
+
});
|
|
277
|
+
process.stdin.on('error', function () { process.exit(0); });
|
|
278
|
+
|
|
279
|
+
function extractPrompt(j) {
|
|
280
|
+
for (const key of ['prompt', 'message', 'user_prompt', 'text', 'input']) {
|
|
281
|
+
if (typeof j[key] === 'string' && j[key].trim()) return j[key];
|
|
282
|
+
}
|
|
283
|
+
return null;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
async function main(payload) {
|
|
287
|
+
if (fs.existsSync(DISABLE_FILE)) process.exit(0);
|
|
288
|
+
const prompt = extractPrompt(payload);
|
|
289
|
+
if (!prompt || prompt.trim().length < MIN_PROMPT_LEN) process.exit(0);
|
|
290
|
+
|
|
291
|
+
const found = await finder.findSidecar(payload.cwd);
|
|
292
|
+
const sc = found.sidecar;
|
|
293
|
+
if (!sc) {
|
|
294
|
+
finder.debugLog('prime', { agent: found.config.agentId, sidecar: null, live: found.all.length });
|
|
295
|
+
process.exit(0);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const body = JSON.stringify({
|
|
299
|
+
hook_event_name: 'UserPromptSubmit',
|
|
300
|
+
session_id: payload.session_id,
|
|
301
|
+
cwd: payload.cwd,
|
|
302
|
+
prompt: prompt,
|
|
303
|
+
minConfidence: MIN_CONFIDENCE,
|
|
304
|
+
});
|
|
305
|
+
const req = http.request({
|
|
306
|
+
host: '127.0.0.1', port: sc.port, path: '/hooks/prime', method: 'POST',
|
|
307
|
+
headers: {
|
|
308
|
+
'Content-Type': 'application/json',
|
|
309
|
+
'Content-Length': Buffer.byteLength(body),
|
|
310
|
+
'Authorization': 'Bearer ' + found.config.secret,
|
|
311
|
+
},
|
|
312
|
+
timeout: TIMEOUT_MS,
|
|
313
|
+
}, function (res) {
|
|
314
|
+
let data = '';
|
|
315
|
+
res.on('data', function (c) { data += c; });
|
|
316
|
+
res.on('end', function () {
|
|
317
|
+
let out = null;
|
|
318
|
+
try { out = JSON.parse(data); } catch (e) { out = null; }
|
|
319
|
+
const inject = out && typeof out.inject === 'string' ? out.inject : '';
|
|
320
|
+
finder.debugLog('prime', { agent: found.config.agentId, port: sc.port, status: res.statusCode, kept: out && out.kept, total: out && out.total, reason: out && out.reason });
|
|
321
|
+
if (res.statusCode === 200 && inject) {
|
|
322
|
+
process.stdout.write(JSON.stringify({
|
|
323
|
+
hookSpecificOutput: { hookEventName: 'UserPromptSubmit', additionalContext: inject },
|
|
324
|
+
}));
|
|
325
|
+
}
|
|
326
|
+
process.exit(0);
|
|
327
|
+
});
|
|
328
|
+
});
|
|
329
|
+
req.on('error', function () { process.exit(0); });
|
|
330
|
+
req.on('timeout', function () { req.destroy(); process.exit(0); });
|
|
331
|
+
req.end(body);
|
|
332
|
+
}
|
|
333
|
+
`;
|
|
334
|
+
/** Everything `awm setup` places in ~/.claude/hooks/. */
|
|
335
|
+
export const HOOK_SCRIPTS = [
|
|
336
|
+
{ file: 'awm-find-sidecar.cjs', source: FIND_SIDECAR_SCRIPT },
|
|
337
|
+
{ file: 'awm-checkpoint.cjs', source: CHECKPOINT_HOOK_SCRIPT },
|
|
338
|
+
{ file: 'awm-prime.cjs', source: PRIME_HOOK_SCRIPT },
|
|
339
|
+
];
|
|
340
|
+
//# sourceMappingURL=hook-scripts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hook-scripts.js","sourceRoot":"","sources":["../../src/adapters/hook-scripts.ts"],"names":[],"mappings":"AAAA,gDAAgD;AAChD,sCAAsC;AAEtC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH;kFACkF;AAClF,MAAM,CAAC,MAAM,iBAAiB,GAAG,QAAQ,CAAC;AAE1C,4EAA4E;AAC5E,MAAM,CAAC,MAAM,kBAAkB,GAAG,oBAAoB,CAAC;AAEvD,MAAM,MAAM,GAAG,CAAC,IAAY,EAAE,OAAe,EAAE,EAAE,CAAC;KAC7C,IAAI,yCAAyC,iBAAiB,MAAM,OAAO;;;CAG/E,CAAC;AAEF,gFAAgF;AAEhF,MAAM,CAAC,MAAM,mBAAmB,GAAG,MAAM,CACvC,sBAAsB,EACtB,6DAA6D,CAC9D,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6IH,CAAC;AAEF,gFAAgF;AAEhF,MAAM,CAAC,MAAM,sBAAsB,GAAG,MAAM,CAC1C,oBAAoB,EACpB,yEAAyE,CAC1E,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmDH,CAAC;AAEF,gFAAgF;AAEhF,MAAM,CAAC,MAAM,iBAAiB,GAAG,MAAM,CACrC,eAAe,EACf,0EAA0E,CAC3E,GAAG;;;;;;;8DAO0D,kBAAkB;;;;;;;;;;;;;;;;;;;;;oEAqBZ,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiErF,CAAC;AAEF,yDAAyD;AACzD,MAAM,CAAC,MAAM,YAAY,GAAoD;IAC3E,EAAE,IAAI,EAAE,sBAAsB,EAAE,MAAM,EAAE,mBAAmB,EAAE;IAC7D,EAAE,IAAI,EAAE,oBAAoB,EAAE,MAAM,EAAE,sBAAsB,EAAE;IAC9D,EAAE,IAAI,EAAE,eAAe,EAAE,MAAM,EAAE,iBAAiB,EAAE;CACrD,CAAC"}
|
package/dist/adapters/types.d.ts
CHANGED
|
@@ -24,8 +24,12 @@ export interface SetupContext {
|
|
|
24
24
|
hasDist: boolean;
|
|
25
25
|
/** Hook sidecar secret token */
|
|
26
26
|
hookSecret: string;
|
|
27
|
-
/** Hook sidecar port */
|
|
27
|
+
/** Hook sidecar port (preferred; the sidecar walks upward from here when busy) */
|
|
28
28
|
hookPort: string;
|
|
29
|
+
/** How many ports upward from hookPort the sidecar may try (0.14.2) */
|
|
30
|
+
hookPortRange: string;
|
|
31
|
+
/** Install the UserPromptSubmit prime hook (0.14.6; `--no-prime` turns it off) */
|
|
32
|
+
installPrime: boolean;
|
|
29
33
|
/** Whether to use global scope */
|
|
30
34
|
isGlobal: boolean;
|
|
31
35
|
/** Windows platform */
|
|
@@ -59,7 +63,13 @@ export interface CLIAdapter {
|
|
|
59
63
|
writeInstructions(ctx: SetupContext, skip: boolean): string;
|
|
60
64
|
/** Configure hooks (if supported). Returns action summary. */
|
|
61
65
|
writeHooks(ctx: SetupContext, skip: boolean): string;
|
|
62
|
-
/** Validate that the setup is healthy. */
|
|
63
|
-
diagnose(ctx: SetupContext): DiagnosticResult[]
|
|
66
|
+
/** Validate that the setup is healthy. May probe the network, hence possibly async. */
|
|
67
|
+
diagnose(ctx: SetupContext): DiagnosticResult[] | Promise<DiagnosticResult[]>;
|
|
68
|
+
/**
|
|
69
|
+
* The env block of an AWM server entry this adapter previously wrote, if any.
|
|
70
|
+
* `awm setup` re-runs preserve these values unless a flag overrides them, so an
|
|
71
|
+
* upgrade never repoints the database or renames the agent (0.14.6).
|
|
72
|
+
*/
|
|
73
|
+
readExistingEnv?(isGlobal: boolean, cwd: string): Record<string, string> | null;
|
|
64
74
|
}
|
|
65
75
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/adapters/types.ts"],"names":[],"mappings":"AAGA;;;;;;GAMG;AAEH,MAAM,WAAW,YAAY;IAC3B,gCAAgC;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,8CAA8C;IAC9C,WAAW,EAAE,MAAM,CAAC;IACpB,+BAA+B;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,2CAA2C;IAC3C,MAAM,EAAE,MAAM,CAAC;IACf,4CAA4C;IAC5C,WAAW,EAAE,MAAM,CAAC;IACpB,yDAAyD;IACzD,OAAO,EAAE,MAAM,CAAC;IAChB,mDAAmD;IACnD,SAAS,EAAE,MAAM,CAAC;IAClB,+DAA+D;IAC/D,OAAO,EAAE,OAAO,CAAC;IACjB,gCAAgC;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/adapters/types.ts"],"names":[],"mappings":"AAGA;;;;;;GAMG;AAEH,MAAM,WAAW,YAAY;IAC3B,gCAAgC;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,8CAA8C;IAC9C,WAAW,EAAE,MAAM,CAAC;IACpB,+BAA+B;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,2CAA2C;IAC3C,MAAM,EAAE,MAAM,CAAC;IACf,4CAA4C;IAC5C,WAAW,EAAE,MAAM,CAAC;IACpB,yDAAyD;IACzD,OAAO,EAAE,MAAM,CAAC;IAChB,mDAAmD;IACnD,SAAS,EAAE,MAAM,CAAC;IAClB,+DAA+D;IAC/D,OAAO,EAAE,OAAO,CAAC;IACjB,gCAAgC;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,kFAAkF;IAClF,QAAQ,EAAE,MAAM,CAAC;IACjB,uEAAuE;IACvE,aAAa,EAAE,MAAM,CAAC;IACtB,kFAAkF;IAClF,YAAY,EAAE,OAAO,CAAC;IACtB,kCAAkC;IAClC,QAAQ,EAAE,OAAO,CAAC;IAClB,uBAAuB;IACvB,SAAS,EAAE,OAAO,CAAC;IACnB,yDAAyD;IACzD,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AAED,MAAM,WAAW,WAAW;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,CAAC;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,UAAU;IACzB,iEAAiE;IACjE,EAAE,EAAE,MAAM,CAAC;IACX,8BAA8B;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,qDAAqD;IACrD,oBAAoB,EAAE,OAAO,CAAC;IAC9B,8CAA8C;IAC9C,mBAAmB,EAAE,OAAO,CAAC;IAE7B,wEAAwE;IACxE,cAAc,CAAC,GAAG,EAAE,YAAY,GAAG,MAAM,CAAC;IAE1C,gEAAgE;IAChE,iBAAiB,CAAC,GAAG,EAAE,YAAY,EAAE,IAAI,EAAE,OAAO,GAAG,MAAM,CAAC;IAE5D,8DAA8D;IAC9D,UAAU,CAAC,GAAG,EAAE,YAAY,EAAE,IAAI,EAAE,OAAO,GAAG,MAAM,CAAC;IAErD,uFAAuF;IACvF,QAAQ,CAAC,GAAG,EAAE,YAAY,GAAG,gBAAgB,EAAE,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAAC;IAE9E;;;;OAIG;IACH,eAAe,CAAC,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;CACjF"}
|
package/dist/cli.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* awm health — check if a running server is healthy
|
|
12
12
|
*/
|
|
13
13
|
import { readFileSync, writeFileSync, existsSync } from 'node:fs';
|
|
14
|
-
import { resolve, dirname, basename } from 'node:path';
|
|
14
|
+
import { resolve, join, dirname, basename } from 'node:path';
|
|
15
15
|
import { execSync } from 'node:child_process';
|
|
16
16
|
import { fileURLToPath } from 'node:url';
|
|
17
17
|
import { VERSION } from './version.js';
|
|
@@ -44,6 +44,7 @@ AgentWorkingMemory — Cognitive memory for AI agents
|
|
|
44
44
|
|
|
45
45
|
Usage:
|
|
46
46
|
awm setup [target] [options] Configure AWM for an AI CLI
|
|
47
|
+
awm plugin Install as a Claude Code plugin (local, no GitHub)
|
|
47
48
|
awm doctor [target|--all] Validate AWM integrations
|
|
48
49
|
awm mcp Start MCP server (stdio)
|
|
49
50
|
awm serve [--port <port>] Start HTTP API server
|
|
@@ -71,7 +72,10 @@ Setup options:
|
|
|
71
72
|
--no-instructions Skip instruction file (CLAUDE.md, AGENTS.md, etc.)
|
|
72
73
|
--no-claude-md Alias for --no-instructions
|
|
73
74
|
--no-hooks Skip hook installation
|
|
74
|
-
--
|
|
75
|
+
--no-prime Skip the UserPromptSubmit prime hook (auto-inject memories per prompt)
|
|
76
|
+
--hook-port PORT Preferred sidecar port (default: 8401; each session walks upward when busy)
|
|
77
|
+
--hook-port-range N Ports to try upward from --hook-port (default: 10)
|
|
78
|
+
Re-running setup keeps your existing agent id, db path, port and any env you added.
|
|
75
79
|
|
|
76
80
|
Examples:
|
|
77
81
|
awm setup --global Claude Code, global (recommended)
|
|
@@ -90,7 +94,9 @@ async function setup() {
|
|
|
90
94
|
let skipInstructions = false;
|
|
91
95
|
let isGlobal = false;
|
|
92
96
|
let skipHooks = false;
|
|
93
|
-
let
|
|
97
|
+
let installPrime = true;
|
|
98
|
+
let hookPort;
|
|
99
|
+
let hookPortRange;
|
|
94
100
|
for (let i = 1; i < args.length; i++) {
|
|
95
101
|
if (args[i] === '--agent-id' && args[i + 1]) {
|
|
96
102
|
agentId = args[++i];
|
|
@@ -104,9 +110,15 @@ async function setup() {
|
|
|
104
110
|
else if (args[i] === '--no-hooks') {
|
|
105
111
|
skipHooks = true;
|
|
106
112
|
}
|
|
113
|
+
else if (args[i] === '--no-prime') {
|
|
114
|
+
installPrime = false;
|
|
115
|
+
}
|
|
107
116
|
else if (args[i] === '--hook-port' && args[i + 1]) {
|
|
108
117
|
hookPort = args[++i];
|
|
109
118
|
}
|
|
119
|
+
else if (args[i] === '--hook-port-range' && args[i + 1]) {
|
|
120
|
+
hookPortRange = args[++i];
|
|
121
|
+
}
|
|
110
122
|
else if (args[i] === '--global') {
|
|
111
123
|
isGlobal = true;
|
|
112
124
|
}
|
|
@@ -130,8 +142,11 @@ async function setup() {
|
|
|
130
142
|
if (!adapter.supportsProjectScope && !isGlobal) {
|
|
131
143
|
isGlobal = true;
|
|
132
144
|
}
|
|
133
|
-
// Build context
|
|
134
|
-
|
|
145
|
+
// Build context. A previous install's values survive unless a flag overrides them
|
|
146
|
+
// (0.14.6) — an upgrade must never repoint the database or rename the agent.
|
|
147
|
+
const existingEnv = adapter.readExistingEnv?.(isGlobal, process.cwd()) ?? null;
|
|
148
|
+
// `awm setup claude-code` -> client=claude-code, cursor -> client=cursor, and so on.
|
|
149
|
+
const ctx = buildSetupContext({ agentId, dbPath, isGlobal, hookPort, hookPortRange, installPrime, existingEnv, client: target });
|
|
135
150
|
// Run adapter
|
|
136
151
|
const configAction = adapter.writeMcpConfig(ctx);
|
|
137
152
|
const instructionsAction = adapter.writeInstructions(ctx, skipInstructions);
|
|
@@ -151,8 +166,56 @@ AWM configured for ${adapter.name}${isGlobal ? ' (global)' : ''}
|
|
|
151
166
|
Next steps:
|
|
152
167
|
1. Restart ${adapter.name} to pick up the MCP server
|
|
153
168
|
2. Memory tools will appear automatically${adapter.id === 'codex' ? ' (verify with /mcp)' : ''}
|
|
169
|
+
3. \`awm doctor\` checks the install and lists the live sidecars
|
|
154
170
|
`.trim());
|
|
155
171
|
}
|
|
172
|
+
// ─── PLUGIN ──────────────────────────────────────
|
|
173
|
+
/**
|
|
174
|
+
* Print the two lines that install AWM as a Claude Code plugin FROM THIS INSTALL.
|
|
175
|
+
*
|
|
176
|
+
* Most users are on Windows and will never clone the repository, so
|
|
177
|
+
* `/plugin marketplace add CompleteIdeas/agent-working-memory` — which needs GitHub — is the
|
|
178
|
+
* wrong default for them. npm has already put a complete marketplace on their disk; they
|
|
179
|
+
* just have no way to know where it is. This tells them, with the real resolved path.
|
|
180
|
+
*/
|
|
181
|
+
function pluginInfo() {
|
|
182
|
+
// dist/cli.js -> package root
|
|
183
|
+
const pkgRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
184
|
+
const marketplace = join(pkgRoot, '.claude-plugin', 'marketplace.json');
|
|
185
|
+
if (!existsSync(marketplace)) {
|
|
186
|
+
console.log(`
|
|
187
|
+
AWM ${VERSION} — plugin not found in this install
|
|
188
|
+
|
|
189
|
+
Looked for: ${marketplace}
|
|
190
|
+
|
|
191
|
+
This happens when AWM is running from a source checkout that has not been built,
|
|
192
|
+
or from a package published before the plugin was bundled (< 0.15.3).
|
|
193
|
+
|
|
194
|
+
From a checkout: npm run build && npm run build:plugin
|
|
195
|
+
Otherwise: npm install -g agent-working-memory@latest
|
|
196
|
+
`.trimStart());
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
console.log(`
|
|
200
|
+
AWM ${VERSION} — install as a Claude Code plugin
|
|
201
|
+
|
|
202
|
+
Paste these two lines into Claude Code:
|
|
203
|
+
|
|
204
|
+
/plugin marketplace add ${pkgRoot}
|
|
205
|
+
/plugin install awm@agent-working-memory
|
|
206
|
+
|
|
207
|
+
Then restart Claude Code. You get 19 memory tools, the session hooks, and the
|
|
208
|
+
usage guidance as a skill.
|
|
209
|
+
|
|
210
|
+
No GitHub access needed — the marketplace is this npm install, already on disk.
|
|
211
|
+
|
|
212
|
+
The store defaults to the same file \`awm setup --global\` uses, so a plugin
|
|
213
|
+
install and a CLI install share one memory rather than quietly keeping two.
|
|
214
|
+
|
|
215
|
+
Check it worked: awm doctor claude-code
|
|
216
|
+
More: https://github.com/CompleteIdeas/agent-working-memory/blob/master/docs/plugin.md
|
|
217
|
+
`.trimStart());
|
|
218
|
+
}
|
|
156
219
|
// ─── DOCTOR ──────────────────────────────────────
|
|
157
220
|
async function doctor() {
|
|
158
221
|
const { getAdapter, listAdapters } = await import('./adapters/index.js');
|
|
@@ -185,7 +248,9 @@ async function doctor() {
|
|
|
185
248
|
continue;
|
|
186
249
|
}
|
|
187
250
|
console.log(` ${adapter.name}:`);
|
|
188
|
-
const
|
|
251
|
+
const existingEnv = adapter.readExistingEnv?.(true, process.cwd()) ?? null;
|
|
252
|
+
const ctx = buildSetupContext({ isGlobal: true, existingEnv });
|
|
253
|
+
const results = await adapter.diagnose(ctx);
|
|
189
254
|
for (const r of results) {
|
|
190
255
|
const icon = r.status === 'ok' ? '+' : r.status === 'warn' ? '~' : 'x';
|
|
191
256
|
console.log(` [${icon}] ${r.check}: ${r.message}`);
|
|
@@ -838,6 +903,9 @@ switch (command) {
|
|
|
838
903
|
case 'setup':
|
|
839
904
|
await setup();
|
|
840
905
|
break;
|
|
906
|
+
case 'plugin':
|
|
907
|
+
pluginInfo();
|
|
908
|
+
break;
|
|
841
909
|
case 'doctor':
|
|
842
910
|
await doctor();
|
|
843
911
|
break;
|