@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
@@ -502,7 +502,12 @@ export function currentArtifact(run: ProtocolRun): ProtocolRunArtifact | null {
502
502
  return run.artifacts[run.artifacts.length - 1] || null
503
503
  }
504
504
 
505
- export function appendArtifact(run: ProtocolRun, artifact: ProtocolRunArtifact, deps?: ProtocolRunDeps): ProtocolRun {
505
+ export function appendArtifact(
506
+ run: ProtocolRun,
507
+ artifact: ProtocolRunArtifact,
508
+ deps?: ProtocolRunDeps,
509
+ options?: { citations?: import('@/types').KnowledgeCitation[] },
510
+ ): ProtocolRun {
506
511
  const next = persistRun({
507
512
  ...run,
508
513
  artifacts: [...(run.artifacts || []), artifact],
@@ -518,6 +523,7 @@ export function appendArtifact(run: ProtocolRun, artifact: ProtocolRunArtifact,
518
523
  phaseId: artifact.phaseId || null,
519
524
  artifactId: artifact.id,
520
525
  summary: `Emitted ${artifact.kind.replace(/_/g, ' ')}: ${artifact.title}`,
526
+ citations: options?.citations,
521
527
  }, deps)
522
528
  return next
523
529
  }
@@ -97,7 +97,12 @@ export async function collectResponses(
97
97
  for (const agentId of current.participantAgentIds) {
98
98
  if (responded.has(agentId)) continue
99
99
  renewProtocolLease(current.id)
100
- let response: { text: string; toolEvents: import('@/types').MessageToolEvent[] }
100
+ let response: {
101
+ text: string
102
+ toolEvents: import('@/types').MessageToolEvent[]
103
+ citations?: import('@/types').KnowledgeCitation[]
104
+ retrievalTrace?: import('@/types').KnowledgeRetrievalTrace | null
105
+ }
101
106
  try {
102
107
  response = await executeAgentTurn({
103
108
  run: current,
@@ -116,7 +121,13 @@ export async function collectResponses(
116
121
  response = { text: `[Agent error: ${errMsg}]`, toolEvents: [] }
117
122
  }
118
123
  responded.add(agentId)
119
- cachedResponses.push({ agentId, text: response.text, toolEvents: response.toolEvents })
124
+ cachedResponses.push({
125
+ agentId,
126
+ text: response.text,
127
+ toolEvents: response.toolEvents,
128
+ citations: response.citations,
129
+ retrievalTrace: response.retrievalTrace,
130
+ })
120
131
  current = persistRun({
121
132
  ...current,
122
133
  phaseState: {
@@ -143,6 +154,7 @@ export async function collectResponses(
143
154
  phaseId: phase.id,
144
155
  agentId,
145
156
  summary: `Captured a response from ${participantAgents[agentId]?.name || agentId}.`,
157
+ citations: response.citations,
146
158
  }, deps)
147
159
  }
148
160
  }
@@ -163,6 +175,7 @@ export async function collectResponses(
163
175
  phaseId: phase.id,
164
176
  agentId: response.agentId,
165
177
  summary: `Captured an independent response from ${participantAgents[response.agentId]?.name || response.agentId}.`,
178
+ citations: response.citations,
166
179
  }, deps)
167
180
  }
168
181
  current = persistRun({
@@ -210,7 +223,7 @@ export async function processFacilitatorArtifactPhase(
210
223
  ...(result.toolEvents.length > 0 ? { toolEvents: result.toolEvents } : {}),
211
224
  }, deps)
212
225
  }
213
- const updated = appendArtifact(run, artifact, deps)
226
+ const updated = appendArtifact(run, artifact, deps, { citations: result.citations })
214
227
  return finishPhase(updated, phase, deps)
215
228
  }
216
229
 
@@ -5,6 +5,8 @@
5
5
  import type {
6
6
  BoardTask,
7
7
  Chatroom,
8
+ KnowledgeCitation,
9
+ KnowledgeRetrievalTrace,
8
10
  MessageToolEvent,
9
11
  ProtocolBranchCase,
10
12
  ProtocolPhaseDefinition,
@@ -72,6 +74,8 @@ export interface UpsertProtocolTemplateInput {
72
74
  export interface ProtocolAgentTurnResult {
73
75
  text: string
74
76
  toolEvents: MessageToolEvent[]
77
+ citations?: KnowledgeCitation[]
78
+ retrievalTrace?: KnowledgeRetrievalTrace | null
75
79
  }
76
80
 
77
81
  export interface ProtocolRunDeps {
@@ -1223,9 +1223,14 @@ function stopConnectorHealthMonitor() {
1223
1223
 
1224
1224
  function runConsolidationTick() {
1225
1225
  import('@/lib/server/memory/memory-consolidation').then(({ runDailyConsolidation, registerConsolidationIdleCallback, registerCompactionIdleCallback }) => {
1226
- // Wire idle-window callbacks so consolidation and compaction run during quiet periods
1226
+ // Wire idle-window callbacks so consolidation, compaction, and dreaming run during quiet periods
1227
1227
  registerConsolidationIdleCallback()
1228
1228
  registerCompactionIdleCallback()
1229
+ import('@/lib/server/memory/dream-idle-callback').then(({ registerDreamIdleCallback }) => {
1230
+ registerDreamIdleCallback()
1231
+ }).catch((err: unknown) => {
1232
+ log.error(TAG, '[daemon] Dream idle callback registration failed:', errorMessage(err))
1233
+ })
1229
1234
 
1230
1235
  return runDailyConsolidation().then((stats) => {
1231
1236
  if (stats.digests > 0 || stats.pruned > 0 || stats.deduped > 0) {
@@ -0,0 +1,120 @@
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('buildRetrievalSummary preserves citation count and dedupes source ids', () => {
6
+ const output = runWithTempDataDir<{
7
+ empty: unknown
8
+ summary: { citationCount: number; sourceIds: string[] } | null
9
+ }>(`
10
+ const ledgerMod = await import('./src/lib/server/runtime/run-ledger.ts')
11
+ const ledger = ledgerMod.default || ledgerMod
12
+
13
+ const summary = ledger.buildRetrievalSummary([
14
+ { sourceId: 'source-a' },
15
+ { sourceId: 'source-a' },
16
+ { sourceId: 'source-b' },
17
+ ])
18
+
19
+ console.log(JSON.stringify({
20
+ empty: ledger.buildRetrievalSummary([]),
21
+ summary,
22
+ }))
23
+ `, { prefix: 'swarmclaw-run-ledger-summary-' })
24
+
25
+ assert.equal(output.empty, null)
26
+ assert.deepEqual(output.summary, {
27
+ citationCount: 3,
28
+ sourceIds: ['source-a', 'source-b'],
29
+ })
30
+ })
31
+
32
+ test('appendPersistedRunEvent stores citations and retrieval traces', () => {
33
+ const output = runWithTempDataDir<{
34
+ eventCount: number
35
+ citationCount: number
36
+ selectorStatus: string | null
37
+ sourceIds: string[]
38
+ }>(`
39
+ const ledgerMod = await import('./src/lib/server/runtime/run-ledger.ts')
40
+ const storageMod = await import('./src/lib/server/storage.ts')
41
+ const ledger = ledgerMod.default || ledgerMod
42
+ const storage = storageMod.default || storageMod
43
+
44
+ ledger.persistRun({
45
+ id: 'run-grounded',
46
+ sessionId: 'session-1',
47
+ source: 'chat',
48
+ internal: false,
49
+ mode: 'followup',
50
+ status: 'completed',
51
+ messagePreview: 'grounded response',
52
+ queuedAt: 1,
53
+ retrievalSummary: null,
54
+ })
55
+
56
+ ledger.appendPersistedRunEvent({
57
+ runId: 'run-grounded',
58
+ sessionId: 'session-1',
59
+ phase: 'status',
60
+ status: 'completed',
61
+ citations: [{
62
+ sourceId: 'source-a',
63
+ sourceTitle: 'Gateway Runbook',
64
+ sourceKind: 'manual',
65
+ sourceUrl: null,
66
+ sourceLabel: null,
67
+ chunkId: 'chunk-1',
68
+ chunkIndex: 0,
69
+ chunkCount: 1,
70
+ charStart: 0,
71
+ charEnd: 42,
72
+ sectionLabel: null,
73
+ snippet: 'Use blue green deployment for gateway changes.',
74
+ whyMatched: 'Matched query terms: gateway, deployment',
75
+ score: 0.91,
76
+ }],
77
+ retrievalTrace: {
78
+ query: 'gateway deployment',
79
+ scope: 'source_knowledge',
80
+ hits: [{
81
+ sourceId: 'source-a',
82
+ sourceTitle: 'Gateway Runbook',
83
+ sourceKind: 'manual',
84
+ sourceUrl: null,
85
+ sourceLabel: null,
86
+ chunkId: 'chunk-1',
87
+ chunkIndex: 0,
88
+ chunkCount: 1,
89
+ charStart: 0,
90
+ charEnd: 42,
91
+ sectionLabel: null,
92
+ snippet: 'Use blue green deployment for gateway changes.',
93
+ whyMatched: 'Matched query terms: gateway, deployment',
94
+ score: 0.91,
95
+ }],
96
+ retrievedAt: 123,
97
+ selectorStatus: 'selected',
98
+ },
99
+ event: {
100
+ t: 'md',
101
+ text: '{"run":{"status":"completed"}}',
102
+ },
103
+ })
104
+
105
+ const events = Object.values(storage.loadRuntimeRunEvents())
106
+ const stored = events[0]
107
+
108
+ console.log(JSON.stringify({
109
+ eventCount: events.length,
110
+ citationCount: Array.isArray(stored?.citations) ? stored.citations.length : 0,
111
+ selectorStatus: stored?.retrievalTrace?.selectorStatus || null,
112
+ sourceIds: Array.isArray(stored?.citations) ? stored.citations.map((citation) => citation.sourceId) : [],
113
+ }))
114
+ `, { prefix: 'swarmclaw-run-ledger-events-' })
115
+
116
+ assert.equal(output.eventCount, 1)
117
+ assert.equal(output.citationCount, 1)
118
+ assert.equal(output.selectorStatus, 'selected')
119
+ assert.deepEqual(output.sourceIds, ['source-a'])
120
+ })
@@ -1,5 +1,12 @@
1
1
  import { genId } from '@/lib/id'
2
- import type { RunEventRecord, SessionRunRecord, SessionRunStatus, SSEEvent } from '@/types'
2
+ import type {
3
+ KnowledgeCitation,
4
+ KnowledgeRetrievalTrace,
5
+ RunEventRecord,
6
+ SessionRunRecord,
7
+ SessionRunStatus,
8
+ SSEEvent,
9
+ } from '@/types'
3
10
  import {
4
11
  deleteRuntimeRun,
5
12
  deleteRuntimeRunEvent,
@@ -26,6 +33,21 @@ function now(): number {
26
33
  return Date.now()
27
34
  }
28
35
 
36
+ export function buildRetrievalSummary(
37
+ citations?: KnowledgeCitation[] | null,
38
+ ): SessionRunRecord['retrievalSummary'] {
39
+ if (!Array.isArray(citations) || citations.length === 0) return null
40
+ const sourceIds = Array.from(new Set(
41
+ citations
42
+ .map((citation) => (typeof citation.sourceId === 'string' ? citation.sourceId.trim() : ''))
43
+ .filter(Boolean),
44
+ ))
45
+ return {
46
+ citationCount: citations.length,
47
+ sourceIds,
48
+ }
49
+ }
50
+
29
51
  function summarizeEvent(event: SSEEvent): string | undefined {
30
52
  const raw = event.text || event.toolOutput || event.toolInput || event.toolName || ''
31
53
  if (!raw) return undefined
@@ -82,6 +104,8 @@ export function appendPersistedRunEvent(input: {
82
104
  event: SSEEvent
83
105
  timestamp?: number
84
106
  summary?: string
107
+ citations?: KnowledgeCitation[]
108
+ retrievalTrace?: KnowledgeRetrievalTrace | null
85
109
  }): RunEventRecord {
86
110
  const timestamp = typeof input.timestamp === 'number' && Number.isFinite(input.timestamp)
87
111
  ? Math.trunc(input.timestamp)
@@ -99,6 +123,8 @@ export function appendPersistedRunEvent(input: {
99
123
  status: input.status,
100
124
  summary: input.summary || summarizeEvent(input.event),
101
125
  event: input.event,
126
+ citations: input.citations,
127
+ retrievalTrace: input.retrievalTrace || undefined,
102
128
  }
103
129
  upsertRuntimeRunEvent(record.id, record)
104
130
  return record
@@ -4,6 +4,7 @@ import { isInternalHeartbeatRun } from '@/lib/server/runtime/heartbeat-source'
4
4
  import { notify } from '@/lib/server/ws-hub'
5
5
  import { errorMessage } from '@/lib/shared-utils'
6
6
  import { handleMainLoopRunResult } from '@/lib/server/agents/main-agent-loop'
7
+ import { buildRetrievalSummary } from '@/lib/server/runtime/run-ledger'
7
8
 
8
9
  import {
9
10
  clearDeferredDrain,
@@ -119,6 +120,7 @@ export async function drainExecution(
119
120
  next.run.endedAt = next.run.endedAt || now()
120
121
  next.run.error = aborted ? (next.run.error || 'Cancelled') : result.error
121
122
  next.run.resultPreview = result.text?.slice(0, 280)
123
+ next.run.retrievalSummary = buildRetrievalSummary(result.citations)
122
124
  if (typeof result.inputTokens === 'number') next.run.totalInputTokens = result.inputTokens
123
125
  if (typeof result.outputTokens === 'number') next.run.totalOutputTokens = result.outputTokens
124
126
  if (typeof result.estimatedCost === 'number') next.run.estimatedCost = result.estimatedCost
@@ -127,6 +129,9 @@ export async function drainExecution(
127
129
  persisted: result.persisted,
128
130
  hasText: !!result.text,
129
131
  error: next.run.error || null,
132
+ }, {
133
+ citations: result.citations,
134
+ retrievalTrace: result.retrievalTrace,
130
135
  })
131
136
  log.info('session-run', `Run finished ${next.run.id}`, {
132
137
  sessionId: next.run.sessionId,
@@ -113,6 +113,8 @@ export function persistEventForRun(entry: SessionRunQueueEntry, event: SSEEvent,
113
113
  phase?: RunEventRecord['phase']
114
114
  status?: SessionRunStatus
115
115
  summary?: string
116
+ citations?: import('@/types').KnowledgeCitation[]
117
+ retrievalTrace?: import('@/types').KnowledgeRetrievalTrace | null
116
118
  }): void {
117
119
  if (!shouldPersistRunEvent(event)) return
118
120
  appendPersistedRunEvent({
@@ -125,6 +127,8 @@ export function persistEventForRun(entry: SessionRunQueueEntry, event: SSEEvent,
125
127
  phase: opts?.phase || 'event',
126
128
  status: opts?.status,
127
129
  summary: opts?.summary,
130
+ citations: opts?.citations,
131
+ retrievalTrace: opts?.retrievalTrace || null,
128
132
  event,
129
133
  })
130
134
  }
@@ -149,7 +153,15 @@ export function emitToSubscribers(entry: SessionRunQueueEntry, event: SSEEvent)
149
153
  }
150
154
  }
151
155
 
152
- export function emitRunMeta(entry: SessionRunQueueEntry, status: SessionRunStatus, extra?: Record<string, unknown>) {
156
+ export function emitRunMeta(
157
+ entry: SessionRunQueueEntry,
158
+ status: SessionRunStatus,
159
+ extra?: Record<string, unknown>,
160
+ persist?: {
161
+ citations?: import('@/types').KnowledgeCitation[]
162
+ retrievalTrace?: import('@/types').KnowledgeRetrievalTrace | null
163
+ },
164
+ ) {
153
165
  const event: SSEEvent = {
154
166
  t: 'md',
155
167
  text: JSON.stringify({
@@ -163,7 +175,12 @@ export function emitRunMeta(entry: SessionRunQueueEntry, status: SessionRunStatu
163
175
  },
164
176
  }),
165
177
  }
166
- persistEventForRun(entry, event, { phase: 'status', status })
178
+ persistEventForRun(entry, event, {
179
+ phase: 'status',
180
+ status,
181
+ citations: persist?.citations,
182
+ retrievalTrace: persist?.retrievalTrace || null,
183
+ })
167
184
  for (const send of entry.onEvents) {
168
185
  try {
169
186
  send(event)
@@ -507,6 +507,11 @@ function normalizeStoredRecordInner(
507
507
  agent.delegationEnabled = false
508
508
  agent.heartbeatEnabled = false
509
509
  }
510
+ // Dreaming defaults
511
+ if (agent.dreamEnabled === undefined) agent.dreamEnabled = false
512
+ if (agent.dreamConfig === undefined) agent.dreamConfig = null
513
+ if (agent.lastDreamAt === undefined) agent.lastDreamAt = null
514
+ if (typeof agent.dreamCycleCount !== 'number') agent.dreamCycleCount = 0
510
515
  // Persisted spend rollup defaults
511
516
  if (typeof agent.spentMonthlyCents !== 'number') agent.spentMonthlyCents = 0
512
517
  if (typeof agent.spentDailyCents !== 'number') agent.spentDailyCents = 0
@@ -22,6 +22,7 @@ import type {
22
22
  ExternalAgentRuntime,
23
23
  GatewayProfile,
24
24
  GuardianCheckpoint,
25
+ KnowledgeSource,
25
26
  LearnedSkill,
26
27
  Message,
27
28
  ProtocolTemplate,
@@ -126,6 +127,7 @@ const COLLECTIONS = [
126
127
  'connectors',
127
128
  'documents',
128
129
  'document_revisions',
130
+ 'knowledge_sources',
129
131
  'webhooks',
130
132
  'model_overrides',
131
133
  'mcp_servers',
@@ -1429,6 +1431,19 @@ const documentRevisionsStore = createCollectionStore('document_revisions')
1429
1431
  export const loadDocumentRevisions = documentRevisionsStore.load
1430
1432
  export const upsertDocumentRevision = documentRevisionsStore.upsert
1431
1433
 
1434
+ // --- Knowledge Sources ---
1435
+ const knowledgeSourcesStore = createCollectionStore('knowledge_sources')
1436
+ export const loadKnowledgeSources = knowledgeSourcesStore.load as () => Record<string, KnowledgeSource>
1437
+ export const saveKnowledgeSources = knowledgeSourcesStore.save as (data: Record<string, KnowledgeSource>) => void
1438
+ export const loadKnowledgeSource = knowledgeSourcesStore.loadItem as (id: string) => KnowledgeSource | null
1439
+ export const upsertKnowledgeSource = knowledgeSourcesStore.upsert as (id: string, value: KnowledgeSource) => void
1440
+ export const upsertKnowledgeSources = knowledgeSourcesStore.upsertMany
1441
+ export const patchKnowledgeSource = knowledgeSourcesStore.patch as (
1442
+ id: string,
1443
+ updater: (current: KnowledgeSource | null) => KnowledgeSource | null,
1444
+ ) => KnowledgeSource | null
1445
+ export const deleteKnowledgeSource = knowledgeSourcesStore.deleteItem
1446
+
1432
1447
  // --- Webhooks ---
1433
1448
  const webhooksStore = createCollectionStore('webhooks')
1434
1449
  export const loadWebhooks = webhooksStore.load
@@ -10,19 +10,32 @@ export function runWithTempDataDir<T = unknown>(
10
10
  script: string,
11
11
  options: {
12
12
  prefix?: string
13
+ dataDir?: string
14
+ workspaceDir?: string
15
+ browserProfilesDir?: string
13
16
  } = {},
14
17
  ): T {
15
18
  const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), options.prefix || 'swarmclaw-test-'))
16
- const workspaceDir = path.join(tempDir, 'workspace')
19
+ const resolveTempPath = (value: string | undefined, fallback: string): string =>
20
+ path.isAbsolute(value || '') ? String(value) : path.join(tempDir, value || fallback)
21
+ const dataDir = resolveTempPath(options.dataDir, '')
22
+ const workspaceDir = resolveTempPath(options.workspaceDir, 'workspace')
23
+ const browserProfilesDir = options.browserProfilesDir
24
+ ? resolveTempPath(options.browserProfilesDir, 'browser-profiles')
25
+ : null
26
+
27
+ fs.mkdirSync(dataDir, { recursive: true })
17
28
  fs.mkdirSync(workspaceDir, { recursive: true })
29
+ if (browserProfilesDir) fs.mkdirSync(browserProfilesDir, { recursive: true })
18
30
 
19
31
  try {
20
32
  const result = spawnSync(process.execPath, ['--import', 'tsx', '--input-type=module', '--eval', script], {
21
33
  cwd: repoRoot,
22
34
  env: {
23
35
  ...process.env,
24
- DATA_DIR: tempDir,
36
+ DATA_DIR: dataDir,
25
37
  WORKSPACE_DIR: workspaceDir,
38
+ ...(browserProfilesDir ? { BROWSER_PROFILES_DIR: browserProfilesDir } : {}),
26
39
  },
27
40
  encoding: 'utf-8',
28
41
  })
@@ -71,6 +71,8 @@ export interface UiSlice {
71
71
  setKnowledgeSheetOpen: (open: boolean) => void
72
72
  editingKnowledgeId: string | null
73
73
  setEditingKnowledgeId: (id: string | null) => void
74
+ selectedKnowledgeSourceId: string | null
75
+ setSelectedKnowledgeSourceId: (id: string | null) => void
74
76
  knowledgeRefreshKey: number
75
77
  triggerKnowledgeRefresh: () => void
76
78
  extensionSheetOpen: boolean
@@ -169,6 +171,8 @@ export const createUiSlice: StateCreator<AppState, [], [], UiSlice> = (set, get)
169
171
  setKnowledgeSheetOpen: (open) => set({ knowledgeSheetOpen: open }),
170
172
  editingKnowledgeId: null,
171
173
  setEditingKnowledgeId: (id) => set({ editingKnowledgeId: id }),
174
+ selectedKnowledgeSourceId: null,
175
+ setSelectedKnowledgeSourceId: (id) => set({ selectedKnowledgeSourceId: id }),
172
176
  knowledgeRefreshKey: 0,
173
177
  triggerKnowledgeRefresh: () => set((s) => ({ knowledgeRefreshKey: s.knowledgeRefreshKey + 1 })),
174
178
  extensionSheetOpen: false,
@@ -1,6 +1,7 @@
1
1
  import type { ProviderId, ProviderType, OllamaMode } from './provider'
2
2
  import type { SessionResetMode, IdentityContinuityState } from './session'
3
3
  import type { SkillAllowlistMode } from './skill'
4
+ import type { DreamConfig } from './dream'
4
5
 
5
6
  // --- Agent / Delegation ---
6
7
 
@@ -164,6 +165,12 @@ export interface Agent {
164
165
  lastSpendRollupAt?: number
165
166
  maxFollowupChain?: number
166
167
 
168
+ // Dreaming (idle-time memory consolidation)
169
+ dreamEnabled?: boolean
170
+ dreamConfig?: Partial<DreamConfig> | null
171
+ lastDreamAt?: number | null
172
+ dreamCycleCount?: number
173
+
167
174
  // Orchestrator Mode
168
175
  orchestratorEnabled?: boolean
169
176
  orchestratorMission?: string
@@ -0,0 +1,45 @@
1
+ // --- Dreaming (idle-time memory consolidation) ---
2
+
3
+ export type DreamStatus = 'pending' | 'running' | 'completed' | 'failed'
4
+ export type DreamTrigger = 'idle' | 'manual'
5
+
6
+ export interface DreamCycleResult {
7
+ decayed: number
8
+ pruned: number
9
+ promoted: number
10
+ deduped: number
11
+ consolidated: number
12
+ reflections: string[]
13
+ memoriesReviewed: number
14
+ durationMs: number
15
+ errors: string[]
16
+ }
17
+
18
+ export interface DreamCycle {
19
+ id: string
20
+ agentId: string
21
+ status: DreamStatus
22
+ trigger: DreamTrigger
23
+ startedAt: number
24
+ completedAt?: number | null
25
+ result?: DreamCycleResult | null
26
+ error?: string | null
27
+ }
28
+
29
+ export interface DreamConfig {
30
+ enabled: boolean
31
+ cooldownMinutes: number
32
+ decayAgeDays: number
33
+ pruneThresholdDays: number
34
+ tier2Enabled: boolean
35
+ tier2MaxMemories: number
36
+ }
37
+
38
+ export const DEFAULT_DREAM_CONFIG: DreamConfig = {
39
+ enabled: true,
40
+ cooldownMinutes: 360,
41
+ decayAgeDays: 30,
42
+ pruneThresholdDays: 90,
43
+ tier2Enabled: true,
44
+ tier2MaxMemories: 50,
45
+ }
@@ -15,3 +15,4 @@ export * from './approval'
15
15
  export * from './misc'
16
16
  export * from './goal'
17
17
  export * from './swarmdock'
18
+ export * from './dream'
@@ -1,4 +1,5 @@
1
1
  import type { MessageSource } from './connector'
2
+ import type { KnowledgeCitation, KnowledgeRetrievalTrace } from './misc'
2
3
 
3
4
  export interface MessageToolEvent {
4
5
  name: string
@@ -53,4 +54,6 @@ export interface Message {
53
54
  runId?: string
54
55
  /** Cached turn semantics used for routing, delegation, and reflection. */
55
56
  semantics?: MessageSemanticsSummary
57
+ citations?: KnowledgeCitation[]
58
+ retrievalTrace?: KnowledgeRetrievalTrace | null
56
59
  }
package/src/types/misc.ts CHANGED
@@ -469,6 +469,137 @@ export interface MemoryEntry {
469
469
  updatedAt: number
470
470
  }
471
471
 
472
+ export type KnowledgeSourceKind = 'manual' | 'file' | 'url'
473
+ export type KnowledgeSyncStatus = 'ready' | 'syncing' | 'error'
474
+ export type KnowledgeHygieneActionKind = 'sync' | 'reindex' | 'archive' | 'restore' | 'supersede'
475
+ export type KnowledgeHygieneFindingKind = 'stale' | 'duplicate' | 'overlap' | 'broken' | 'archived' | 'superseded'
476
+
477
+ export interface KnowledgeCitation {
478
+ sourceId: string
479
+ sourceTitle: string
480
+ sourceKind: KnowledgeSourceKind
481
+ sourceUrl?: string | null
482
+ sourceLabel?: string | null
483
+ chunkId: string
484
+ chunkIndex: number
485
+ chunkCount: number
486
+ charStart: number
487
+ charEnd: number
488
+ sectionLabel?: string | null
489
+ snippet: string
490
+ whyMatched?: string | null
491
+ score: number
492
+ }
493
+
494
+ export interface KnowledgeRetrievalTrace {
495
+ query: string
496
+ scope: 'source_knowledge'
497
+ hits: KnowledgeCitation[]
498
+ retrievedAt: number
499
+ selectorStatus?: 'not_run' | 'selected' | 'no_match'
500
+ }
501
+
502
+ export interface KnowledgeSource {
503
+ id: string
504
+ kind: KnowledgeSourceKind
505
+ title: string
506
+ content?: string | null
507
+ sourceLabel?: string | null
508
+ sourceUrl?: string | null
509
+ sourcePath?: string | null
510
+ sourceHash?: string | null
511
+ scope: 'global' | 'agent'
512
+ agentIds: string[]
513
+ tags: string[]
514
+ syncStatus: KnowledgeSyncStatus
515
+ lastIndexedAt?: number | null
516
+ lastSyncedAt?: number | null
517
+ lastError?: string | null
518
+ archivedAt?: number | null
519
+ archivedReason?: string | null
520
+ duplicateOfSourceId?: string | null
521
+ supersededBySourceId?: string | null
522
+ maintenanceUpdatedAt?: number | null
523
+ maintenanceNotes?: string | null
524
+ nextSyncAt?: number | null
525
+ lastAutoSyncAt?: number | null
526
+ chunkCount: number
527
+ contentLength: number
528
+ createdAt: number
529
+ updatedAt: number
530
+ metadata?: Record<string, unknown>
531
+ }
532
+
533
+ export interface KnowledgeSearchHit {
534
+ id: string
535
+ sourceId: string
536
+ sourceTitle: string
537
+ sourceKind: KnowledgeSourceKind
538
+ sourceUrl?: string | null
539
+ sourceLabel?: string | null
540
+ scope: 'global' | 'agent'
541
+ agentIds: string[]
542
+ tags: string[]
543
+ syncStatus: KnowledgeSyncStatus
544
+ stale: boolean
545
+ title: string
546
+ snippet: string
547
+ content: string
548
+ chunkIndex: number
549
+ chunkCount: number
550
+ charStart: number
551
+ charEnd: number
552
+ sectionLabel?: string | null
553
+ score: number
554
+ whyMatched?: string | null
555
+ createdAt: number
556
+ updatedAt: number
557
+ }
558
+
559
+ export interface KnowledgeSourceSummary extends KnowledgeSource {
560
+ stale: boolean
561
+ topSnippet?: string | null
562
+ matchCount?: number
563
+ topScore?: number
564
+ }
565
+
566
+ export interface KnowledgeSourceDetail {
567
+ source: KnowledgeSourceSummary
568
+ chunks: MemoryEntry[]
569
+ }
570
+
571
+ export interface KnowledgeHygieneFinding {
572
+ kind: KnowledgeHygieneFindingKind
573
+ sourceId: string
574
+ sourceTitle: string
575
+ relatedSourceId?: string | null
576
+ relatedSourceTitle?: string | null
577
+ detail: string
578
+ createdAt: number
579
+ }
580
+
581
+ export interface KnowledgeHygieneAction {
582
+ kind: KnowledgeHygieneActionKind
583
+ sourceId: string
584
+ relatedSourceId?: string | null
585
+ summary: string
586
+ createdAt: number
587
+ }
588
+
589
+ export interface KnowledgeHygieneSummary {
590
+ scannedAt: number
591
+ counts: {
592
+ stale: number
593
+ duplicate: number
594
+ overlap: number
595
+ broken: number
596
+ archived: number
597
+ superseded: number
598
+ }
599
+ findings: KnowledgeHygieneFinding[]
600
+ recentActions: KnowledgeHygieneAction[]
601
+ }
602
+
472
603
  // --- MCP Servers ---
473
604
 
474
605
  export type McpTransport = 'stdio' | 'sse' | 'streamable-http'