@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.
Files changed (73) hide show
  1. package/README.md +20 -76
  2. package/package.json +3 -2
  3. package/skills/swarmclaw.md +17 -0
  4. package/src/app/api/agents/[id]/dream/route.ts +45 -0
  5. package/src/app/api/knowledge/[id]/route.ts +48 -49
  6. package/src/app/api/knowledge/hygiene/route.ts +13 -0
  7. package/src/app/api/knowledge/route.ts +70 -42
  8. package/src/app/api/knowledge/sources/[id]/archive/route.ts +15 -0
  9. package/src/app/api/knowledge/sources/[id]/restore/route.ts +10 -0
  10. package/src/app/api/knowledge/sources/[id]/route.ts +1 -0
  11. package/src/app/api/knowledge/sources/[id]/supersede/route.ts +26 -0
  12. package/src/app/api/knowledge/sources/[id]/sync/route.ts +17 -0
  13. package/src/app/api/knowledge/sources/route.ts +1 -0
  14. package/src/app/api/knowledge/upload/route.ts +3 -51
  15. package/src/app/api/memory/dream/[id]/route.ts +19 -0
  16. package/src/app/api/memory/dream/route.ts +34 -0
  17. package/src/app/knowledge/layout.tsx +1 -1
  18. package/src/app/knowledge/page.tsx +2 -22
  19. package/src/app/protocols/page.tsx +21 -2
  20. package/src/cli/index.js +16 -0
  21. package/src/cli/spec.js +5 -0
  22. package/src/components/agents/agent-sheet.tsx +65 -0
  23. package/src/components/chat/message-bubble.tsx +10 -0
  24. package/src/components/knowledge/grounding-panel.tsx +99 -0
  25. package/src/components/knowledge/knowledge-detail.tsx +402 -0
  26. package/src/components/knowledge/knowledge-list.tsx +351 -126
  27. package/src/components/knowledge/knowledge-sheet.tsx +208 -119
  28. package/src/components/memory/dream-history.tsx +155 -0
  29. package/src/components/memory/memory-card.tsx +7 -0
  30. package/src/components/memory/memory-detail.tsx +46 -0
  31. package/src/components/runs/run-list.tsx +23 -0
  32. package/src/lib/server/api-routes.test.ts +43 -2
  33. package/src/lib/server/chat-execution/chat-execution-disabled.test.ts +14 -31
  34. package/src/lib/server/chat-execution/chat-execution-eval-history.test.ts +11 -34
  35. package/src/lib/server/chat-execution/chat-execution-grounding.test.ts +108 -0
  36. package/src/lib/server/chat-execution/chat-execution-session-sync.test.ts +35 -36
  37. package/src/lib/server/chat-execution/chat-execution-types.ts +8 -1
  38. package/src/lib/server/chat-execution/chat-execution.ts +1 -0
  39. package/src/lib/server/chat-execution/chat-turn-finalization.ts +21 -1
  40. package/src/lib/server/chat-execution/chat-turn-stream-execution.ts +6 -1
  41. package/src/lib/server/chat-execution/post-stream-finalization.ts +15 -3
  42. package/src/lib/server/chat-execution/prompt-sections.ts +29 -3
  43. package/src/lib/server/chat-execution/stream-agent-chat.ts +6 -1
  44. package/src/lib/server/execution-engine/task-attempt.ts +8 -2
  45. package/src/lib/server/knowledge-import.ts +159 -0
  46. package/src/lib/server/knowledge-sources.test.ts +261 -0
  47. package/src/lib/server/knowledge-sources.ts +1284 -0
  48. package/src/lib/server/memory/dream-cycles.ts +49 -0
  49. package/src/lib/server/memory/dream-idle-callback.ts +38 -0
  50. package/src/lib/server/memory/dream-service.ts +315 -0
  51. package/src/lib/server/memory/memory-db.ts +37 -2
  52. package/src/lib/server/protocols/protocol-agent-turn.ts +7 -0
  53. package/src/lib/server/protocols/protocol-run-lifecycle.ts +19 -6
  54. package/src/lib/server/protocols/protocol-service.test.ts +99 -0
  55. package/src/lib/server/protocols/protocol-step-helpers.ts +7 -1
  56. package/src/lib/server/protocols/protocol-step-processors.ts +16 -3
  57. package/src/lib/server/protocols/protocol-types.ts +4 -0
  58. package/src/lib/server/runtime/daemon-state/core.ts +6 -1
  59. package/src/lib/server/runtime/run-ledger.test.ts +120 -0
  60. package/src/lib/server/runtime/run-ledger.ts +27 -1
  61. package/src/lib/server/runtime/session-run-manager/drain.ts +5 -0
  62. package/src/lib/server/runtime/session-run-manager/state.ts +19 -2
  63. package/src/lib/server/storage-normalization.ts +5 -0
  64. package/src/lib/server/storage.ts +15 -0
  65. package/src/lib/server/test-utils/run-with-temp-data-dir.ts +15 -2
  66. package/src/stores/slices/ui-slice.ts +4 -0
  67. package/src/types/agent.ts +7 -0
  68. package/src/types/dream.ts +45 -0
  69. package/src/types/index.ts +1 -0
  70. package/src/types/message.ts +3 -0
  71. package/src/types/misc.ts +131 -0
  72. package/src/types/protocol.ts +4 -0
  73. 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
+ })