@swarmclawai/swarmclaw 0.6.8 → 0.7.0

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 (166) hide show
  1. package/README.md +70 -45
  2. package/next.config.ts +31 -6
  3. package/package.json +3 -2
  4. package/src/app/api/agents/[id]/thread/route.ts +1 -0
  5. package/src/app/api/agents/route.ts +18 -5
  6. package/src/app/api/approvals/route.ts +22 -0
  7. package/src/app/api/clawhub/install/route.ts +2 -2
  8. package/src/app/api/mcp-servers/[id]/conformance/route.ts +26 -0
  9. package/src/app/api/mcp-servers/[id]/invoke/route.ts +81 -0
  10. package/src/app/api/memory/route.ts +36 -5
  11. package/src/app/api/notifications/route.ts +3 -0
  12. package/src/app/api/plugins/install/route.ts +57 -5
  13. package/src/app/api/plugins/marketplace/route.ts +73 -22
  14. package/src/app/api/plugins/route.ts +61 -1
  15. package/src/app/api/plugins/ui/route.ts +34 -0
  16. package/src/app/api/settings/route.ts +62 -0
  17. package/src/app/api/setup/doctor/route.ts +22 -5
  18. package/src/app/api/tasks/[id]/approve/route.ts +4 -3
  19. package/src/app/api/tasks/[id]/route.ts +11 -3
  20. package/src/app/api/tasks/route.ts +8 -2
  21. package/src/app/globals.css +27 -0
  22. package/src/app/page.tsx +10 -5
  23. package/src/cli/index.js +13 -0
  24. package/src/components/activity/activity-feed.tsx +9 -2
  25. package/src/components/agents/agent-avatar.tsx +5 -1
  26. package/src/components/agents/agent-card.tsx +55 -9
  27. package/src/components/agents/agent-sheet.tsx +86 -29
  28. package/src/components/agents/inspector-panel.tsx +1 -1
  29. package/src/components/auth/access-key-gate.tsx +63 -54
  30. package/src/components/auth/user-picker.tsx +37 -32
  31. package/src/components/chat/chat-area.tsx +11 -0
  32. package/src/components/chat/chat-header.tsx +69 -25
  33. package/src/components/chat/chat-tool-toggles.tsx +2 -2
  34. package/src/components/chat/code-block.tsx +3 -1
  35. package/src/components/chat/exec-approval-card.tsx +8 -1
  36. package/src/components/chat/message-bubble.tsx +164 -4
  37. package/src/components/chat/message-list.tsx +30 -4
  38. package/src/components/chat/session-approval-card.tsx +80 -0
  39. package/src/components/chat/streaming-bubble.tsx +6 -5
  40. package/src/components/chat/thinking-indicator.tsx +48 -12
  41. package/src/components/chat/tool-request-banner.tsx +39 -20
  42. package/src/components/chatrooms/chatroom-list.tsx +11 -4
  43. package/src/components/chatrooms/chatroom-sheet.tsx +7 -2
  44. package/src/components/connectors/connector-list.tsx +33 -11
  45. package/src/components/connectors/connector-sheet.tsx +29 -6
  46. package/src/components/home/home-view.tsx +20 -14
  47. package/src/components/input/chat-input.tsx +22 -1
  48. package/src/components/knowledge/knowledge-list.tsx +17 -18
  49. package/src/components/knowledge/knowledge-sheet.tsx +9 -5
  50. package/src/components/layout/app-layout.tsx +73 -21
  51. package/src/components/mcp-servers/mcp-server-list.tsx +352 -50
  52. package/src/components/mcp-servers/mcp-server-sheet.tsx +25 -9
  53. package/src/components/memory/memory-list.tsx +20 -13
  54. package/src/components/plugins/plugin-list.tsx +213 -59
  55. package/src/components/plugins/plugin-sheet.tsx +119 -24
  56. package/src/components/projects/project-list.tsx +17 -9
  57. package/src/components/providers/provider-list.tsx +21 -6
  58. package/src/components/providers/provider-sheet.tsx +42 -25
  59. package/src/components/runs/run-list.tsx +17 -13
  60. package/src/components/schedules/schedule-card.tsx +10 -3
  61. package/src/components/schedules/schedule-list.tsx +2 -2
  62. package/src/components/schedules/schedule-sheet.tsx +19 -7
  63. package/src/components/secrets/secret-sheet.tsx +7 -2
  64. package/src/components/secrets/secrets-list.tsx +18 -5
  65. package/src/components/sessions/new-session-sheet.tsx +183 -376
  66. package/src/components/sessions/session-card.tsx +10 -2
  67. package/src/components/settings/gateway-connection-panel.tsx +9 -8
  68. package/src/components/shared/command-palette.tsx +13 -5
  69. package/src/components/shared/empty-state.tsx +20 -8
  70. package/src/components/shared/notification-center.tsx +134 -86
  71. package/src/components/shared/profile-sheet.tsx +4 -0
  72. package/src/components/shared/settings/plugin-manager.tsx +360 -135
  73. package/src/components/shared/settings/section-capability-policy.tsx +3 -3
  74. package/src/components/shared/settings/section-runtime-loop.tsx +144 -0
  75. package/src/components/skills/clawhub-browser.tsx +1 -0
  76. package/src/components/skills/skill-list.tsx +31 -12
  77. package/src/components/skills/skill-sheet.tsx +20 -7
  78. package/src/components/tasks/approvals-panel.tsx +170 -66
  79. package/src/components/tasks/task-board.tsx +20 -12
  80. package/src/components/tasks/task-card.tsx +21 -7
  81. package/src/components/tasks/task-column.tsx +4 -3
  82. package/src/components/tasks/task-list.tsx +1 -1
  83. package/src/components/tasks/task-sheet.tsx +130 -1
  84. package/src/components/ui/dialog.tsx +1 -0
  85. package/src/components/ui/sheet.tsx +1 -0
  86. package/src/components/usage/metrics-dashboard.tsx +66 -64
  87. package/src/components/wallets/wallet-panel.tsx +65 -41
  88. package/src/components/wallets/wallet-section.tsx +9 -3
  89. package/src/components/webhooks/webhook-list.tsx +21 -12
  90. package/src/components/webhooks/webhook-sheet.tsx +13 -3
  91. package/src/lib/approval-display.test.ts +45 -0
  92. package/src/lib/approval-display.ts +62 -0
  93. package/src/lib/clipboard.ts +38 -0
  94. package/src/lib/memory.ts +8 -0
  95. package/src/lib/providers/claude-cli.ts +5 -3
  96. package/src/lib/providers/index.ts +67 -21
  97. package/src/lib/runtime-loop.ts +3 -2
  98. package/src/lib/server/approvals.ts +150 -0
  99. package/src/lib/server/chat-execution.ts +223 -62
  100. package/src/lib/server/clawhub-client.ts +82 -6
  101. package/src/lib/server/connectors/manager.ts +27 -1
  102. package/src/lib/server/cost.test.ts +73 -0
  103. package/src/lib/server/cost.ts +165 -34
  104. package/src/lib/server/daemon-state.ts +42 -0
  105. package/src/lib/server/data-dir.ts +18 -1
  106. package/src/lib/server/integrity-monitor.ts +208 -0
  107. package/src/lib/server/llm-response-cache.test.ts +102 -0
  108. package/src/lib/server/llm-response-cache.ts +227 -0
  109. package/src/lib/server/main-agent-loop.ts +1 -1
  110. package/src/lib/server/main-session.ts +6 -3
  111. package/src/lib/server/mcp-conformance.test.ts +18 -0
  112. package/src/lib/server/mcp-conformance.ts +233 -0
  113. package/src/lib/server/memory-db.ts +180 -17
  114. package/src/lib/server/memory-retrieval.test.ts +56 -0
  115. package/src/lib/server/orchestrator-lg.ts +4 -1
  116. package/src/lib/server/orchestrator.ts +4 -3
  117. package/src/lib/server/plugins.ts +650 -142
  118. package/src/lib/server/process-manager.ts +18 -0
  119. package/src/lib/server/queue.ts +253 -11
  120. package/src/lib/server/runtime-settings.ts +9 -0
  121. package/src/lib/server/session-run-manager.test.ts +23 -0
  122. package/src/lib/server/session-run-manager.ts +11 -1
  123. package/src/lib/server/session-tools/canvas.ts +85 -50
  124. package/src/lib/server/session-tools/chatroom.ts +130 -127
  125. package/src/lib/server/session-tools/connector.ts +233 -454
  126. package/src/lib/server/session-tools/context-mgmt.ts +87 -105
  127. package/src/lib/server/session-tools/crud.ts +84 -7
  128. package/src/lib/server/session-tools/delegate.ts +351 -752
  129. package/src/lib/server/session-tools/discovery.ts +198 -0
  130. package/src/lib/server/session-tools/edit_file.ts +82 -0
  131. package/src/lib/server/session-tools/file-send.test.ts +39 -0
  132. package/src/lib/server/session-tools/file.ts +257 -425
  133. package/src/lib/server/session-tools/git.ts +87 -47
  134. package/src/lib/server/session-tools/http.ts +85 -33
  135. package/src/lib/server/session-tools/index.ts +205 -160
  136. package/src/lib/server/session-tools/memory.ts +152 -265
  137. package/src/lib/server/session-tools/monitor.ts +126 -0
  138. package/src/lib/server/session-tools/normalize-tool-args.test.ts +61 -0
  139. package/src/lib/server/session-tools/normalize-tool-args.ts +48 -0
  140. package/src/lib/server/session-tools/openclaw-nodes.ts +82 -99
  141. package/src/lib/server/session-tools/openclaw-workspace.ts +103 -93
  142. package/src/lib/server/session-tools/platform.ts +86 -0
  143. package/src/lib/server/session-tools/plugin-creator.ts +239 -0
  144. package/src/lib/server/session-tools/sample-ui.ts +97 -0
  145. package/src/lib/server/session-tools/sandbox.ts +175 -148
  146. package/src/lib/server/session-tools/schedule.ts +66 -31
  147. package/src/lib/server/session-tools/session-info.ts +104 -410
  148. package/src/lib/server/session-tools/shell-normalize.test.ts +43 -0
  149. package/src/lib/server/session-tools/shell.ts +171 -143
  150. package/src/lib/server/session-tools/subagent.ts +77 -77
  151. package/src/lib/server/session-tools/wallet.ts +182 -106
  152. package/src/lib/server/session-tools/web.ts +179 -349
  153. package/src/lib/server/storage.ts +24 -0
  154. package/src/lib/server/stream-agent-chat.ts +301 -244
  155. package/src/lib/server/task-quality-gate.test.ts +44 -0
  156. package/src/lib/server/task-quality-gate.ts +67 -0
  157. package/src/lib/server/task-validation.test.ts +78 -0
  158. package/src/lib/server/task-validation.ts +67 -2
  159. package/src/lib/server/tool-aliases.ts +68 -0
  160. package/src/lib/server/tool-capability-policy.ts +23 -5
  161. package/src/lib/tasks.ts +7 -1
  162. package/src/lib/tool-definitions.ts +23 -23
  163. package/src/lib/validation/schemas.ts +12 -0
  164. package/src/lib/view-routes.ts +2 -24
  165. package/src/stores/use-app-store.ts +23 -1
  166. package/src/types/index.ts +121 -7
