opencode-swarm-plugin 0.30.0 → 0.30.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/.turbo/turbo-build.log +4 -4
- package/CHANGELOG.md +47 -0
- package/README.md +3 -6
- package/bin/swarm.ts +151 -22
- package/dist/hive.d.ts.map +1 -1
- package/dist/index.d.ts +94 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18826 -3467
- package/dist/memory-tools.d.ts +209 -0
- package/dist/memory-tools.d.ts.map +1 -0
- package/dist/memory.d.ts +124 -0
- package/dist/memory.d.ts.map +1 -0
- package/dist/plugin.js +18766 -3418
- package/dist/schemas/index.d.ts +7 -0
- package/dist/schemas/index.d.ts.map +1 -1
- package/dist/schemas/worker-handoff.d.ts +78 -0
- package/dist/schemas/worker-handoff.d.ts.map +1 -0
- package/dist/swarm-orchestrate.d.ts +50 -0
- package/dist/swarm-orchestrate.d.ts.map +1 -1
- package/dist/swarm-prompts.d.ts +1 -1
- package/dist/swarm-prompts.d.ts.map +1 -1
- package/dist/swarm-review.d.ts +4 -0
- package/dist/swarm-review.d.ts.map +1 -1
- package/docs/planning/ADR-008-worker-handoff-protocol.md +293 -0
- package/package.json +3 -1
- package/src/hive.integration.test.ts +114 -0
- package/src/hive.ts +33 -22
- package/src/index.ts +38 -3
- package/src/memory-tools.test.ts +111 -0
- package/src/memory-tools.ts +273 -0
- package/src/memory.integration.test.ts +266 -0
- package/src/memory.test.ts +334 -0
- package/src/memory.ts +441 -0
- package/src/schemas/index.ts +18 -0
- package/src/schemas/worker-handoff.test.ts +271 -0
- package/src/schemas/worker-handoff.ts +131 -0
- package/src/swarm-orchestrate.ts +262 -24
- package/src/swarm-prompts.ts +48 -5
- package/src/swarm-review.ts +7 -0
- package/src/swarm.integration.test.ts +386 -9
|
@@ -16,9 +16,11 @@ import {
|
|
|
16
16
|
hive_start,
|
|
17
17
|
hive_ready,
|
|
18
18
|
hive_link_thread,
|
|
19
|
+
hive_sync,
|
|
19
20
|
HiveError,
|
|
20
21
|
getHiveAdapter,
|
|
21
22
|
setHiveWorkingDirectory,
|
|
23
|
+
getHiveWorkingDirectory,
|
|
22
24
|
// Legacy aliases for backward compatibility tests
|
|
23
25
|
beads_link_thread,
|
|
24
26
|
BeadError,
|
|
@@ -1120,6 +1122,118 @@ describe("beads integration", () => {
|
|
|
1120
1122
|
});
|
|
1121
1123
|
});
|
|
1122
1124
|
|
|
1125
|
+
describe("hive_sync", () => {
|
|
1126
|
+
it("commits .hive changes before pulling (regression test for unstaged changes error)", async () => {
|
|
1127
|
+
const { mkdirSync, rmSync, writeFileSync, existsSync } = await import("node:fs");
|
|
1128
|
+
const { join } = await import("node:path");
|
|
1129
|
+
const { tmpdir } = await import("node:os");
|
|
1130
|
+
const { execSync } = await import("node:child_process");
|
|
1131
|
+
|
|
1132
|
+
// Create a temp git repository
|
|
1133
|
+
const tempProject = join(tmpdir(), `hive-sync-test-${Date.now()}`);
|
|
1134
|
+
mkdirSync(tempProject, { recursive: true });
|
|
1135
|
+
|
|
1136
|
+
// Initialize git repo
|
|
1137
|
+
execSync("git init", { cwd: tempProject });
|
|
1138
|
+
execSync('git config user.email "test@example.com"', { cwd: tempProject });
|
|
1139
|
+
execSync('git config user.name "Test User"', { cwd: tempProject });
|
|
1140
|
+
|
|
1141
|
+
// Create .hive directory and issues.jsonl
|
|
1142
|
+
const hiveDir = join(tempProject, ".hive");
|
|
1143
|
+
mkdirSync(hiveDir, { recursive: true });
|
|
1144
|
+
const issuesPath = join(hiveDir, "issues.jsonl");
|
|
1145
|
+
writeFileSync(issuesPath, "");
|
|
1146
|
+
|
|
1147
|
+
// Initial commit
|
|
1148
|
+
execSync("git add .", { cwd: tempProject });
|
|
1149
|
+
execSync('git commit -m "initial commit"', { cwd: tempProject });
|
|
1150
|
+
|
|
1151
|
+
// Set working directory for hive commands
|
|
1152
|
+
const originalDir = getHiveWorkingDirectory();
|
|
1153
|
+
setHiveWorkingDirectory(tempProject);
|
|
1154
|
+
|
|
1155
|
+
try {
|
|
1156
|
+
// Create a cell (this will mark it dirty and flush will write to JSONL)
|
|
1157
|
+
await hive_create.execute(
|
|
1158
|
+
{ title: "Sync test cell", type: "task" },
|
|
1159
|
+
mockContext,
|
|
1160
|
+
);
|
|
1161
|
+
|
|
1162
|
+
// Sync with auto_pull=false (skip pull since no remote configured)
|
|
1163
|
+
const result = await hive_sync.execute(
|
|
1164
|
+
{ auto_pull: false },
|
|
1165
|
+
mockContext,
|
|
1166
|
+
);
|
|
1167
|
+
|
|
1168
|
+
// Should succeed
|
|
1169
|
+
expect(result).toContain("successfully");
|
|
1170
|
+
|
|
1171
|
+
// Verify .hive changes were committed (working tree should be clean)
|
|
1172
|
+
const status = execSync("git status --porcelain", {
|
|
1173
|
+
cwd: tempProject,
|
|
1174
|
+
encoding: "utf-8",
|
|
1175
|
+
});
|
|
1176
|
+
expect(status.trim()).toBe("");
|
|
1177
|
+
|
|
1178
|
+
// Verify commit exists
|
|
1179
|
+
const log = execSync("git log --oneline", {
|
|
1180
|
+
cwd: tempProject,
|
|
1181
|
+
encoding: "utf-8",
|
|
1182
|
+
});
|
|
1183
|
+
expect(log).toContain("chore: sync hive");
|
|
1184
|
+
} finally {
|
|
1185
|
+
// Restore original working directory
|
|
1186
|
+
setHiveWorkingDirectory(originalDir);
|
|
1187
|
+
|
|
1188
|
+
// Cleanup
|
|
1189
|
+
rmSync(tempProject, { recursive: true, force: true });
|
|
1190
|
+
}
|
|
1191
|
+
});
|
|
1192
|
+
|
|
1193
|
+
it("handles case with no changes to commit", async () => {
|
|
1194
|
+
const { mkdirSync, rmSync, writeFileSync } = await import("node:fs");
|
|
1195
|
+
const { join } = await import("node:path");
|
|
1196
|
+
const { tmpdir } = await import("node:os");
|
|
1197
|
+
const { execSync } = await import("node:child_process");
|
|
1198
|
+
|
|
1199
|
+
// Create temp git repo
|
|
1200
|
+
const tempProject = join(tmpdir(), `hive-sync-test-${Date.now()}`);
|
|
1201
|
+
mkdirSync(tempProject, { recursive: true });
|
|
1202
|
+
|
|
1203
|
+
// Initialize git
|
|
1204
|
+
execSync("git init", { cwd: tempProject });
|
|
1205
|
+
execSync('git config user.email "test@example.com"', { cwd: tempProject });
|
|
1206
|
+
execSync('git config user.name "Test User"', { cwd: tempProject });
|
|
1207
|
+
|
|
1208
|
+
// Create .hive directory with committed issues.jsonl
|
|
1209
|
+
const hiveDir = join(tempProject, ".hive");
|
|
1210
|
+
mkdirSync(hiveDir, { recursive: true });
|
|
1211
|
+
writeFileSync(join(hiveDir, "issues.jsonl"), "");
|
|
1212
|
+
|
|
1213
|
+
// Commit everything
|
|
1214
|
+
execSync("git add .", { cwd: tempProject });
|
|
1215
|
+
execSync('git commit -m "initial"', { cwd: tempProject });
|
|
1216
|
+
|
|
1217
|
+
// Set working directory
|
|
1218
|
+
const originalDir = getHiveWorkingDirectory();
|
|
1219
|
+
setHiveWorkingDirectory(tempProject);
|
|
1220
|
+
|
|
1221
|
+
try {
|
|
1222
|
+
// Sync with no changes (should handle gracefully)
|
|
1223
|
+
const result = await hive_sync.execute(
|
|
1224
|
+
{ auto_pull: false },
|
|
1225
|
+
mockContext,
|
|
1226
|
+
);
|
|
1227
|
+
|
|
1228
|
+
// Should return "No cells to sync" since no dirty cells
|
|
1229
|
+
expect(result).toContain("No cells to sync");
|
|
1230
|
+
} finally {
|
|
1231
|
+
setHiveWorkingDirectory(originalDir);
|
|
1232
|
+
rmSync(tempProject, { recursive: true, force: true });
|
|
1233
|
+
}
|
|
1234
|
+
});
|
|
1235
|
+
});
|
|
1236
|
+
|
|
1123
1237
|
describe("mergeHistoricBeads", () => {
|
|
1124
1238
|
it("merges empty base file - no changes", async () => {
|
|
1125
1239
|
const { mergeHistoricBeads } = await import("./hive");
|
package/src/hive.ts
CHANGED
|
@@ -1053,38 +1053,49 @@ export const hive_sync = tool({
|
|
|
1053
1053
|
}
|
|
1054
1054
|
}
|
|
1055
1055
|
|
|
1056
|
-
// 6. Pull if requested
|
|
1056
|
+
// 6. Pull if requested (check if remote exists first)
|
|
1057
1057
|
if (autoPull) {
|
|
1058
|
-
const
|
|
1059
|
-
|
|
1060
|
-
TIMEOUT_MS,
|
|
1061
|
-
"git pull --rebase",
|
|
1062
|
-
);
|
|
1058
|
+
const remoteCheckResult = await runGitCommand(["remote"]);
|
|
1059
|
+
const hasRemote = remoteCheckResult.stdout.trim() !== "";
|
|
1063
1060
|
|
|
1064
|
-
if (
|
|
1065
|
-
|
|
1066
|
-
|
|
1061
|
+
if (hasRemote) {
|
|
1062
|
+
const pullResult = await withTimeout(
|
|
1063
|
+
runGitCommand(["pull", "--rebase"]),
|
|
1064
|
+
TIMEOUT_MS,
|
|
1067
1065
|
"git pull --rebase",
|
|
1068
|
-
pullResult.exitCode,
|
|
1069
1066
|
);
|
|
1067
|
+
|
|
1068
|
+
if (pullResult.exitCode !== 0) {
|
|
1069
|
+
throw new HiveError(
|
|
1070
|
+
`Failed to pull: ${pullResult.stderr}`,
|
|
1071
|
+
"git pull --rebase",
|
|
1072
|
+
pullResult.exitCode,
|
|
1073
|
+
);
|
|
1074
|
+
}
|
|
1070
1075
|
}
|
|
1071
1076
|
}
|
|
1072
1077
|
|
|
1073
|
-
// 7. Push
|
|
1074
|
-
const
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
`Failed to push: ${pushResult.stderr}`,
|
|
1078
|
+
// 7. Push (check if remote exists first)
|
|
1079
|
+
const remoteCheckResult = await runGitCommand(["remote"]);
|
|
1080
|
+
const hasRemote = remoteCheckResult.stdout.trim() !== "";
|
|
1081
|
+
|
|
1082
|
+
if (hasRemote) {
|
|
1083
|
+
const pushResult = await withTimeout(
|
|
1084
|
+
runGitCommand(["push"]),
|
|
1085
|
+
TIMEOUT_MS,
|
|
1082
1086
|
"git push",
|
|
1083
|
-
pushResult.exitCode,
|
|
1084
1087
|
);
|
|
1088
|
+
if (pushResult.exitCode !== 0) {
|
|
1089
|
+
throw new HiveError(
|
|
1090
|
+
`Failed to push: ${pushResult.stderr}`,
|
|
1091
|
+
"git push",
|
|
1092
|
+
pushResult.exitCode,
|
|
1093
|
+
);
|
|
1094
|
+
}
|
|
1095
|
+
return "Hive synced and pushed successfully";
|
|
1096
|
+
} else {
|
|
1097
|
+
return "Hive synced successfully (no remote configured)";
|
|
1085
1098
|
}
|
|
1086
|
-
|
|
1087
|
-
return "Hive synced and pushed successfully";
|
|
1088
1099
|
},
|
|
1089
1100
|
});
|
|
1090
1101
|
|
package/src/index.ts
CHANGED
|
@@ -46,6 +46,7 @@ import { reviewTools } from "./swarm-review";
|
|
|
46
46
|
import { repoCrawlTools } from "./repo-crawl";
|
|
47
47
|
import { skillsTools, setSkillsProjectDirectory } from "./skills";
|
|
48
48
|
import { mandateTools } from "./mandates";
|
|
49
|
+
import { memoryTools } from "./memory-tools";
|
|
49
50
|
import {
|
|
50
51
|
guardrailOutput,
|
|
51
52
|
DEFAULT_GUARDRAIL_CONFIG,
|
|
@@ -69,6 +70,7 @@ import {
|
|
|
69
70
|
* - repo-crawl:* - GitHub API tools for repository research
|
|
70
71
|
* - skills:* - Agent skills discovery, activation, and execution
|
|
71
72
|
* - mandate:* - Agent voting system for collaborative knowledge curation
|
|
73
|
+
* - semantic-memory:* - Semantic memory with vector embeddings (Ollama + PGLite)
|
|
72
74
|
*
|
|
73
75
|
* @param input - Plugin context from OpenCode
|
|
74
76
|
* @returns Plugin hooks including tools, events, and tool execution hooks
|
|
@@ -148,9 +150,10 @@ export const SwarmPlugin: Plugin = async (
|
|
|
148
150
|
* - beads:* - Legacy aliases (deprecated, use hive:* instead)
|
|
149
151
|
* - agent-mail:init, agent-mail:send, agent-mail:reserve, etc. (legacy MCP)
|
|
150
152
|
* - swarm-mail:init, swarm-mail:send, swarm-mail:reserve, etc. (embedded)
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
153
|
+
* - repo-crawl:readme, repo-crawl:structure, etc.
|
|
154
|
+
* - mandate:file, mandate:vote, mandate:query, etc.
|
|
155
|
+
* - semantic-memory:store, semantic-memory:find, semantic-memory:get, etc.
|
|
156
|
+
*/
|
|
154
157
|
tool: {
|
|
155
158
|
...hiveTools,
|
|
156
159
|
...swarmMailTools,
|
|
@@ -161,6 +164,7 @@ export const SwarmPlugin: Plugin = async (
|
|
|
161
164
|
...repoCrawlTools,
|
|
162
165
|
...skillsTools,
|
|
163
166
|
...mandateTools,
|
|
167
|
+
...memoryTools,
|
|
164
168
|
},
|
|
165
169
|
|
|
166
170
|
/**
|
|
@@ -417,6 +421,7 @@ export const allTools = {
|
|
|
417
421
|
...repoCrawlTools,
|
|
418
422
|
...skillsTools,
|
|
419
423
|
...mandateTools,
|
|
424
|
+
...memoryTools,
|
|
420
425
|
} as const;
|
|
421
426
|
|
|
422
427
|
/**
|
|
@@ -644,3 +649,33 @@ export {
|
|
|
644
649
|
* ```
|
|
645
650
|
*/
|
|
646
651
|
export { SWARM_COMPACTION_CONTEXT, createCompactionHook } from "./compaction-hook";
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* Re-export memory module
|
|
655
|
+
*
|
|
656
|
+
* Includes:
|
|
657
|
+
* - memoryTools - All semantic-memory tools (store, find, get, remove, validate, list, stats, check)
|
|
658
|
+
* - createMemoryAdapter - Factory function for memory adapter
|
|
659
|
+
* - resetMemoryCache - Cache management for testing
|
|
660
|
+
*
|
|
661
|
+
* Types:
|
|
662
|
+
* - MemoryAdapter - Memory adapter interface
|
|
663
|
+
* - StoreArgs, FindArgs, IdArgs, ListArgs - Tool argument types
|
|
664
|
+
* - StoreResult, FindResult, StatsResult, HealthResult, OperationResult - Result types
|
|
665
|
+
*/
|
|
666
|
+
export {
|
|
667
|
+
memoryTools,
|
|
668
|
+
createMemoryAdapter,
|
|
669
|
+
resetMemoryCache,
|
|
670
|
+
type MemoryAdapter,
|
|
671
|
+
type StoreArgs,
|
|
672
|
+
type FindArgs,
|
|
673
|
+
type IdArgs,
|
|
674
|
+
type ListArgs,
|
|
675
|
+
type StoreResult,
|
|
676
|
+
type FindResult,
|
|
677
|
+
type StatsResult,
|
|
678
|
+
type HealthResult,
|
|
679
|
+
type OperationResult,
|
|
680
|
+
} from "./memory-tools";
|
|
681
|
+
export type { Memory, SearchResult, SearchOptions } from "swarm-mail";
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory Tools Integration Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests for semantic-memory_* tool registration and execution.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, test, expect, beforeAll, afterAll } from "bun:test";
|
|
8
|
+
import { memoryTools, resetMemoryCache } from "./memory-tools";
|
|
9
|
+
import { closeAllSwarmMail } from "swarm-mail";
|
|
10
|
+
|
|
11
|
+
describe("memory tools integration", () => {
|
|
12
|
+
afterAll(async () => {
|
|
13
|
+
resetMemoryCache();
|
|
14
|
+
await closeAllSwarmMail();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test("all tools are registered with correct names", () => {
|
|
18
|
+
const toolNames = Object.keys(memoryTools);
|
|
19
|
+
expect(toolNames).toContain("semantic-memory_store");
|
|
20
|
+
expect(toolNames).toContain("semantic-memory_find");
|
|
21
|
+
expect(toolNames).toContain("semantic-memory_get");
|
|
22
|
+
expect(toolNames).toContain("semantic-memory_remove");
|
|
23
|
+
expect(toolNames).toContain("semantic-memory_validate");
|
|
24
|
+
expect(toolNames).toContain("semantic-memory_list");
|
|
25
|
+
expect(toolNames).toContain("semantic-memory_stats");
|
|
26
|
+
expect(toolNames).toContain("semantic-memory_check");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("tools have execute functions", () => {
|
|
30
|
+
for (const [name, tool] of Object.entries(memoryTools)) {
|
|
31
|
+
expect(typeof tool.execute).toBe("function");
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe("semantic-memory_store", () => {
|
|
36
|
+
test("executes and returns JSON", async () => {
|
|
37
|
+
const tool = memoryTools["semantic-memory_store"];
|
|
38
|
+
const result = await tool.execute(
|
|
39
|
+
{
|
|
40
|
+
information: "Test memory for tools integration",
|
|
41
|
+
tags: "test",
|
|
42
|
+
},
|
|
43
|
+
{ sessionID: "test-session" } as any,
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
expect(typeof result).toBe("string");
|
|
47
|
+
const parsed = JSON.parse(result);
|
|
48
|
+
expect(parsed.id).toBeDefined();
|
|
49
|
+
expect(parsed.id).toMatch(/^mem_/);
|
|
50
|
+
expect(parsed.message).toContain("Stored memory");
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe("semantic-memory_find", () => {
|
|
55
|
+
test("executes and returns JSON array", async () => {
|
|
56
|
+
// Store a memory first
|
|
57
|
+
const storeTool = memoryTools["semantic-memory_store"];
|
|
58
|
+
await storeTool.execute(
|
|
59
|
+
{
|
|
60
|
+
information: "Findable test memory with unique keyword xyztest123",
|
|
61
|
+
},
|
|
62
|
+
{ sessionID: "test-session" } as any,
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
// Search for it
|
|
66
|
+
const findTool = memoryTools["semantic-memory_find"];
|
|
67
|
+
const result = await findTool.execute(
|
|
68
|
+
{
|
|
69
|
+
query: "xyztest123",
|
|
70
|
+
limit: 5,
|
|
71
|
+
},
|
|
72
|
+
{ sessionID: "test-session" } as any,
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
expect(typeof result).toBe("string");
|
|
76
|
+
const parsed = JSON.parse(result);
|
|
77
|
+
expect(parsed.results).toBeDefined();
|
|
78
|
+
expect(Array.isArray(parsed.results)).toBe(true);
|
|
79
|
+
expect(parsed.count).toBeGreaterThanOrEqual(0);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe("semantic-memory_stats", () => {
|
|
84
|
+
test("returns memory and embedding counts", async () => {
|
|
85
|
+
const tool = memoryTools["semantic-memory_stats"];
|
|
86
|
+
const result = await tool.execute(
|
|
87
|
+
{},
|
|
88
|
+
{ sessionID: "test-session" } as any,
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
expect(typeof result).toBe("string");
|
|
92
|
+
const parsed = JSON.parse(result);
|
|
93
|
+
expect(typeof parsed.memories).toBe("number");
|
|
94
|
+
expect(typeof parsed.embeddings).toBe("number");
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe("semantic-memory_check", () => {
|
|
99
|
+
test("checks Ollama health", async () => {
|
|
100
|
+
const tool = memoryTools["semantic-memory_check"];
|
|
101
|
+
const result = await tool.execute(
|
|
102
|
+
{},
|
|
103
|
+
{ sessionID: "test-session" } as any,
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
expect(typeof result).toBe("string");
|
|
107
|
+
const parsed = JSON.parse(result);
|
|
108
|
+
expect(typeof parsed.ollama).toBe("boolean");
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
});
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Semantic Memory Plugin Tools - Embedded implementation
|
|
3
|
+
*
|
|
4
|
+
* Provides semantic memory operations using swarm-mail's MemoryStore + Ollama.
|
|
5
|
+
* Replaces external MCP-based semantic-memory calls with embedded storage.
|
|
6
|
+
*
|
|
7
|
+
* Key features:
|
|
8
|
+
* - Vector similarity search with Ollama embeddings
|
|
9
|
+
* - Full-text search fallback
|
|
10
|
+
* - Memory decay tracking (TODO: implement in MemoryStore)
|
|
11
|
+
* - Collection-based organization
|
|
12
|
+
*
|
|
13
|
+
* Tool signatures maintained for backward compatibility with existing prompts.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { tool } from "@opencode-ai/plugin";
|
|
17
|
+
import { getSwarmMail } from "swarm-mail";
|
|
18
|
+
import {
|
|
19
|
+
createMemoryAdapter,
|
|
20
|
+
type MemoryAdapter,
|
|
21
|
+
type StoreArgs,
|
|
22
|
+
type FindArgs,
|
|
23
|
+
type IdArgs,
|
|
24
|
+
type ListArgs,
|
|
25
|
+
type StoreResult,
|
|
26
|
+
type FindResult,
|
|
27
|
+
type StatsResult,
|
|
28
|
+
type HealthResult,
|
|
29
|
+
type OperationResult,
|
|
30
|
+
} from "./memory";
|
|
31
|
+
|
|
32
|
+
// Re-export types for external use
|
|
33
|
+
export type {
|
|
34
|
+
MemoryAdapter,
|
|
35
|
+
StoreArgs,
|
|
36
|
+
FindArgs,
|
|
37
|
+
IdArgs,
|
|
38
|
+
ListArgs,
|
|
39
|
+
StoreResult,
|
|
40
|
+
FindResult,
|
|
41
|
+
StatsResult,
|
|
42
|
+
HealthResult,
|
|
43
|
+
OperationResult,
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// ============================================================================
|
|
47
|
+
// Types
|
|
48
|
+
// ============================================================================
|
|
49
|
+
|
|
50
|
+
/** Tool execution context from OpenCode plugin */
|
|
51
|
+
interface ToolContext {
|
|
52
|
+
sessionID: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ============================================================================
|
|
56
|
+
// Memory Adapter Cache
|
|
57
|
+
// ============================================================================
|
|
58
|
+
|
|
59
|
+
let cachedAdapter: MemoryAdapter | null = null;
|
|
60
|
+
let cachedProjectPath: string | null = null;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Get or create memory adapter for the current project
|
|
64
|
+
*
|
|
65
|
+
* @param projectPath - Project path (uses CWD if not provided)
|
|
66
|
+
* @returns Memory adapter instance
|
|
67
|
+
*/
|
|
68
|
+
async function getMemoryAdapter(
|
|
69
|
+
projectPath?: string,
|
|
70
|
+
): Promise<MemoryAdapter> {
|
|
71
|
+
const path = projectPath || process.cwd();
|
|
72
|
+
|
|
73
|
+
// Return cached adapter if same project
|
|
74
|
+
if (cachedAdapter && cachedProjectPath === path) {
|
|
75
|
+
return cachedAdapter;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Create new adapter
|
|
79
|
+
const swarmMail = await getSwarmMail(path);
|
|
80
|
+
const db = await swarmMail.getDatabase();
|
|
81
|
+
cachedAdapter = await createMemoryAdapter(db);
|
|
82
|
+
cachedProjectPath = path;
|
|
83
|
+
|
|
84
|
+
return cachedAdapter;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Reset adapter cache (for testing)
|
|
89
|
+
*/
|
|
90
|
+
export function resetMemoryCache(): void {
|
|
91
|
+
cachedAdapter = null;
|
|
92
|
+
cachedProjectPath = null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Re-export createMemoryAdapter for external use
|
|
96
|
+
export { createMemoryAdapter };
|
|
97
|
+
|
|
98
|
+
// ============================================================================
|
|
99
|
+
// Plugin Tools
|
|
100
|
+
// ============================================================================
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Store a memory with semantic embedding
|
|
104
|
+
*/
|
|
105
|
+
export const semantic_memory_store = tool({
|
|
106
|
+
description:
|
|
107
|
+
"Store a memory with semantic embedding. Memories are searchable by semantic similarity and can be organized into collections.",
|
|
108
|
+
args: {
|
|
109
|
+
information: tool.schema
|
|
110
|
+
.string()
|
|
111
|
+
.describe("The information to store (required)"),
|
|
112
|
+
collection: tool.schema
|
|
113
|
+
.string()
|
|
114
|
+
.optional()
|
|
115
|
+
.describe("Collection name (defaults to 'default')"),
|
|
116
|
+
tags: tool.schema
|
|
117
|
+
.string()
|
|
118
|
+
.optional()
|
|
119
|
+
.describe("Comma-separated tags (e.g., 'auth,tokens,oauth')"),
|
|
120
|
+
metadata: tool.schema
|
|
121
|
+
.string()
|
|
122
|
+
.optional()
|
|
123
|
+
.describe("JSON string with additional metadata"),
|
|
124
|
+
},
|
|
125
|
+
async execute(args, ctx: ToolContext) {
|
|
126
|
+
const adapter = await getMemoryAdapter();
|
|
127
|
+
const result = await adapter.store(args);
|
|
128
|
+
return JSON.stringify(result, null, 2);
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Find memories by semantic similarity or full-text search
|
|
134
|
+
*/
|
|
135
|
+
export const semantic_memory_find = tool({
|
|
136
|
+
description:
|
|
137
|
+
"Search memories by semantic similarity (vector search) or full-text search. Returns results ranked by relevance score.",
|
|
138
|
+
args: {
|
|
139
|
+
query: tool.schema.string().describe("Search query (required)"),
|
|
140
|
+
limit: tool.schema
|
|
141
|
+
.number()
|
|
142
|
+
.optional()
|
|
143
|
+
.describe("Maximum number of results (default: 10)"),
|
|
144
|
+
collection: tool.schema
|
|
145
|
+
.string()
|
|
146
|
+
.optional()
|
|
147
|
+
.describe("Filter by collection name"),
|
|
148
|
+
expand: tool.schema
|
|
149
|
+
.boolean()
|
|
150
|
+
.optional()
|
|
151
|
+
.describe("Return full content instead of truncated preview (default: false)"),
|
|
152
|
+
fts: tool.schema
|
|
153
|
+
.boolean()
|
|
154
|
+
.optional()
|
|
155
|
+
.describe("Use full-text search instead of vector search (default: false)"),
|
|
156
|
+
},
|
|
157
|
+
async execute(args, ctx: ToolContext) {
|
|
158
|
+
const adapter = await getMemoryAdapter();
|
|
159
|
+
const result = await adapter.find(args);
|
|
160
|
+
return JSON.stringify(result, null, 2);
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Get a single memory by ID
|
|
166
|
+
*/
|
|
167
|
+
export const semantic_memory_get = tool({
|
|
168
|
+
description: "Retrieve a specific memory by its ID.",
|
|
169
|
+
args: {
|
|
170
|
+
id: tool.schema.string().describe("Memory ID (required)"),
|
|
171
|
+
},
|
|
172
|
+
async execute(args, ctx: ToolContext) {
|
|
173
|
+
const adapter = await getMemoryAdapter();
|
|
174
|
+
const memory = await adapter.get(args);
|
|
175
|
+
return memory ? JSON.stringify(memory, null, 2) : "Memory not found";
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Remove a memory
|
|
181
|
+
*/
|
|
182
|
+
export const semantic_memory_remove = tool({
|
|
183
|
+
description: "Delete a memory by ID. Use this to remove outdated or incorrect memories.",
|
|
184
|
+
args: {
|
|
185
|
+
id: tool.schema.string().describe("Memory ID (required)"),
|
|
186
|
+
},
|
|
187
|
+
async execute(args, ctx: ToolContext) {
|
|
188
|
+
const adapter = await getMemoryAdapter();
|
|
189
|
+
const result = await adapter.remove(args);
|
|
190
|
+
return JSON.stringify(result, null, 2);
|
|
191
|
+
},
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Validate a memory (reset decay timer)
|
|
196
|
+
*/
|
|
197
|
+
export const semantic_memory_validate = tool({
|
|
198
|
+
description:
|
|
199
|
+
"Validate that a memory is still accurate and reset its decay timer. Use when you confirm a memory is correct.",
|
|
200
|
+
args: {
|
|
201
|
+
id: tool.schema.string().describe("Memory ID (required)"),
|
|
202
|
+
},
|
|
203
|
+
async execute(args, ctx: ToolContext) {
|
|
204
|
+
const adapter = await getMemoryAdapter();
|
|
205
|
+
const result = await adapter.validate(args);
|
|
206
|
+
return JSON.stringify(result, null, 2);
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* List memories
|
|
212
|
+
*/
|
|
213
|
+
export const semantic_memory_list = tool({
|
|
214
|
+
description: "List all stored memories, optionally filtered by collection.",
|
|
215
|
+
args: {
|
|
216
|
+
collection: tool.schema
|
|
217
|
+
.string()
|
|
218
|
+
.optional()
|
|
219
|
+
.describe("Filter by collection name"),
|
|
220
|
+
},
|
|
221
|
+
async execute(args, ctx: ToolContext) {
|
|
222
|
+
const adapter = await getMemoryAdapter();
|
|
223
|
+
const memories = await adapter.list(args);
|
|
224
|
+
return JSON.stringify(memories, null, 2);
|
|
225
|
+
},
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Get memory statistics
|
|
230
|
+
*/
|
|
231
|
+
export const semantic_memory_stats = tool({
|
|
232
|
+
description: "Get statistics about stored memories and embeddings.",
|
|
233
|
+
args: {},
|
|
234
|
+
async execute(args, ctx: ToolContext) {
|
|
235
|
+
const adapter = await getMemoryAdapter();
|
|
236
|
+
const stats = await adapter.stats();
|
|
237
|
+
return JSON.stringify(stats, null, 2);
|
|
238
|
+
},
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Check Ollama health
|
|
243
|
+
*/
|
|
244
|
+
export const semantic_memory_check = tool({
|
|
245
|
+
description:
|
|
246
|
+
"Check if Ollama is running and available for embedding generation.",
|
|
247
|
+
args: {},
|
|
248
|
+
async execute(args, ctx: ToolContext) {
|
|
249
|
+
const adapter = await getMemoryAdapter();
|
|
250
|
+
const health = await adapter.checkHealth();
|
|
251
|
+
return JSON.stringify(health, null, 2);
|
|
252
|
+
},
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
// ============================================================================
|
|
256
|
+
// Tool Registry
|
|
257
|
+
// ============================================================================
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* All semantic memory tools
|
|
261
|
+
*
|
|
262
|
+
* Register these in the plugin with spread operator: { ...memoryTools }
|
|
263
|
+
*/
|
|
264
|
+
export const memoryTools = {
|
|
265
|
+
"semantic-memory_store": semantic_memory_store,
|
|
266
|
+
"semantic-memory_find": semantic_memory_find,
|
|
267
|
+
"semantic-memory_get": semantic_memory_get,
|
|
268
|
+
"semantic-memory_remove": semantic_memory_remove,
|
|
269
|
+
"semantic-memory_validate": semantic_memory_validate,
|
|
270
|
+
"semantic-memory_list": semantic_memory_list,
|
|
271
|
+
"semantic-memory_stats": semantic_memory_stats,
|
|
272
|
+
"semantic-memory_check": semantic_memory_check,
|
|
273
|
+
} as const;
|