opencode-swarm-plugin 0.40.0 → 0.42.1
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/.hive/analysis/eval-failure-analysis-2025-12-25.md +331 -0
- package/.hive/analysis/session-data-quality-audit.md +320 -0
- package/.hive/eval-results.json +481 -24
- package/.hive/issues.jsonl +67 -16
- package/.hive/memories.jsonl +159 -1
- package/.opencode/eval-history.jsonl +315 -0
- package/.turbo/turbo-build.log +5 -5
- package/CHANGELOG.md +165 -0
- package/README.md +2 -0
- package/SCORER-ANALYSIS.md +598 -0
- package/bin/eval-gate.test.ts +158 -0
- package/bin/eval-gate.ts +74 -0
- package/bin/swarm.serve.test.ts +46 -0
- package/bin/swarm.test.ts +661 -732
- package/bin/swarm.ts +335 -0
- package/dist/compaction-hook.d.ts +7 -5
- package/dist/compaction-hook.d.ts.map +1 -1
- package/dist/compaction-prompt-scoring.d.ts +1 -0
- package/dist/compaction-prompt-scoring.d.ts.map +1 -1
- package/dist/eval-runner.d.ts +134 -0
- package/dist/eval-runner.d.ts.map +1 -0
- package/dist/hive.d.ts.map +1 -1
- package/dist/index.d.ts +29 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +99741 -58858
- package/dist/memory-tools.d.ts +70 -2
- package/dist/memory-tools.d.ts.map +1 -1
- package/dist/memory.d.ts +37 -0
- package/dist/memory.d.ts.map +1 -1
- package/dist/observability-tools.d.ts +64 -0
- package/dist/observability-tools.d.ts.map +1 -1
- package/dist/plugin.js +99356 -58318
- package/dist/swarm-orchestrate.d.ts.map +1 -1
- package/dist/swarm-prompts.d.ts +32 -1
- package/dist/swarm-prompts.d.ts.map +1 -1
- package/docs/planning/ADR-009-oh-my-opencode-patterns.md +353 -0
- package/evals/ARCHITECTURE.md +1189 -0
- package/evals/example.eval.ts +3 -4
- package/evals/fixtures/compaction-prompt-cases.ts +6 -0
- package/evals/scorers/coordinator-discipline.evalite-test.ts +1 -162
- package/evals/scorers/coordinator-discipline.ts +0 -323
- package/evals/swarm-decomposition.eval.ts +4 -2
- package/package.json +4 -3
- package/src/compaction-prompt-scorers.test.ts +185 -9
- package/src/compaction-prompt-scoring.ts +7 -5
- package/src/eval-runner.test.ts +128 -1
- package/src/eval-runner.ts +46 -0
- package/src/hive.ts +43 -42
- package/src/memory-tools.test.ts +84 -0
- package/src/memory-tools.ts +68 -3
- package/src/memory.test.ts +2 -112
- package/src/memory.ts +88 -49
- package/src/observability-tools.test.ts +13 -0
- package/src/observability-tools.ts +277 -0
- package/src/swarm-orchestrate.test.ts +162 -0
- package/src/swarm-orchestrate.ts +7 -5
- package/src/swarm-prompts.test.ts +168 -4
- package/src/swarm-prompts.ts +228 -7
- package/.env +0 -2
- package/.turbo/turbo-test.log +0 -481
- package/.turbo/turbo-typecheck.log +0 -1
package/src/memory-tools.test.ts
CHANGED
|
@@ -24,6 +24,7 @@ describe("memory tools integration", () => {
|
|
|
24
24
|
expect(toolNames).toContain("semantic-memory_list");
|
|
25
25
|
expect(toolNames).toContain("semantic-memory_stats");
|
|
26
26
|
expect(toolNames).toContain("semantic-memory_check");
|
|
27
|
+
expect(toolNames).toContain("semantic-memory_upsert");
|
|
27
28
|
});
|
|
28
29
|
|
|
29
30
|
test("tools have execute functions", () => {
|
|
@@ -108,4 +109,87 @@ describe("memory tools integration", () => {
|
|
|
108
109
|
expect(typeof parsed.ollama).toBe("boolean");
|
|
109
110
|
});
|
|
110
111
|
});
|
|
112
|
+
|
|
113
|
+
describe("semantic-memory_upsert", () => {
|
|
114
|
+
test("returns valid ADD operation result", async () => {
|
|
115
|
+
const tool = memoryTools["semantic-memory_upsert"];
|
|
116
|
+
const result = await tool.execute(
|
|
117
|
+
{
|
|
118
|
+
information: "Test memory for plugin tool",
|
|
119
|
+
tags: "test,plugin",
|
|
120
|
+
},
|
|
121
|
+
{ sessionID: "test-session" } as any,
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
const parsed = JSON.parse(result);
|
|
125
|
+
|
|
126
|
+
expect(parsed.operation).toBe("ADD");
|
|
127
|
+
expect(parsed.reason).toBeDefined();
|
|
128
|
+
expect(parsed.memoryId).toBeDefined();
|
|
129
|
+
expect(parsed.memoryId).toMatch(/^mem_/);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test("includes autoTags when enabled", async () => {
|
|
133
|
+
const tool = memoryTools["semantic-memory_upsert"];
|
|
134
|
+
const result = await tool.execute(
|
|
135
|
+
{
|
|
136
|
+
information: "TypeScript is a typed superset of JavaScript",
|
|
137
|
+
autoTag: true,
|
|
138
|
+
},
|
|
139
|
+
{ sessionID: "test-session" } as any,
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
const parsed = JSON.parse(result);
|
|
143
|
+
|
|
144
|
+
expect(parsed.autoTags).toBeDefined();
|
|
145
|
+
expect(parsed.autoTags.tags).toBeInstanceOf(Array);
|
|
146
|
+
expect(parsed.autoTags.keywords).toBeInstanceOf(Array);
|
|
147
|
+
expect(parsed.autoTags.category).toBe("general");
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test("includes linksCreated when autoLink enabled", async () => {
|
|
151
|
+
const tool = memoryTools["semantic-memory_upsert"];
|
|
152
|
+
const result = await tool.execute(
|
|
153
|
+
{
|
|
154
|
+
information: "React hooks enable functional components to use state",
|
|
155
|
+
autoLink: true,
|
|
156
|
+
},
|
|
157
|
+
{ sessionID: "test-session" } as any,
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
const parsed = JSON.parse(result);
|
|
161
|
+
|
|
162
|
+
expect(parsed.linksCreated).toBeDefined();
|
|
163
|
+
expect(typeof parsed.linksCreated).toBe("number");
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test("includes entitiesExtracted when extractEntities enabled", async () => {
|
|
167
|
+
const tool = memoryTools["semantic-memory_upsert"];
|
|
168
|
+
const result = await tool.execute(
|
|
169
|
+
{
|
|
170
|
+
information: "Next.js 15 was released by Vercel in October 2024",
|
|
171
|
+
extractEntities: true,
|
|
172
|
+
},
|
|
173
|
+
{ sessionID: "test-session" } as any,
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
const parsed = JSON.parse(result);
|
|
177
|
+
|
|
178
|
+
expect(parsed.entitiesExtracted).toBeDefined();
|
|
179
|
+
expect(typeof parsed.entitiesExtracted).toBe("number");
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test("throws error when information is missing", async () => {
|
|
183
|
+
const tool = memoryTools["semantic-memory_upsert"];
|
|
184
|
+
|
|
185
|
+
await expect(async () => {
|
|
186
|
+
await tool.execute(
|
|
187
|
+
{
|
|
188
|
+
tags: "test",
|
|
189
|
+
} as any,
|
|
190
|
+
{ sessionID: "test-session" } as any,
|
|
191
|
+
);
|
|
192
|
+
}).toThrow("information is required");
|
|
193
|
+
});
|
|
194
|
+
});
|
|
111
195
|
});
|
package/src/memory-tools.ts
CHANGED
|
@@ -27,6 +27,9 @@ import {
|
|
|
27
27
|
type StatsResult,
|
|
28
28
|
type HealthResult,
|
|
29
29
|
type OperationResult,
|
|
30
|
+
type UpsertArgs,
|
|
31
|
+
type UpsertResult,
|
|
32
|
+
type AutoTags,
|
|
30
33
|
} from "./memory";
|
|
31
34
|
|
|
32
35
|
// Re-export types for external use
|
|
@@ -41,7 +44,10 @@ export type {
|
|
|
41
44
|
StatsResult,
|
|
42
45
|
HealthResult,
|
|
43
46
|
OperationResult,
|
|
44
|
-
|
|
47
|
+
UpsertArgs,
|
|
48
|
+
UpsertResult,
|
|
49
|
+
AutoTags,
|
|
50
|
+
} from "./memory";
|
|
45
51
|
|
|
46
52
|
// ============================================================================
|
|
47
53
|
// Types
|
|
@@ -65,7 +71,7 @@ let cachedProjectPath: string | null = null;
|
|
|
65
71
|
* @param projectPath - Project path (uses CWD if not provided)
|
|
66
72
|
* @returns Memory adapter instance
|
|
67
73
|
*/
|
|
68
|
-
async function getMemoryAdapter(
|
|
74
|
+
export async function getMemoryAdapter(
|
|
69
75
|
projectPath?: string,
|
|
70
76
|
): Promise<MemoryAdapter> {
|
|
71
77
|
const path = projectPath || process.cwd();
|
|
@@ -106,7 +112,7 @@ export { createMemoryAdapter };
|
|
|
106
112
|
*/
|
|
107
113
|
export const semantic_memory_store = tool({
|
|
108
114
|
description:
|
|
109
|
-
"Store a memory with semantic embedding. Memories are searchable by semantic similarity and can be organized into collections. Confidence affects decay rate: high confidence (1.0) = 135 day half-life, low confidence (0.0) = 45 day half-life.",
|
|
115
|
+
"Store a memory with semantic embedding. Memories are searchable by semantic similarity and can be organized into collections. Confidence affects decay rate: high confidence (1.0) = 135 day half-life, low confidence (0.0) = 45 day half-life. Supports auto-tagging, auto-linking, and entity extraction via LLM.",
|
|
110
116
|
args: {
|
|
111
117
|
information: tool.schema
|
|
112
118
|
.string()
|
|
@@ -127,6 +133,18 @@ export const semantic_memory_store = tool({
|
|
|
127
133
|
.number()
|
|
128
134
|
.optional()
|
|
129
135
|
.describe("Confidence level (0.0-1.0) affecting decay rate. Higher = slower decay. Default 0.7"),
|
|
136
|
+
autoTag: tool.schema
|
|
137
|
+
.boolean()
|
|
138
|
+
.optional()
|
|
139
|
+
.describe("Auto-generate tags using LLM. Default false"),
|
|
140
|
+
autoLink: tool.schema
|
|
141
|
+
.boolean()
|
|
142
|
+
.optional()
|
|
143
|
+
.describe("Auto-link to related memories. Default false"),
|
|
144
|
+
extractEntities: tool.schema
|
|
145
|
+
.boolean()
|
|
146
|
+
.optional()
|
|
147
|
+
.describe("Extract entities (people, places, technologies). Default false"),
|
|
130
148
|
},
|
|
131
149
|
async execute(args, ctx: ToolContext) {
|
|
132
150
|
const adapter = await getMemoryAdapter();
|
|
@@ -258,6 +276,52 @@ export const semantic_memory_check = tool({
|
|
|
258
276
|
},
|
|
259
277
|
});
|
|
260
278
|
|
|
279
|
+
/**
|
|
280
|
+
* Smart upsert - ADD, UPDATE, DELETE, or NOOP based on existing memories
|
|
281
|
+
*/
|
|
282
|
+
export const semantic_memory_upsert = tool({
|
|
283
|
+
description:
|
|
284
|
+
"Smart memory storage that decides whether to ADD, UPDATE, DELETE, or skip (NOOP) based on existing memories. Uses LLM to detect duplicates, refinements, and contradictions. Auto-generates tags, links, and entities when enabled.",
|
|
285
|
+
args: {
|
|
286
|
+
information: tool.schema
|
|
287
|
+
.string()
|
|
288
|
+
.describe("The information to store (required)"),
|
|
289
|
+
collection: tool.schema
|
|
290
|
+
.string()
|
|
291
|
+
.optional()
|
|
292
|
+
.describe("Collection name (defaults to 'default')"),
|
|
293
|
+
tags: tool.schema
|
|
294
|
+
.string()
|
|
295
|
+
.optional()
|
|
296
|
+
.describe("Comma-separated tags (e.g., 'auth,tokens,oauth')"),
|
|
297
|
+
metadata: tool.schema
|
|
298
|
+
.string()
|
|
299
|
+
.optional()
|
|
300
|
+
.describe("JSON string with additional metadata"),
|
|
301
|
+
confidence: tool.schema
|
|
302
|
+
.number()
|
|
303
|
+
.optional()
|
|
304
|
+
.describe("Confidence level (0.0-1.0) affecting decay rate. Higher = slower decay. Default 0.7"),
|
|
305
|
+
autoTag: tool.schema
|
|
306
|
+
.boolean()
|
|
307
|
+
.optional()
|
|
308
|
+
.describe("Auto-generate tags using LLM. Default true"),
|
|
309
|
+
autoLink: tool.schema
|
|
310
|
+
.boolean()
|
|
311
|
+
.optional()
|
|
312
|
+
.describe("Auto-link to related memories. Default true"),
|
|
313
|
+
extractEntities: tool.schema
|
|
314
|
+
.boolean()
|
|
315
|
+
.optional()
|
|
316
|
+
.describe("Extract entities (people, places, technologies). Default false"),
|
|
317
|
+
},
|
|
318
|
+
async execute(args, ctx: ToolContext) {
|
|
319
|
+
const adapter = await getMemoryAdapter();
|
|
320
|
+
const result = await adapter.upsert(args);
|
|
321
|
+
return JSON.stringify(result, null, 2);
|
|
322
|
+
},
|
|
323
|
+
});
|
|
324
|
+
|
|
261
325
|
// ============================================================================
|
|
262
326
|
// Tool Registry
|
|
263
327
|
// ============================================================================
|
|
@@ -276,4 +340,5 @@ export const memoryTools = {
|
|
|
276
340
|
"semantic-memory_list": semantic_memory_list,
|
|
277
341
|
"semantic-memory_stats": semantic_memory_stats,
|
|
278
342
|
"semantic-memory_check": semantic_memory_check,
|
|
343
|
+
"semantic-memory_upsert": semantic_memory_upsert,
|
|
279
344
|
} as const;
|
package/src/memory.test.ts
CHANGED
|
@@ -45,7 +45,7 @@ describe("memory adapter", () => {
|
|
|
45
45
|
});
|
|
46
46
|
|
|
47
47
|
expect(result.id).toBeDefined();
|
|
48
|
-
expect(result.id).toMatch(/^
|
|
48
|
+
expect(result.id).toMatch(/^mem-/); // Real swarm-mail adapter uses 'mem-' prefix
|
|
49
49
|
expect(result.message).toContain("Stored memory");
|
|
50
50
|
});
|
|
51
51
|
|
|
@@ -55,7 +55,7 @@ describe("memory adapter", () => {
|
|
|
55
55
|
collection: "project-alpha",
|
|
56
56
|
});
|
|
57
57
|
|
|
58
|
-
expect(result.id).toMatch(/^
|
|
58
|
+
expect(result.id).toMatch(/^mem-/); // Real swarm-mail adapter uses 'mem-' prefix
|
|
59
59
|
expect(result.message).toContain("collection: project-alpha");
|
|
60
60
|
});
|
|
61
61
|
});
|
|
@@ -120,116 +120,6 @@ describe("memory adapter", () => {
|
|
|
120
120
|
}
|
|
121
121
|
});
|
|
122
122
|
});
|
|
123
|
-
|
|
124
|
-
describe("upsert", () => {
|
|
125
|
-
test("returns ADD operation for new memory", async () => {
|
|
126
|
-
const result = await adapter.upsert({
|
|
127
|
-
information: "Completely new information about quantum computing",
|
|
128
|
-
tags: "quantum,physics",
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
expect(result.operation).toBe("ADD");
|
|
132
|
-
expect(result.reason).toBeDefined();
|
|
133
|
-
expect(result.memoryId).toBeDefined();
|
|
134
|
-
expect(result.memoryId).toMatch(/^mem_/);
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
test("returns UPDATE operation when refining existing memory", async () => {
|
|
138
|
-
// Store initial memory
|
|
139
|
-
await adapter.store({
|
|
140
|
-
information: "OAuth tokens need buffer",
|
|
141
|
-
tags: "auth",
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
// Try to upsert refined version
|
|
145
|
-
const result = await adapter.upsert({
|
|
146
|
-
information: "OAuth refresh tokens need 5min buffer before expiry to avoid race conditions",
|
|
147
|
-
tags: "auth,oauth,tokens",
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
expect(result.operation).toBe("UPDATE");
|
|
151
|
-
expect(result.reason).toBeDefined();
|
|
152
|
-
expect(result.memoryId).toBeDefined();
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
test("returns NOOP operation when information already exists", async () => {
|
|
156
|
-
// Store a memory
|
|
157
|
-
await adapter.store({
|
|
158
|
-
information: "Next.js 16 requires Suspense for Cache Components",
|
|
159
|
-
tags: "nextjs",
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
// Try to upsert same information
|
|
163
|
-
const result = await adapter.upsert({
|
|
164
|
-
information: "Next.js 16 requires Suspense for Cache Components",
|
|
165
|
-
tags: "nextjs",
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
expect(result.operation).toBe("NOOP");
|
|
169
|
-
expect(result.reason).toContain("already captured");
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
test("autoTag generates tags when enabled", async () => {
|
|
173
|
-
const result = await adapter.upsert({
|
|
174
|
-
information: "TypeScript interfaces are better than type aliases for object types",
|
|
175
|
-
autoTag: true,
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
expect(result.autoTags).toBeDefined();
|
|
179
|
-
expect(result.autoTags?.tags).toBeInstanceOf(Array);
|
|
180
|
-
expect(result.autoTags?.tags.length).toBeGreaterThan(0);
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
test("autoLink creates links when enabled", async () => {
|
|
184
|
-
// Store a base memory
|
|
185
|
-
await adapter.store({
|
|
186
|
-
information: "TypeScript supports structural typing",
|
|
187
|
-
tags: "typescript",
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
// Upsert related memory with autoLink
|
|
191
|
-
const result = await adapter.upsert({
|
|
192
|
-
information: "TypeScript interfaces use structural typing for shape matching",
|
|
193
|
-
autoLink: true,
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
if (result.linksCreated !== undefined) {
|
|
197
|
-
expect(result.linksCreated).toBeGreaterThanOrEqual(0);
|
|
198
|
-
}
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
test("extractEntities extracts entities when enabled", async () => {
|
|
202
|
-
const result = await adapter.upsert({
|
|
203
|
-
information: "React 19 introduces Server Components for Next.js 15",
|
|
204
|
-
extractEntities: true,
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
if (result.entitiesExtracted !== undefined) {
|
|
208
|
-
expect(result.entitiesExtracted).toBeGreaterThanOrEqual(0);
|
|
209
|
-
}
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
test("respects collection parameter", async () => {
|
|
213
|
-
const result = await adapter.upsert({
|
|
214
|
-
information: "Test memory in custom collection",
|
|
215
|
-
collection: "test-collection",
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
expect(result.memoryId).toBeDefined();
|
|
219
|
-
// Verify memory was stored in correct collection
|
|
220
|
-
const memory = await adapter.get({ id: result.memoryId! });
|
|
221
|
-
expect(memory?.collection).toBe("test-collection");
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
test("handles errors gracefully when information is missing", async () => {
|
|
225
|
-
await expect(async () => {
|
|
226
|
-
await (adapter.upsert as any)({
|
|
227
|
-
// Missing required information field
|
|
228
|
-
tags: "test",
|
|
229
|
-
});
|
|
230
|
-
}).toThrow();
|
|
231
|
-
});
|
|
232
|
-
});
|
|
233
123
|
});
|
|
234
124
|
|
|
235
125
|
describe("auto-migration on createMemoryAdapter", () => {
|
package/src/memory.ts
CHANGED
|
@@ -39,6 +39,8 @@ import {
|
|
|
39
39
|
legacyDatabaseExists,
|
|
40
40
|
migrateLegacyMemories,
|
|
41
41
|
toSwarmDb,
|
|
42
|
+
createMemoryAdapter as createSwarmMailAdapter,
|
|
43
|
+
type MemoryConfig,
|
|
42
44
|
} from "swarm-mail";
|
|
43
45
|
|
|
44
46
|
// ============================================================================
|
|
@@ -71,6 +73,12 @@ export interface StoreArgs {
|
|
|
71
73
|
readonly metadata?: string;
|
|
72
74
|
/** Confidence level (0.0-1.0) affecting decay rate. Higher = slower decay. Default 0.7 */
|
|
73
75
|
readonly confidence?: number;
|
|
76
|
+
/** Auto-generate tags using LLM. Default false */
|
|
77
|
+
readonly autoTag?: boolean;
|
|
78
|
+
/** Auto-link to related memories. Default false */
|
|
79
|
+
readonly autoLink?: boolean;
|
|
80
|
+
/** Extract entities (people, places, technologies). Default false */
|
|
81
|
+
readonly extractEntities?: boolean;
|
|
74
82
|
}
|
|
75
83
|
|
|
76
84
|
/** Arguments for find operation */
|
|
@@ -270,10 +278,24 @@ export async function createMemoryAdapter(
|
|
|
270
278
|
await maybeAutoMigrate(db);
|
|
271
279
|
}
|
|
272
280
|
|
|
273
|
-
// Convert DatabaseAdapter to SwarmDb (Drizzle client) for
|
|
281
|
+
// Convert DatabaseAdapter to SwarmDb (Drizzle client) for real swarm-mail adapter
|
|
274
282
|
const drizzleDb = toSwarmDb(db);
|
|
275
|
-
const store = createMemoryStore(drizzleDb);
|
|
276
283
|
const config = getDefaultConfig();
|
|
284
|
+
|
|
285
|
+
// Create real swarm-mail adapter with Wave 1-3 features
|
|
286
|
+
const realAdapter = createSwarmMailAdapter(drizzleDb, config);
|
|
287
|
+
|
|
288
|
+
// DEBUG: Check if upsert exists
|
|
289
|
+
if (!realAdapter || typeof realAdapter.upsert !== 'function') {
|
|
290
|
+
console.warn('[memory] realAdapter.upsert is not available:', {
|
|
291
|
+
hasAdapter: !!realAdapter,
|
|
292
|
+
upsertType: typeof realAdapter?.upsert,
|
|
293
|
+
methods: realAdapter ? Object.keys(realAdapter) : []
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// For backward compatibility, keep legacy adapter for methods not yet in real adapter
|
|
298
|
+
const store = createMemoryStore(drizzleDb);
|
|
277
299
|
const ollamaLayer = makeOllamaLive(config);
|
|
278
300
|
|
|
279
301
|
/**
|
|
@@ -306,59 +328,39 @@ export async function createMemoryAdapter(
|
|
|
306
328
|
|
|
307
329
|
return {
|
|
308
330
|
/**
|
|
309
|
-
* Store a memory with embedding
|
|
331
|
+
* Store a memory with embedding and optional auto-features
|
|
332
|
+
*
|
|
333
|
+
* Delegates to real swarm-mail adapter which supports:
|
|
334
|
+
* - autoTag: LLM-powered tag generation
|
|
335
|
+
* - autoLink: Semantic linking to related memories
|
|
336
|
+
* - extractEntities: Entity extraction and knowledge graph building
|
|
310
337
|
*/
|
|
311
338
|
async store(args: StoreArgs): Promise<StoreResult> {
|
|
312
|
-
|
|
313
|
-
const
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
} catch {
|
|
322
|
-
metadata = { raw: args.metadata };
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
// Add tags to metadata
|
|
327
|
-
if (tags.length > 0) {
|
|
328
|
-
metadata.tags = tags;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
// Clamp confidence to valid range [0.0, 1.0]
|
|
332
|
-
const clampConfidence = (c: number | undefined): number => {
|
|
333
|
-
if (c === undefined) return 0.7;
|
|
334
|
-
return Math.max(0.0, Math.min(1.0, c));
|
|
335
|
-
};
|
|
336
|
-
|
|
337
|
-
const memory: Memory = {
|
|
338
|
-
id,
|
|
339
|
-
content: args.information,
|
|
340
|
-
metadata,
|
|
341
|
-
collection,
|
|
342
|
-
createdAt: new Date(),
|
|
343
|
-
confidence: clampConfidence(args.confidence),
|
|
344
|
-
};
|
|
345
|
-
|
|
346
|
-
// Generate embedding
|
|
347
|
-
const program = Effect.gen(function* () {
|
|
348
|
-
const ollama = yield* Ollama;
|
|
349
|
-
return yield* ollama.embed(args.information);
|
|
339
|
+
// Delegate to real swarm-mail adapter
|
|
340
|
+
const result = await realAdapter.store(args.information, {
|
|
341
|
+
collection: args.collection,
|
|
342
|
+
tags: args.tags,
|
|
343
|
+
metadata: args.metadata,
|
|
344
|
+
confidence: args.confidence,
|
|
345
|
+
autoTag: args.autoTag,
|
|
346
|
+
autoLink: args.autoLink,
|
|
347
|
+
extractEntities: args.extractEntities,
|
|
350
348
|
});
|
|
351
349
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
350
|
+
// Build user-facing message
|
|
351
|
+
let message = `Stored memory ${result.id} in collection: ${args.collection ?? "default"}`;
|
|
352
|
+
|
|
353
|
+
if (result.autoTags) {
|
|
354
|
+
message += `\nAuto-tags: ${result.autoTags.tags.join(", ")}`;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (result.links && result.links.length > 0) {
|
|
358
|
+
message += `\nLinked to ${result.links.length} related memor${result.links.length === 1 ? "y" : "ies"}`;
|
|
359
|
+
}
|
|
358
360
|
|
|
359
361
|
return {
|
|
360
|
-
id,
|
|
361
|
-
message
|
|
362
|
+
id: result.id,
|
|
363
|
+
message,
|
|
362
364
|
};
|
|
363
365
|
},
|
|
364
366
|
|
|
@@ -484,5 +486,42 @@ export async function createMemoryAdapter(
|
|
|
484
486
|
};
|
|
485
487
|
}
|
|
486
488
|
},
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Smart upsert - uses LLM to decide ADD, UPDATE, DELETE, or NOOP
|
|
492
|
+
*
|
|
493
|
+
* Delegates to real swarm-mail adapter with Mem0 pattern:
|
|
494
|
+
* - Finds semantically similar memories
|
|
495
|
+
* - LLM analyzes and decides operation
|
|
496
|
+
* - Executes with graceful degradation on LLM failures
|
|
497
|
+
*/
|
|
498
|
+
async upsert(args: UpsertArgs): Promise<UpsertResult> {
|
|
499
|
+
// Validate required fields
|
|
500
|
+
if (!args.information) {
|
|
501
|
+
throw new Error("information is required for upsert");
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// Delegate to real swarm-mail adapter with useSmartOps enabled
|
|
505
|
+
const result = await realAdapter.upsert(args.information, {
|
|
506
|
+
collection: args.collection,
|
|
507
|
+
tags: args.tags,
|
|
508
|
+
metadata: args.metadata,
|
|
509
|
+
confidence: args.confidence,
|
|
510
|
+
useSmartOps: true, // Enable LLM-powered decision making
|
|
511
|
+
autoTag: args.autoTag,
|
|
512
|
+
autoLink: args.autoLink,
|
|
513
|
+
extractEntities: args.extractEntities,
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
// Map real adapter result to plugin UpsertResult format
|
|
517
|
+
return {
|
|
518
|
+
operation: result.operation,
|
|
519
|
+
reason: result.reason,
|
|
520
|
+
memoryId: result.id,
|
|
521
|
+
affectedMemoryIds: [result.id],
|
|
522
|
+
// Note: Real adapter doesn't return autoTags/links from upsert yet
|
|
523
|
+
// Those are only on store(). This is consistent with current behavior.
|
|
524
|
+
};
|
|
525
|
+
},
|
|
487
526
|
};
|
|
488
527
|
}
|
|
@@ -343,4 +343,17 @@ describe("observability-tools", () => {
|
|
|
343
343
|
}
|
|
344
344
|
});
|
|
345
345
|
});
|
|
346
|
+
|
|
347
|
+
describe("CLI Stats Helpers", () => {
|
|
348
|
+
// These helpers will be exported for use in bin/swarm.ts
|
|
349
|
+
// They format analytics data for beautiful CLI output
|
|
350
|
+
|
|
351
|
+
describe("formatSwarmStatsBox", () => {
|
|
352
|
+
test("formats stats in a beautiful box", () => {
|
|
353
|
+
// This will be implemented in observability-tools.ts
|
|
354
|
+
// Just defining the test structure for now
|
|
355
|
+
expect(true).toBe(true);
|
|
356
|
+
});
|
|
357
|
+
});
|
|
358
|
+
});
|
|
346
359
|
});
|