@swarmclawai/swarmclaw 1.2.1 → 1.2.3
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 +16 -85
- package/bin/server-cmd.js +64 -1
- package/package.json +2 -2
- package/skills/coding-agent/SKILL.md +111 -0
- package/skills/github/SKILL.md +140 -0
- package/skills/nano-banana-pro/SKILL.md +62 -0
- package/skills/nano-banana-pro/scripts/generate_image.py +235 -0
- package/skills/nano-pdf/SKILL.md +53 -0
- package/skills/openai-image-gen/SKILL.md +78 -0
- package/skills/openai-image-gen/scripts/gen.py +328 -0
- package/skills/resourceful-problem-solving/SKILL.md +49 -0
- package/skills/skill-creator/SKILL.md +147 -0
- package/skills/skill-creator/scripts/init_skill.py +378 -0
- package/skills/skill-creator/scripts/quick_validate.py +159 -0
- package/skills/summarize/SKILL.md +77 -0
- package/src/app/api/auth/route.ts +20 -5
- package/src/app/api/chats/[id]/devserver/route.ts +13 -19
- package/src/app/api/chats/[id]/messages/route.ts +13 -15
- package/src/app/api/chats/[id]/route.ts +9 -10
- package/src/app/api/chats/[id]/stop/route.ts +5 -7
- package/src/app/api/chats/messages-route.test.ts +8 -6
- package/src/app/api/chats/route.ts +9 -10
- package/src/app/api/ip/route.ts +2 -2
- package/src/app/api/preview-server/route.ts +1 -1
- package/src/app/api/projects/[id]/route.ts +7 -46
- package/src/cli/server-cmd.test.js +74 -0
- package/src/components/chat/chat-area.tsx +45 -23
- package/src/components/chat/message-bubble.test.ts +35 -0
- package/src/components/chat/message-bubble.tsx +19 -9
- package/src/components/chat/message-list.tsx +37 -3
- package/src/components/input/chat-input.tsx +34 -14
- package/src/components/openclaw/openclaw-deploy-panel.tsx +4 -0
- package/src/instrumentation.ts +1 -1
- package/src/lib/chat/assistant-render-id.ts +3 -0
- package/src/lib/chat/chat-streaming-state.test.ts +42 -3
- package/src/lib/chat/chat-streaming-state.ts +20 -8
- package/src/lib/chat/queued-message-queue.test.ts +23 -1
- package/src/lib/chat/queued-message-queue.ts +11 -2
- package/src/lib/providers/cli-utils.test.ts +124 -0
- package/src/lib/server/activity/activity-log.ts +21 -0
- package/src/lib/server/agents/agent-availability.test.ts +10 -5
- package/src/lib/server/agents/agent-cascade.ts +79 -59
- package/src/lib/server/agents/agent-registry.ts +3 -1
- package/src/lib/server/agents/agent-repository.ts +90 -0
- package/src/lib/server/agents/delegation-job-repository.ts +53 -0
- package/src/lib/server/agents/delegation-jobs.ts +11 -4
- package/src/lib/server/agents/guardian-checkpoint-repository.ts +35 -0
- package/src/lib/server/agents/guardian.ts +2 -2
- package/src/lib/server/agents/main-agent-loop.ts +10 -3
- package/src/lib/server/agents/main-loop-state-repository.ts +38 -0
- package/src/lib/server/agents/subagent-runtime.ts +9 -6
- package/src/lib/server/agents/subagent-swarm.ts +3 -2
- package/src/lib/server/agents/task-session.ts +3 -4
- package/src/lib/server/approvals/approval-repository.ts +30 -0
- package/src/lib/server/autonomy/supervisor-incident-repository.ts +42 -0
- package/src/lib/server/chat-execution/chat-execution-types.ts +38 -0
- package/src/lib/server/chat-execution/chat-execution-utils.ts +1 -1
- package/src/lib/server/chat-execution/chat-execution.ts +84 -1926
- package/src/lib/server/chat-execution/chat-turn-finalization.ts +620 -0
- package/src/lib/server/chat-execution/chat-turn-partial-persistence.ts +221 -0
- package/src/lib/server/chat-execution/chat-turn-preflight.ts +133 -0
- package/src/lib/server/chat-execution/chat-turn-preparation.ts +817 -0
- package/src/lib/server/chat-execution/chat-turn-stream-execution.ts +296 -0
- package/src/lib/server/chat-execution/chat-turn-tool-routing.ts +5 -5
- package/src/lib/server/chat-execution/message-classifier.test.ts +329 -0
- package/src/lib/server/chat-execution/post-stream-finalization.ts +1 -1
- package/src/lib/server/chat-execution/prompt-builder.ts +11 -0
- package/src/lib/server/chat-execution/prompt-sections.ts +5 -6
- package/src/lib/server/chat-execution/situational-awareness.ts +12 -7
- package/src/lib/server/chat-execution/stream-agent-chat.ts +16 -13
- package/src/lib/server/chatrooms/chatroom-repository.ts +32 -0
- package/src/lib/server/connectors/connector-repository.ts +58 -0
- package/src/lib/server/connectors/runtime-state.test.ts +117 -0
- package/src/lib/server/credentials/credential-repository.ts +7 -0
- package/src/lib/server/gateways/gateway-profile-repository.ts +4 -0
- package/src/lib/server/memory/memory-abstract.test.ts +59 -0
- package/src/lib/server/missions/mission-repository.ts +74 -0
- package/src/lib/server/missions/mission-service/actions.ts +6 -0
- package/src/lib/server/missions/mission-service/bindings.ts +9 -0
- package/src/lib/server/missions/mission-service/context.ts +4 -0
- package/src/lib/server/missions/mission-service/core.ts +2269 -0
- package/src/lib/server/missions/mission-service/queries.ts +12 -0
- package/src/lib/server/missions/mission-service/recovery.ts +5 -0
- package/src/lib/server/missions/mission-service/ticks.ts +9 -0
- package/src/lib/server/missions/mission-service.test.ts +9 -2
- package/src/lib/server/missions/mission-service.ts +6 -2266
- package/src/lib/server/openclaw/deploy.test.ts +42 -3
- package/src/lib/server/openclaw/deploy.ts +26 -12
- package/src/lib/server/persistence/repository-utils.ts +154 -0
- package/src/lib/server/persistence/storage-context.ts +51 -0
- package/src/lib/server/persistence/transaction.ts +1 -0
- package/src/lib/server/projects/project-repository.ts +36 -0
- package/src/lib/server/projects/project-service.ts +79 -0
- package/src/lib/server/protocols/protocol-normalization.test.ts +6 -4
- package/src/lib/server/runtime/alert-dispatch.ts +1 -1
- package/src/lib/server/runtime/daemon-policy.ts +1 -1
- package/src/lib/server/runtime/daemon-state/core.ts +1570 -0
- package/src/lib/server/runtime/daemon-state/health.ts +6 -0
- package/src/lib/server/runtime/daemon-state/policy.ts +7 -0
- package/src/lib/server/runtime/daemon-state/supervisor.ts +6 -0
- package/src/lib/server/runtime/daemon-state.test.ts +48 -0
- package/src/lib/server/runtime/daemon-state.ts +3 -1470
- package/src/lib/server/runtime/estop-repository.ts +4 -0
- package/src/lib/server/runtime/estop.ts +3 -1
- package/src/lib/server/runtime/heartbeat-service.test.ts +2 -2
- package/src/lib/server/runtime/heartbeat-service.ts +55 -34
- package/src/lib/server/runtime/heartbeat-wake.ts +6 -4
- package/src/lib/server/runtime/idle-window.ts +2 -2
- package/src/lib/server/runtime/network.ts +11 -0
- package/src/lib/server/runtime/orchestrator-events.ts +2 -2
- package/src/lib/server/runtime/queue/claims.ts +4 -0
- package/src/lib/server/runtime/queue/core.ts +2079 -0
- package/src/lib/server/runtime/queue/execution.ts +7 -0
- package/src/lib/server/runtime/queue/followups.ts +4 -0
- package/src/lib/server/runtime/queue/queries.ts +12 -0
- package/src/lib/server/runtime/queue/recovery.ts +7 -0
- package/src/lib/server/runtime/queue-recovery.test.ts +48 -13
- package/src/lib/server/runtime/queue-repository.ts +17 -0
- package/src/lib/server/runtime/queue.ts +5 -2061
- package/src/lib/server/runtime/run-ledger.ts +6 -5
- package/src/lib/server/runtime/run-repository.ts +73 -0
- package/src/lib/server/runtime/runtime-lock-repository.ts +8 -0
- package/src/lib/server/runtime/runtime-settings.ts +1 -1
- package/src/lib/server/runtime/runtime-state.ts +99 -0
- package/src/lib/server/runtime/scheduler.ts +4 -2
- package/src/lib/server/runtime/session-run-manager/cancellation.ts +157 -0
- package/src/lib/server/runtime/session-run-manager/drain.ts +246 -0
- package/src/lib/server/runtime/session-run-manager/enqueue.ts +287 -0
- package/src/lib/server/runtime/session-run-manager/queries.ts +117 -0
- package/src/lib/server/runtime/session-run-manager/recovery.ts +238 -0
- package/src/lib/server/runtime/session-run-manager/state.ts +441 -0
- package/src/lib/server/runtime/session-run-manager/types.ts +74 -0
- package/src/lib/server/runtime/session-run-manager.ts +72 -1377
- package/src/lib/server/runtime/watch-job-repository.ts +35 -0
- package/src/lib/server/runtime/watch-jobs.ts +3 -1
- package/src/lib/server/schedules/schedule-repository.ts +42 -0
- package/src/lib/server/sessions/session-repository.ts +85 -0
- package/src/lib/server/settings/settings-repository.ts +25 -0
- package/src/lib/server/skills/skill-discovery.test.ts +2 -2
- package/src/lib/server/skills/skill-discovery.ts +2 -2
- package/src/lib/server/skills/skill-repository.ts +14 -0
- package/src/lib/server/storage.ts +13 -24
- package/src/lib/server/tasks/task-repository.ts +54 -0
- package/src/lib/server/usage/usage-repository.ts +30 -0
- package/src/lib/server/webhooks/webhook-repository.ts +10 -0
- package/src/lib/strip-internal-metadata.test.ts +42 -41
- package/src/stores/use-chat-store.test.ts +54 -0
- package/src/stores/use-chat-store.ts +21 -5
- /package/{bundled-skills → skills}/google-workspace/SKILL.md +0 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { WatchJob } from '@/types'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
deleteWatchJob as deleteStoredWatchJob,
|
|
5
|
+
loadWatchJobs as loadStoredWatchJobs,
|
|
6
|
+
upsertWatchJob as upsertStoredWatchJob,
|
|
7
|
+
upsertWatchJobs as upsertStoredWatchJobs,
|
|
8
|
+
} from '@/lib/server/storage'
|
|
9
|
+
import { createRecordRepository } from '@/lib/server/persistence/repository-utils'
|
|
10
|
+
|
|
11
|
+
export const watchJobRepository = createRecordRepository<WatchJob>(
|
|
12
|
+
'watch-jobs',
|
|
13
|
+
{
|
|
14
|
+
get(id) {
|
|
15
|
+
return (loadStoredWatchJobs() as Record<string, WatchJob>)[id] || null
|
|
16
|
+
},
|
|
17
|
+
list() {
|
|
18
|
+
return loadStoredWatchJobs() as Record<string, WatchJob>
|
|
19
|
+
},
|
|
20
|
+
upsert(id, value) {
|
|
21
|
+
upsertStoredWatchJob(id, value as WatchJob)
|
|
22
|
+
},
|
|
23
|
+
upsertMany(entries) {
|
|
24
|
+
upsertStoredWatchJobs(entries as Array<[string, WatchJob]>)
|
|
25
|
+
},
|
|
26
|
+
delete(id) {
|
|
27
|
+
deleteStoredWatchJob(id)
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
export const loadWatchJobs = () => watchJobRepository.list()
|
|
33
|
+
export const upsertWatchJob = (id: string, value: WatchJob | Record<string, unknown>) => watchJobRepository.upsert(id, value as WatchJob)
|
|
34
|
+
export const upsertWatchJobs = (entries: Array<[string, WatchJob | Record<string, unknown>]>) => watchJobRepository.upsertMany(entries as Array<[string, WatchJob]>)
|
|
35
|
+
export const deleteWatchJob = (id: string) => watchJobRepository.delete(id)
|
|
@@ -5,7 +5,9 @@ import { genId } from '@/lib/id'
|
|
|
5
5
|
import type { MailboxEnvelope, WatchJob } from '@/types'
|
|
6
6
|
import { dispatchWake } from '@/lib/server/runtime/wake-dispatcher'
|
|
7
7
|
import { enqueueSystemEvent } from '@/lib/server/runtime/system-events'
|
|
8
|
-
import { loadApprovals
|
|
8
|
+
import { loadApprovals } from '@/lib/server/approvals/approval-repository'
|
|
9
|
+
import { loadTasks } from '@/lib/server/tasks/task-repository'
|
|
10
|
+
import { loadWatchJobs, upsertWatchJob, upsertWatchJobs } from '@/lib/server/runtime/watch-job-repository'
|
|
9
11
|
import { notify } from '@/lib/server/ws-hub'
|
|
10
12
|
import { fetchMailboxMessages, getMailboxHighwaterUid } from '@/lib/server/chatrooms/mailbox-utils'
|
|
11
13
|
import { errorMessage } from '@/lib/shared-utils'
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { Schedule } from '@/types'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
deleteSchedule as deleteStoredSchedule,
|
|
5
|
+
loadSchedule as loadStoredSchedule,
|
|
6
|
+
loadSchedules as loadStoredSchedules,
|
|
7
|
+
saveSchedules as saveStoredSchedules,
|
|
8
|
+
upsertSchedule as upsertStoredSchedule,
|
|
9
|
+
upsertSchedules as upsertStoredSchedules,
|
|
10
|
+
} from '@/lib/server/storage'
|
|
11
|
+
import { createRecordRepository } from '@/lib/server/persistence/repository-utils'
|
|
12
|
+
|
|
13
|
+
export const scheduleRepository = createRecordRepository<Schedule>(
|
|
14
|
+
'schedules',
|
|
15
|
+
{
|
|
16
|
+
get(id) {
|
|
17
|
+
return loadStoredSchedule(id) as Schedule | null
|
|
18
|
+
},
|
|
19
|
+
list() {
|
|
20
|
+
return loadStoredSchedules() as Record<string, Schedule>
|
|
21
|
+
},
|
|
22
|
+
upsert(id, value) {
|
|
23
|
+
upsertStoredSchedule(id, value as Schedule)
|
|
24
|
+
},
|
|
25
|
+
upsertMany(entries) {
|
|
26
|
+
upsertStoredSchedules(entries as Array<[string, Schedule]>)
|
|
27
|
+
},
|
|
28
|
+
replace(data) {
|
|
29
|
+
saveStoredSchedules(data as Record<string, Schedule>)
|
|
30
|
+
},
|
|
31
|
+
delete(id) {
|
|
32
|
+
deleteStoredSchedule(id)
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
export const loadSchedules = () => scheduleRepository.list()
|
|
38
|
+
export const loadSchedule = (id: string) => scheduleRepository.get(id)
|
|
39
|
+
export const saveSchedules = (items: Record<string, Schedule | Record<string, unknown>>) => scheduleRepository.replace(items as Record<string, Schedule>)
|
|
40
|
+
export const upsertSchedule = (id: string, value: Schedule | Record<string, unknown>) => scheduleRepository.upsert(id, value as Schedule)
|
|
41
|
+
export const upsertSchedules = (entries: Array<[string, Schedule | Record<string, unknown>]>) => scheduleRepository.upsertMany(entries as Array<[string, Schedule]>)
|
|
42
|
+
export const deleteSchedule = (id: string) => scheduleRepository.delete(id)
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { Message, Session } from '@/types'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
deleteSession as deleteStoredSession,
|
|
5
|
+
disableAllSessionHeartbeats as disableAllStoredSessionHeartbeats,
|
|
6
|
+
getSessionMessages as getStoredSessionMessages,
|
|
7
|
+
loadSession as loadStoredSession,
|
|
8
|
+
loadSessions as loadStoredSessions,
|
|
9
|
+
patchSession as patchStoredSession,
|
|
10
|
+
saveSessions as replaceStoredSessions,
|
|
11
|
+
upsertSession as upsertStoredSession,
|
|
12
|
+
} from '@/lib/server/storage'
|
|
13
|
+
import { createRecordRepository } from '@/lib/server/persistence/repository-utils'
|
|
14
|
+
|
|
15
|
+
export const sessionRepository = createRecordRepository<Session>(
|
|
16
|
+
'sessions',
|
|
17
|
+
{
|
|
18
|
+
get(id) {
|
|
19
|
+
return loadStoredSession(id) as Session | null
|
|
20
|
+
},
|
|
21
|
+
list() {
|
|
22
|
+
return loadStoredSessions() as Record<string, Session>
|
|
23
|
+
},
|
|
24
|
+
upsert(id, value) {
|
|
25
|
+
upsertStoredSession(id, value as Session)
|
|
26
|
+
},
|
|
27
|
+
upsertMany(entries) {
|
|
28
|
+
replaceStoredSessions(Object.fromEntries(entries))
|
|
29
|
+
},
|
|
30
|
+
replace(data) {
|
|
31
|
+
replaceStoredSessions(data)
|
|
32
|
+
},
|
|
33
|
+
patch(id, updater) {
|
|
34
|
+
return patchStoredSession(id, updater as (current: Session | null) => Session | null) as Session | null
|
|
35
|
+
},
|
|
36
|
+
delete(id) {
|
|
37
|
+
deleteStoredSession(id)
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
export function listSessions(): Record<string, Session> {
|
|
43
|
+
return sessionRepository.list()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function getSession(id: string): Session | null {
|
|
47
|
+
return sessionRepository.get(id)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function getSessions(ids: string[]): Record<string, Session> {
|
|
51
|
+
return sessionRepository.getMany(ids)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function saveSession(id: string, session: Session | Record<string, unknown>): void {
|
|
55
|
+
sessionRepository.upsert(id, session as Session)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function saveSessionMany(entries: Array<[string, Session | Record<string, unknown>]>): void {
|
|
59
|
+
sessionRepository.upsertMany(entries as Array<[string, Session]>)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function replaceSessions(sessions: Record<string, Session | Record<string, unknown>>): void {
|
|
63
|
+
sessionRepository.replace(sessions as Record<string, Session>)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function patchSession(id: string, updater: (current: Session | null) => Session | null): Session | null {
|
|
67
|
+
return sessionRepository.patch(id, updater)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function deleteSession(id: string): void {
|
|
71
|
+
sessionRepository.delete(id)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function getSessionMessages(sessionId: string): Message[] {
|
|
75
|
+
return getStoredSessionMessages(sessionId)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function disableAllSessionHeartbeats(): number {
|
|
79
|
+
return disableAllStoredSessionHeartbeats()
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export const loadSessions = listSessions
|
|
83
|
+
export const loadSession = getSession
|
|
84
|
+
export const saveSessions = replaceSessions
|
|
85
|
+
export const upsertSession = saveSession
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { AppSettings } from '@/types'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
loadPublicSettings as loadStoredPublicSettings,
|
|
5
|
+
loadSettings as loadStoredSettings,
|
|
6
|
+
saveSettings as saveStoredSettings,
|
|
7
|
+
} from '@/lib/server/storage'
|
|
8
|
+
import { createSingletonRepository } from '@/lib/server/persistence/repository-utils'
|
|
9
|
+
|
|
10
|
+
export const settingsRepository = createSingletonRepository<AppSettings>(
|
|
11
|
+
'settings',
|
|
12
|
+
{
|
|
13
|
+
get() {
|
|
14
|
+
return loadStoredSettings()
|
|
15
|
+
},
|
|
16
|
+
save(value) {
|
|
17
|
+
saveStoredSettings(value)
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
export const loadSettings = () => settingsRepository.get()
|
|
23
|
+
export const saveSettings = (value: AppSettings | Record<string, unknown>) => settingsRepository.save(value as AppSettings)
|
|
24
|
+
export const patchSettings = (updater: (current: AppSettings) => AppSettings | Record<string, unknown>) => settingsRepository.patch(updater as (current: AppSettings) => AppSettings)
|
|
25
|
+
export const loadPublicSettings = () => loadStoredPublicSettings()
|
|
@@ -5,14 +5,14 @@ import path from 'node:path'
|
|
|
5
5
|
import test from 'node:test'
|
|
6
6
|
import { clearDiscoveredSkillsCache, discoverSkills } from './skill-discovery'
|
|
7
7
|
|
|
8
|
-
test('discoverSkills includes tracked bundled skills from
|
|
8
|
+
test('discoverSkills includes tracked bundled skills from skills', () => {
|
|
9
9
|
const skills = discoverSkills({ cwd: path.join(process.cwd(), 'src') })
|
|
10
10
|
const googleWorkspaceSkill = skills.find((skill) => skill.name === 'google-workspace')
|
|
11
11
|
|
|
12
12
|
assert.ok(googleWorkspaceSkill)
|
|
13
13
|
assert.equal(googleWorkspaceSkill?.source, 'bundled')
|
|
14
14
|
assert.equal(
|
|
15
|
-
googleWorkspaceSkill?.sourcePath.endsWith(path.join('
|
|
15
|
+
googleWorkspaceSkill?.sourcePath.endsWith(path.join('skills', 'google-workspace', 'SKILL.md')),
|
|
16
16
|
true,
|
|
17
17
|
)
|
|
18
18
|
})
|
|
@@ -19,7 +19,7 @@ interface DiscoveryCache {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
const CACHE_TTL_MS = 5_000
|
|
22
|
-
const BUNDLED_SKILLS_DIR = path.join(process.cwd(), '
|
|
22
|
+
const BUNDLED_SKILLS_DIR = path.join(process.cwd(), 'skills')
|
|
23
23
|
const LEGACY_BUNDLED_SKILLS_DIR = path.join(DATA_DIR, 'skills')
|
|
24
24
|
|
|
25
25
|
let cache: DiscoveryCache | null = null
|
|
@@ -84,7 +84,7 @@ function scanLayer(
|
|
|
84
84
|
|
|
85
85
|
/**
|
|
86
86
|
* Discover skills from three layers:
|
|
87
|
-
* 1. Bundled: `
|
|
87
|
+
* 1. Bundled: `skills/` (tracked with the app)
|
|
88
88
|
* Legacy fallback: `data/skills/`
|
|
89
89
|
* 2. Workspace: `<swarmclaw-home>/skills/` (user-installed)
|
|
90
90
|
* 3. Project: `<cwd>/skills/` (project-local)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export {
|
|
2
|
+
deleteLearnedSkill,
|
|
3
|
+
deleteSkill,
|
|
4
|
+
loadLearnedSkill,
|
|
5
|
+
loadLearnedSkills,
|
|
6
|
+
loadSkillSuggestions,
|
|
7
|
+
loadSkills,
|
|
8
|
+
patchLearnedSkill,
|
|
9
|
+
patchSkillSuggestion,
|
|
10
|
+
saveLearnedSkills,
|
|
11
|
+
saveSkills,
|
|
12
|
+
upsertLearnedSkill,
|
|
13
|
+
upsertSkillSuggestion,
|
|
14
|
+
} from '@/lib/server/storage'
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import fs from 'fs'
|
|
2
2
|
import path from 'path'
|
|
3
3
|
import crypto from 'crypto'
|
|
4
|
-
import os from 'os'
|
|
5
|
-
import type { ChildProcess } from 'node:child_process'
|
|
6
4
|
import Database from 'better-sqlite3'
|
|
7
5
|
|
|
8
6
|
import { perf } from '@/lib/server/runtime/perf'
|
|
@@ -105,11 +103,6 @@ export function withTransaction<T>(fn: () => T): T {
|
|
|
105
103
|
type StoredObject = Record<string, unknown>
|
|
106
104
|
type StoredSessionRecord = Session
|
|
107
105
|
type StoredAgentRecord = Agent
|
|
108
|
-
type ActiveProcess = ChildProcess | {
|
|
109
|
-
runId?: string | null
|
|
110
|
-
source?: string
|
|
111
|
-
kill: (signal?: NodeJS.Signals | number) => boolean | void
|
|
112
|
-
}
|
|
113
106
|
|
|
114
107
|
// Collection tables (id → JSON blob)
|
|
115
108
|
const COLLECTIONS = [
|
|
@@ -306,7 +299,18 @@ function deleteCollectionItem(table: string, id: string) {
|
|
|
306
299
|
db.prepare(`DELETE FROM ${table} WHERE id = ?`).run(id)
|
|
307
300
|
const cached = collectionCache.get(table)
|
|
308
301
|
if (cached) cached.delete(id)
|
|
302
|
+
invalidateDerivedCollectionCaches(table)
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function invalidateDerivedCollectionCaches(table: string): void {
|
|
309
306
|
factoryTtlCaches.get(table)?.invalidate()
|
|
307
|
+
if (table === 'sessions') {
|
|
308
|
+
getSessionsCache().invalidate()
|
|
309
|
+
return
|
|
310
|
+
}
|
|
311
|
+
if (table === 'agents') {
|
|
312
|
+
getAgentsCache().invalidate()
|
|
313
|
+
}
|
|
310
314
|
}
|
|
311
315
|
|
|
312
316
|
/**
|
|
@@ -322,7 +326,7 @@ function upsertCollectionItem(table: string, id: string, value: unknown) {
|
|
|
322
326
|
if (cached) {
|
|
323
327
|
cached.set(id, serialized)
|
|
324
328
|
}
|
|
325
|
-
|
|
329
|
+
invalidateDerivedCollectionCaches(table)
|
|
326
330
|
}
|
|
327
331
|
|
|
328
332
|
function loadCollectionItem(table: string, id: string): unknown | null {
|
|
@@ -356,7 +360,7 @@ function upsertCollectionItems(table: string, entries: Array<[string, unknown]>)
|
|
|
356
360
|
cached.set(id, serialized)
|
|
357
361
|
}
|
|
358
362
|
}
|
|
359
|
-
|
|
363
|
+
invalidateDerivedCollectionCaches(table)
|
|
360
364
|
}
|
|
361
365
|
|
|
362
366
|
export function loadStoredItem(table: StorageCollection, id: string): unknown | null {
|
|
@@ -1438,21 +1442,6 @@ const webhooksStore = createCollectionStore('webhooks')
|
|
|
1438
1442
|
export const loadWebhooks = webhooksStore.load
|
|
1439
1443
|
export const saveWebhooks = webhooksStore.save
|
|
1440
1444
|
|
|
1441
|
-
// --- Active processes ---
|
|
1442
|
-
export const active = new Map<string, ActiveProcess>()
|
|
1443
|
-
export const devServers = new Map<string, { proc: ChildProcess; url: string }>()
|
|
1444
|
-
|
|
1445
|
-
// --- Utilities ---
|
|
1446
|
-
export function localIP(): string {
|
|
1447
|
-
for (const ifaces of Object.values(os.networkInterfaces())) {
|
|
1448
|
-
if (!ifaces) continue
|
|
1449
|
-
for (const i of ifaces) {
|
|
1450
|
-
if (i.family === 'IPv4' && !i.internal) return i.address
|
|
1451
|
-
}
|
|
1452
|
-
}
|
|
1453
|
-
return 'localhost'
|
|
1454
|
-
}
|
|
1455
|
-
|
|
1456
1445
|
// --- MCP Servers ---
|
|
1457
1446
|
const mcpServersStore = createCollectionStore('mcp_servers')
|
|
1458
1447
|
export const loadMcpServers = mcpServersStore.load
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { BoardTask } from '@/types'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
deleteTask as deleteStoredTask,
|
|
5
|
+
loadTask as loadStoredTask,
|
|
6
|
+
loadTasks as loadStoredTasks,
|
|
7
|
+
patchTask as patchStoredTask,
|
|
8
|
+
saveTasks as saveStoredTasks,
|
|
9
|
+
upsertTask as upsertStoredTask,
|
|
10
|
+
upsertTasks as upsertStoredTasks,
|
|
11
|
+
} from '@/lib/server/storage'
|
|
12
|
+
import { createRecordRepository } from '@/lib/server/persistence/repository-utils'
|
|
13
|
+
|
|
14
|
+
export const taskRepository = createRecordRepository<BoardTask>(
|
|
15
|
+
'tasks',
|
|
16
|
+
{
|
|
17
|
+
get(id) {
|
|
18
|
+
return loadStoredTask(id) as BoardTask | null
|
|
19
|
+
},
|
|
20
|
+
list() {
|
|
21
|
+
return loadStoredTasks() as Record<string, BoardTask>
|
|
22
|
+
},
|
|
23
|
+
upsert(id, value) {
|
|
24
|
+
upsertStoredTask(id, value as BoardTask)
|
|
25
|
+
},
|
|
26
|
+
upsertMany(entries) {
|
|
27
|
+
upsertStoredTasks(entries as Array<[string, BoardTask]>)
|
|
28
|
+
},
|
|
29
|
+
replace(data) {
|
|
30
|
+
saveStoredTasks(data as Record<string, BoardTask>)
|
|
31
|
+
},
|
|
32
|
+
patch(id, updater) {
|
|
33
|
+
return patchStoredTask(id, updater as (current: BoardTask | null) => BoardTask | null) as BoardTask | null
|
|
34
|
+
},
|
|
35
|
+
delete(id) {
|
|
36
|
+
deleteStoredTask(id)
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
export const getTask = (id: string) => taskRepository.get(id)
|
|
42
|
+
export const getTasks = (ids: string[]) => taskRepository.getMany(ids)
|
|
43
|
+
export const listTasks = () => taskRepository.list()
|
|
44
|
+
export const saveTask = (id: string, task: BoardTask | Record<string, unknown>) => taskRepository.upsert(id, task as BoardTask)
|
|
45
|
+
export const saveTaskMany = (entries: Array<[string, BoardTask | Record<string, unknown>]>) => taskRepository.upsertMany(entries as Array<[string, BoardTask]>)
|
|
46
|
+
export const replaceTasks = (items: Record<string, BoardTask | Record<string, unknown>>) => taskRepository.replace(items as Record<string, BoardTask>)
|
|
47
|
+
export const patchTask = (id: string, updater: (current: BoardTask | null) => BoardTask | null) => taskRepository.patch(id, updater)
|
|
48
|
+
export const deleteTask = (id: string) => taskRepository.delete(id)
|
|
49
|
+
|
|
50
|
+
export const loadTasks = listTasks
|
|
51
|
+
export const loadTask = getTask
|
|
52
|
+
export const saveTasks = replaceTasks
|
|
53
|
+
export const upsertTask = saveTask
|
|
54
|
+
export const upsertTasks = saveTaskMany
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { UsageRecord } from '@/types'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
appendUsage as appendStoredUsage,
|
|
5
|
+
getUsageSpendSince as getStoredUsageSpendSince,
|
|
6
|
+
loadUsage as loadStoredUsage,
|
|
7
|
+
pruneOldUsage as pruneStoredUsage,
|
|
8
|
+
saveUsage as saveStoredUsage,
|
|
9
|
+
} from '@/lib/server/storage'
|
|
10
|
+
import { perf } from '@/lib/server/runtime/perf'
|
|
11
|
+
|
|
12
|
+
export function loadUsage(): Record<string, UsageRecord[]> {
|
|
13
|
+
return perf.measureSync('repository', 'usage.list', () => loadStoredUsage())
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function saveUsage(data: Record<string, UsageRecord[]>): void {
|
|
17
|
+
perf.measureSync('repository', 'usage.replace', () => saveStoredUsage(data), { count: Object.keys(data).length })
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function appendUsage(sessionId: string, record: unknown): void {
|
|
21
|
+
perf.measureSync('repository', 'usage.append', () => appendStoredUsage(sessionId, record), { sessionId })
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function getUsageSpendSince(minTimestamp: number): number {
|
|
25
|
+
return perf.measureSync('repository', 'usage.spendSince', () => getStoredUsageSpendSince(minTimestamp), { minTimestamp })
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function pruneOldUsage(maxAgeMs: number): number {
|
|
29
|
+
return perf.measureSync('repository', 'usage.prune', () => pruneStoredUsage(maxAgeMs), { maxAgeMs })
|
|
30
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { describe, it } from 'node:test'
|
|
2
3
|
import { stripInternalJson, stripLoopDetectionMessages, stripAllInternalMetadata } from './strip-internal-metadata'
|
|
3
4
|
|
|
4
5
|
// ---------------------------------------------------------------------------
|
|
@@ -8,37 +9,37 @@ import { stripInternalJson, stripLoopDetectionMessages, stripAllInternalMetadata
|
|
|
8
9
|
describe('stripInternalJson', () => {
|
|
9
10
|
it('removes single-line classification JSON', () => {
|
|
10
11
|
const input = '{ "isDeliverableTask": true, "quality_score": 0.8, "isBroadGoal": false }'
|
|
11
|
-
|
|
12
|
+
assert.equal(stripInternalJson(input).trim(), '')
|
|
12
13
|
})
|
|
13
14
|
|
|
14
15
|
it('removes classification JSON embedded in surrounding text', () => {
|
|
15
16
|
const input = 'Here is the answer.\n{ "isDeliverableTask": true, "confidence": 0.9 }\nMore text follows.'
|
|
16
17
|
const result = stripInternalJson(input)
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
assert.match(result, /Here is the answer\./)
|
|
19
|
+
assert.match(result, /More text follows\./)
|
|
20
|
+
assert.doesNotMatch(result, /isDeliverableTask/)
|
|
20
21
|
})
|
|
21
22
|
|
|
22
23
|
it('preserves legitimate JSON that does not contain internal keys', () => {
|
|
23
24
|
const input = 'The config is { "name": "test", "port": 3000 }'
|
|
24
|
-
|
|
25
|
+
assert.equal(stripInternalJson(input), input)
|
|
25
26
|
})
|
|
26
27
|
|
|
27
28
|
it('preserves JSON with nested objects if no internal keys', () => {
|
|
28
29
|
const input = '{ "user": { "name": "alice" } }'
|
|
29
|
-
|
|
30
|
+
assert.equal(stripInternalJson(input), input)
|
|
30
31
|
})
|
|
31
32
|
|
|
32
33
|
it('removes JSON with nested objects when internal keys are present', () => {
|
|
33
34
|
const input = '{ "walletIntent": "send", "details": { "amount": 100 } }'
|
|
34
|
-
|
|
35
|
+
assert.equal(stripInternalJson(input).trim(), '')
|
|
35
36
|
})
|
|
36
37
|
|
|
37
38
|
it('handles multiple JSON blocks, only removing internal ones', () => {
|
|
38
39
|
const input = '{ "isDeliverableTask": true } some text { "foo": "bar" }'
|
|
39
40
|
const result = stripInternalJson(input)
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
assert.doesNotMatch(result, /isDeliverableTask/)
|
|
42
|
+
assert.match(result, /\{ "foo": "bar" \}/)
|
|
42
43
|
})
|
|
43
44
|
})
|
|
44
45
|
|
|
@@ -49,110 +50,110 @@ describe('stripInternalJson', () => {
|
|
|
49
50
|
describe('stripLoopDetectionMessages', () => {
|
|
50
51
|
it('strips tool frequency "called N times" messages', () => {
|
|
51
52
|
const input = 'Tool "shell" called 30 times this turn. Excessive repetition — wrap up with available results.'
|
|
52
|
-
|
|
53
|
+
assert.equal(stripLoopDetectionMessages(input).trim(), '')
|
|
53
54
|
})
|
|
54
55
|
|
|
55
56
|
it('strips tool frequency "would be called" messages', () => {
|
|
56
57
|
const input = 'Tool "shell" would be called 31 times this turn. Excessive repetition — wrap up with available results.'
|
|
57
|
-
|
|
58
|
+
assert.equal(stripLoopDetectionMessages(input).trim(), '')
|
|
58
59
|
})
|
|
59
60
|
|
|
60
61
|
it('strips "nearing overuse" messages', () => {
|
|
61
62
|
const input = 'Tool "read" is nearing overuse (15 calls this turn). Consider whether another call is needed.'
|
|
62
|
-
|
|
63
|
+
assert.equal(stripLoopDetectionMessages(input).trim(), '')
|
|
63
64
|
})
|
|
64
65
|
|
|
65
66
|
it('strips generic repeat "You called" messages', () => {
|
|
66
67
|
const input = 'You called "browser" 6 times with identical input. Input: "{"action":"screenshot"}" — State your blocker or deliver what you have.'
|
|
67
|
-
|
|
68
|
+
assert.equal(stripLoopDetectionMessages(input).trim(), '')
|
|
68
69
|
})
|
|
69
70
|
|
|
70
71
|
it('strips generic repeat "You called" warning messages', () => {
|
|
71
72
|
const input = 'You called "search" 4 times with identical input. Input: "query" — Try a fundamentally different approach or deliver partial results.'
|
|
72
|
-
|
|
73
|
+
assert.equal(stripLoopDetectionMessages(input).trim(), '')
|
|
73
74
|
})
|
|
74
75
|
|
|
75
76
|
it('strips "would repeat the same input" messages', () => {
|
|
76
77
|
const input = '"search" would repeat the same input 12 times. Input: "query" — State your blocker or deliver what you have.'
|
|
77
|
-
|
|
78
|
+
assert.equal(stripLoopDetectionMessages(input).trim(), '')
|
|
78
79
|
})
|
|
79
80
|
|
|
80
81
|
it('strips "is about to repeat the same input" messages', () => {
|
|
81
82
|
const input = '"search" is about to repeat the same input 6 times. Input: "query" — Try a different approach.'
|
|
82
|
-
|
|
83
|
+
assert.equal(stripLoopDetectionMessages(input).trim(), '')
|
|
83
84
|
})
|
|
84
85
|
|
|
85
86
|
it('strips circuit breaker messages', () => {
|
|
86
87
|
const input = 'Circuit breaker: "shell" called 20 times with identical input. Halting to prevent runaway.'
|
|
87
|
-
|
|
88
|
+
assert.equal(stripLoopDetectionMessages(input).trim(), '')
|
|
88
89
|
})
|
|
89
90
|
|
|
90
91
|
it('strips circuit breaker preview messages', () => {
|
|
91
92
|
const input = 'Circuit breaker: "shell" would be called 20 times with identical input. Halting before another runaway call.'
|
|
92
|
-
|
|
93
|
+
assert.equal(stripLoopDetectionMessages(input).trim(), '')
|
|
93
94
|
})
|
|
94
95
|
|
|
95
96
|
it('strips polling stall messages', () => {
|
|
96
97
|
const input = 'Polling stall: "status_check" returned identical output 8 times consecutively. The polled resource is not changing.'
|
|
97
|
-
|
|
98
|
+
assert.equal(stripLoopDetectionMessages(input).trim(), '')
|
|
98
99
|
})
|
|
99
100
|
|
|
100
101
|
it('strips ping-pong "are alternating" messages', () => {
|
|
101
102
|
const input = 'Ping-pong: "read" and "write" are alternating with identical results (5 cycles). Breaking the loop.'
|
|
102
|
-
|
|
103
|
+
assert.equal(stripLoopDetectionMessages(input).trim(), '')
|
|
103
104
|
})
|
|
104
105
|
|
|
105
106
|
it('strips ping-pong "may be stuck" messages', () => {
|
|
106
107
|
const input = 'Ping-pong: "read" and "write" may be stuck in an alternating loop (3 cycles).'
|
|
107
|
-
|
|
108
|
+
assert.equal(stripLoopDetectionMessages(input).trim(), '')
|
|
108
109
|
})
|
|
109
110
|
|
|
110
111
|
it('strips output stagnation messages', () => {
|
|
111
112
|
const input = 'Output stagnation: last 8 tool calls all produced identical output. The approach is not working — try something fundamentally different or report the blocker.'
|
|
112
|
-
|
|
113
|
+
assert.equal(stripLoopDetectionMessages(input).trim(), '')
|
|
113
114
|
})
|
|
114
115
|
|
|
115
116
|
it('strips output stagnation warning messages', () => {
|
|
116
117
|
const input = 'Output stagnation: 6 of the last 8 tool calls produced identical output. Your tools may not be making progress.'
|
|
117
|
-
|
|
118
|
+
assert.equal(stripLoopDetectionMessages(input).trim(), '')
|
|
118
119
|
})
|
|
119
120
|
|
|
120
121
|
it('strips error convergence messages', () => {
|
|
121
122
|
const input = 'Error convergence: 5 of the last 6 tool calls returned errors. Stop retrying and report the underlying issue (likely an infrastructure or configuration problem).'
|
|
122
|
-
|
|
123
|
+
assert.equal(stripLoopDetectionMessages(input).trim(), '')
|
|
123
124
|
})
|
|
124
125
|
|
|
125
126
|
it('strips error convergence warning messages', () => {
|
|
126
127
|
const input = 'Error convergence: 4 of the last 6 tool calls returned errors. You may be hitting a systemic issue — consider a different approach or report the blocker.'
|
|
127
|
-
|
|
128
|
+
assert.equal(stripLoopDetectionMessages(input).trim(), '')
|
|
128
129
|
})
|
|
129
130
|
|
|
130
131
|
it('strips messages wrapped in [Error: ...] brackets', () => {
|
|
131
132
|
const input = '[Error: You called "browser" 6 times with identical input. Input: "{"action":"screenshot"}" — State your blocker or deliver what you have.]'
|
|
132
|
-
|
|
133
|
+
assert.equal(stripLoopDetectionMessages(input).trim(), '')
|
|
133
134
|
})
|
|
134
135
|
|
|
135
136
|
it('strips [Error: ...] wrapped tool frequency messages', () => {
|
|
136
137
|
const input = '[Error: Tool "shell" called 30 times this turn. Excessive repetition — wrap up with available results.]'
|
|
137
|
-
|
|
138
|
+
assert.equal(stripLoopDetectionMessages(input).trim(), '')
|
|
138
139
|
})
|
|
139
140
|
|
|
140
141
|
it('strips [Error: ...] wrapped output stagnation messages', () => {
|
|
141
142
|
const input = '[Error: Output stagnation: last 8 tool calls all produced identical output.]'
|
|
142
|
-
|
|
143
|
+
assert.equal(stripLoopDetectionMessages(input).trim(), '')
|
|
143
144
|
})
|
|
144
145
|
|
|
145
146
|
it('preserves normal text that mentions tools', () => {
|
|
146
147
|
const input = 'I used the shell tool to run the command.'
|
|
147
|
-
|
|
148
|
+
assert.equal(stripLoopDetectionMessages(input), input)
|
|
148
149
|
})
|
|
149
150
|
|
|
150
151
|
it('strips loop message embedded in surrounding text', () => {
|
|
151
152
|
const input = 'Working on it.\nTool "shell" called 30 times this turn. Excessive repetition — wrap up with available results.\nHere are the results.'
|
|
152
153
|
const result = stripLoopDetectionMessages(input)
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
154
|
+
assert.match(result, /Working on it\./)
|
|
155
|
+
assert.match(result, /Here are the results\./)
|
|
156
|
+
assert.doesNotMatch(result, /called 30 times/)
|
|
156
157
|
})
|
|
157
158
|
})
|
|
158
159
|
|
|
@@ -169,32 +170,32 @@ describe('stripAllInternalMetadata', () => {
|
|
|
169
170
|
'The answer is 42.',
|
|
170
171
|
].join('\n')
|
|
171
172
|
const result = stripAllInternalMetadata(input)
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
173
|
+
assert.doesNotMatch(result, /isDeliverableTask/)
|
|
174
|
+
assert.doesNotMatch(result, /called 30 times/)
|
|
175
|
+
assert.match(result, /Here is my analysis\./)
|
|
176
|
+
assert.match(result, /The answer is 42\./)
|
|
176
177
|
})
|
|
177
178
|
|
|
178
179
|
it('collapses excessive newlines after stripping', () => {
|
|
179
180
|
const input = 'Hello.\n\n\n\n{ "isDeliverableTask": true }\n\n\n\nWorld.'
|
|
180
181
|
const result = stripAllInternalMetadata(input)
|
|
181
|
-
|
|
182
|
+
assert.doesNotMatch(result, /\n{3,}/)
|
|
182
183
|
})
|
|
183
184
|
|
|
184
185
|
it('returns empty string for purely internal content', () => {
|
|
185
186
|
const input = '{ "isDeliverableTask": true, "quality_score": 0.9 }'
|
|
186
|
-
|
|
187
|
+
assert.equal(stripAllInternalMetadata(input), '')
|
|
187
188
|
})
|
|
188
189
|
|
|
189
190
|
it('leaves normal messages untouched', () => {
|
|
190
191
|
const input = 'Here is a normal response with no internal metadata.'
|
|
191
|
-
|
|
192
|
+
assert.equal(stripAllInternalMetadata(input), input)
|
|
192
193
|
})
|
|
193
194
|
|
|
194
195
|
it('preserves code blocks with JSON that happen to have similar-looking keys', () => {
|
|
195
196
|
// JSON in code blocks uses real braces, so the regex will match the block.
|
|
196
197
|
// But since 'name' and 'age' are not internal keys, it should be preserved.
|
|
197
198
|
const input = 'Result: { "name": "Alice", "age": 30 }'
|
|
198
|
-
|
|
199
|
+
assert.equal(stripAllInternalMetadata(input), input)
|
|
199
200
|
})
|
|
200
201
|
})
|