@@ -34,6 +34,140 @@ export const MEMORY_FTS_STOP_WORDS = new Set([
34
34
  'you', 'your',
35
35
  ])
36
36
 
37
+ export type MemoryScopeMode = 'auto' | 'all' | 'global' | 'agent' | 'session' | 'project'
38
+ export type MemoryRerankMode = 'balanced' | 'semantic' | 'lexical'
39
+
40
+ export interface MemoryScopeFilter {
41
+ mode: MemoryScopeMode
42
+ agentId?: string | null
43
+ sessionId?: string | null
44
+ projectRoot?: string | null
45
+ }
46
+
47
+ export interface MemorySearchOptions {
48
+ scope?: MemoryScopeFilter
49
+ rerankMode?: MemoryRerankMode
50
+ }
51
+
52
+ function normalizeScopeIdentifier(value: unknown): string | null {
53
+ if (typeof value !== 'string') return null
54
+ const trimmed = value.trim()
55
+ return trimmed ? trimmed : null
56
+ }
57
+
58
+ function normalizePathForScope(value: unknown): string | null {
59
+ if (typeof value !== 'string') return null
60
+ const trimmed = value.trim()
61
+ if (!trimmed) return null
62
+ return path.normalize(trimmed).replace(/\\/g, '/').replace(/\/+$/, '').toLowerCase()
63
+ }
64
+
65
+ function memorySearchText(entry: MemoryEntry): string {
66
+ const refs = Array.isArray(entry.references)
67
+ ? entry.references.map((ref) => `${ref.type} ${ref.path || ''} ${ref.title || ''} ${ref.note || ''}`).join(' ')
68
+ : ''
69
+ return `${entry.title || ''} ${entry.content || ''} ${refs}`.toLowerCase()
70
+ }
71
+
72
+ function tokenizeForRerank(input: string): string[] {
73
+ const raw = String(input || '').toLowerCase().match(/[a-z0-9][a-z0-9._:/-]*/g) || []
74
+ const out: string[] = []
75
+ const seen = new Set<string>()
76
+ for (const token of raw) {
77
+ if (token.length < 2) continue
78
+ if (MEMORY_FTS_STOP_WORDS.has(token)) continue
79
+ if (seen.has(token)) continue
80
+ seen.add(token)
81
+ out.push(token)
82
+ }
83
+ return out
84
+ }
85
+
86
+ function keywordOverlapScore(queryTokens: string[], entryText: string): number {
87
+ if (!queryTokens.length) return 0
88
+ const corpus = entryText.toLowerCase()
89
+ let matched = 0
90
+ for (const token of queryTokens) {
91
+ if (token.length < 3) continue
92
+ if (corpus.includes(token)) matched++
93
+ }
94
+ return matched / queryTokens.length
95
+ }
96
+
97
+ function entryRootsForScope(entry: MemoryEntry): string[] {
98
+ const roots = new Set<string>()
99
+ const add = (raw: unknown) => {
100
+ const normalized = normalizePathForScope(raw)
101
+ if (normalized) roots.add(normalized)
102
+ }
103
+
104
+ add((entry.metadata as Record<string, unknown> | undefined)?.projectRoot)
105
+
106
+ if (Array.isArray(entry.references)) {
107
+ for (const ref of entry.references) {
108
+ add(ref.projectRoot)
109
+ if (ref.type === 'project') add(ref.path)
110
+ if (ref.type === 'folder' || ref.type === 'file') add(ref.path)
111
+ }
112
+ }
113
+
114
+ if (Array.isArray(entry.filePaths)) {
115
+ for (const ref of entry.filePaths) {
116
+ add(ref.projectRoot)
117
+ add(ref.path)
118
+ }
119
+ }
120
+
121
+ return [...roots]
122
+ }
123
+
124
+ function scopeAllowsAgentAccess(entry: MemoryEntry, agentId: string): boolean {
125
+ if (entry.agentId === agentId) return true
126
+ if (Array.isArray(entry.sharedWith) && entry.sharedWith.includes(agentId)) return true
127
+ return false
128
+ }
129
+
130
+ export function normalizeMemoryScopeMode(raw: unknown): MemoryScopeMode {
131
+ const value = typeof raw === 'string' ? raw.trim().toLowerCase() : ''
132
+ if (value === 'shared') return 'global'
133
+ if (value === 'all' || value === 'global' || value === 'agent' || value === 'session' || value === 'project') return value
134
+ return 'auto'
135
+ }
136
+
137
+ export function filterMemoriesByScope(entries: MemoryEntry[], scope?: MemoryScopeFilter): MemoryEntry[] {
138
+ if (!scope || scope.mode === 'all') return entries
139
+ const mode = normalizeMemoryScopeMode(scope.mode)
140
+ const agentId = normalizeScopeIdentifier(scope.agentId)
141
+ const sessionId = normalizeScopeIdentifier(scope.sessionId)
142
+ const projectRoot = normalizePathForScope(scope.projectRoot)
143
+
144
+ if (mode === 'global') {
145
+ return entries.filter((entry) => !entry.agentId)
146
+ }
147
+
148
+ if (mode === 'agent') {
149
+ if (!agentId) return []
150
+ return entries.filter((entry) => scopeAllowsAgentAccess(entry, agentId))
151
+ }
152
+
153
+ if (mode === 'session') {
154
+ if (!sessionId) return []
155
+ return entries.filter((entry) => entry.sessionId === sessionId)
156
+ }
157
+
158
+ if (mode === 'project') {
159
+ if (!projectRoot) return []
160
+ return entries.filter((entry) => {
161
+ const roots = entryRootsForScope(entry)
162
+ return roots.some((root) => root === projectRoot || root.startsWith(`${projectRoot}/`))
163
+ })
164
+ }
165
+
166
+ // auto
167
+ if (!agentId) return entries
168
+ return entries.filter((entry) => !entry.agentId || scopeAllowsAgentAccess(entry, agentId))
169
+ }
170
+
37
171
  function computeContentHash(category: string, content: string): string {
38
172
  const normalized = `${category}|${content.toLowerCase().trim()}`
39
173
  return createHash('sha256').update(normalized).digest('hex').slice(0, 16)
@@ -612,8 +746,8 @@ function initDb() {
612
746
  const getAllWithEmbeddings = db.prepare(
613
747
  `SELECT * FROM memories WHERE embedding IS NOT NULL`
614
748
  )
615
- const getAllWithEmbeddingsByAgent = db.prepare(
616
- `SELECT * FROM memories WHERE embedding IS NOT NULL AND agentId = ?`
749
+ const getAllWithEmbeddingsByAgentOrShared = db.prepare(
750
+ `SELECT * FROM memories WHERE embedding IS NOT NULL AND (agentId = ? OR sharedWith LIKE ?)`
617
751
  )
618
752
 
619
753
  return {
@@ -852,17 +986,35 @@ function initDb() {
852
986
  return this.get(id)
853
987
  },
854
988
 
855
- search(query: string, agentId?: string): MemoryEntry[] {
989
+ search(query: string, agentId?: string, options: MemorySearchOptions = {}): MemoryEntry[] {
856
990
  if (shouldSkipSearchQuery(query)) return []
857
991
  const startedAt = Date.now()
992
+ const normalizedAgentId = normalizeScopeIdentifier(agentId)
993
+ const rerankMode: MemoryRerankMode = options.rerankMode === 'semantic' || options.rerankMode === 'lexical'
994
+ ? options.rerankMode
995
+ : 'balanced'
996
+ const scopeMode = options.scope
997
+ ? normalizeMemoryScopeMode(options.scope.mode)
998
+ : (normalizedAgentId ? 'agent' : 'all')
999
+ const scopeFilter: MemoryScopeFilter | undefined = scopeMode === 'all'
1000
+ ? undefined
1001
+ : {
1002
+ mode: scopeMode,
1003
+ agentId: options.scope?.agentId ?? normalizedAgentId,
1004
+ sessionId: options.scope?.sessionId,
1005
+ projectRoot: options.scope?.projectRoot,
1006
+ }
1007
+
858
1008
  // FTS keyword search (includes memories shared with this agent)
859
1009
  const ftsQuery = buildFtsQuery(query)
1010
+ const fastAgentOnlyScope = scopeMode === 'agent' && !!normalizedAgentId
860
1011
  const ftsResults: MemoryEntry[] = ftsQuery
861
- ? (agentId
862
- ? stmts.searchByAgentOrShared.all(ftsQuery, agentId, `%"${agentId}"%`) as any[]
1012
+ ? (fastAgentOnlyScope
1013
+ ? stmts.searchByAgentOrShared.all(ftsQuery, normalizedAgentId, `%"${normalizedAgentId}"%`) as any[]
863
1014
  : stmts.search.all(ftsQuery) as any[]
864
1015
  ).map(rowToEntry)
865
1016
  : []
1017
+ const ftsHitIds = new Set<string>(ftsResults.map((entry) => entry.id))
866
1018
 
867
1019
  // Attempt vector search (synchronous — uses cached embedding if available)
868
1020
  const vectorSimilarityScores = new Map<string, number>()
@@ -873,8 +1025,8 @@ function initDb() {
873
1025
  const queryEmbedding = getEmbeddingSync(query)
874
1026
  queryEmbeddingResult = queryEmbedding || undefined
875
1027
  if (queryEmbedding) {
876
- const rows = agentId
877
- ? getAllWithEmbeddingsByAgent.all(agentId) as any[]
1028
+ const rows = fastAgentOnlyScope
1029
+ ? getAllWithEmbeddingsByAgentOrShared.all(normalizedAgentId, `%"${normalizedAgentId}"%`) as any[]
878
1030
  : getAllWithEmbeddings.all() as any[]
879
1031
 
880
1032
  const scored = rows
@@ -907,24 +1059,34 @@ function initDb() {
907
1059
  merged.push(entry)
908
1060
  }
909
1061
  }
1062
+ const scopedMerged = scopeFilter ? filterMemoriesByScope(merged, scopeFilter) : merged
910
1063
 
911
- // Apply salience scoring: similarity * recencyDecay * reinforcement * pinnedBoost
1064
+ // Retrieval v2 rerank: hybrid relevance (semantic + lexical + FTS signal), then salience decay/boosting.
1065
+ const queryTokens = tokenizeForRerank(query)
912
1066
  const now = Date.now()
913
1067
  const HALF_LIFE_DAYS = 30
914
- const salienceScored = merged.map((entry) => {
915
- const similarity = vectorSimilarityScores.get(entry.id) ?? 0.5
1068
+ const salienceScored = scopedMerged.map((entry) => {
1069
+ const semantic = vectorSimilarityScores.get(entry.id) ?? (ftsHitIds.has(entry.id) ? 0.42 : 0.18)
1070
+ const lexical = keywordOverlapScore(queryTokens, memorySearchText(entry))
1071
+ const ftsSignal = ftsHitIds.has(entry.id) ? 1 : 0
1072
+ const relevance = rerankMode === 'semantic'
1073
+ ? (semantic * 0.78 + ftsSignal * 0.22)
1074
+ : rerankMode === 'lexical'
1075
+ ? (lexical * 0.78 + ftsSignal * 0.22)
1076
+ : (semantic * 0.50 + lexical * 0.35 + ftsSignal * 0.15)
916
1077
  const daysSinceAccess = (now - (entry.lastAccessedAt || entry.updatedAt)) / 86_400_000
917
1078
  const recencyDecay = Math.exp(-0.693 * daysSinceAccess / HALF_LIFE_DAYS)
918
1079
  const reinforcement = Math.log((entry.reinforcementCount || 0) + 1) + 1
919
1080
  const pinnedBoost = entry.pinned ? 1.5 : 1.0
920
- const salience = similarity * recencyDecay * reinforcement * pinnedBoost
1081
+ const salience = Math.max(0.0001, relevance) * recencyDecay * reinforcement * pinnedBoost
921
1082
  return { entry, salience, embedding: rawEmbeddings.get(entry.id) }
922
1083
  })
923
-
924
- // Apply MMR if we have a query embedding, otherwise standard sort
1084
+
1085
+ // Apply MMR when semantic reranking is enabled and query embeddings are available.
925
1086
  let out: MemoryEntry[] = []
926
- if (queryEmbeddingResult) {
927
- out = applyMMR(queryEmbeddingResult, salienceScored, MAX_MERGED_RESULTS, 0.6) // Lambda 0.6 = favor relevance slightly over diversity
1087
+ const useMmr = !!queryEmbeddingResult && rerankMode !== 'lexical'
1088
+ if (useMmr && queryEmbeddingResult) {
1089
+ out = applyMMR(queryEmbeddingResult, salienceScored, MAX_MERGED_RESULTS, 0.6)
928
1090
  } else {
929
1091
  salienceScored.sort((a, b) => b.salience - a.salience)
930
1092
  out = salienceScored.slice(0, MAX_MERGED_RESULTS).map((s) => s.entry)
@@ -944,7 +1106,7 @@ function initDb() {
944
1106
  const elapsed = Date.now() - startedAt
945
1107
  if (elapsed > 1200) {
946
1108
  console.warn(
947
- `[memory-db] Slow search ${elapsed}ms (agent=${agentId || 'all'}, rawLen=${String(query || '').length}, fts="${ftsQuery.slice(0, 180)}")`,
1109
+ `[memory-db] Slow search ${elapsed}ms (scope=${scopeMode}, rerank=${rerankMode}, rawLen=${String(query || '').length}, fts="${ftsQuery.slice(0, 180)}")`,
948
1110
  )
949
1111
  }
950
1112
  return out
@@ -957,8 +1119,9 @@ function initDb() {
957
1119
  maxDepth?: number,
958
1120
  maxResults?: number,
959
1121
  maxLinkedExpansion?: number,
1122
+ options: MemorySearchOptions = {},
960
1123
  ): { entries: MemoryEntry[]; truncated: boolean; expandedLinkedCount: number; limits: MemoryLookupLimits } {
961
- const baseResults = this.search(query, agentId)
1124
+ const baseResults = this.search(query, agentId, options)
962
1125
  const defaults = getMemoryLookupLimits()
963
1126
  const limits = resolveLookupRequest(defaults, {
964
1127
  depth: maxDepth ?? defaults.maxDepth,
@@ -0,0 +1,56 @@
1
+ import assert from 'node:assert/strict'
2
+ import { test } from 'node:test'
3
+ import type { MemoryEntry } from '@/types'
4
+ import { filterMemoriesByScope, normalizeMemoryScopeMode } from './memory-db.ts'
5
+
6
+ function makeEntry(id: string, patch: Partial<MemoryEntry> = {}): MemoryEntry {
7
+ return {
8
+ id,
9
+ agentId: null,
10
+ sessionId: null,
11
+ category: 'note',
12
+ title: `Entry ${id}`,
13
+ content: `content ${id}`,
14
+ createdAt: 1,
15
+ updatedAt: 1,
16
+ ...patch,
17
+ }
18
+ }
19
+
20
+ test('normalizeMemoryScopeMode maps shared alias to global', () => {
21
+ assert.equal(normalizeMemoryScopeMode('shared'), 'global')
22
+ assert.equal(normalizeMemoryScopeMode('project'), 'project')
23
+ assert.equal(normalizeMemoryScopeMode('unknown'), 'auto')
24
+ })
25
+
26
+ test('filterMemoriesByScope auto includes global + own + shared-with-agent', () => {
27
+ const rows: MemoryEntry[] = [
28
+ makeEntry('global', { agentId: null }),
29
+ makeEntry('mine', { agentId: 'agent-a' }),
30
+ makeEntry('shared', { agentId: 'agent-b', sharedWith: ['agent-a'] }),
31
+ makeEntry('other', { agentId: 'agent-b' }),
32
+ ]
33
+ const filtered = filterMemoriesByScope(rows, { mode: 'auto', agentId: 'agent-a' })
34
+ assert.deepEqual(filtered.map((r) => r.id), ['global', 'mine', 'shared'])
35
+ })
36
+
37
+ test('filterMemoriesByScope supports project and session scopes', () => {
38
+ const rows: MemoryEntry[] = [
39
+ makeEntry('proj-hit', {
40
+ sessionId: 's-1',
41
+ references: [{ type: 'project', path: '/repo/swarm', projectRoot: '/repo/swarm', timestamp: 1 }],
42
+ }),
43
+ makeEntry('proj-miss', {
44
+ sessionId: 's-1',
45
+ references: [{ type: 'project', path: '/repo/other', projectRoot: '/repo/other', timestamp: 1 }],
46
+ }),
47
+ makeEntry('session-hit', { sessionId: 's-2' }),
48
+ ]
49
+
50
+ const projectFiltered = filterMemoriesByScope(rows, { mode: 'project', projectRoot: '/repo/swarm' })
51
+ assert.deepEqual(projectFiltered.map((r) => r.id), ['proj-hit'])
52
+
53
+ const sessionFiltered = filterMemoriesByScope(rows, { mode: 'session', sessionId: 's-2' })
54
+ assert.deepEqual(sessionFiltered.map((r) => r.id), ['session-hit'])
55
+ })
56
+
@@ -604,7 +604,10 @@ export async function executeLangGraphOrchestrator(
604
604
 
605
605
  // Extract summary from mark_complete if present
606
606
  const completeMatch = finalResult.match(/ORCHESTRATION_COMPLETE:\s*([\s\S]+)/)
607
- return completeMatch ? completeMatch[1].trim() : finalResult
607
+ const summary = completeMatch ? completeMatch[1].trim() : finalResult
608
+
609
+ // Append meta so the main-loop knows we are done
610
+ return `${summary}\n\n[MAIN_LOOP_META] {"status":"ok", "follow_up":false, "summary":${JSON.stringify(summary.slice(0, 300))}, "mission_task_id":${JSON.stringify(taskId || null)}}`
608
611
  }
609
612
 
610
613
  /**
@@ -77,13 +77,14 @@ export async function executeOrchestrator(
77
77
 
78
78
  // claude-cli fallback (no structured tool calling)
79
79
  console.warn(`[orchestrator] Using legacy regex-based engine for ${orchestrator.name} (claude-cli)`)
80
- return executeOrchestratorLegacy(orchestrator, task, sessionId)
80
+ return executeOrchestratorLegacy(orchestrator, task, sessionId, taskId)
81
81
  }
82
82
 
83
83
  async function executeOrchestratorLegacy(
84
84
  orchestrator: Agent,
85
85
  task: string,
86
86
  sessionId: string,
87
+ taskId?: string,
87
88
  ): Promise<string> {
88
89
  const allAgents = loadAgents()
89
90
  const sessions = loadSessions()
@@ -240,7 +241,7 @@ async function executeOrchestratorLegacy(
240
241
 
241
242
  if (cmd.done) {
242
243
  result = cmd.summary || fullText
243
- return result
244
+ return `${result}\n\n[MAIN_LOOP_META] {"status":"ok", "follow_up":false, "summary":${JSON.stringify(result.slice(0, 300))}, "mission_task_id":${JSON.stringify(taskId || null)}}`
244
245
  }
245
246
  }
246
247
 
@@ -255,7 +256,7 @@ async function executeOrchestratorLegacy(
255
256
  result = `Loop stopped after reaching max turns (${maxTurns}).`
256
257
  }
257
258
 
258
- return result
259
+ return `${result}\n\n[MAIN_LOOP_META] {"status":"ok", "follow_up":false, "summary":${JSON.stringify(result.slice(0, 300))}, "mission_task_id":${JSON.stringify(taskId || null)}}`
259
260
  }
260
261
 
261
262
  async function executeSubTask(