@swarmclawai/swarmclaw 1.1.8 → 1.2.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.
- package/README.md +17 -0
- package/next.config.ts +0 -1
- package/package.json +3 -3
- package/src/app/activity/loading.tsx +5 -0
- package/src/app/api/agents/[id]/thread/route.ts +2 -1
- package/src/app/api/logs/route.ts +32 -5
- package/src/app/home/loading.tsx +5 -0
- package/src/app/logs/loading.tsx +5 -0
- package/src/app/memory/loading.tsx +5 -0
- package/src/app/tasks/loading.tsx +5 -0
- package/src/components/agents/agent-list.tsx +7 -12
- package/src/components/chat/chat-area.tsx +3 -3
- package/src/components/chat/chat-list.tsx +13 -2
- package/src/components/layout/sidebar-rail.tsx +14 -6
- package/src/components/memory/memory-graph-view.tsx +120 -68
- package/src/components/org-chart/mini-chat-bubble.tsx +58 -16
- package/src/components/shared/command-palette.tsx +35 -20
- package/src/components/shared/notification-center.tsx +1 -1
- package/src/hooks/use-app-bootstrap.ts +2 -1
- package/src/instrumentation.ts +27 -0
- package/src/lib/providers/anthropic.ts +14 -8
- package/src/lib/providers/openai.ts +3 -3
- package/src/lib/server/agents/agent-thread-session.ts +20 -14
- package/src/lib/server/chat-execution/continuation-evaluator.ts +2 -2
- package/src/lib/server/chat-execution/message-classifier.ts +2 -1
- package/src/lib/server/chat-execution/stream-agent-chat.ts +4 -19
- package/src/lib/server/chat-execution/stream-continuation.ts +4 -3
- package/src/lib/server/llm-response-cache.ts +2 -1
- package/src/lib/server/playwright-proxy.mjs +25 -6
- package/src/lib/server/runtime/run-ledger.ts +4 -4
- package/src/lib/server/runtime/scheduler.ts +9 -6
- package/src/lib/server/session-tools/skill-runtime.ts +10 -1
- package/src/lib/server/storage-cache.ts +2 -0
- package/src/lib/server/storage.ts +30 -3
- package/src/lib/server/tasks/task-quality-gate.test.ts +1 -1
- package/src/lib/server/tasks/task-quality-gate.ts +1 -1
- package/src/stores/slices/data-slice.ts +11 -0
- package/src/stores/slices/session-slice.ts +3 -0
|
@@ -266,6 +266,15 @@ async function dispatchSkillRun(params: {
|
|
|
266
266
|
const toolArgs = normalizeDispatchArgs(params.rawArgs)
|
|
267
267
|
const { buildSessionTools } = await import('./index')
|
|
268
268
|
const built = await buildSessionTools(params.bctx.cwd, params.bctx.activeExtensions, params.bctx.ctx)
|
|
269
|
+
if (!built?.tools) {
|
|
270
|
+
return JSON.stringify({
|
|
271
|
+
ok: false,
|
|
272
|
+
executed: false,
|
|
273
|
+
mode: 'dispatch_blocked',
|
|
274
|
+
skill: summarizeRuntimeSkill(params.skill),
|
|
275
|
+
blocker: 'Unable to load session tools for skill dispatch.',
|
|
276
|
+
})
|
|
277
|
+
}
|
|
269
278
|
try {
|
|
270
279
|
const targetTool = built.tools.find((entry) => entry.name === dispatch.toolName)
|
|
271
280
|
if (!targetTool) {
|
|
@@ -298,7 +307,7 @@ async function dispatchSkillRun(params: {
|
|
|
298
307
|
: toolOutput,
|
|
299
308
|
})
|
|
300
309
|
} finally {
|
|
301
|
-
await built
|
|
310
|
+
await built?.cleanup()
|
|
302
311
|
}
|
|
303
312
|
}
|
|
304
313
|
|
|
@@ -38,11 +38,13 @@ export class TTLCache<T> {
|
|
|
38
38
|
type TTLCacheStore = {
|
|
39
39
|
settings?: TTLCache<AppSettings>
|
|
40
40
|
agents?: TTLCache<Record<string, unknown>>
|
|
41
|
+
sessions?: TTLCache<Record<string, unknown>>
|
|
41
42
|
}
|
|
42
43
|
const ttlCaches: TTLCacheStore = hmrSingleton<TTLCacheStore>('__swarmclaw_ttl_caches__', () => ({}))
|
|
43
44
|
|
|
44
45
|
export function getSettingsCache() { return ttlCaches.settings ?? (ttlCaches.settings = new TTLCache(60_000)) }
|
|
45
46
|
export function getAgentsCache() { return ttlCaches.agents ?? (ttlCaches.agents = new TTLCache(15_000)) }
|
|
47
|
+
export function getSessionsCache() { return ttlCaches.sessions ?? (ttlCaches.sessions = new TTLCache(5_000)) }
|
|
46
48
|
|
|
47
49
|
// --- LRU Cache ---
|
|
48
50
|
|
|
@@ -48,6 +48,7 @@ import {
|
|
|
48
48
|
capacityFor,
|
|
49
49
|
getSettingsCache,
|
|
50
50
|
getAgentsCache,
|
|
51
|
+
getSessionsCache,
|
|
51
52
|
} from './storage-cache'
|
|
52
53
|
import { normalizeStoredRecord, type NormalizationResult } from './storage-normalization'
|
|
53
54
|
import {
|
|
@@ -86,6 +87,9 @@ const db = new Database(DB_PATH)
|
|
|
86
87
|
if (!IS_BUILD_BOOTSTRAP) {
|
|
87
88
|
db.pragma('journal_mode = WAL')
|
|
88
89
|
db.pragma('busy_timeout = 5000')
|
|
90
|
+
db.pragma('synchronous = NORMAL')
|
|
91
|
+
db.pragma('cache_size = -64000')
|
|
92
|
+
db.pragma('mmap_size = 268435456')
|
|
89
93
|
}
|
|
90
94
|
db.pragma('foreign_keys = ON')
|
|
91
95
|
|
|
@@ -710,8 +714,12 @@ Be concise but not curt. Warmth doesn't require verbosity. When someone asks "ho
|
|
|
710
714
|
|
|
711
715
|
// --- Sessions ---
|
|
712
716
|
export function loadSessions(): Record<string, StoredSessionRecord> {
|
|
717
|
+
const sessionsCache = getSessionsCache()
|
|
718
|
+
const cached = sessionsCache.get()
|
|
719
|
+
if (cached) return structuredClone(cached) as unknown as Record<string, StoredSessionRecord>
|
|
720
|
+
|
|
713
721
|
const sessions = loadCollection('sessions') as unknown as Record<string, StoredSessionRecord>
|
|
714
|
-
const agents =
|
|
722
|
+
const agents = loadAgents()
|
|
715
723
|
const changedEntries: Array<[string, StoredSessionRecord]> = []
|
|
716
724
|
|
|
717
725
|
for (const [id, session] of Object.entries(sessions)) {
|
|
@@ -750,6 +758,7 @@ export function loadSessions(): Record<string, StoredSessionRecord> {
|
|
|
750
758
|
|
|
751
759
|
// Upsert only changed entries — never full-replace, which deletes concurrent sessions
|
|
752
760
|
if (changedEntries.length > 0) upsertCollectionItems('sessions', changedEntries)
|
|
761
|
+
sessionsCache.set(sessions as unknown as Record<string, unknown>)
|
|
753
762
|
return sessions
|
|
754
763
|
}
|
|
755
764
|
|
|
@@ -761,6 +770,7 @@ export function saveSessions(s: Record<string, Session | StoredObject>) {
|
|
|
761
770
|
normalizeValue('sessions', structuredClone(session as unknown as StoredObject)),
|
|
762
771
|
])
|
|
763
772
|
if (entries.length > 0) upsertCollectionItems('sessions', entries)
|
|
773
|
+
getSessionsCache().invalidate()
|
|
764
774
|
}
|
|
765
775
|
|
|
766
776
|
export function loadSession(id: string): Session | null {
|
|
@@ -769,10 +779,13 @@ export function loadSession(id: string): Session | null {
|
|
|
769
779
|
|
|
770
780
|
export function upsertSession(id: string, session: Session | Record<string, unknown>) {
|
|
771
781
|
upsertCollectionItem('sessions', id, session)
|
|
782
|
+
getSessionsCache().invalidate()
|
|
772
783
|
}
|
|
773
784
|
|
|
774
785
|
export function patchSession(id: string, updater: (current: Session | null) => Session | null): Session | null {
|
|
775
|
-
|
|
786
|
+
const result = patchStoredItem<Session>('sessions', id, updater)
|
|
787
|
+
getSessionsCache().invalidate()
|
|
788
|
+
return result
|
|
776
789
|
}
|
|
777
790
|
|
|
778
791
|
export function disableAllSessionHeartbeats(): number {
|
|
@@ -966,7 +979,7 @@ export const upsertTask = tasksStore.upsert
|
|
|
966
979
|
export const upsertTasks = tasksStore.upsertMany
|
|
967
980
|
export const patchTask = tasksStore.patch as (id: string, updater: (current: BoardTask | null) => BoardTask | null) => BoardTask | null
|
|
968
981
|
export const deleteTask = tasksStore.deleteItem
|
|
969
|
-
export function deleteSession(id: string) { deleteCollectionItem('sessions', id) }
|
|
982
|
+
export function deleteSession(id: string) { deleteCollectionItem('sessions', id); getSessionsCache().invalidate() }
|
|
970
983
|
export function deleteAgent(id: string) { deleteCollectionItem('agents', id); getAgentsCache().invalidate() }
|
|
971
984
|
export const deleteSchedule = schedulesStore.deleteItem
|
|
972
985
|
export function deleteSkill(id: string) { skillsStore.deleteItem(id) }
|
|
@@ -1296,6 +1309,20 @@ export const loadRuntimeRunEvents = runtimeRunEventsStore.load as () => Record<s
|
|
|
1296
1309
|
export const saveRuntimeRunEvents = runtimeRunEventsStore.save as (items: Record<string, RunEventRecord>) => void
|
|
1297
1310
|
export const upsertRuntimeRunEvent = runtimeRunEventsStore.upsert as (id: string, value: RunEventRecord) => void
|
|
1298
1311
|
|
|
1312
|
+
/** Load run events filtered by runId at the SQL level (avoids full-table scan). */
|
|
1313
|
+
export function loadRuntimeRunEventsByRunId(runId: string): RunEventRecord[] {
|
|
1314
|
+
const rows = db.prepare(
|
|
1315
|
+
`SELECT data FROM runtime_run_events WHERE json_extract(data, '$.runId') = ? ORDER BY json_extract(data, '$.timestamp') ASC`,
|
|
1316
|
+
).all(runId) as Array<{ data: string }>
|
|
1317
|
+
const results: RunEventRecord[] = []
|
|
1318
|
+
for (const row of rows) {
|
|
1319
|
+
try {
|
|
1320
|
+
results.push(JSON.parse(row.data) as RunEventRecord)
|
|
1321
|
+
} catch { /* skip malformed */ }
|
|
1322
|
+
}
|
|
1323
|
+
return results
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1299
1326
|
const runtimeEstopStore = createCollectionStore('runtime_estop')
|
|
1300
1327
|
const ESTOP_STATE_ID = 'global'
|
|
1301
1328
|
export const loadPersistedEstopState = () => runtimeEstopStore.loadItem(ESTOP_STATE_ID) as EstopState | null
|
|
@@ -6,7 +6,7 @@ test('normalizeTaskQualityGate uses defaults when unset', () => {
|
|
|
6
6
|
const gate = normalizeTaskQualityGate(undefined, undefined)
|
|
7
7
|
assert.equal(gate.enabled, true)
|
|
8
8
|
assert.equal(gate.minResultChars, 80)
|
|
9
|
-
assert.equal(gate.minEvidenceItems,
|
|
9
|
+
assert.equal(gate.minEvidenceItems, 1)
|
|
10
10
|
assert.equal(gate.requireVerification, false)
|
|
11
11
|
assert.equal(gate.requireArtifact, false)
|
|
12
12
|
assert.equal(gate.requireReport, false)
|
|
@@ -12,7 +12,7 @@ export interface NormalizedTaskQualityGate {
|
|
|
12
12
|
export const DEFAULT_TASK_QUALITY_GATE: NormalizedTaskQualityGate = {
|
|
13
13
|
enabled: true,
|
|
14
14
|
minResultChars: 80,
|
|
15
|
-
minEvidenceItems:
|
|
15
|
+
minEvidenceItems: 1,
|
|
16
16
|
requireVerification: false,
|
|
17
17
|
requireArtifact: false,
|
|
18
18
|
requireReport: false,
|
|
@@ -30,6 +30,8 @@ export interface DataSlice {
|
|
|
30
30
|
loadGatewayProfiles: () => Promise<void>
|
|
31
31
|
skills: Record<string, Skill>
|
|
32
32
|
loadSkills: () => Promise<void>
|
|
33
|
+
skillDraftCount: number
|
|
34
|
+
loadSkillDraftCount: () => Promise<void>
|
|
33
35
|
connectors: Record<string, Connector>
|
|
34
36
|
loadConnectors: () => Promise<void>
|
|
35
37
|
webhooks: Record<string, Webhook>
|
|
@@ -82,6 +84,15 @@ export const createDataSlice: StateCreator<AppState, [], [], DataSlice> = (set,
|
|
|
82
84
|
loadGatewayProfiles: createLoader<AppState>(set, 'gatewayProfiles', () => api<GatewayProfile[]>('GET', '/gateways'), []),
|
|
83
85
|
skills: {},
|
|
84
86
|
loadSkills: createLoader<AppState>(set, 'skills', () => api<Record<string, Skill>>('GET', '/skills'), {}),
|
|
87
|
+
skillDraftCount: 0,
|
|
88
|
+
loadSkillDraftCount: async () => {
|
|
89
|
+
try {
|
|
90
|
+
const result = await api<{ total: number }>('GET', '/skill-review-counts')
|
|
91
|
+
setIfChanged<AppState>(set, 'skillDraftCount', result.total)
|
|
92
|
+
} catch (err: unknown) {
|
|
93
|
+
console.warn('Store error:', err)
|
|
94
|
+
}
|
|
95
|
+
},
|
|
85
96
|
connectors: {},
|
|
86
97
|
loadConnectors: createLoader<AppState>(set, 'connectors', () => api<Record<string, Connector>>('GET', '/connectors'), {}),
|
|
87
98
|
webhooks: {},
|
|
@@ -33,6 +33,9 @@ export const createSessionSlice: StateCreator<AppState, [], [], SessionSlice> =
|
|
|
33
33
|
await sessionRefreshDedup.dedup(id, async () => {
|
|
34
34
|
try {
|
|
35
35
|
const session = await fetchChat(id)
|
|
36
|
+
const existing = get().sessions[id]
|
|
37
|
+
// Skip update if the session data hasn't changed
|
|
38
|
+
if (existing && JSON.stringify(existing) === JSON.stringify(session)) return
|
|
36
39
|
invalidateFingerprint('sessions')
|
|
37
40
|
set({
|
|
38
41
|
sessions: { ...get().sessions, [id]: session },
|