@swarmclawai/swarmclaw 1.3.4 → 1.3.6
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 +20 -76
- package/package.json +3 -2
- package/skills/swarmclaw.md +17 -0
- package/src/app/api/agents/[id]/dream/route.ts +45 -0
- package/src/app/api/knowledge/[id]/route.ts +48 -49
- package/src/app/api/knowledge/hygiene/route.ts +13 -0
- package/src/app/api/knowledge/route.ts +70 -42
- package/src/app/api/knowledge/sources/[id]/archive/route.ts +15 -0
- package/src/app/api/knowledge/sources/[id]/restore/route.ts +10 -0
- package/src/app/api/knowledge/sources/[id]/route.ts +1 -0
- package/src/app/api/knowledge/sources/[id]/supersede/route.ts +26 -0
- package/src/app/api/knowledge/sources/[id]/sync/route.ts +17 -0
- package/src/app/api/knowledge/sources/route.ts +1 -0
- package/src/app/api/knowledge/upload/route.ts +3 -51
- package/src/app/api/memory/dream/[id]/route.ts +19 -0
- package/src/app/api/memory/dream/route.ts +34 -0
- package/src/app/knowledge/layout.tsx +1 -1
- package/src/app/knowledge/page.tsx +2 -22
- package/src/app/protocols/page.tsx +21 -2
- package/src/cli/index.js +16 -0
- package/src/cli/spec.js +5 -0
- package/src/components/agents/agent-sheet.tsx +65 -0
- package/src/components/chat/message-bubble.tsx +10 -0
- package/src/components/knowledge/grounding-panel.tsx +99 -0
- package/src/components/knowledge/knowledge-detail.tsx +402 -0
- package/src/components/knowledge/knowledge-list.tsx +351 -126
- package/src/components/knowledge/knowledge-sheet.tsx +208 -119
- package/src/components/memory/dream-history.tsx +155 -0
- package/src/components/memory/memory-card.tsx +7 -0
- package/src/components/memory/memory-detail.tsx +46 -0
- package/src/components/runs/run-list.tsx +23 -0
- package/src/lib/server/api-routes.test.ts +43 -2
- package/src/lib/server/chat-execution/chat-execution-disabled.test.ts +14 -31
- package/src/lib/server/chat-execution/chat-execution-eval-history.test.ts +11 -34
- package/src/lib/server/chat-execution/chat-execution-grounding.test.ts +108 -0
- package/src/lib/server/chat-execution/chat-execution-session-sync.test.ts +35 -36
- package/src/lib/server/chat-execution/chat-execution-types.ts +8 -1
- package/src/lib/server/chat-execution/chat-execution.ts +1 -0
- package/src/lib/server/chat-execution/chat-turn-finalization.ts +21 -1
- package/src/lib/server/chat-execution/chat-turn-stream-execution.ts +6 -1
- package/src/lib/server/chat-execution/post-stream-finalization.ts +15 -3
- package/src/lib/server/chat-execution/prompt-sections.ts +29 -3
- package/src/lib/server/chat-execution/stream-agent-chat.ts +6 -1
- package/src/lib/server/execution-engine/task-attempt.ts +8 -2
- package/src/lib/server/knowledge-import.ts +159 -0
- package/src/lib/server/knowledge-sources.test.ts +261 -0
- package/src/lib/server/knowledge-sources.ts +1284 -0
- package/src/lib/server/memory/dream-cycles.ts +49 -0
- package/src/lib/server/memory/dream-idle-callback.ts +38 -0
- package/src/lib/server/memory/dream-service.ts +315 -0
- package/src/lib/server/memory/memory-db.ts +37 -2
- package/src/lib/server/protocols/protocol-agent-turn.ts +7 -0
- package/src/lib/server/protocols/protocol-run-lifecycle.ts +19 -6
- package/src/lib/server/protocols/protocol-service.test.ts +99 -0
- package/src/lib/server/protocols/protocol-step-helpers.ts +7 -1
- package/src/lib/server/protocols/protocol-step-processors.ts +16 -3
- package/src/lib/server/protocols/protocol-types.ts +4 -0
- package/src/lib/server/runtime/daemon-state/core.ts +6 -1
- package/src/lib/server/runtime/run-ledger.test.ts +120 -0
- package/src/lib/server/runtime/run-ledger.ts +27 -1
- package/src/lib/server/runtime/session-run-manager/drain.ts +5 -0
- package/src/lib/server/runtime/session-run-manager/state.ts +19 -2
- package/src/lib/server/storage-normalization.ts +5 -0
- package/src/lib/server/storage.ts +15 -0
- package/src/lib/server/test-utils/run-with-temp-data-dir.ts +15 -2
- package/src/stores/slices/ui-slice.ts +4 -0
- package/src/types/agent.ts +7 -0
- package/src/types/dream.ts +45 -0
- package/src/types/index.ts +1 -0
- package/src/types/message.ts +3 -0
- package/src/types/misc.ts +131 -0
- package/src/types/protocol.ts +4 -0
- package/src/types/run.ts +4 -1
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { test } from 'node:test'
|
|
3
|
+
import { runWithTempDataDir } from '@/lib/server/test-utils/run-with-temp-data-dir'
|
|
4
|
+
|
|
5
|
+
test('buildKnowledgeRetrievalTrace returns active hits and selectKnowledgeCitations marks empty replies as no_match', () => {
|
|
6
|
+
const output = runWithTempDataDir<{
|
|
7
|
+
sourceId: string | null
|
|
8
|
+
hitCount: number
|
|
9
|
+
firstHitSourceId: string | null
|
|
10
|
+
matchedStatus: string | null
|
|
11
|
+
matchedCitationCount: number
|
|
12
|
+
whyMatched: string | null
|
|
13
|
+
unmatchedStatus: string | null
|
|
14
|
+
unmatchedCitationCount: number
|
|
15
|
+
}>(`
|
|
16
|
+
const knowledgeMod = await import('./src/lib/server/knowledge-sources.ts')
|
|
17
|
+
const knowledge = knowledgeMod.default || knowledgeMod
|
|
18
|
+
|
|
19
|
+
const detail = await knowledge.createKnowledgeSource({
|
|
20
|
+
kind: 'manual',
|
|
21
|
+
title: 'Gateway Migration Runbook',
|
|
22
|
+
content: 'Use blue green deployment for gateway migrations so rollback stays simple and downtime stays low.',
|
|
23
|
+
tags: ['deploy'],
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
const trace = await knowledge.buildKnowledgeRetrievalTrace({
|
|
27
|
+
query: 'gateway blue green rollback',
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
const matched = knowledge.selectKnowledgeCitations({
|
|
31
|
+
responseText: 'Use blue green deployment for the gateway migration so rollback stays simple.',
|
|
32
|
+
retrievalTrace: trace,
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
const unmatched = knowledge.selectKnowledgeCitations({
|
|
36
|
+
responseText: '',
|
|
37
|
+
retrievalTrace: trace,
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
console.log(JSON.stringify({
|
|
41
|
+
sourceId: detail?.source?.id || null,
|
|
42
|
+
hitCount: trace?.hits?.length || 0,
|
|
43
|
+
firstHitSourceId: trace?.hits?.[0]?.sourceId || null,
|
|
44
|
+
matchedStatus: matched.retrievalTrace?.selectorStatus || null,
|
|
45
|
+
matchedCitationCount: matched.citations.length,
|
|
46
|
+
whyMatched: matched.citations[0]?.whyMatched || null,
|
|
47
|
+
unmatchedStatus: unmatched.retrievalTrace?.selectorStatus || null,
|
|
48
|
+
unmatchedCitationCount: unmatched.citations.length,
|
|
49
|
+
}))
|
|
50
|
+
`, { prefix: 'swarmclaw-knowledge-trace-' })
|
|
51
|
+
|
|
52
|
+
assert.ok(output.sourceId)
|
|
53
|
+
assert.ok(output.hitCount >= 1)
|
|
54
|
+
assert.equal(output.firstHitSourceId, output.sourceId)
|
|
55
|
+
assert.equal(output.matchedStatus, 'selected')
|
|
56
|
+
assert.ok(output.matchedCitationCount >= 1)
|
|
57
|
+
assert.match(output.whyMatched || '', /Matched|Retrieved/)
|
|
58
|
+
assert.equal(output.unmatchedStatus, 'no_match')
|
|
59
|
+
assert.equal(output.unmatchedCitationCount, 0)
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
test('archived and superseded sources are excluded by default, restore re-enables search, and restore actions are recorded explicitly', () => {
|
|
63
|
+
const output = runWithTempDataDir<{
|
|
64
|
+
archivedDefaultCount: number
|
|
65
|
+
archivedIncludedCount: number
|
|
66
|
+
restoredCount: number
|
|
67
|
+
restoreActionKind: string | null
|
|
68
|
+
supersededDefaultCount: number
|
|
69
|
+
supersededIncludedCount: number
|
|
70
|
+
supersededFinding: boolean
|
|
71
|
+
}>(`
|
|
72
|
+
const knowledgeMod = await import('./src/lib/server/knowledge-sources.ts')
|
|
73
|
+
const storageMod = await import('./src/lib/server/storage.ts')
|
|
74
|
+
const knowledge = knowledgeMod.default || knowledgeMod
|
|
75
|
+
const storage = storageMod.default || storageMod
|
|
76
|
+
|
|
77
|
+
const archived = await knowledge.createKnowledgeSource({
|
|
78
|
+
kind: 'manual',
|
|
79
|
+
title: 'Orchard Rollback Notes',
|
|
80
|
+
content: 'orchard sentinel rollback checklist',
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
await knowledge.archiveKnowledgeSource(archived.source.id, { reason: 'manual review' })
|
|
84
|
+
|
|
85
|
+
const archivedDefault = await knowledge.searchKnowledgeHits({ query: 'orchard' })
|
|
86
|
+
const archivedIncluded = await knowledge.searchKnowledgeHits({
|
|
87
|
+
query: 'orchard',
|
|
88
|
+
includeArchived: true,
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
await knowledge.restoreKnowledgeSource(archived.source.id)
|
|
92
|
+
const restored = await knowledge.searchKnowledgeHits({ query: 'orchard' })
|
|
93
|
+
|
|
94
|
+
const older = await knowledge.createKnowledgeSource({
|
|
95
|
+
kind: 'manual',
|
|
96
|
+
title: 'Legacy API Notes',
|
|
97
|
+
content: 'legacy endpoint alpha is still enabled',
|
|
98
|
+
sourceUrl: 'https://example.com/api/reference',
|
|
99
|
+
})
|
|
100
|
+
const newer = await knowledge.createKnowledgeSource({
|
|
101
|
+
kind: 'manual',
|
|
102
|
+
title: 'Current API Notes',
|
|
103
|
+
content: 'modern endpoint beta replaced the older route',
|
|
104
|
+
sourceUrl: 'https://example.com/api/reference',
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
storage.patchKnowledgeSource(older.source.id, (current) => current ? {
|
|
108
|
+
...current,
|
|
109
|
+
lastIndexedAt: 1_000,
|
|
110
|
+
updatedAt: 1_000,
|
|
111
|
+
} : null)
|
|
112
|
+
storage.patchKnowledgeSource(newer.source.id, (current) => current ? {
|
|
113
|
+
...current,
|
|
114
|
+
lastIndexedAt: 2_000,
|
|
115
|
+
updatedAt: 2_000,
|
|
116
|
+
} : null)
|
|
117
|
+
|
|
118
|
+
await knowledge.runKnowledgeHygieneMaintenance()
|
|
119
|
+
|
|
120
|
+
const supersededDefault = await knowledge.searchKnowledgeHits({ query: 'alpha' })
|
|
121
|
+
const supersededIncluded = await knowledge.searchKnowledgeHits({
|
|
122
|
+
query: 'alpha',
|
|
123
|
+
includeArchived: true,
|
|
124
|
+
})
|
|
125
|
+
const summary = await knowledge.getKnowledgeHygieneSummary()
|
|
126
|
+
|
|
127
|
+
console.log(JSON.stringify({
|
|
128
|
+
archivedDefaultCount: archivedDefault.length,
|
|
129
|
+
archivedIncludedCount: archivedIncluded.length,
|
|
130
|
+
restoredCount: restored.length,
|
|
131
|
+
restoreActionKind: summary.recentActions.find((action) => action.summary === 'Restored Orchard Rollback Notes')?.kind || null,
|
|
132
|
+
supersededDefaultCount: supersededDefault.length,
|
|
133
|
+
supersededIncludedCount: supersededIncluded.length,
|
|
134
|
+
supersededFinding: summary.findings.some((finding) => finding.kind === 'superseded' && finding.sourceId === older.source.id),
|
|
135
|
+
}))
|
|
136
|
+
`, { prefix: 'swarmclaw-knowledge-lifecycle-' })
|
|
137
|
+
|
|
138
|
+
assert.equal(output.archivedDefaultCount, 0)
|
|
139
|
+
assert.equal(output.archivedIncludedCount, 1)
|
|
140
|
+
assert.equal(output.restoredCount, 1)
|
|
141
|
+
assert.equal(output.restoreActionKind, 'restore')
|
|
142
|
+
assert.equal(output.supersededDefaultCount, 0)
|
|
143
|
+
assert.equal(output.supersededIncludedCount, 1)
|
|
144
|
+
assert.equal(output.supersededFinding, true)
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
test('runKnowledgeHygieneMaintenance reindexes stale file sources and archives exact duplicates', () => {
|
|
148
|
+
const output = runWithTempDataDir<{
|
|
149
|
+
fileLastAutoSyncAt: number | null
|
|
150
|
+
fileChunksContainUpdatedText: boolean
|
|
151
|
+
refreshedHitCount: number
|
|
152
|
+
archivedDuplicateCount: number
|
|
153
|
+
recentActionKinds: string[]
|
|
154
|
+
}>(`
|
|
155
|
+
const fs = await import('node:fs')
|
|
156
|
+
const path = await import('node:path')
|
|
157
|
+
const knowledgeMod = await import('./src/lib/server/knowledge-sources.ts')
|
|
158
|
+
const storageMod = await import('./src/lib/server/storage.ts')
|
|
159
|
+
const knowledge = knowledgeMod.default || knowledgeMod
|
|
160
|
+
const storage = storageMod.default || storageMod
|
|
161
|
+
|
|
162
|
+
const filePath = path.join(process.env.WORKSPACE_DIR, 'ops-runbook.txt')
|
|
163
|
+
fs.writeFileSync(filePath, 'Initial runbook placeholder.')
|
|
164
|
+
|
|
165
|
+
const fileSource = await knowledge.createKnowledgeSource({
|
|
166
|
+
kind: 'file',
|
|
167
|
+
title: 'Ops Runbook',
|
|
168
|
+
sourcePath: filePath,
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
fs.writeFileSync(filePath, 'Updated runbook adds rollback choreography and incident checklist.')
|
|
172
|
+
storage.patchKnowledgeSource(fileSource.source.id, (current) => current ? {
|
|
173
|
+
...current,
|
|
174
|
+
lastIndexedAt: 1,
|
|
175
|
+
nextSyncAt: 1,
|
|
176
|
+
updatedAt: 1,
|
|
177
|
+
} : null)
|
|
178
|
+
|
|
179
|
+
const duplicateA = await knowledge.createKnowledgeSource({
|
|
180
|
+
kind: 'manual',
|
|
181
|
+
title: 'Duplicate A',
|
|
182
|
+
content: 'duplicate payload for archival',
|
|
183
|
+
})
|
|
184
|
+
const duplicateB = await knowledge.createKnowledgeSource({
|
|
185
|
+
kind: 'manual',
|
|
186
|
+
title: 'Duplicate B',
|
|
187
|
+
content: 'duplicate payload for archival',
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
const summary = await knowledge.runKnowledgeHygieneMaintenance()
|
|
191
|
+
const refreshed = await knowledge.getKnowledgeSourceDetail(fileSource.source.id)
|
|
192
|
+
const duplicateADetail = await knowledge.getKnowledgeSourceDetail(duplicateA.source.id)
|
|
193
|
+
const duplicateBDetail = await knowledge.getKnowledgeSourceDetail(duplicateB.source.id)
|
|
194
|
+
const refreshedHits = await knowledge.searchKnowledgeHits({ query: 'choreography' })
|
|
195
|
+
|
|
196
|
+
const archivedDuplicateCount = [duplicateADetail, duplicateBDetail]
|
|
197
|
+
.filter((detail) => !!detail?.source?.archivedAt)
|
|
198
|
+
.length
|
|
199
|
+
|
|
200
|
+
console.log(JSON.stringify({
|
|
201
|
+
fileLastAutoSyncAt: refreshed?.source?.lastAutoSyncAt || null,
|
|
202
|
+
fileChunksContainUpdatedText: (refreshed?.chunks || []).some((chunk) => chunk.content.includes('rollback choreography')),
|
|
203
|
+
refreshedHitCount: refreshedHits.length,
|
|
204
|
+
archivedDuplicateCount,
|
|
205
|
+
recentActionKinds: summary.recentActions.map((action) => action.kind),
|
|
206
|
+
}))
|
|
207
|
+
`, { prefix: 'swarmclaw-knowledge-maintenance-' })
|
|
208
|
+
|
|
209
|
+
assert.ok(typeof output.fileLastAutoSyncAt === 'number' && output.fileLastAutoSyncAt > 0)
|
|
210
|
+
assert.equal(output.fileChunksContainUpdatedText, true)
|
|
211
|
+
assert.ok(output.refreshedHitCount >= 1)
|
|
212
|
+
assert.equal(output.archivedDuplicateCount, 1)
|
|
213
|
+
assert.ok(output.recentActionKinds.includes('archive'))
|
|
214
|
+
assert.ok(output.recentActionKinds.includes('reindex') || output.recentActionKinds.includes('sync'))
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
test('runKnowledgeHygieneMaintenance keeps same-content sources separate when visibility differs', () => {
|
|
218
|
+
const output = runWithTempDataDir<{
|
|
219
|
+
globalArchived: boolean
|
|
220
|
+
agentArchived: boolean
|
|
221
|
+
agent1Hits: number
|
|
222
|
+
agent2Hits: number
|
|
223
|
+
}>(`
|
|
224
|
+
const knowledgeMod = await import('./src/lib/server/knowledge-sources.ts')
|
|
225
|
+
const knowledge = knowledgeMod.default || knowledgeMod
|
|
226
|
+
|
|
227
|
+
const globalSource = await knowledge.createKnowledgeSource({
|
|
228
|
+
kind: 'manual',
|
|
229
|
+
title: 'Global Policy',
|
|
230
|
+
content: 'identical content',
|
|
231
|
+
scope: 'global',
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
const agentSource = await knowledge.createKnowledgeSource({
|
|
235
|
+
kind: 'manual',
|
|
236
|
+
title: 'Scoped Policy',
|
|
237
|
+
content: 'identical content',
|
|
238
|
+
scope: 'agent',
|
|
239
|
+
agentIds: ['agent-1'],
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
await knowledge.runKnowledgeHygieneMaintenance()
|
|
243
|
+
|
|
244
|
+
const globalDetail = await knowledge.getKnowledgeSourceDetail(globalSource.source.id)
|
|
245
|
+
const agentDetail = await knowledge.getKnowledgeSourceDetail(agentSource.source.id)
|
|
246
|
+
const agent1Hits = await knowledge.searchKnowledgeHits({ query: 'identical', viewerAgentId: 'agent-1' })
|
|
247
|
+
const agent2Hits = await knowledge.searchKnowledgeHits({ query: 'identical', viewerAgentId: 'agent-2' })
|
|
248
|
+
|
|
249
|
+
console.log(JSON.stringify({
|
|
250
|
+
globalArchived: !!globalDetail?.source?.archivedAt,
|
|
251
|
+
agentArchived: !!agentDetail?.source?.archivedAt,
|
|
252
|
+
agent1Hits: agent1Hits.length,
|
|
253
|
+
agent2Hits: agent2Hits.length,
|
|
254
|
+
}))
|
|
255
|
+
`, { prefix: 'swarmclaw-knowledge-visibility-' })
|
|
256
|
+
|
|
257
|
+
assert.equal(output.globalArchived, false)
|
|
258
|
+
assert.equal(output.agentArchived, false)
|
|
259
|
+
assert.equal(output.agent1Hits, 2)
|
|
260
|
+
assert.equal(output.agent2Hits, 1)
|
|
261
|
+
})
|