@momomemory/opencode-momo 0.1.3 → 0.1.5
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 +16 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +55 -32
- package/dist/config.d.ts +4 -5
- package/dist/config.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +583 -69
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -115,6 +115,14 @@ The `momo` tool supports 6 operational modes:
|
|
|
115
115
|
- **list**: List recent memories for a given scope.
|
|
116
116
|
- **forget**: Remove a specific memory by its ID.
|
|
117
117
|
|
|
118
|
+
### Ingestion Tools (RAG Pipeline)
|
|
119
|
+
For richer ingestion than plain `read`, the plugin also exposes dedicated tools that route through Momo's document pipeline:
|
|
120
|
+
- **momo_ingest**: Ingest text, URLs, or local files into documents + retrieval index, with optional memory extraction.
|
|
121
|
+
- **momo_ocr**: Ingest image files, run OCR, and optionally extract memories from recognized text.
|
|
122
|
+
- **momo_transcribe**: Ingest audio/video files, run transcription, and optionally extract memories.
|
|
123
|
+
|
|
124
|
+
These tools are especially useful with non-vision models: image/audio content is converted into searchable text in Momo.
|
|
125
|
+
|
|
118
126
|
### Codebase Indexing
|
|
119
127
|
The `/momo-init` command provides a structured workflow for agents to explore a new codebase and store its architecture, conventions, and key facts into project memory.
|
|
120
128
|
|
|
@@ -138,6 +146,14 @@ Momo supports the `<private>` tag. Any content wrapped in `<private>` tags is au
|
|
|
138
146
|
**Scopes**: `user` (cross-project), `project` (current project, default)
|
|
139
147
|
**Memory Types**: `fact`, `preference`, `episode`
|
|
140
148
|
|
|
149
|
+
### Ingestion Tool Arguments (high-level)
|
|
150
|
+
|
|
151
|
+
| Tool | Required Args | Optional Args |
|
|
152
|
+
|------|--------------|---------------|
|
|
153
|
+
| `momo_ingest` | `input` | `inputType`, `scope`, `extractMemories`, `metadataJson`, `contentType`, `wait`, `timeoutMs`, `pollIntervalMs` |
|
|
154
|
+
| `momo_ocr` | `filePath` | `scope`, `extractMemories`, `contentType`, `wait`, `timeoutMs`, `pollIntervalMs` |
|
|
155
|
+
| `momo_transcribe` | `filePath` | `scope`, `extractMemories`, `contentType`, `wait`, `timeoutMs`, `pollIntervalMs` |
|
|
156
|
+
|
|
141
157
|
## Memory Scoping
|
|
142
158
|
|
|
143
159
|
Memories are isolated using container tags derived from your environment:
|
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AA2IA;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CA+CjE;AA0ID;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG;IACpD,WAAW,EAAE,OAAO,CAAC;IACrB,gBAAgB,EAAE,OAAO,CAAC;CAC3B,CAWA;AAED;;GAEG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAC7C,IAAI,
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AA2IA;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CA+CjE;AA0ID;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG;IACpD,WAAW,EAAE,OAAO,CAAC;IACrB,gBAAgB,EAAE,OAAO,CAAC;CAC3B,CAWA;AAED;;GAEG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAC7C,IAAI,CAwDN"}
|
package/dist/cli.js
CHANGED
|
@@ -11,10 +11,38 @@ import {
|
|
|
11
11
|
} from "node:fs";
|
|
12
12
|
import { join as join2 } from "node:path";
|
|
13
13
|
import { createInterface } from "node:readline";
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
// node_modules/@momomemory/sdk/dist/plugin-config.js
|
|
15
|
+
import { homedir, hostname } from "node:os";
|
|
16
16
|
import { join } from "node:path";
|
|
17
|
-
|
|
17
|
+
var OPENCLAW_DEFAULTS = {
|
|
18
|
+
baseUrl: "http://localhost:3000",
|
|
19
|
+
containerTag: `oclw_${hostname()}`,
|
|
20
|
+
perAgentMemory: false,
|
|
21
|
+
autoRecall: true,
|
|
22
|
+
autoCapture: true,
|
|
23
|
+
maxRecallResults: 10,
|
|
24
|
+
profileFrequency: 50,
|
|
25
|
+
captureMode: "all",
|
|
26
|
+
debug: false
|
|
27
|
+
};
|
|
28
|
+
var OPENCODE_CONFIG_DIR = join(homedir(), ".config", "opencode");
|
|
29
|
+
var PI_DEFAULTS = {
|
|
30
|
+
baseUrl: "http://localhost:3000",
|
|
31
|
+
containerTag: `pi_${hostname()}`,
|
|
32
|
+
autoRecall: true,
|
|
33
|
+
autoCapture: true,
|
|
34
|
+
maxRecallResults: 10,
|
|
35
|
+
profileFrequency: 50,
|
|
36
|
+
debug: false
|
|
37
|
+
};
|
|
38
|
+
// src/config.ts
|
|
39
|
+
function getConfigDir() {
|
|
40
|
+
const xdg = process.env.XDG_CONFIG_HOME;
|
|
41
|
+
if (xdg) {
|
|
42
|
+
return `${xdg}/opencode`;
|
|
43
|
+
}
|
|
44
|
+
return `${process.env.HOME}/.config/opencode`;
|
|
45
|
+
}
|
|
18
46
|
|
|
19
47
|
// src/services/jsonc.ts
|
|
20
48
|
function stripJsoncComments(input) {
|
|
@@ -86,15 +114,6 @@ function stripJsoncComments(input) {
|
|
|
86
114
|
return out.join("").replace(/,\s*([}\]])/g, "$1");
|
|
87
115
|
}
|
|
88
116
|
|
|
89
|
-
// src/config.ts
|
|
90
|
-
function getConfigDir() {
|
|
91
|
-
const xdg = process.env.XDG_CONFIG_HOME;
|
|
92
|
-
if (xdg) {
|
|
93
|
-
return join(xdg, "opencode");
|
|
94
|
-
}
|
|
95
|
-
return join(homedir(), ".config", "opencode");
|
|
96
|
-
}
|
|
97
|
-
|
|
98
117
|
// src/cli.ts
|
|
99
118
|
var PLUGIN_NAME = "@momomemory/opencode-momo@latest";
|
|
100
119
|
var MOMO_INIT_MD = `---
|
|
@@ -156,10 +175,10 @@ bunx @momomemory/opencode-momo configure
|
|
|
156
175
|
\`\`\`
|
|
157
176
|
|
|
158
177
|
Or set environment variables:
|
|
159
|
-
- \`
|
|
160
|
-
- \`
|
|
161
|
-
- \`
|
|
162
|
-
- \`
|
|
178
|
+
- \`MOMO_OPENCODE_BASE_URL\` - Momo server URL (default: http://localhost:3000)
|
|
179
|
+
- \`MOMO_OPENCODE_API_KEY\` - API key for authentication
|
|
180
|
+
- \`MOMO_OPENCODE_CONTAINER_TAG_USER\` - Optional user container tag override
|
|
181
|
+
- \`MOMO_OPENCODE_CONTAINER_TAG_PROJECT\` - Optional project container tag override
|
|
163
182
|
|
|
164
183
|
After configuration, restart OpenCode to activate.
|
|
165
184
|
`;
|
|
@@ -409,10 +428,11 @@ function writeMomoConfig(configDir, options) {
|
|
|
409
428
|
existing = {};
|
|
410
429
|
}
|
|
411
430
|
}
|
|
412
|
-
const
|
|
413
|
-
const
|
|
414
|
-
const
|
|
415
|
-
const
|
|
431
|
+
const existingOpenCode = existing.opencode && typeof existing.opencode === "object" && !Array.isArray(existing.opencode) ? existing.opencode : {};
|
|
432
|
+
const priorBaseUrl = typeof existingOpenCode.baseUrl === "string" ? existingOpenCode.baseUrl : undefined;
|
|
433
|
+
const priorApiKey = typeof existingOpenCode.apiKey === "string" ? existingOpenCode.apiKey : undefined;
|
|
434
|
+
const priorContainerTagUser = typeof existingOpenCode.containerTagUser === "string" ? existingOpenCode.containerTagUser : undefined;
|
|
435
|
+
const priorContainerTagProject = typeof existingOpenCode.containerTagProject === "string" ? existingOpenCode.containerTagProject : undefined;
|
|
416
436
|
const config = {
|
|
417
437
|
baseUrl: options.baseUrl ?? priorBaseUrl ?? "http://localhost:3000",
|
|
418
438
|
apiKey: options.apiKey ?? priorApiKey ?? "",
|
|
@@ -420,16 +440,19 @@ function writeMomoConfig(configDir, options) {
|
|
|
420
440
|
containerTagProject: priorContainerTagProject ?? ""
|
|
421
441
|
};
|
|
422
442
|
const lines = ["{"];
|
|
423
|
-
lines.push(" //
|
|
424
|
-
lines.push(
|
|
425
|
-
lines.push("
|
|
426
|
-
lines.push(`
|
|
427
|
-
lines.push("
|
|
428
|
-
lines.push(`
|
|
429
|
-
lines.push("
|
|
430
|
-
lines.push(`
|
|
431
|
-
lines.push("
|
|
432
|
-
lines.push("
|
|
443
|
+
lines.push(" // OpenCode plugin config for Momo");
|
|
444
|
+
lines.push(' "opencode": {');
|
|
445
|
+
lines.push(" // Momo server URL (default: http://localhost:3000)");
|
|
446
|
+
lines.push(` "baseUrl": ${JSON.stringify(config.baseUrl)},`);
|
|
447
|
+
lines.push(" // API key for authentication (leave empty if your server has auth disabled)");
|
|
448
|
+
lines.push(` "apiKey": ${JSON.stringify(config.apiKey)},`);
|
|
449
|
+
lines.push(" // Optional override for user memory container tag (default: auto-derived from username)");
|
|
450
|
+
lines.push(` "containerTagUser": ${JSON.stringify(config.containerTagUser)},`);
|
|
451
|
+
lines.push(" // Optional override for project memory container tag (default: auto-derived from project directory)");
|
|
452
|
+
lines.push(` "containerTagProject": ${JSON.stringify(config.containerTagProject)}`);
|
|
453
|
+
lines.push(" }");
|
|
454
|
+
lines.push(" // Note: project-local config file (.momo.jsonc) overrides this global file,");
|
|
455
|
+
lines.push(" // and MOMO_OPENCODE_* environment variables override both.");
|
|
433
456
|
lines.push("}");
|
|
434
457
|
lines.push("");
|
|
435
458
|
writeFileSync(configPath, lines.join(`
|
|
@@ -483,8 +506,8 @@ Next steps:
|
|
|
483
506
|
$ opencode-momo configure --base-url http://localhost:3000 --api-key YOUR_KEY
|
|
484
507
|
|
|
485
508
|
Or set environment variables:
|
|
486
|
-
$ export
|
|
487
|
-
$ export
|
|
509
|
+
$ export MOMO_OPENCODE_API_KEY=your-key
|
|
510
|
+
$ export MOMO_OPENCODE_BASE_URL=http://localhost:3000
|
|
488
511
|
|
|
489
512
|
2. Restart OpenCode to activate the plugin.
|
|
490
513
|
|
package/dist/config.d.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { type ResolvedOpenCodePluginConfig } from "@momomemory/sdk";
|
|
2
|
+
export type { ResolvedOpenCodePluginConfig };
|
|
3
|
+
export type MomoConfig = ResolvedOpenCodePluginConfig & {
|
|
3
4
|
baseUrl: string;
|
|
4
|
-
|
|
5
|
-
containerTagProject?: string;
|
|
6
|
-
}
|
|
5
|
+
};
|
|
7
6
|
export declare function getConfigDir(): string;
|
|
8
7
|
export declare function loadConfig(directory?: string): MomoConfig;
|
|
9
8
|
export declare function isConfigured(directory?: string): boolean;
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,KAAK,4BAA4B,EAAE,MAAM,iBAAiB,CAAC;AAE9F,YAAY,EAAE,4BAA4B,EAAE,CAAC;AAE7C,MAAM,MAAM,UAAU,GAAG,4BAA4B,GAAG;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAE5E,wBAAgB,YAAY,IAAI,MAAM,CAMrC;AAED,wBAAgB,UAAU,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,UAAU,CAMzD;AAED,wBAAgB,YAAY,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAGxD"}
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAe,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAe,MAAM,qBAAqB,CAAC;AAuG/D,eAAO,MAAM,UAAU,EAAE,MAqpBxB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -12329,6 +12329,10 @@ function tool(input) {
|
|
|
12329
12329
|
return input;
|
|
12330
12330
|
}
|
|
12331
12331
|
tool.schema = exports_external;
|
|
12332
|
+
// src/index.ts
|
|
12333
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
12334
|
+
import { basename as basename2 } from "node:path";
|
|
12335
|
+
|
|
12332
12336
|
// node_modules/openapi-fetch/dist/index.js
|
|
12333
12337
|
var PATH_PARAM_RE = /\{[^{}]+\}/g;
|
|
12334
12338
|
var supportsRequestInitExt = () => {
|
|
@@ -12354,7 +12358,7 @@ function createClient(clientOptions) {
|
|
|
12354
12358
|
async function coreFetch(schemaPath, fetchOptions) {
|
|
12355
12359
|
const {
|
|
12356
12360
|
baseUrl: localBaseUrl,
|
|
12357
|
-
fetch = baseFetch,
|
|
12361
|
+
fetch: fetch2 = baseFetch,
|
|
12358
12362
|
Request = CustomRequest,
|
|
12359
12363
|
headers,
|
|
12360
12364
|
params = {},
|
|
@@ -12399,7 +12403,7 @@ function createClient(clientOptions) {
|
|
|
12399
12403
|
id = randomID();
|
|
12400
12404
|
options = Object.freeze({
|
|
12401
12405
|
baseUrl: finalBaseUrl,
|
|
12402
|
-
fetch,
|
|
12406
|
+
fetch: fetch2,
|
|
12403
12407
|
parseAs,
|
|
12404
12408
|
querySerializer,
|
|
12405
12409
|
bodySerializer
|
|
@@ -12428,7 +12432,7 @@ function createClient(clientOptions) {
|
|
|
12428
12432
|
}
|
|
12429
12433
|
if (!response) {
|
|
12430
12434
|
try {
|
|
12431
|
-
response = await
|
|
12435
|
+
response = await fetch2(request, requestInitExt);
|
|
12432
12436
|
} catch (error46) {
|
|
12433
12437
|
let errorAfterMiddleware = error46;
|
|
12434
12438
|
if (middlewares.length) {
|
|
@@ -12859,6 +12863,11 @@ class DocumentsGroup {
|
|
|
12859
12863
|
formData.append("containerTag", containerTag);
|
|
12860
12864
|
if (body?.metadata)
|
|
12861
12865
|
formData.append("metadata", body.metadata);
|
|
12866
|
+
if (body?.extractMemories !== undefined) {
|
|
12867
|
+
formData.append("extractMemories", String(body.extractMemories));
|
|
12868
|
+
}
|
|
12869
|
+
if (body?.contentType)
|
|
12870
|
+
formData.append("contentType", body.contentType);
|
|
12862
12871
|
const headers = { ...opts?.headers ?? {} };
|
|
12863
12872
|
const apiKey = this.config.getApiKey ? await this.config.getApiKey() : this.config.apiKey;
|
|
12864
12873
|
if (apiKey)
|
|
@@ -13415,26 +13424,24 @@ class MomoClient {
|
|
|
13415
13424
|
this.health = new HealthGroup(this.raw, config2);
|
|
13416
13425
|
}
|
|
13417
13426
|
}
|
|
13418
|
-
//
|
|
13419
|
-
import {
|
|
13427
|
+
// node_modules/@momomemory/sdk/dist/plugin-config.js
|
|
13428
|
+
import { homedir, hostname as hostname3 } from "node:os";
|
|
13420
13429
|
import { join } from "node:path";
|
|
13421
|
-
import {
|
|
13422
|
-
|
|
13423
|
-
|
|
13424
|
-
function
|
|
13425
|
-
var Mode;
|
|
13426
|
-
((Mode2) => {
|
|
13430
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
13431
|
+
function stripJsoncComments(content) {
|
|
13432
|
+
let Mode;
|
|
13433
|
+
(function(Mode2) {
|
|
13427
13434
|
Mode2[Mode2["Code"] = 0] = "Code";
|
|
13428
13435
|
Mode2[Mode2["String"] = 1] = "String";
|
|
13429
13436
|
Mode2[Mode2["LineComment"] = 2] = "LineComment";
|
|
13430
13437
|
Mode2[Mode2["BlockComment"] = 3] = "BlockComment";
|
|
13431
|
-
})(Mode
|
|
13438
|
+
})(Mode || (Mode = {}));
|
|
13432
13439
|
const out = [];
|
|
13433
|
-
let mode =
|
|
13434
|
-
for (let i = 0;i <
|
|
13435
|
-
const ch =
|
|
13436
|
-
const next =
|
|
13437
|
-
if (mode ===
|
|
13440
|
+
let mode = Mode.Code;
|
|
13441
|
+
for (let i = 0;i < content.length; i++) {
|
|
13442
|
+
const ch = content[i];
|
|
13443
|
+
const next = content[i + 1];
|
|
13444
|
+
if (mode === Mode.String) {
|
|
13438
13445
|
out.push(ch);
|
|
13439
13446
|
if (ch === "\\") {
|
|
13440
13447
|
if (next !== undefined) {
|
|
@@ -13444,22 +13451,22 @@ function stripJsoncComments(input) {
|
|
|
13444
13451
|
continue;
|
|
13445
13452
|
}
|
|
13446
13453
|
if (ch === '"') {
|
|
13447
|
-
mode =
|
|
13454
|
+
mode = Mode.Code;
|
|
13448
13455
|
}
|
|
13449
13456
|
continue;
|
|
13450
13457
|
}
|
|
13451
|
-
if (mode ===
|
|
13458
|
+
if (mode === Mode.LineComment) {
|
|
13452
13459
|
if (ch === `
|
|
13453
13460
|
`) {
|
|
13454
|
-
mode =
|
|
13461
|
+
mode = Mode.Code;
|
|
13455
13462
|
out.push(`
|
|
13456
13463
|
`);
|
|
13457
13464
|
}
|
|
13458
13465
|
continue;
|
|
13459
13466
|
}
|
|
13460
|
-
if (mode ===
|
|
13467
|
+
if (mode === Mode.BlockComment) {
|
|
13461
13468
|
if (ch === "*" && next === "/") {
|
|
13462
|
-
mode =
|
|
13469
|
+
mode = Mode.Code;
|
|
13463
13470
|
i++;
|
|
13464
13471
|
continue;
|
|
13465
13472
|
}
|
|
@@ -13471,17 +13478,17 @@ function stripJsoncComments(input) {
|
|
|
13471
13478
|
continue;
|
|
13472
13479
|
}
|
|
13473
13480
|
if (ch === '"') {
|
|
13474
|
-
mode =
|
|
13481
|
+
mode = Mode.String;
|
|
13475
13482
|
out.push(ch);
|
|
13476
13483
|
continue;
|
|
13477
13484
|
}
|
|
13478
13485
|
if (ch === "/" && next === "/") {
|
|
13479
|
-
mode =
|
|
13486
|
+
mode = Mode.LineComment;
|
|
13480
13487
|
i++;
|
|
13481
13488
|
continue;
|
|
13482
13489
|
}
|
|
13483
13490
|
if (ch === "/" && next === "*") {
|
|
13484
|
-
mode =
|
|
13491
|
+
mode = Mode.BlockComment;
|
|
13485
13492
|
i++;
|
|
13486
13493
|
continue;
|
|
13487
13494
|
}
|
|
@@ -13489,56 +13496,248 @@ function stripJsoncComments(input) {
|
|
|
13489
13496
|
}
|
|
13490
13497
|
return out.join("").replace(/,\s*([}\]])/g, "$1");
|
|
13491
13498
|
}
|
|
13492
|
-
|
|
13493
|
-
// src/config.ts
|
|
13494
|
-
var DEFAULT_BASE_URL = "http://localhost:3000";
|
|
13495
|
-
function getConfigDir() {
|
|
13496
|
-
const xdg = process.env.XDG_CONFIG_HOME;
|
|
13497
|
-
if (xdg) {
|
|
13498
|
-
return join(xdg, "opencode");
|
|
13499
|
-
}
|
|
13500
|
-
return join(homedir(), ".config", "opencode");
|
|
13501
|
-
}
|
|
13502
|
-
function readConfigFile() {
|
|
13503
|
-
const configPath = join(getConfigDir(), "momo.jsonc");
|
|
13504
|
-
return readJsoncConfigFile(configPath);
|
|
13505
|
-
}
|
|
13506
|
-
function readJsoncConfigFile(configPath) {
|
|
13499
|
+
function readJsoncFile(path) {
|
|
13507
13500
|
try {
|
|
13508
|
-
|
|
13501
|
+
if (!existsSync(path))
|
|
13502
|
+
return null;
|
|
13503
|
+
const raw = readFileSync(path, "utf-8");
|
|
13509
13504
|
const stripped = stripJsoncComments(raw);
|
|
13510
13505
|
return JSON.parse(stripped);
|
|
13511
13506
|
} catch {
|
|
13512
|
-
return
|
|
13507
|
+
return null;
|
|
13513
13508
|
}
|
|
13514
13509
|
}
|
|
13515
|
-
function
|
|
13516
|
-
|
|
13517
|
-
|
|
13518
|
-
|
|
13519
|
-
|
|
13520
|
-
|
|
13510
|
+
function hasOwn(obj, key) {
|
|
13511
|
+
return Object.prototype.hasOwnProperty.call(obj, key);
|
|
13512
|
+
}
|
|
13513
|
+
function sanitizeContainerTag(value) {
|
|
13514
|
+
return value.replace(/[^A-Za-z0-9_]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "");
|
|
13515
|
+
}
|
|
13516
|
+
function interpolateEnvVars(value) {
|
|
13517
|
+
return value.replace(/\$\{([^}]+)\}/g, (_chunk, key) => {
|
|
13518
|
+
const envValue = process.env[key];
|
|
13519
|
+
if (!envValue) {
|
|
13520
|
+
throw new Error(`Environment variable ${key} is not set`);
|
|
13521
|
+
}
|
|
13522
|
+
return envValue;
|
|
13523
|
+
});
|
|
13524
|
+
}
|
|
13525
|
+
function toBoolean(value, fallback, trueValues = ["true", "1", "yes"], falseValues = ["false", "0", "no"]) {
|
|
13526
|
+
if (typeof value === "boolean")
|
|
13527
|
+
return value;
|
|
13528
|
+
if (typeof value === "string") {
|
|
13529
|
+
const lower = value.toLowerCase();
|
|
13530
|
+
if (trueValues.includes(lower))
|
|
13531
|
+
return true;
|
|
13532
|
+
if (falseValues.includes(lower))
|
|
13533
|
+
return false;
|
|
13521
13534
|
}
|
|
13522
|
-
|
|
13523
|
-
|
|
13524
|
-
|
|
13535
|
+
return fallback;
|
|
13536
|
+
}
|
|
13537
|
+
function toBoundedInt(value, fallback, min, max) {
|
|
13538
|
+
let n;
|
|
13539
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
13540
|
+
n = Math.round(value);
|
|
13541
|
+
} else if (typeof value === "string") {
|
|
13542
|
+
const parsed = parseInt(value, 10);
|
|
13543
|
+
if (Number.isFinite(parsed)) {
|
|
13544
|
+
n = Math.round(parsed);
|
|
13545
|
+
} else {
|
|
13546
|
+
return fallback;
|
|
13547
|
+
}
|
|
13548
|
+
} else {
|
|
13549
|
+
return fallback;
|
|
13550
|
+
}
|
|
13551
|
+
if (n < min)
|
|
13552
|
+
return min;
|
|
13553
|
+
if (n > max)
|
|
13554
|
+
return max;
|
|
13555
|
+
return n;
|
|
13556
|
+
}
|
|
13557
|
+
function pickString(key, envKey, config2, fallback) {
|
|
13558
|
+
const envValue = process.env[envKey];
|
|
13559
|
+
if (envValue !== undefined)
|
|
13560
|
+
return interpolateEnvVars(envValue);
|
|
13561
|
+
if (hasOwn(config2, key) && typeof config2[key] === "string") {
|
|
13562
|
+
return interpolateEnvVars(config2[key]);
|
|
13563
|
+
}
|
|
13564
|
+
return fallback;
|
|
13565
|
+
}
|
|
13566
|
+
function pickOptionalString(key, envKey, config2) {
|
|
13567
|
+
const envValue = process.env[envKey];
|
|
13568
|
+
if (envValue !== undefined)
|
|
13569
|
+
return interpolateEnvVars(envValue);
|
|
13570
|
+
if (hasOwn(config2, key) && typeof config2[key] === "string") {
|
|
13571
|
+
return interpolateEnvVars(config2[key]);
|
|
13572
|
+
}
|
|
13573
|
+
return;
|
|
13574
|
+
}
|
|
13575
|
+
function pickBoolean(key, envKey, config2, fallback) {
|
|
13576
|
+
const envValue = process.env[envKey];
|
|
13577
|
+
if (envValue !== undefined) {
|
|
13578
|
+
return toBoolean(envValue, fallback);
|
|
13579
|
+
}
|
|
13580
|
+
return toBoolean(config2[key], fallback);
|
|
13581
|
+
}
|
|
13582
|
+
function pickBoundedInt(key, envKey, config2, fallback, min, max) {
|
|
13583
|
+
const envValue = process.env[envKey];
|
|
13584
|
+
if (envValue !== undefined) {
|
|
13585
|
+
return toBoundedInt(envValue, fallback, min, max);
|
|
13586
|
+
}
|
|
13587
|
+
return toBoundedInt(config2[key], fallback, min, max);
|
|
13588
|
+
}
|
|
13589
|
+
var OPENCLAW_DEFAULTS = {
|
|
13590
|
+
baseUrl: "http://localhost:3000",
|
|
13591
|
+
containerTag: `oclw_${hostname3()}`,
|
|
13592
|
+
perAgentMemory: false,
|
|
13593
|
+
autoRecall: true,
|
|
13594
|
+
autoCapture: true,
|
|
13595
|
+
maxRecallResults: 10,
|
|
13596
|
+
profileFrequency: 50,
|
|
13597
|
+
captureMode: "all",
|
|
13598
|
+
debug: false
|
|
13599
|
+
};
|
|
13600
|
+
var OPENCLAW_ENV_PREFIX = "MOMO_OPENCLAW_";
|
|
13601
|
+
function loadOpenClawConfig(config2) {
|
|
13602
|
+
const pluginConfig = config2.openclaw ?? {};
|
|
13603
|
+
const prefix = OPENCLAW_ENV_PREFIX;
|
|
13604
|
+
return {
|
|
13605
|
+
baseUrl: pickString("baseUrl", `${prefix}BASE_URL`, pluginConfig, OPENCLAW_DEFAULTS.baseUrl),
|
|
13606
|
+
apiKey: pickOptionalString("apiKey", `${prefix}API_KEY`, pluginConfig),
|
|
13607
|
+
containerTag: sanitizeContainerTag(pickString("containerTag", `${prefix}CONTAINER_TAG`, pluginConfig, OPENCLAW_DEFAULTS.containerTag)),
|
|
13608
|
+
perAgentMemory: toBoolean(pluginConfig.perAgentMemory, OPENCLAW_DEFAULTS.perAgentMemory),
|
|
13609
|
+
autoRecall: toBoolean(pluginConfig.autoRecall, OPENCLAW_DEFAULTS.autoRecall),
|
|
13610
|
+
autoCapture: toBoolean(pluginConfig.autoCapture, OPENCLAW_DEFAULTS.autoCapture),
|
|
13611
|
+
maxRecallResults: toBoundedInt(pluginConfig.maxRecallResults, OPENCLAW_DEFAULTS.maxRecallResults, 1, 20),
|
|
13612
|
+
profileFrequency: toBoundedInt(pluginConfig.profileFrequency, OPENCLAW_DEFAULTS.profileFrequency, 1, 500),
|
|
13613
|
+
captureMode: pluginConfig.captureMode === "everything" ? "everything" : OPENCLAW_DEFAULTS.captureMode,
|
|
13614
|
+
debug: toBoolean(pluginConfig.debug, OPENCLAW_DEFAULTS.debug)
|
|
13615
|
+
};
|
|
13616
|
+
}
|
|
13617
|
+
var OPENCODE_DEFAULTS = {
|
|
13618
|
+
baseUrl: "http://localhost:3000"
|
|
13619
|
+
};
|
|
13620
|
+
var OPENCODE_ENV_PREFIX = "MOMO_OPENCODE_";
|
|
13621
|
+
var OPENCODE_CONFIG_DIR = join(homedir(), ".config", "opencode");
|
|
13622
|
+
function loadOpenCodeConfig(config2) {
|
|
13623
|
+
const pluginConfig = config2.opencode ?? {};
|
|
13624
|
+
const prefix = OPENCODE_ENV_PREFIX;
|
|
13625
|
+
return {
|
|
13626
|
+
baseUrl: pickString("baseUrl", `${prefix}BASE_URL`, pluginConfig, OPENCODE_DEFAULTS.baseUrl),
|
|
13627
|
+
apiKey: pickOptionalString("apiKey", `${prefix}API_KEY`, pluginConfig),
|
|
13628
|
+
containerTagUser: pickOptionalString("containerTagUser", `${prefix}CONTAINER_TAG_USER`, pluginConfig),
|
|
13629
|
+
containerTagProject: pickOptionalString("containerTagProject", `${prefix}CONTAINER_TAG_PROJECT`, pluginConfig)
|
|
13630
|
+
};
|
|
13631
|
+
}
|
|
13632
|
+
var PI_DEFAULTS = {
|
|
13633
|
+
baseUrl: "http://localhost:3000",
|
|
13634
|
+
containerTag: `pi_${hostname3()}`,
|
|
13635
|
+
autoRecall: true,
|
|
13636
|
+
autoCapture: true,
|
|
13637
|
+
maxRecallResults: 10,
|
|
13638
|
+
profileFrequency: 50,
|
|
13639
|
+
debug: false
|
|
13640
|
+
};
|
|
13641
|
+
var PI_ENV_PREFIX = "MOMO_PI_";
|
|
13642
|
+
function loadPiConfig(config2) {
|
|
13643
|
+
const pluginConfig = config2.pi ?? {};
|
|
13644
|
+
const record2 = pluginConfig;
|
|
13645
|
+
const prefix = PI_ENV_PREFIX;
|
|
13646
|
+
return {
|
|
13647
|
+
baseUrl: pickString("baseUrl", `${prefix}BASE_URL`, record2, PI_DEFAULTS.baseUrl),
|
|
13648
|
+
apiKey: pickOptionalString("apiKey", `${prefix}API_KEY`, record2),
|
|
13649
|
+
containerTag: sanitizeContainerTag(pickString("containerTag", `${prefix}CONTAINER_TAG`, record2, PI_DEFAULTS.containerTag)),
|
|
13650
|
+
autoRecall: pickBoolean("autoRecall", `${prefix}AUTO_RECALL`, record2, PI_DEFAULTS.autoRecall),
|
|
13651
|
+
autoCapture: pickBoolean("autoCapture", `${prefix}AUTO_CAPTURE`, record2, PI_DEFAULTS.autoCapture),
|
|
13652
|
+
maxRecallResults: pickBoundedInt("maxRecallResults", `${prefix}MAX_RECALL_RESULTS`, record2, PI_DEFAULTS.maxRecallResults, 1, 20),
|
|
13653
|
+
profileFrequency: pickBoundedInt("profileFrequency", `${prefix}PROFILE_FREQUENCY`, record2, PI_DEFAULTS.profileFrequency, 1, 500),
|
|
13654
|
+
debug: pickBoolean("debug", `${prefix}DEBUG`, record2, PI_DEFAULTS.debug)
|
|
13655
|
+
};
|
|
13656
|
+
}
|
|
13657
|
+
function getConfigPaths(plugin, cwd = process.cwd(), globalConfigDir) {
|
|
13658
|
+
switch (plugin) {
|
|
13659
|
+
case "opencode":
|
|
13660
|
+
return {
|
|
13661
|
+
project: join(cwd, ".momo.jsonc"),
|
|
13662
|
+
global: join(globalConfigDir ?? OPENCODE_CONFIG_DIR, "momo.jsonc")
|
|
13663
|
+
};
|
|
13664
|
+
case "openclaw":
|
|
13665
|
+
return {
|
|
13666
|
+
project: join(cwd, ".momo.jsonc"),
|
|
13667
|
+
global: join(globalConfigDir ?? join(homedir(), ".openclaw"), "momo.jsonc")
|
|
13668
|
+
};
|
|
13669
|
+
case "pi":
|
|
13670
|
+
return {
|
|
13671
|
+
project: join(cwd, ".momo.jsonc"),
|
|
13672
|
+
global: join(globalConfigDir ?? join(homedir(), ".pi"), "momo.jsonc")
|
|
13673
|
+
};
|
|
13525
13674
|
}
|
|
13526
|
-
return {};
|
|
13527
13675
|
}
|
|
13528
|
-
function
|
|
13529
|
-
const
|
|
13530
|
-
const
|
|
13531
|
-
|
|
13532
|
-
|
|
13533
|
-
|
|
13534
|
-
|
|
13676
|
+
function mergeConfigs(...configs) {
|
|
13677
|
+
const result = {};
|
|
13678
|
+
for (const config2 of configs) {
|
|
13679
|
+
if (!config2)
|
|
13680
|
+
continue;
|
|
13681
|
+
for (const [key, value] of Object.entries(config2)) {
|
|
13682
|
+
if (value !== undefined) {
|
|
13683
|
+
result[key] = value;
|
|
13684
|
+
}
|
|
13685
|
+
}
|
|
13686
|
+
}
|
|
13687
|
+
return result;
|
|
13688
|
+
}
|
|
13689
|
+
function loadPluginConfig(plugin, options) {
|
|
13690
|
+
const cwd = options?.cwd ?? process.cwd();
|
|
13691
|
+
const globalConfigDir = options?.globalConfigDir;
|
|
13692
|
+
const paths = getConfigPaths(plugin, cwd, globalConfigDir);
|
|
13693
|
+
const projectConfig = readJsoncFile(paths.project ?? "");
|
|
13694
|
+
const globalConfig2 = readJsoncFile(paths.global ?? "");
|
|
13695
|
+
const mergedConfig = {
|
|
13696
|
+
openclaw: mergeConfigs(globalConfig2?.openclaw, projectConfig?.openclaw),
|
|
13697
|
+
opencode: mergeConfigs(globalConfig2?.opencode, projectConfig?.opencode),
|
|
13698
|
+
pi: mergeConfigs(globalConfig2?.pi, projectConfig?.pi)
|
|
13699
|
+
};
|
|
13700
|
+
let config2;
|
|
13701
|
+
switch (plugin) {
|
|
13702
|
+
case "openclaw":
|
|
13703
|
+
config2 = loadOpenClawConfig(mergedConfig);
|
|
13704
|
+
break;
|
|
13705
|
+
case "opencode":
|
|
13706
|
+
config2 = loadOpenCodeConfig(mergedConfig);
|
|
13707
|
+
break;
|
|
13708
|
+
case "pi":
|
|
13709
|
+
config2 = loadPiConfig(mergedConfig);
|
|
13710
|
+
break;
|
|
13711
|
+
}
|
|
13535
13712
|
return {
|
|
13536
|
-
|
|
13537
|
-
|
|
13538
|
-
|
|
13539
|
-
|
|
13713
|
+
config: config2,
|
|
13714
|
+
meta: {
|
|
13715
|
+
cwd,
|
|
13716
|
+
files: {
|
|
13717
|
+
project: paths.project && existsSync(paths.project) ? paths.project : undefined,
|
|
13718
|
+
global: paths.global && existsSync(paths.global) ? paths.global : undefined
|
|
13719
|
+
}
|
|
13720
|
+
}
|
|
13540
13721
|
};
|
|
13541
13722
|
}
|
|
13723
|
+
function loadOpenCodePluginConfig(options) {
|
|
13724
|
+
return loadPluginConfig("opencode", options);
|
|
13725
|
+
}
|
|
13726
|
+
// src/config.ts
|
|
13727
|
+
function getConfigDir() {
|
|
13728
|
+
const xdg = process.env.XDG_CONFIG_HOME;
|
|
13729
|
+
if (xdg) {
|
|
13730
|
+
return `${xdg}/opencode`;
|
|
13731
|
+
}
|
|
13732
|
+
return `${process.env.HOME}/.config/opencode`;
|
|
13733
|
+
}
|
|
13734
|
+
function loadConfig(directory) {
|
|
13735
|
+
const { config: config2 } = loadOpenCodePluginConfig({
|
|
13736
|
+
cwd: directory ?? process.cwd(),
|
|
13737
|
+
globalConfigDir: getConfigDir()
|
|
13738
|
+
});
|
|
13739
|
+
return config2;
|
|
13740
|
+
}
|
|
13542
13741
|
function isConfigured(directory) {
|
|
13543
13742
|
const config2 = loadConfig(directory);
|
|
13544
13743
|
return config2.apiKey != null && config2.apiKey.length > 0;
|
|
@@ -13697,6 +13896,8 @@ async function handleCompactionEvent(event, momo, containerTag) {
|
|
|
13697
13896
|
|
|
13698
13897
|
// src/index.ts
|
|
13699
13898
|
var z = tool.schema;
|
|
13899
|
+
var DEFAULT_INGEST_TIMEOUT_MS = 120000;
|
|
13900
|
+
var DEFAULT_POLL_INTERVAL_MS = 1500;
|
|
13700
13901
|
var injectedSessions = new Set;
|
|
13701
13902
|
var HELP_TEXT = `**Momo Memory Commands**
|
|
13702
13903
|
|
|
@@ -13718,6 +13919,57 @@ var HELP_TEXT = `**Momo Memory Commands**
|
|
|
13718
13919
|
- \`momo({ mode: "search", query: "database schema" })\`
|
|
13719
13920
|
- \`momo({ mode: "forget", memoryId: "mem_abc123" })\`
|
|
13720
13921
|
`;
|
|
13922
|
+
function truncate(value, max = 600) {
|
|
13923
|
+
if (value.length <= max)
|
|
13924
|
+
return value;
|
|
13925
|
+
return `${value.slice(0, max)}...`;
|
|
13926
|
+
}
|
|
13927
|
+
function inferInputType(input, inputType) {
|
|
13928
|
+
if (inputType && inputType !== "auto")
|
|
13929
|
+
return inputType;
|
|
13930
|
+
if (input.startsWith("http://") || input.startsWith("https://"))
|
|
13931
|
+
return "url";
|
|
13932
|
+
if (existsSync2(input))
|
|
13933
|
+
return "file";
|
|
13934
|
+
return "text";
|
|
13935
|
+
}
|
|
13936
|
+
function parseMetadataJson(metadataJson) {
|
|
13937
|
+
if (!metadataJson)
|
|
13938
|
+
return {};
|
|
13939
|
+
try {
|
|
13940
|
+
const parsed = JSON.parse(metadataJson);
|
|
13941
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
13942
|
+
return { error: "metadataJson must be a JSON object" };
|
|
13943
|
+
}
|
|
13944
|
+
return parsed;
|
|
13945
|
+
} catch (error45) {
|
|
13946
|
+
const message = error45 instanceof Error ? error45.message : String(error45);
|
|
13947
|
+
return { error: `Invalid metadataJson: ${message}` };
|
|
13948
|
+
}
|
|
13949
|
+
}
|
|
13950
|
+
async function delay(ms) {
|
|
13951
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
13952
|
+
}
|
|
13953
|
+
function formatSearchContent(result) {
|
|
13954
|
+
if (result.type === "memory") {
|
|
13955
|
+
if (result.content?.trim())
|
|
13956
|
+
return result.content;
|
|
13957
|
+
return result.memoryId ? `[memory:${result.memoryId}]` : "(empty memory)";
|
|
13958
|
+
}
|
|
13959
|
+
const fromContent = result.content?.trim();
|
|
13960
|
+
if (fromContent)
|
|
13961
|
+
return fromContent;
|
|
13962
|
+
const fromChunk = result.chunks?.find((c) => c.content?.trim())?.content;
|
|
13963
|
+
if (fromChunk)
|
|
13964
|
+
return fromChunk;
|
|
13965
|
+
const fromTitle = result.title?.trim();
|
|
13966
|
+
if (fromTitle)
|
|
13967
|
+
return fromTitle;
|
|
13968
|
+
const fromSummary = result.summary?.trim();
|
|
13969
|
+
if (fromSummary)
|
|
13970
|
+
return fromSummary;
|
|
13971
|
+
return result.documentId ? `[document:${result.documentId}]` : "(empty document match)";
|
|
13972
|
+
}
|
|
13721
13973
|
var MomoPlugin = async (ctx) => {
|
|
13722
13974
|
const config2 = loadConfig(ctx.directory);
|
|
13723
13975
|
const configured = isConfigured(ctx.directory);
|
|
@@ -13734,7 +13986,7 @@ var MomoPlugin = async (ctx) => {
|
|
|
13734
13986
|
});
|
|
13735
13987
|
function requireMomo() {
|
|
13736
13988
|
if (!momo) {
|
|
13737
|
-
throw new Error("Momo is not configured. Set
|
|
13989
|
+
throw new Error("Momo is not configured. Set MOMO_OPENCODE_API_KEY or configure ~/.config/opencode/momo.jsonc (or project-local .momo.jsonc).");
|
|
13738
13990
|
}
|
|
13739
13991
|
return momo;
|
|
13740
13992
|
}
|
|
@@ -13743,6 +13995,100 @@ var MomoPlugin = async (ctx) => {
|
|
|
13743
13995
|
return tags.user;
|
|
13744
13996
|
return tags.project;
|
|
13745
13997
|
}
|
|
13998
|
+
async function waitForIngestion(client, ingestionId, timeoutMs, pollIntervalMs) {
|
|
13999
|
+
const start = Date.now();
|
|
14000
|
+
let transientErrors = 0;
|
|
14001
|
+
while (Date.now() - start < timeoutMs) {
|
|
14002
|
+
let status;
|
|
14003
|
+
try {
|
|
14004
|
+
status = await client.documents.getIngestionStatus(ingestionId, {
|
|
14005
|
+
timeoutMs
|
|
14006
|
+
});
|
|
14007
|
+
} catch (error45) {
|
|
14008
|
+
const message = error45 instanceof Error ? error45.message.toLowerCase() : String(error45).toLowerCase();
|
|
14009
|
+
const isTransient = message.includes("database is locked") || message.includes("internal error") || message.includes("timeout") || message.includes("503") || message.includes("502");
|
|
14010
|
+
if (!isTransient)
|
|
14011
|
+
throw error45;
|
|
14012
|
+
transientErrors += 1;
|
|
14013
|
+
if (transientErrors > 5) {
|
|
14014
|
+
throw error45;
|
|
14015
|
+
}
|
|
14016
|
+
await delay(pollIntervalMs);
|
|
14017
|
+
continue;
|
|
14018
|
+
}
|
|
14019
|
+
if (status.status === "completed" || status.status === "failed") {
|
|
14020
|
+
return {
|
|
14021
|
+
status: status.status,
|
|
14022
|
+
title: status.title ?? undefined,
|
|
14023
|
+
documentId: status.documentId
|
|
14024
|
+
};
|
|
14025
|
+
}
|
|
14026
|
+
await delay(pollIntervalMs);
|
|
14027
|
+
}
|
|
14028
|
+
return { status: "processing" };
|
|
14029
|
+
}
|
|
14030
|
+
async function formatDocumentResult(client, documentId, scope, extractedBy) {
|
|
14031
|
+
try {
|
|
14032
|
+
const doc2 = await client.documents.get(documentId);
|
|
14033
|
+
const lines = [
|
|
14034
|
+
`Document ready (scope: ${scope}).`,
|
|
14035
|
+
`- documentId: ${doc2.documentId}`,
|
|
14036
|
+
`- docType: ${doc2.docType}`,
|
|
14037
|
+
`- ingestionStatus: ${doc2.ingestionStatus}`
|
|
14038
|
+
];
|
|
14039
|
+
if (extractedBy)
|
|
14040
|
+
lines.push(`- extractor: ${extractedBy}`);
|
|
14041
|
+
if (doc2.title)
|
|
14042
|
+
lines.push(`- title: ${doc2.title}`);
|
|
14043
|
+
if (doc2.errorMessage)
|
|
14044
|
+
lines.push(`- notes: ${doc2.errorMessage}`);
|
|
14045
|
+
if (doc2.content)
|
|
14046
|
+
lines.push(`- extractedTextPreview: ${truncate(doc2.content, 800)}`);
|
|
14047
|
+
return lines.join(`
|
|
14048
|
+
`);
|
|
14049
|
+
} catch {
|
|
14050
|
+
return [
|
|
14051
|
+
`Document ready (scope: ${scope}).`,
|
|
14052
|
+
`- documentId: ${documentId}`,
|
|
14053
|
+
extractedBy ? `- extractor: ${extractedBy}` : undefined
|
|
14054
|
+
].filter(Boolean).join(`
|
|
14055
|
+
`);
|
|
14056
|
+
}
|
|
14057
|
+
}
|
|
14058
|
+
async function uploadDocumentFromPath(filePath, params) {
|
|
14059
|
+
const fs = await Function("return import('fs/promises')")();
|
|
14060
|
+
const { buffer } = await fs.readFile(filePath);
|
|
14061
|
+
const file2 = new File([new Blob([new Uint8Array(buffer)])], basename2(filePath));
|
|
14062
|
+
const formData = new FormData;
|
|
14063
|
+
formData.append("file", file2);
|
|
14064
|
+
formData.append("containerTag", params.containerTag);
|
|
14065
|
+
formData.append("extractMemories", String(params.extractMemories));
|
|
14066
|
+
if (params.contentType)
|
|
14067
|
+
formData.append("contentType", params.contentType);
|
|
14068
|
+
if (params.metadata && Object.keys(params.metadata).length > 0) {
|
|
14069
|
+
formData.append("metadata", JSON.stringify(params.metadata));
|
|
14070
|
+
}
|
|
14071
|
+
const headers = {};
|
|
14072
|
+
if (config2.apiKey)
|
|
14073
|
+
headers.Authorization = `Bearer ${config2.apiKey}`;
|
|
14074
|
+
const response = await fetch(`${config2.baseUrl}/api/v1/documents:upload`, {
|
|
14075
|
+
method: "POST",
|
|
14076
|
+
body: formData,
|
|
14077
|
+
headers,
|
|
14078
|
+
signal: AbortSignal.timeout(params.timeoutMs)
|
|
14079
|
+
});
|
|
14080
|
+
const json2 = await response.json();
|
|
14081
|
+
if (!response.ok || json2.error) {
|
|
14082
|
+
const message = json2.error?.message ?? `Upload failed with status ${response.status}`;
|
|
14083
|
+
throw new Error(message);
|
|
14084
|
+
}
|
|
14085
|
+
const documentId = json2.data?.documentId;
|
|
14086
|
+
const ingestionId = json2.data?.ingestionId;
|
|
14087
|
+
if (!documentId || !ingestionId) {
|
|
14088
|
+
throw new Error("Upload response missing documentId/ingestionId");
|
|
14089
|
+
}
|
|
14090
|
+
return { documentId, ingestionId };
|
|
14091
|
+
}
|
|
13746
14092
|
const momoTool = tool({
|
|
13747
14093
|
description: "Interact with Momo long-term memory. Modes: help, add, search, profile, list, forget.",
|
|
13748
14094
|
args: {
|
|
@@ -13789,18 +14135,23 @@ var MomoPlugin = async (ctx) => {
|
|
|
13789
14135
|
q: args.query,
|
|
13790
14136
|
containerTags,
|
|
13791
14137
|
limit: args.limit ?? 10,
|
|
13792
|
-
scope: "hybrid"
|
|
14138
|
+
scope: "hybrid",
|
|
14139
|
+
include: {
|
|
14140
|
+
documents: true,
|
|
14141
|
+
chunks: true
|
|
14142
|
+
}
|
|
13793
14143
|
});
|
|
13794
14144
|
if (!results.results || results.results.length === 0) {
|
|
13795
|
-
return "No
|
|
14145
|
+
return "No results found matching your query.";
|
|
13796
14146
|
}
|
|
13797
14147
|
const formatted = results.results.map((r, i) => {
|
|
13798
14148
|
const relevance = r.type === "memory" ? r.similarity : r.score;
|
|
13799
14149
|
const scoreStr = ` (score: ${relevance.toFixed(2)})`;
|
|
13800
|
-
|
|
14150
|
+
const content = formatSearchContent(r);
|
|
14151
|
+
return `${i + 1}. ${content}${scoreStr}`;
|
|
13801
14152
|
}).join(`
|
|
13802
14153
|
`);
|
|
13803
|
-
return `Found ${results.results.length}
|
|
14154
|
+
return `Found ${results.results.length} results:
|
|
13804
14155
|
|
|
13805
14156
|
${formatted}`;
|
|
13806
14157
|
}
|
|
@@ -13866,6 +14217,166 @@ ${formatted}`;
|
|
|
13866
14217
|
}
|
|
13867
14218
|
}
|
|
13868
14219
|
});
|
|
14220
|
+
const momoIngestTool = tool({
|
|
14221
|
+
description: "Ingest text, URLs, and files through Momo's document pipeline (RAG + optional memory extraction).",
|
|
14222
|
+
args: {
|
|
14223
|
+
input: z.string().describe("Text, URL, or local file path to ingest"),
|
|
14224
|
+
inputType: z.enum(["auto", "text", "url", "file"]).optional().describe("How to interpret input (default: auto)"),
|
|
14225
|
+
scope: z.enum(["user", "project"]).optional().describe("Memory/document scope (default: project)"),
|
|
14226
|
+
extractMemories: z.boolean().optional().describe("Whether to extract memories from ingested content (default: true)"),
|
|
14227
|
+
metadataJson: z.string().optional().describe("Optional JSON object string attached as metadata"),
|
|
14228
|
+
contentType: z.string().optional().describe("Optional content type hint (e.g. audio/mpeg, video/webm)"),
|
|
14229
|
+
wait: z.boolean().optional().describe("Wait for ingestion to finish before returning (default: true)"),
|
|
14230
|
+
timeoutMs: z.number().optional().describe("Max wait timeout in milliseconds (default: 120000)"),
|
|
14231
|
+
pollIntervalMs: z.number().optional().describe("Polling interval while waiting (default: 1500)")
|
|
14232
|
+
},
|
|
14233
|
+
async execute(args, _context) {
|
|
14234
|
+
const client = requireMomo();
|
|
14235
|
+
const scope = args.scope ?? "project";
|
|
14236
|
+
const containerTag = resolveContainerTag(scope);
|
|
14237
|
+
const extractMemories = args.extractMemories ?? true;
|
|
14238
|
+
const wait = args.wait ?? true;
|
|
14239
|
+
const timeoutMs = args.timeoutMs ?? DEFAULT_INGEST_TIMEOUT_MS;
|
|
14240
|
+
const pollIntervalMs = args.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
14241
|
+
const parsedMetadata = parseMetadataJson(args.metadataJson);
|
|
14242
|
+
if ("error" in parsedMetadata)
|
|
14243
|
+
return `Error: ${parsedMetadata.error}`;
|
|
14244
|
+
const inferred = inferInputType(args.input, args.inputType);
|
|
14245
|
+
const ingestResult = inferred === "file" ? await uploadDocumentFromPath(args.input, {
|
|
14246
|
+
containerTag,
|
|
14247
|
+
metadata: parsedMetadata,
|
|
14248
|
+
extractMemories,
|
|
14249
|
+
contentType: args.contentType,
|
|
14250
|
+
timeoutMs
|
|
14251
|
+
}) : await client.documents.create({
|
|
14252
|
+
content: args.input,
|
|
14253
|
+
containerTag,
|
|
14254
|
+
extractMemories,
|
|
14255
|
+
contentType: args.contentType,
|
|
14256
|
+
metadata: parsedMetadata
|
|
14257
|
+
}, { timeoutMs });
|
|
14258
|
+
const documentId = ingestResult.documentId;
|
|
14259
|
+
const ingestionId = ingestResult.ingestionId;
|
|
14260
|
+
if (!wait) {
|
|
14261
|
+
return [
|
|
14262
|
+
`Ingestion queued (scope: ${scope}).`,
|
|
14263
|
+
`- inputType: ${inferred}`,
|
|
14264
|
+
`- documentId: ${documentId}`,
|
|
14265
|
+
`- ingestionId: ${ingestionId}`,
|
|
14266
|
+
`- extractMemories: ${extractMemories}`
|
|
14267
|
+
].join(`
|
|
14268
|
+
`);
|
|
14269
|
+
}
|
|
14270
|
+
const status = await waitForIngestion(client, ingestionId, timeoutMs, pollIntervalMs);
|
|
14271
|
+
if (status.status !== "completed") {
|
|
14272
|
+
return [
|
|
14273
|
+
`Ingestion status: ${status.status} (scope: ${scope}).`,
|
|
14274
|
+
`- inputType: ${inferred}`,
|
|
14275
|
+
`- documentId: ${documentId}`,
|
|
14276
|
+
`- ingestionId: ${ingestionId}`,
|
|
14277
|
+
status.title ? `- title: ${status.title}` : undefined
|
|
14278
|
+
].filter(Boolean).join(`
|
|
14279
|
+
`);
|
|
14280
|
+
}
|
|
14281
|
+
return formatDocumentResult(client, documentId, scope);
|
|
14282
|
+
}
|
|
14283
|
+
});
|
|
14284
|
+
const momoOcrTool = tool({
|
|
14285
|
+
description: "OCR an image file via Momo ingestion, then return extracted text preview and memory extraction status.",
|
|
14286
|
+
args: {
|
|
14287
|
+
filePath: z.string().describe("Local path to an image file"),
|
|
14288
|
+
scope: z.enum(["user", "project"]).optional().describe("Memory/document scope (default: project)"),
|
|
14289
|
+
extractMemories: z.boolean().optional().describe("Extract memories from OCR text (default: true)"),
|
|
14290
|
+
wait: z.boolean().optional().describe("Wait for OCR ingestion to finish (default: true)"),
|
|
14291
|
+
timeoutMs: z.number().optional().describe("Max wait timeout in milliseconds"),
|
|
14292
|
+
pollIntervalMs: z.number().optional().describe("Polling interval in milliseconds"),
|
|
14293
|
+
contentType: z.string().optional().describe("Optional content type hint (e.g. image/png)")
|
|
14294
|
+
},
|
|
14295
|
+
async execute(args, _context) {
|
|
14296
|
+
const client = requireMomo();
|
|
14297
|
+
const scope = args.scope ?? "project";
|
|
14298
|
+
const containerTag = resolveContainerTag(scope);
|
|
14299
|
+
const extractMemories = args.extractMemories ?? true;
|
|
14300
|
+
const wait = args.wait ?? true;
|
|
14301
|
+
const timeoutMs = args.timeoutMs ?? DEFAULT_INGEST_TIMEOUT_MS;
|
|
14302
|
+
const pollIntervalMs = args.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
14303
|
+
const ingest = await uploadDocumentFromPath(args.filePath, {
|
|
14304
|
+
containerTag,
|
|
14305
|
+
metadata: undefined,
|
|
14306
|
+
extractMemories,
|
|
14307
|
+
contentType: args.contentType,
|
|
14308
|
+
timeoutMs
|
|
14309
|
+
});
|
|
14310
|
+
if (!wait) {
|
|
14311
|
+
return [
|
|
14312
|
+
`OCR ingestion queued (scope: ${scope}).`,
|
|
14313
|
+
`- documentId: ${ingest.documentId}`,
|
|
14314
|
+
`- ingestionId: ${ingest.ingestionId}`,
|
|
14315
|
+
`- extractMemories: ${extractMemories}`
|
|
14316
|
+
].join(`
|
|
14317
|
+
`);
|
|
14318
|
+
}
|
|
14319
|
+
const status = await waitForIngestion(client, ingest.ingestionId, timeoutMs, pollIntervalMs);
|
|
14320
|
+
if (status.status !== "completed") {
|
|
14321
|
+
return [
|
|
14322
|
+
`OCR ingestion status: ${status.status} (scope: ${scope}).`,
|
|
14323
|
+
`- documentId: ${ingest.documentId}`,
|
|
14324
|
+
`- ingestionId: ${ingest.ingestionId}`,
|
|
14325
|
+
status.title ? `- title: ${status.title}` : undefined
|
|
14326
|
+
].filter(Boolean).join(`
|
|
14327
|
+
`);
|
|
14328
|
+
}
|
|
14329
|
+
return formatDocumentResult(client, ingest.documentId, scope, "ocr");
|
|
14330
|
+
}
|
|
14331
|
+
});
|
|
14332
|
+
const momoTranscribeTool = tool({
|
|
14333
|
+
description: "Transcribe audio/video files via Momo ingestion, then return transcript preview and memory extraction status.",
|
|
14334
|
+
args: {
|
|
14335
|
+
filePath: z.string().describe("Local path to an audio or video file"),
|
|
14336
|
+
scope: z.enum(["user", "project"]).optional().describe("Memory/document scope (default: project)"),
|
|
14337
|
+
extractMemories: z.boolean().optional().describe("Extract memories from transcript (default: true)"),
|
|
14338
|
+
wait: z.boolean().optional().describe("Wait for transcription ingestion to finish (default: true)"),
|
|
14339
|
+
timeoutMs: z.number().optional().describe("Max wait timeout in milliseconds"),
|
|
14340
|
+
pollIntervalMs: z.number().optional().describe("Polling interval in milliseconds"),
|
|
14341
|
+
contentType: z.string().optional().describe("Optional content type hint (e.g. audio/mpeg, video/mp4)")
|
|
14342
|
+
},
|
|
14343
|
+
async execute(args, _context) {
|
|
14344
|
+
const client = requireMomo();
|
|
14345
|
+
const scope = args.scope ?? "project";
|
|
14346
|
+
const containerTag = resolveContainerTag(scope);
|
|
14347
|
+
const extractMemories = args.extractMemories ?? true;
|
|
14348
|
+
const wait = args.wait ?? true;
|
|
14349
|
+
const timeoutMs = args.timeoutMs ?? DEFAULT_INGEST_TIMEOUT_MS;
|
|
14350
|
+
const pollIntervalMs = args.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
14351
|
+
const ingest = await uploadDocumentFromPath(args.filePath, {
|
|
14352
|
+
containerTag,
|
|
14353
|
+
metadata: undefined,
|
|
14354
|
+
extractMemories,
|
|
14355
|
+
contentType: args.contentType,
|
|
14356
|
+
timeoutMs
|
|
14357
|
+
});
|
|
14358
|
+
if (!wait) {
|
|
14359
|
+
return [
|
|
14360
|
+
`Transcription queued (scope: ${scope}).`,
|
|
14361
|
+
`- documentId: ${ingest.documentId}`,
|
|
14362
|
+
`- ingestionId: ${ingest.ingestionId}`,
|
|
14363
|
+
`- extractMemories: ${extractMemories}`
|
|
14364
|
+
].join(`
|
|
14365
|
+
`);
|
|
14366
|
+
}
|
|
14367
|
+
const status = await waitForIngestion(client, ingest.ingestionId, timeoutMs, pollIntervalMs);
|
|
14368
|
+
if (status.status !== "completed") {
|
|
14369
|
+
return [
|
|
14370
|
+
`Transcription status: ${status.status} (scope: ${scope}).`,
|
|
14371
|
+
`- documentId: ${ingest.documentId}`,
|
|
14372
|
+
`- ingestionId: ${ingest.ingestionId}`,
|
|
14373
|
+
status.title ? `- title: ${status.title}` : undefined
|
|
14374
|
+
].filter(Boolean).join(`
|
|
14375
|
+
`);
|
|
14376
|
+
}
|
|
14377
|
+
return formatDocumentResult(client, ingest.documentId, scope, "transcription");
|
|
14378
|
+
}
|
|
14379
|
+
});
|
|
13869
14380
|
const chatMessageHook = async (input, output) => {
|
|
13870
14381
|
if (injectedSessions.has(input.sessionID))
|
|
13871
14382
|
return;
|
|
@@ -13921,7 +14432,10 @@ ${formatted}`;
|
|
|
13921
14432
|
};
|
|
13922
14433
|
return {
|
|
13923
14434
|
tool: {
|
|
13924
|
-
momo: momoTool
|
|
14435
|
+
momo: momoTool,
|
|
14436
|
+
momo_ingest: momoIngestTool,
|
|
14437
|
+
momo_ocr: momoOcrTool,
|
|
14438
|
+
momo_transcribe: momoTranscribeTool
|
|
13925
14439
|
},
|
|
13926
14440
|
"chat.message": chatMessageHook,
|
|
13927
14441
|
event: eventHook
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@momomemory/opencode-momo",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "OpenCode plugin that gives coding agents persistent memory using Momo",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -29,7 +29,8 @@
|
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@opencode-ai/plugin": "^1.1.53",
|
|
32
|
-
"@
|
|
32
|
+
"@opencode-ai/sdk": "^1.1.53",
|
|
33
|
+
"@momomemory/sdk": "^0.2.1"
|
|
33
34
|
},
|
|
34
35
|
"devDependencies": {
|
|
35
36
|
"@types/bun": "latest",
|