@mrc2204/agent-smart-memo 5.0.2 → 5.1.2
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/README.md +209 -375
- package/bin/asm.mjs +365 -0
- package/bin/opencode-mcp-server.mjs +320 -0
- package/dist/core/contracts/adapter-contracts.d.ts +1 -1
- package/dist/core/contracts/adapter-contracts.d.ts.map +1 -1
- package/dist/core/contracts/change-overlay-contracts.d.ts +69 -0
- package/dist/core/contracts/change-overlay-contracts.d.ts.map +1 -0
- package/dist/core/contracts/change-overlay-contracts.js +2 -0
- package/dist/core/contracts/change-overlay-contracts.js.map +1 -0
- package/dist/core/contracts/feature-pack-contracts.d.ts +37 -0
- package/dist/core/contracts/feature-pack-contracts.d.ts.map +1 -0
- package/dist/core/contracts/feature-pack-contracts.js +8 -0
- package/dist/core/contracts/feature-pack-contracts.js.map +1 -0
- package/dist/core/contracts/project-query-contracts.d.ts +84 -0
- package/dist/core/contracts/project-query-contracts.d.ts.map +1 -0
- package/dist/core/contracts/project-query-contracts.js +2 -0
- package/dist/core/contracts/project-query-contracts.js.map +1 -0
- package/dist/core/graph/code-graph-model.d.ts +9 -0
- package/dist/core/graph/code-graph-model.d.ts.map +1 -0
- package/dist/core/graph/code-graph-model.js +70 -0
- package/dist/core/graph/code-graph-model.js.map +1 -0
- package/dist/core/graph/code-graph-populator.d.ts +20 -0
- package/dist/core/graph/code-graph-populator.d.ts.map +1 -0
- package/dist/core/graph/code-graph-populator.js +760 -0
- package/dist/core/graph/code-graph-populator.js.map +1 -0
- package/dist/core/graph/contracts.d.ts +29 -0
- package/dist/core/graph/contracts.d.ts.map +1 -0
- package/dist/core/graph/contracts.js +47 -0
- package/dist/core/graph/contracts.js.map +1 -0
- package/dist/core/ingest/contracts.d.ts +44 -0
- package/dist/core/ingest/contracts.d.ts.map +1 -0
- package/dist/core/ingest/contracts.js +2 -0
- package/dist/core/ingest/contracts.js.map +1 -0
- package/dist/core/ingest/ids.d.ts +5 -0
- package/dist/core/ingest/ids.d.ts.map +1 -0
- package/dist/core/ingest/ids.js +17 -0
- package/dist/core/ingest/ids.js.map +1 -0
- package/dist/core/ingest/ingest-pipeline.d.ts +4 -0
- package/dist/core/ingest/ingest-pipeline.d.ts.map +1 -0
- package/dist/core/ingest/ingest-pipeline.js +105 -0
- package/dist/core/ingest/ingest-pipeline.js.map +1 -0
- package/dist/core/ingest/semantic-block-extractor.d.ts +9 -0
- package/dist/core/ingest/semantic-block-extractor.d.ts.map +1 -0
- package/dist/core/ingest/semantic-block-extractor.js +171 -0
- package/dist/core/ingest/semantic-block-extractor.js.map +1 -0
- package/dist/core/usecases/default-memory-usecase-port.d.ts +38 -0
- package/dist/core/usecases/default-memory-usecase-port.d.ts.map +1 -1
- package/dist/core/usecases/default-memory-usecase-port.js +1686 -12
- package/dist/core/usecases/default-memory-usecase-port.js.map +1 -1
- package/dist/db/graph-db.d.ts +24 -0
- package/dist/db/graph-db.d.ts.map +1 -1
- package/dist/db/graph-db.js +81 -2
- package/dist/db/graph-db.js.map +1 -1
- package/dist/db/slot-db.d.ts +235 -2
- package/dist/db/slot-db.d.ts.map +1 -1
- package/dist/db/slot-db.js +840 -18
- package/dist/db/slot-db.js.map +1 -1
- package/dist/index.d.ts +7 -247
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +32 -119
- package/dist/index.js.map +1 -1
- package/dist/shared/asm-config.d.ts +82 -0
- package/dist/shared/asm-config.d.ts.map +1 -0
- package/dist/shared/asm-config.js +254 -0
- package/dist/shared/asm-config.js.map +1 -0
- package/dist/shared/slotdb-path.d.ts +4 -3
- package/dist/shared/slotdb-path.d.ts.map +1 -1
- package/dist/shared/slotdb-path.js +15 -6
- package/dist/shared/slotdb-path.js.map +1 -1
- package/dist/tools/graph-tools.d.ts.map +1 -1
- package/dist/tools/graph-tools.js +131 -0
- package/dist/tools/graph-tools.js.map +1 -1
- package/dist/tools/project-tools.d.ts.map +1 -1
- package/dist/tools/project-tools.js +543 -0
- package/dist/tools/project-tools.js.map +1 -1
- package/openclaw.plugin.json +5 -164
- package/package.json +61 -26
- package/scripts/init-openclaw.mjs +727 -0
package/bin/asm.mjs
ADDED
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import { runInitOpenClaw } from "../scripts/init-openclaw.mjs";
|
|
5
|
+
import { createShellRunner, runInitSetupFlow, runInstallPlatformFlow } from "../src/cli/platform-installers.ts";
|
|
6
|
+
import { runOpencodeMcpServer } from "./opencode-mcp-server.mjs";
|
|
7
|
+
|
|
8
|
+
const ASM_PLUGIN_PACKAGE = "@mrc2204/agent-smart-memo";
|
|
9
|
+
const ASM_PLUGIN_ID = "agent-smart-memo";
|
|
10
|
+
|
|
11
|
+
function text(value) {
|
|
12
|
+
return typeof value === "string" ? value.trim() : "";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function includesAsmPlugin(output) {
|
|
16
|
+
const haystack = String(output || "").toLowerCase();
|
|
17
|
+
return (
|
|
18
|
+
haystack.includes(ASM_PLUGIN_ID) ||
|
|
19
|
+
haystack.includes(ASM_PLUGIN_PACKAGE.toLowerCase())
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function parseAsmCliArgs(argv = []) {
|
|
24
|
+
const args = Array.isArray(argv) ? argv.map((x) => String(x)) : [];
|
|
25
|
+
const first = args[0] || "";
|
|
26
|
+
|
|
27
|
+
if (!first || first === "help" || first === "--help" || first === "-h") {
|
|
28
|
+
return { command: "help", argv: [] };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (first === "setup-openclaw") {
|
|
32
|
+
return { command: "setup-openclaw", argv: args.slice(1) };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (first === "setup" && (args[1] || "") === "openclaw") {
|
|
36
|
+
return { command: "setup-openclaw", argv: args.slice(2) };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (first === "install" && (args[1] || "")) {
|
|
40
|
+
return {
|
|
41
|
+
command: "install-platform",
|
|
42
|
+
platform: String(args[1] || "").trim().toLowerCase(),
|
|
43
|
+
argv: args.slice(2),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (first === "init-setup") {
|
|
48
|
+
return { command: "init-setup", argv: args.slice(1) };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (first === "init" && (args[1] || "") === "setup") {
|
|
52
|
+
return { command: "init-setup", argv: args.slice(2) };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (first === "init-openclaw") {
|
|
56
|
+
return { command: "init-openclaw", argv: args.slice(1) };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (first === "init" && (args[1] || "") === "openclaw") {
|
|
60
|
+
return { command: "init-openclaw", argv: args.slice(2) };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (first === "project-event") {
|
|
64
|
+
return { command: "project-event", argv: args.slice(1) };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (first === "mcp" && (args[1] || "") === "opencode") {
|
|
68
|
+
return { command: "mcp-opencode", argv: args.slice(2) };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return { command: "unknown", argv: args };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function printHelp(log = console.log) {
|
|
75
|
+
log("asm - Agent Smart Memo CLI");
|
|
76
|
+
log("");
|
|
77
|
+
log("Usage:");
|
|
78
|
+
log(" asm setup-openclaw [--yes]");
|
|
79
|
+
log(" asm setup openclaw [--yes]");
|
|
80
|
+
log(" asm install openclaw [--yes]");
|
|
81
|
+
log(" asm install paperclip");
|
|
82
|
+
log(" asm install opencode");
|
|
83
|
+
log(" asm init-setup [--yes]");
|
|
84
|
+
log(" asm init setup [--yes]");
|
|
85
|
+
log(" asm init-openclaw [--non-interactive]");
|
|
86
|
+
log(" asm init openclaw [--non-interactive]");
|
|
87
|
+
log(" asm project-event --project-id <id> --repo-root <path> [--event-type post_commit|post_merge|post_rewrite|manual] [--source-rev <sha>] [--changed-files a,b] [--deleted-files x,y] [--trusted-sync 0|1] [--full-snapshot 0|1]");
|
|
88
|
+
log(" asm help");
|
|
89
|
+
log("");
|
|
90
|
+
log("Roadmap commands (not implemented yet):");
|
|
91
|
+
log(" asm doctor");
|
|
92
|
+
log(" asm test-openclaw");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function detectPluginInstalled(runner = createShellRunner()) {
|
|
96
|
+
const tryJson = runner("openclaw", ["plugins", "list", "--json"]);
|
|
97
|
+
if (tryJson.ok) {
|
|
98
|
+
try {
|
|
99
|
+
const parsed = JSON.parse(tryJson.stdout || "{}");
|
|
100
|
+
const pool = [
|
|
101
|
+
...(Array.isArray(parsed) ? parsed : []),
|
|
102
|
+
...(Array.isArray(parsed?.plugins) ? parsed.plugins : []),
|
|
103
|
+
];
|
|
104
|
+
|
|
105
|
+
for (const item of pool) {
|
|
106
|
+
const name = text(item?.name || item?.id || item?.package || item?.pluginId);
|
|
107
|
+
if (!name) continue;
|
|
108
|
+
if (includesAsmPlugin(name)) {
|
|
109
|
+
return { installed: true, source: "list-json" };
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
} catch {
|
|
113
|
+
if (includesAsmPlugin(tryJson.stdout)) {
|
|
114
|
+
return { installed: true, source: "list-json-text" };
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const tryText = runner("openclaw", ["plugins", "list"]);
|
|
120
|
+
if (tryText.ok && includesAsmPlugin(tryText.stdout)) {
|
|
121
|
+
return { installed: true, source: "list-text" };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return { installed: false, source: "missing" };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function parseNonInteractiveFlags(argv = []) {
|
|
128
|
+
const args = Array.isArray(argv) ? argv.map((x) => String(x).trim()).filter(Boolean) : [];
|
|
129
|
+
const hasYes = args.includes("--yes") || args.includes("-y");
|
|
130
|
+
const hasNonInteractive = args.includes("--non-interactive");
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
nonInteractive: hasYes || hasNonInteractive,
|
|
134
|
+
autoApply: hasYes || hasNonInteractive,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function parseProjectEventArgs(argv = []) {
|
|
139
|
+
const args = Array.isArray(argv) ? argv.map((x) => String(x)) : [];
|
|
140
|
+
const out = {
|
|
141
|
+
projectId: "",
|
|
142
|
+
repoRoot: "",
|
|
143
|
+
sourceRev: "",
|
|
144
|
+
eventType: "manual",
|
|
145
|
+
changedFiles: [],
|
|
146
|
+
deletedFiles: [],
|
|
147
|
+
trustedSync: false,
|
|
148
|
+
fullSnapshot: false,
|
|
149
|
+
};
|
|
150
|
+
for (let i = 0; i < args.length; i++) {
|
|
151
|
+
const cur = args[i];
|
|
152
|
+
const next = args[i + 1] || "";
|
|
153
|
+
if (cur === "--project-id") { out.projectId = next; i++; continue; }
|
|
154
|
+
if (cur === "--repo-root") { out.repoRoot = next; i++; continue; }
|
|
155
|
+
if (cur === "--source-rev") { out.sourceRev = next; i++; continue; }
|
|
156
|
+
if (cur === "--event-type") { out.eventType = next || "manual"; i++; continue; }
|
|
157
|
+
if (cur === "--changed-files") { out.changedFiles = next ? next.split(",").map((s) => s.trim()).filter(Boolean) : []; i++; continue; }
|
|
158
|
+
if (cur === "--deleted-files") { out.deletedFiles = next ? next.split(",").map((s) => s.trim()).filter(Boolean) : []; i++; continue; }
|
|
159
|
+
if (cur === "--trusted-sync") { out.trustedSync = next === "1" || next === "true"; i++; continue; }
|
|
160
|
+
if (cur === "--full-snapshot") { out.fullSnapshot = next === "1" || next === "true"; i++; continue; }
|
|
161
|
+
}
|
|
162
|
+
return out;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export async function runSetupOpenClawFlow({
|
|
166
|
+
runner = createShellRunner(),
|
|
167
|
+
initOpenClaw = runInitOpenClaw,
|
|
168
|
+
log = console.log,
|
|
169
|
+
argv = [],
|
|
170
|
+
} = {}) {
|
|
171
|
+
log("[ASM-84] setup-openclaw: checking OpenClaw CLI ...");
|
|
172
|
+
const openclawVersion = runner("openclaw", ["--version"]);
|
|
173
|
+
if (!openclawVersion.ok) {
|
|
174
|
+
log("[ASM-84] ❌ openclaw binary not found or not executable.");
|
|
175
|
+
if (openclawVersion.stderr) log(`[ASM-84] details: ${openclawVersion.stderr}`);
|
|
176
|
+
if (openclawVersion.error) log(`[ASM-84] error: ${openclawVersion.error}`);
|
|
177
|
+
log("[ASM-84] Please install OpenClaw first, then re-run: asm setup-openclaw");
|
|
178
|
+
return { ok: false, step: "check-openclaw" };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const pluginState = detectPluginInstalled(runner);
|
|
182
|
+
const setupSummary = pluginState.installed
|
|
183
|
+
? {
|
|
184
|
+
alreadyConfigured: [`plugin installed: ${ASM_PLUGIN_PACKAGE}`],
|
|
185
|
+
willAdd: [],
|
|
186
|
+
willUpdate: ["openclaw.json bootstrap via init-openclaw wizard"],
|
|
187
|
+
}
|
|
188
|
+
: {
|
|
189
|
+
alreadyConfigured: [],
|
|
190
|
+
willAdd: [`plugin install: ${ASM_PLUGIN_PACKAGE}`],
|
|
191
|
+
willUpdate: ["openclaw.json bootstrap via init-openclaw wizard"],
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
log("[ASM-84] Setup summary (before execution):");
|
|
195
|
+
log(`- already configured (${setupSummary.alreadyConfigured.length})`);
|
|
196
|
+
if (!setupSummary.alreadyConfigured.length) log(" • (none)");
|
|
197
|
+
for (const item of setupSummary.alreadyConfigured) log(` • ${item}`);
|
|
198
|
+
log(`- will add (${setupSummary.willAdd.length})`);
|
|
199
|
+
if (!setupSummary.willAdd.length) log(" • (none)");
|
|
200
|
+
for (const item of setupSummary.willAdd) log(` • ${item}`);
|
|
201
|
+
log(`- will update (${setupSummary.willUpdate.length})`);
|
|
202
|
+
if (!setupSummary.willUpdate.length) log(" • (none)");
|
|
203
|
+
for (const item of setupSummary.willUpdate) log(` • ${item}`);
|
|
204
|
+
|
|
205
|
+
if (pluginState.installed) {
|
|
206
|
+
log(`[ASM-84] plugin already installed (${pluginState.source}).`);
|
|
207
|
+
} else {
|
|
208
|
+
log(`[ASM-84] plugin not detected. Installing: ${ASM_PLUGIN_PACKAGE}`);
|
|
209
|
+
const install = runner("openclaw", ["plugins", "install", ASM_PLUGIN_PACKAGE]);
|
|
210
|
+
if (!install.ok) {
|
|
211
|
+
log("[ASM-84] ❌ failed to install plugin via OpenClaw CLI.");
|
|
212
|
+
if (install.stdout) log(install.stdout);
|
|
213
|
+
if (install.stderr) log(install.stderr);
|
|
214
|
+
return { ok: false, step: "install-plugin" };
|
|
215
|
+
}
|
|
216
|
+
log("[ASM-84] plugin install command completed.");
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const mode = parseNonInteractiveFlags(argv);
|
|
220
|
+
if (mode.nonInteractive) {
|
|
221
|
+
log("[ASM-84] non-interactive mode enabled; applying defaults/merged config without prompt.");
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
log("[ASM-84] launching init-openclaw bootstrap flow ...");
|
|
225
|
+
const result = await initOpenClaw({ interactive: !mode.nonInteractive, autoApply: mode.autoApply });
|
|
226
|
+
|
|
227
|
+
if (!result?.applied) {
|
|
228
|
+
log("[ASM-84] setup-openclaw ended without changes (aborted or no write).");
|
|
229
|
+
return { ok: true, step: "init-openclaw", applied: false };
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
log("[ASM-84] ✅ setup-openclaw completed.");
|
|
233
|
+
log("[ASM-84] Next steps:");
|
|
234
|
+
log(" 1) Restart OpenClaw runtime");
|
|
235
|
+
log(" 2) Verify plugin loaded and memory tools available");
|
|
236
|
+
log(" 3) Run a quick smoke: memory_slot_set + memory_slot_get");
|
|
237
|
+
|
|
238
|
+
return { ok: true, step: "done", applied: true };
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export async function main(argv = process.argv.slice(2)) {
|
|
242
|
+
const parsed = parseAsmCliArgs(argv);
|
|
243
|
+
if (parsed.command === "help") {
|
|
244
|
+
printHelp();
|
|
245
|
+
return 0;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (parsed.command === "setup-openclaw") {
|
|
249
|
+
const result = await runSetupOpenClawFlow({ argv: parsed.argv });
|
|
250
|
+
return result.ok ? 0 : 1;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (parsed.command === "install-platform") {
|
|
254
|
+
const result = await runInstallPlatformFlow({
|
|
255
|
+
platform: parsed.platform,
|
|
256
|
+
runner: createShellRunner(),
|
|
257
|
+
initOpenClaw: runInitOpenClaw,
|
|
258
|
+
log: console.log,
|
|
259
|
+
argv: parsed.argv,
|
|
260
|
+
env: process.env,
|
|
261
|
+
homeDir: process.env.HOME,
|
|
262
|
+
});
|
|
263
|
+
return result.ok ? 0 : 1;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (parsed.command === "init-setup") {
|
|
267
|
+
const result = await runInitSetupFlow({ log: console.log, env: process.env, homeDir: process.env.HOME, argv: parsed.argv });
|
|
268
|
+
return result.ok ? 0 : 1;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (parsed.command === "init-openclaw") {
|
|
272
|
+
const mode = parseNonInteractiveFlags(parsed.argv);
|
|
273
|
+
try {
|
|
274
|
+
const result = await runInitOpenClaw({ interactive: !mode.nonInteractive, autoApply: mode.autoApply });
|
|
275
|
+
return result?.applied || mode.autoApply ? 0 : 0;
|
|
276
|
+
} catch (error) {
|
|
277
|
+
console.error(`[ASM-84] init-openclaw failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
278
|
+
return 1;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (parsed.command === "mcp-opencode") {
|
|
283
|
+
await runOpencodeMcpServer();
|
|
284
|
+
return 0;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (parsed.command === "project-event") {
|
|
288
|
+
const event = parseProjectEventArgs(parsed.argv);
|
|
289
|
+
if (!event.projectId || !event.repoRoot) {
|
|
290
|
+
console.error('[ASM-87] project-event requires --project-id and --repo-root');
|
|
291
|
+
return 1;
|
|
292
|
+
}
|
|
293
|
+
const pluginId = 'agent-smart-memo';
|
|
294
|
+
const cfgPath = resolve(process.env.HOME || '', '.openclaw', 'openclaw.json');
|
|
295
|
+
let qdrantCollection = 'mrc_bot';
|
|
296
|
+
let llmBaseUrl = 'http://localhost:8317/v1';
|
|
297
|
+
let llmApiKey = 'proxypal-local';
|
|
298
|
+
let llmModel = 'gpt-5.4';
|
|
299
|
+
let embedModel = 'qwen3-embedding:0.6b';
|
|
300
|
+
let embedDimensions = 1024;
|
|
301
|
+
let slotDbDir = resolve(process.env.HOME || '', '.openclaw', 'agent-memo');
|
|
302
|
+
try {
|
|
303
|
+
const raw = JSON.parse(readFileSync(cfgPath, 'utf8'));
|
|
304
|
+
const cfg = raw?.plugins?.entries?.[pluginId]?.config || {};
|
|
305
|
+
qdrantCollection = cfg.qdrantCollection || qdrantCollection;
|
|
306
|
+
llmBaseUrl = cfg.llmBaseUrl || llmBaseUrl;
|
|
307
|
+
llmApiKey = cfg.llmApiKey || llmApiKey;
|
|
308
|
+
llmModel = cfg.llmModel || llmModel;
|
|
309
|
+
embedModel = cfg.embedModel || embedModel;
|
|
310
|
+
embedDimensions = cfg.embedDimensions || embedDimensions;
|
|
311
|
+
slotDbDir = cfg.slotDbDir || slotDbDir;
|
|
312
|
+
} catch {}
|
|
313
|
+
|
|
314
|
+
process.env.OPENCLAW_SLOTDB_DIR = slotDbDir;
|
|
315
|
+
process.env.AGENT_MEMO_PROJECT_WORKSPACE_ROOT = event.repoRoot;
|
|
316
|
+
process.env.AGENT_MEMO_REPO_CLONE_ROOT = event.repoRoot;
|
|
317
|
+
process.env.PROJECT_WORKSPACE_ROOT = event.repoRoot;
|
|
318
|
+
process.env.REPO_CLONE_ROOT = event.repoRoot;
|
|
319
|
+
process.env.QDRANT_COLLECTION = qdrantCollection;
|
|
320
|
+
process.env.LLM_BASE_URL = llmBaseUrl;
|
|
321
|
+
process.env.LLM_API_KEY = llmApiKey;
|
|
322
|
+
process.env.LLM_MODEL = llmModel;
|
|
323
|
+
process.env.EMBED_MODEL = embedModel;
|
|
324
|
+
process.env.EMBEDDING_MODEL = embedModel;
|
|
325
|
+
process.env.EMBEDDING_DIMENSIONS = String(embedDimensions);
|
|
326
|
+
|
|
327
|
+
const { SlotDB } = await import('../dist/db/slot-db.js');
|
|
328
|
+
const { DefaultMemoryUseCasePort } = await import('../dist/core/usecases/default-memory-usecase-port.js');
|
|
329
|
+
const db = new SlotDB(slotDbDir);
|
|
330
|
+
const usecase = new DefaultMemoryUseCasePort(db);
|
|
331
|
+
try {
|
|
332
|
+
const result = await usecase.run('project.index_event', {
|
|
333
|
+
context: { userId: 'telegram:dm:5165741309', agentId: 'assistant', metadata: { projectWorkspaceRoot: event.repoRoot } },
|
|
334
|
+
meta: { source: 'cli', toolName: 'asm.project-event', projectWorkspaceRoot: event.repoRoot },
|
|
335
|
+
payload: {
|
|
336
|
+
project_id: event.projectId,
|
|
337
|
+
repo_root: event.repoRoot,
|
|
338
|
+
source_rev: event.sourceRev || null,
|
|
339
|
+
event_type: event.eventType,
|
|
340
|
+
changed_files: event.changedFiles,
|
|
341
|
+
deleted_files: event.deletedFiles,
|
|
342
|
+
trusted_sync: event.trustedSync,
|
|
343
|
+
full_snapshot: event.fullSnapshot,
|
|
344
|
+
},
|
|
345
|
+
});
|
|
346
|
+
console.log(JSON.stringify(result, null, 2));
|
|
347
|
+
db.close();
|
|
348
|
+
return 0;
|
|
349
|
+
} catch (error) {
|
|
350
|
+
db.close();
|
|
351
|
+
console.error(`[ASM-87] project-event failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
352
|
+
return 1;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
console.error(`[ASM-84] Unknown command: ${argv.join(" ") || "(empty)"}`);
|
|
357
|
+
printHelp(console.error);
|
|
358
|
+
return 1;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
362
|
+
main().then((code) => {
|
|
363
|
+
process.exitCode = code;
|
|
364
|
+
});
|
|
365
|
+
}
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import process from "node:process";
|
|
3
|
+
|
|
4
|
+
const PROTOCOL_VERSION = "2024-11-05";
|
|
5
|
+
|
|
6
|
+
function makeTool(name, description, inputSchema) {
|
|
7
|
+
return { name, description, inputSchema };
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function buildOpencodeMcpToolDescriptors() {
|
|
11
|
+
return [
|
|
12
|
+
makeTool(
|
|
13
|
+
"asm_project_binding_preview",
|
|
14
|
+
"Resolve ASM active project binding in read-only mode using project_id/project_alias/repo_root/session_project_alias selectors.",
|
|
15
|
+
{
|
|
16
|
+
type: "object",
|
|
17
|
+
properties: {
|
|
18
|
+
project_id: { type: "string" },
|
|
19
|
+
project_alias: { type: "string" },
|
|
20
|
+
repo_root: { type: "string" },
|
|
21
|
+
session_project_alias: { type: "string" },
|
|
22
|
+
allow_cross_project: { type: "boolean" },
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
),
|
|
26
|
+
makeTool(
|
|
27
|
+
"asm_project_opencode_search",
|
|
28
|
+
"Run ASM read-only project-scoped retrieval for OpenCode after resolving project binding.",
|
|
29
|
+
{
|
|
30
|
+
type: "object",
|
|
31
|
+
properties: {
|
|
32
|
+
query: { type: "string" },
|
|
33
|
+
limit: { type: "number" },
|
|
34
|
+
project_id: { type: "string" },
|
|
35
|
+
project_alias: { type: "string" },
|
|
36
|
+
repo_root: { type: "string" },
|
|
37
|
+
session_project_alias: { type: "string" },
|
|
38
|
+
explicit_project_id: { type: "string" },
|
|
39
|
+
explicit_project_alias: { type: "string" },
|
|
40
|
+
explicit_cross_project: { type: "boolean" },
|
|
41
|
+
},
|
|
42
|
+
required: ["query"],
|
|
43
|
+
},
|
|
44
|
+
),
|
|
45
|
+
makeTool(
|
|
46
|
+
"asm_project_coding_packet",
|
|
47
|
+
"Build coding packet (foundation lane) for OpenCode using ASM project-aware/code-aware retrieval context.",
|
|
48
|
+
{
|
|
49
|
+
type: "object",
|
|
50
|
+
properties: {
|
|
51
|
+
project_id: { type: "string" },
|
|
52
|
+
project_alias: { type: "string" },
|
|
53
|
+
query: { type: "string" },
|
|
54
|
+
objective: { type: "string" },
|
|
55
|
+
task_id: { type: "string" },
|
|
56
|
+
tracker_issue_key: { type: "string" },
|
|
57
|
+
task_title: { type: "string" },
|
|
58
|
+
symbol_name: { type: "string" },
|
|
59
|
+
relative_path: { type: "string" },
|
|
60
|
+
route_path: { type: "string" },
|
|
61
|
+
limit: { type: "number" },
|
|
62
|
+
acceptance_criteria: { type: "array", items: { type: "string" } },
|
|
63
|
+
constraints: { type: "array", items: { type: "string" } },
|
|
64
|
+
out_of_scope: { type: "array", items: { type: "string" } },
|
|
65
|
+
validation_commands: { type: "array", items: { type: "string" } },
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
),
|
|
69
|
+
];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
let usecasePortPromise = null;
|
|
73
|
+
async function getUseCasePort() {
|
|
74
|
+
if (!usecasePortPromise) {
|
|
75
|
+
usecasePortPromise = (async () => {
|
|
76
|
+
const { resolveAsmCoreSlotDbDir } = await import(new URL("../dist/shared/asm-config.js", import.meta.url));
|
|
77
|
+
const { SlotDB } = await import(new URL("../dist/db/slot-db.js", import.meta.url));
|
|
78
|
+
const { DefaultMemoryUseCasePort } = await import(new URL("../dist/core/usecases/default-memory-usecase-port.js", import.meta.url));
|
|
79
|
+
const slotDbDir =
|
|
80
|
+
resolveAsmCoreSlotDbDir({ env: process.env, homeDir: process.env.HOME }) ||
|
|
81
|
+
process.env.OPENCLAW_SLOTDB_DIR ||
|
|
82
|
+
`${process.env.HOME}/.openclaw/agent-memo`;
|
|
83
|
+
const db = new SlotDB(slotDbDir);
|
|
84
|
+
return { db, usecase: new DefaultMemoryUseCasePort(db) };
|
|
85
|
+
})();
|
|
86
|
+
}
|
|
87
|
+
return usecasePortPromise;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function response(id, result) {
|
|
91
|
+
return { jsonrpc: "2.0", id, result };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function errorResponse(id, code, message) {
|
|
95
|
+
return { jsonrpc: "2.0", id, error: { code, message } };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
let outputTransportMode = "content-length";
|
|
99
|
+
|
|
100
|
+
function setOutputTransportMode(mode) {
|
|
101
|
+
if (mode === "json-line" || mode === "content-length") {
|
|
102
|
+
outputTransportMode = mode;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function writeMessage(message) {
|
|
107
|
+
const encoded = JSON.stringify(message);
|
|
108
|
+
if (outputTransportMode === "json-line") {
|
|
109
|
+
process.stdout.write(`${encoded}\n`);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const body = Buffer.from(encoded, "utf8");
|
|
114
|
+
const header = Buffer.from(`Content-Length: ${body.length}\r\n\r\n`, "utf8");
|
|
115
|
+
process.stdout.write(Buffer.concat([header, body]));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function handleToolCall(name, args) {
|
|
119
|
+
const { usecase } = await getUseCasePort();
|
|
120
|
+
const context = {
|
|
121
|
+
userId: process.env.ASM_MCP_USER_ID || "default",
|
|
122
|
+
agentId: process.env.ASM_MCP_AGENT_ID || "opencode",
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
if (name === "asm_project_binding_preview") {
|
|
126
|
+
const data = await usecase.run("project.binding_preview", {
|
|
127
|
+
context,
|
|
128
|
+
meta: { source: "cli", toolName: "asm.mcp.opencode.binding_preview" },
|
|
129
|
+
payload: args || {},
|
|
130
|
+
});
|
|
131
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (name === "asm_project_opencode_search") {
|
|
135
|
+
const data = await usecase.run("project.opencode_search", {
|
|
136
|
+
context,
|
|
137
|
+
meta: { source: "cli", toolName: "asm.mcp.opencode.search" },
|
|
138
|
+
payload: args || {},
|
|
139
|
+
});
|
|
140
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (name === "asm_project_coding_packet") {
|
|
144
|
+
const data = await usecase.run("project.coding_packet", {
|
|
145
|
+
context,
|
|
146
|
+
meta: { source: "cli", toolName: "asm.mcp.opencode.coding_packet" },
|
|
147
|
+
payload: args || {},
|
|
148
|
+
});
|
|
149
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async function processRpcMessage(message) {
|
|
156
|
+
const { id, method, params } = message || {};
|
|
157
|
+
const hasId = id !== undefined && id !== null;
|
|
158
|
+
|
|
159
|
+
if (method === "initialize") {
|
|
160
|
+
if (!hasId) return;
|
|
161
|
+
writeMessage(response(id, {
|
|
162
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
163
|
+
serverInfo: { name: "asm-opencode-mcp", version: "1.0.0" },
|
|
164
|
+
capabilities: { tools: { listChanged: false } },
|
|
165
|
+
}));
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (method === "notifications/initialized") {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (method === "tools/list") {
|
|
174
|
+
if (!hasId) return;
|
|
175
|
+
writeMessage(response(id, { tools: buildOpencodeMcpToolDescriptors() }));
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (method === "tools/call") {
|
|
180
|
+
if (!hasId) return;
|
|
181
|
+
try {
|
|
182
|
+
const result = await handleToolCall(params?.name, params?.arguments || {});
|
|
183
|
+
writeMessage(response(id, result));
|
|
184
|
+
} catch (error) {
|
|
185
|
+
writeMessage(response(id, {
|
|
186
|
+
content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }],
|
|
187
|
+
isError: true,
|
|
188
|
+
}));
|
|
189
|
+
}
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (!hasId) {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
writeMessage(errorResponse(id, -32601, `Method not found: ${method}`));
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function findHeaderBoundary(buffer) {
|
|
201
|
+
const crlf = buffer.indexOf("\r\n\r\n");
|
|
202
|
+
if (crlf !== -1) {
|
|
203
|
+
return { headerEnd: crlf, separatorLength: 4 };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const lf = buffer.indexOf("\n\n");
|
|
207
|
+
if (lf !== -1) {
|
|
208
|
+
return { headerEnd: lf, separatorLength: 2 };
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function takeJsonLineMessage(buffer) {
|
|
215
|
+
if (!buffer.length) return null;
|
|
216
|
+
const first = String.fromCharCode(buffer[0]);
|
|
217
|
+
if (first !== "{" && first !== "[") return null;
|
|
218
|
+
|
|
219
|
+
const newlineIndex = buffer.indexOf("\n");
|
|
220
|
+
if (newlineIndex === -1) return null;
|
|
221
|
+
|
|
222
|
+
const line = buffer.slice(0, newlineIndex).toString("utf8").trim();
|
|
223
|
+
if (!line) {
|
|
224
|
+
return { consumed: newlineIndex + 1, message: null };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return { consumed: newlineIndex + 1, message: line };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export async function runOpencodeMcpServer() {
|
|
231
|
+
let buffer = Buffer.alloc(0);
|
|
232
|
+
let requestChain = Promise.resolve();
|
|
233
|
+
|
|
234
|
+
const enqueue = (message) => {
|
|
235
|
+
requestChain = requestChain
|
|
236
|
+
.then(() => processRpcMessage(message))
|
|
237
|
+
.catch((error) => {
|
|
238
|
+
writeMessage(errorResponse(null, -32603, error instanceof Error ? error.message : String(error)));
|
|
239
|
+
});
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
const onData = (chunk) => {
|
|
243
|
+
buffer = Buffer.concat([buffer, chunk]);
|
|
244
|
+
|
|
245
|
+
while (true) {
|
|
246
|
+
const boundary = findHeaderBoundary(buffer);
|
|
247
|
+
if (boundary) {
|
|
248
|
+
const { headerEnd, separatorLength } = boundary;
|
|
249
|
+
const headerText = buffer.slice(0, headerEnd).toString("utf8");
|
|
250
|
+
const match = headerText.match(/Content-Length:\s*(\d+)/i);
|
|
251
|
+
if (!match) {
|
|
252
|
+
buffer = buffer.slice(headerEnd + separatorLength);
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const contentLength = Number(match[1]);
|
|
257
|
+
const totalLength = headerEnd + separatorLength + contentLength;
|
|
258
|
+
if (!Number.isFinite(contentLength) || contentLength < 0) {
|
|
259
|
+
buffer = buffer.slice(headerEnd + separatorLength);
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
if (buffer.length < totalLength) break;
|
|
263
|
+
|
|
264
|
+
const body = buffer.slice(headerEnd + separatorLength, totalLength).toString("utf8");
|
|
265
|
+
buffer = buffer.slice(totalLength);
|
|
266
|
+
setOutputTransportMode("content-length");
|
|
267
|
+
|
|
268
|
+
try {
|
|
269
|
+
const message = JSON.parse(body);
|
|
270
|
+
enqueue(message);
|
|
271
|
+
} catch (error) {
|
|
272
|
+
writeMessage(errorResponse(null, -32700, error instanceof Error ? error.message : String(error)));
|
|
273
|
+
}
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const jsonLine = takeJsonLineMessage(buffer);
|
|
278
|
+
if (!jsonLine) {
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
buffer = buffer.slice(jsonLine.consumed);
|
|
283
|
+
if (!jsonLine.message) {
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
setOutputTransportMode("json-line");
|
|
287
|
+
|
|
288
|
+
try {
|
|
289
|
+
const message = JSON.parse(jsonLine.message);
|
|
290
|
+
enqueue(message);
|
|
291
|
+
} catch (error) {
|
|
292
|
+
writeMessage(errorResponse(null, -32700, error instanceof Error ? error.message : String(error)));
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
if (typeof process.stdin.resume === "function") {
|
|
298
|
+
process.stdin.resume();
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
process.stdin.on("data", onData);
|
|
302
|
+
|
|
303
|
+
await new Promise((resolve) => {
|
|
304
|
+
let closed = false;
|
|
305
|
+
const finish = () => {
|
|
306
|
+
if (closed) return;
|
|
307
|
+
closed = true;
|
|
308
|
+
resolve(undefined);
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
process.stdin.on("end", finish);
|
|
312
|
+
process.stdin.on("close", finish);
|
|
313
|
+
process.on("SIGINT", finish);
|
|
314
|
+
process.on("SIGTERM", finish);
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
319
|
+
runOpencodeMcpServer();
|
|
320
|
+
}
|
|
@@ -17,7 +17,7 @@ export interface CoreRequestEnvelope<TPayload> {
|
|
|
17
17
|
requestId?: string;
|
|
18
18
|
};
|
|
19
19
|
}
|
|
20
|
-
export type MemoryUseCaseName = "slot.get" | "slot.set" | "slot.list" | "slot.delete" | "project.register" | "project.get" | "project.list" | "project.set_registration_state" | "project.set_tracker_mapping" | "project.register_command" | "project.link_tracker" | "project.trigger_index" | "project.reindex_diff" | "project.index_watch_get" | "project.task_registry_upsert" | "project.task_lineage_context" | "project.hybrid_search" | "project.legacy_backfill" | "project.telegram_onboarding" | "memory.capture" | "memory.search" | "graph.entity.get" | "graph.entity.set" | "graph.rel.add" | "graph.rel.remove" | "graph.search";
|
|
20
|
+
export type MemoryUseCaseName = "slot.get" | "slot.set" | "slot.list" | "slot.delete" | "project.register" | "project.get" | "project.list" | "project.set_registration_state" | "project.set_tracker_mapping" | "project.register_command" | "project.link_tracker" | "project.trigger_index" | "project.deindex" | "project.detach" | "project.unregister" | "project.purge_preview" | "project.purge" | "project.reindex_diff" | "project.index_event" | "project.install_hooks" | "project.index_watch_get" | "project.task_registry_upsert" | "project.task_lineage_context" | "project.hybrid_search" | "project.change_overlay.query" | "project.legacy_backfill" | "project.telegram_onboarding" | "project.feature_pack.generate" | "project.feature_pack.query" | "project.developer_query" | "project.binding_preview" | "project.opencode_search" | "memory.capture" | "memory.search" | "graph.entity.get" | "graph.entity.set" | "graph.rel.add" | "graph.rel.remove" | "graph.search" | "graph.code.upsert" | "graph.code.chain";
|
|
21
21
|
export interface MemoryUseCasePort {
|
|
22
22
|
run<TReq, TRes>(useCase: MemoryUseCaseName, req: CoreRequestEnvelope<TReq>): Promise<TRes>;
|
|
23
23
|
}
|