@swarmclawai/swarmclaw 1.5.48 → 1.5.49
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 +9 -0
- package/package.json +1 -1
- package/src/app/api/missions/[id]/control/route.ts +57 -0
- package/src/app/api/missions/[id]/events/route.ts +21 -0
- package/src/app/api/missions/[id]/reports/route.ts +33 -0
- package/src/app/api/missions/[id]/route.ts +82 -0
- package/src/app/api/missions/route.test.ts +170 -0
- package/src/app/api/missions/route.ts +58 -0
- package/src/app/missions/page.tsx +635 -0
- package/src/cli/index.js +15 -0
- package/src/cli/spec.js +14 -0
- package/src/components/layout/sidebar-rail.tsx +8 -0
- package/src/lib/app/navigation.ts +1 -0
- package/src/lib/app/view-constants.ts +10 -1
- package/src/lib/server/missions/mission-budget-hook.ts +38 -0
- package/src/lib/server/missions/mission-report-builder.test.ts +106 -0
- package/src/lib/server/missions/mission-report-builder.ts +158 -0
- package/src/lib/server/missions/mission-repository.test.ts +171 -0
- package/src/lib/server/missions/mission-repository.ts +137 -0
- package/src/lib/server/missions/mission-scheduler.ts +107 -0
- package/src/lib/server/missions/mission-service.test.ts +201 -0
- package/src/lib/server/missions/mission-service.ts +299 -0
- package/src/lib/server/runtime/heartbeat-service.ts +5 -0
- package/src/lib/server/runtime/session-run-manager/enqueue.ts +9 -0
- package/src/lib/server/storage-normalization.ts +145 -1
- package/src/lib/server/storage.ts +29 -0
- package/src/types/index.ts +1 -0
- package/src/types/mission.ts +115 -0
- package/src/types/session.ts +3 -1
|
@@ -355,6 +355,135 @@ function normalizeStoredMissionEventRecord(value: unknown): unknown {
|
|
|
355
355
|
return event
|
|
356
356
|
}
|
|
357
357
|
|
|
358
|
+
// --- Agent Mission normalizers (autonomous goal-driven runs, v1.5.49+) ---
|
|
359
|
+
|
|
360
|
+
const VALID_AGENT_MISSION_STATUSES = new Set([
|
|
361
|
+
'draft',
|
|
362
|
+
'running',
|
|
363
|
+
'paused',
|
|
364
|
+
'completed',
|
|
365
|
+
'failed',
|
|
366
|
+
'cancelled',
|
|
367
|
+
'budget_exhausted',
|
|
368
|
+
])
|
|
369
|
+
|
|
370
|
+
const VALID_AGENT_MISSION_REPORT_FORMATS = new Set(['markdown', 'slack', 'discord', 'email', 'audio'])
|
|
371
|
+
|
|
372
|
+
function normalizeFiniteNumber(value: unknown): number | null {
|
|
373
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) return null
|
|
374
|
+
return value
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function normalizeNonNegativeNumber(value: unknown, fallback: number): number {
|
|
378
|
+
const n = normalizeFiniteNumber(value)
|
|
379
|
+
if (n == null || n < 0) return fallback
|
|
380
|
+
return n
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function normalizeStoredAgentMissionRecord(value: unknown): unknown {
|
|
384
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return value
|
|
385
|
+
const mission = value as StoredObject
|
|
386
|
+
|
|
387
|
+
const status = typeof mission.status === 'string' ? mission.status.trim().toLowerCase() : ''
|
|
388
|
+
mission.status = VALID_AGENT_MISSION_STATUSES.has(status) ? status : 'draft'
|
|
389
|
+
|
|
390
|
+
mission.successCriteria = normalizeStoredStringArray(mission.successCriteria, 64)
|
|
391
|
+
mission.agentIds = normalizeStoredStringArray(mission.agentIds, 32)
|
|
392
|
+
mission.reportConnectorIds = normalizeStoredStringArray(mission.reportConnectorIds, 16)
|
|
393
|
+
|
|
394
|
+
const budget = mission.budget && typeof mission.budget === 'object' && !Array.isArray(mission.budget)
|
|
395
|
+
? mission.budget as StoredObject
|
|
396
|
+
: {}
|
|
397
|
+
budget.maxUsd = normalizeFiniteNumber(budget.maxUsd)
|
|
398
|
+
budget.maxTokens = normalizeFiniteNumber(budget.maxTokens)
|
|
399
|
+
budget.maxToolCalls = normalizeFiniteNumber(budget.maxToolCalls)
|
|
400
|
+
budget.maxWallclockSec = normalizeFiniteNumber(budget.maxWallclockSec)
|
|
401
|
+
budget.maxTurns = normalizeFiniteNumber(budget.maxTurns)
|
|
402
|
+
if (!Array.isArray(budget.warnAtFractions)) {
|
|
403
|
+
budget.warnAtFractions = [0.5, 0.8, 0.95]
|
|
404
|
+
} else {
|
|
405
|
+
budget.warnAtFractions = (budget.warnAtFractions as unknown[])
|
|
406
|
+
.map((entry) => normalizeFiniteNumber(entry))
|
|
407
|
+
.filter((entry): entry is number => entry != null && entry > 0 && entry < 1)
|
|
408
|
+
if ((budget.warnAtFractions as number[]).length === 0) {
|
|
409
|
+
budget.warnAtFractions = [0.5, 0.8, 0.95]
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
mission.budget = budget
|
|
413
|
+
|
|
414
|
+
const usage = mission.usage && typeof mission.usage === 'object' && !Array.isArray(mission.usage)
|
|
415
|
+
? mission.usage as StoredObject
|
|
416
|
+
: {}
|
|
417
|
+
usage.usdSpent = normalizeNonNegativeNumber(usage.usdSpent, 0)
|
|
418
|
+
usage.tokensUsed = normalizeNonNegativeNumber(usage.tokensUsed, 0)
|
|
419
|
+
usage.toolCallsUsed = normalizeNonNegativeNumber(usage.toolCallsUsed, 0)
|
|
420
|
+
usage.turnsRun = normalizeNonNegativeNumber(usage.turnsRun, 0)
|
|
421
|
+
usage.wallclockMsElapsed = normalizeNonNegativeNumber(usage.wallclockMsElapsed, 0)
|
|
422
|
+
usage.startedAt = normalizeFiniteNumber(usage.startedAt)
|
|
423
|
+
usage.lastUpdatedAt = normalizeNonNegativeNumber(usage.lastUpdatedAt, 0)
|
|
424
|
+
if (!Array.isArray(usage.warnFractionsHit)) {
|
|
425
|
+
usage.warnFractionsHit = []
|
|
426
|
+
} else {
|
|
427
|
+
usage.warnFractionsHit = (usage.warnFractionsHit as unknown[])
|
|
428
|
+
.map((entry) => normalizeFiniteNumber(entry))
|
|
429
|
+
.filter((entry): entry is number => entry != null)
|
|
430
|
+
}
|
|
431
|
+
mission.usage = usage
|
|
432
|
+
|
|
433
|
+
if (!Array.isArray(mission.milestones)) mission.milestones = []
|
|
434
|
+
// Cap the stored tail so missions don't balloon
|
|
435
|
+
if ((mission.milestones as unknown[]).length > 200) {
|
|
436
|
+
mission.milestones = (mission.milestones as unknown[]).slice(-200)
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const reportSchedule = mission.reportSchedule
|
|
440
|
+
&& typeof mission.reportSchedule === 'object'
|
|
441
|
+
&& !Array.isArray(mission.reportSchedule)
|
|
442
|
+
? mission.reportSchedule as StoredObject
|
|
443
|
+
: null
|
|
444
|
+
if (reportSchedule) {
|
|
445
|
+
const format = typeof reportSchedule.format === 'string' ? reportSchedule.format.trim().toLowerCase() : ''
|
|
446
|
+
reportSchedule.format = VALID_AGENT_MISSION_REPORT_FORMATS.has(format) ? format : 'markdown'
|
|
447
|
+
reportSchedule.intervalSec = normalizeNonNegativeNumber(reportSchedule.intervalSec, 3600)
|
|
448
|
+
reportSchedule.enabled = reportSchedule.enabled !== false
|
|
449
|
+
reportSchedule.lastReportAt = normalizeFiniteNumber(reportSchedule.lastReportAt)
|
|
450
|
+
mission.reportSchedule = reportSchedule
|
|
451
|
+
} else if (mission.reportSchedule !== undefined) {
|
|
452
|
+
mission.reportSchedule = null
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
if (typeof mission.createdAt !== 'number') mission.createdAt = Date.now()
|
|
456
|
+
if (typeof mission.updatedAt !== 'number') mission.updatedAt = mission.createdAt as number
|
|
457
|
+
if (mission.startedAt === undefined) mission.startedAt = null
|
|
458
|
+
if (mission.endedAt === undefined) mission.endedAt = null
|
|
459
|
+
if (mission.endReason === undefined) mission.endReason = null
|
|
460
|
+
|
|
461
|
+
return mission
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function normalizeStoredMissionReportRecord(value: unknown): unknown {
|
|
465
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return value
|
|
466
|
+
const report = value as StoredObject
|
|
467
|
+
const format = typeof report.format === 'string' ? report.format.trim().toLowerCase() : ''
|
|
468
|
+
report.format = VALID_AGENT_MISSION_REPORT_FORMATS.has(format) ? format : 'markdown'
|
|
469
|
+
if (!Array.isArray(report.highlights)) report.highlights = []
|
|
470
|
+
if (!Array.isArray(report.deliveredTo)) report.deliveredTo = []
|
|
471
|
+
if (typeof report.body !== 'string') report.body = ''
|
|
472
|
+
if (typeof report.title !== 'string') report.title = 'Mission report'
|
|
473
|
+
return report
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function normalizeStoredAgentMissionEventRecord(value: unknown): unknown {
|
|
477
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return value
|
|
478
|
+
const event = value as StoredObject
|
|
479
|
+
if (!event.payload || typeof event.payload !== 'object' || Array.isArray(event.payload)) {
|
|
480
|
+
event.payload = {}
|
|
481
|
+
}
|
|
482
|
+
if (typeof event.kind !== 'string' || !event.kind.trim()) event.kind = 'unknown'
|
|
483
|
+
if (typeof event.at !== 'number' || !Number.isFinite(event.at)) event.at = Date.now()
|
|
484
|
+
return event
|
|
485
|
+
}
|
|
486
|
+
|
|
358
487
|
// --- Delegation job normalizer ---
|
|
359
488
|
|
|
360
489
|
function normalizeStoredDelegationJobRecord(value: unknown): unknown {
|
|
@@ -428,7 +557,7 @@ export function normalizeStoredRecord(
|
|
|
428
557
|
value: unknown,
|
|
429
558
|
loadItem: CollectionItemLoader,
|
|
430
559
|
): NormalizationResult {
|
|
431
|
-
// Tables with no normalization
|
|
560
|
+
// Tables with no normalization, early exit.
|
|
432
561
|
if (
|
|
433
562
|
table !== 'agents' && table !== 'tasks' && table !== 'missions'
|
|
434
563
|
&& table !== 'mission_events' && table !== 'delegation_jobs'
|
|
@@ -436,6 +565,9 @@ export function normalizeStoredRecord(
|
|
|
436
565
|
&& table !== 'provider_configs'
|
|
437
566
|
&& table !== 'runtime_runs' && table !== 'runtime_run_events'
|
|
438
567
|
&& table !== 'wallets'
|
|
568
|
+
&& table !== 'agent_missions'
|
|
569
|
+
&& table !== 'mission_reports'
|
|
570
|
+
&& table !== 'agent_mission_events'
|
|
439
571
|
) {
|
|
440
572
|
return { value, changed: false }
|
|
441
573
|
}
|
|
@@ -590,6 +722,18 @@ function normalizeStoredRecordInner(
|
|
|
590
722
|
return normalizeStoredMissionEventRecord(value)
|
|
591
723
|
}
|
|
592
724
|
|
|
725
|
+
if (table === 'agent_missions') {
|
|
726
|
+
return normalizeStoredAgentMissionRecord(value)
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
if (table === 'mission_reports') {
|
|
730
|
+
return normalizeStoredMissionReportRecord(value)
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
if (table === 'agent_mission_events') {
|
|
734
|
+
return normalizeStoredAgentMissionEventRecord(value)
|
|
735
|
+
}
|
|
736
|
+
|
|
593
737
|
if (table === 'delegation_jobs') {
|
|
594
738
|
return normalizeStoredDelegationJobRecord(value)
|
|
595
739
|
}
|
|
@@ -26,6 +26,9 @@ import type {
|
|
|
26
26
|
KnowledgeSource,
|
|
27
27
|
LearnedSkill,
|
|
28
28
|
Message,
|
|
29
|
+
Mission,
|
|
30
|
+
MissionEvent,
|
|
31
|
+
MissionReport,
|
|
29
32
|
ProtocolTemplate,
|
|
30
33
|
ProtocolRun,
|
|
31
34
|
ProtocolRunEvent,
|
|
@@ -176,6 +179,9 @@ const COLLECTIONS = [
|
|
|
176
179
|
'wallets',
|
|
177
180
|
'wallet_transactions',
|
|
178
181
|
'goals',
|
|
182
|
+
'agent_missions',
|
|
183
|
+
'mission_reports',
|
|
184
|
+
'agent_mission_events',
|
|
179
185
|
] as const
|
|
180
186
|
|
|
181
187
|
export type StorageCollection = (typeof COLLECTIONS)[number]
|
|
@@ -1686,6 +1692,29 @@ export const loadGoal = goalsStore.loadItem
|
|
|
1686
1692
|
export const upsertGoal = goalsStore.upsert
|
|
1687
1693
|
export const deleteGoalItem = goalsStore.deleteItem
|
|
1688
1694
|
|
|
1695
|
+
// --- Agent Missions (autonomous goal-driven runs) ---
|
|
1696
|
+
const agentMissionsStore = createCollectionStore<Mission>('agent_missions', { ttlMs: 5_000 })
|
|
1697
|
+
export const loadAgentMissions = agentMissionsStore.load
|
|
1698
|
+
export const saveAgentMissions = agentMissionsStore.save
|
|
1699
|
+
export const loadAgentMission = agentMissionsStore.loadItem
|
|
1700
|
+
export const upsertAgentMission = agentMissionsStore.upsert
|
|
1701
|
+
export const patchAgentMission = agentMissionsStore.patch
|
|
1702
|
+
export const deleteAgentMission = agentMissionsStore.deleteItem
|
|
1703
|
+
|
|
1704
|
+
const missionReportsStore = createCollectionStore<MissionReport>('mission_reports')
|
|
1705
|
+
export const loadMissionReports = missionReportsStore.load
|
|
1706
|
+
export const saveMissionReports = missionReportsStore.save
|
|
1707
|
+
export const loadMissionReport = missionReportsStore.loadItem
|
|
1708
|
+
export const upsertMissionReport = missionReportsStore.upsert
|
|
1709
|
+
export const deleteMissionReport = missionReportsStore.deleteItem
|
|
1710
|
+
|
|
1711
|
+
const agentMissionEventsStore = createCollectionStore<MissionEvent>('agent_mission_events')
|
|
1712
|
+
export const loadAgentMissionEvents = agentMissionEventsStore.load
|
|
1713
|
+
export const saveAgentMissionEvents = agentMissionEventsStore.save
|
|
1714
|
+
export const loadAgentMissionEvent = agentMissionEventsStore.loadItem
|
|
1715
|
+
export const upsertAgentMissionEvent = agentMissionEventsStore.upsert
|
|
1716
|
+
export const deleteAgentMissionEvent = agentMissionEventsStore.deleteItem
|
|
1717
|
+
|
|
1689
1718
|
function legacyMissionStatusToWorkingStatus(value: unknown): 'idle' | 'progress' | 'blocked' | 'completed' {
|
|
1690
1719
|
const normalized = typeof value === 'string' ? value.trim().toLowerCase() : ''
|
|
1691
1720
|
if (normalized === 'achieved' || normalized === 'completed' || normalized === 'ok') return 'completed'
|
package/src/types/index.ts
CHANGED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
export type MissionStatus =
|
|
2
|
+
| 'draft'
|
|
3
|
+
| 'running'
|
|
4
|
+
| 'paused'
|
|
5
|
+
| 'completed'
|
|
6
|
+
| 'failed'
|
|
7
|
+
| 'cancelled'
|
|
8
|
+
| 'budget_exhausted'
|
|
9
|
+
|
|
10
|
+
export type MissionReportFormat = 'markdown' | 'slack' | 'discord' | 'email' | 'audio'
|
|
11
|
+
|
|
12
|
+
export type MissionMilestoneKind =
|
|
13
|
+
| 'started'
|
|
14
|
+
| 'budget_warn'
|
|
15
|
+
| 'budget_hit'
|
|
16
|
+
| 'check_in'
|
|
17
|
+
| 'subgoal_done'
|
|
18
|
+
| 'report_sent'
|
|
19
|
+
| 'paused'
|
|
20
|
+
| 'resumed'
|
|
21
|
+
| 'completed'
|
|
22
|
+
| 'failed'
|
|
23
|
+
| 'cancelled'
|
|
24
|
+
|
|
25
|
+
export interface MissionBudget {
|
|
26
|
+
maxUsd?: number | null
|
|
27
|
+
maxTokens?: number | null
|
|
28
|
+
maxToolCalls?: number | null
|
|
29
|
+
maxWallclockSec?: number | null
|
|
30
|
+
maxTurns?: number | null
|
|
31
|
+
warnAtFractions?: number[]
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface MissionUsage {
|
|
35
|
+
usdSpent: number
|
|
36
|
+
tokensUsed: number
|
|
37
|
+
toolCallsUsed: number
|
|
38
|
+
turnsRun: number
|
|
39
|
+
wallclockMsElapsed: number
|
|
40
|
+
startedAt: number | null
|
|
41
|
+
lastUpdatedAt: number
|
|
42
|
+
warnFractionsHit: number[]
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface MissionMilestone {
|
|
46
|
+
id: string
|
|
47
|
+
at: number
|
|
48
|
+
kind: MissionMilestoneKind
|
|
49
|
+
summary: string
|
|
50
|
+
evidence?: string[]
|
|
51
|
+
sessionId?: string | null
|
|
52
|
+
runId?: string | null
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface MissionReportSchedule {
|
|
56
|
+
intervalSec: number
|
|
57
|
+
format: MissionReportFormat
|
|
58
|
+
enabled: boolean
|
|
59
|
+
lastReportAt?: number | null
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface MissionReportDelivery {
|
|
63
|
+
connectorId?: string | null
|
|
64
|
+
channelId?: string | null
|
|
65
|
+
deliveredAt: number
|
|
66
|
+
status: 'ok' | 'error'
|
|
67
|
+
error?: string | null
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface MissionReport {
|
|
71
|
+
id: string
|
|
72
|
+
missionId: string
|
|
73
|
+
generatedAt: number
|
|
74
|
+
format: MissionReportFormat
|
|
75
|
+
fromAt: number
|
|
76
|
+
toAt: number
|
|
77
|
+
title: string
|
|
78
|
+
body: string
|
|
79
|
+
audioUrl?: string | null
|
|
80
|
+
deliveredTo: MissionReportDelivery[]
|
|
81
|
+
highlights: Array<{ kind: string; summary: string; evidenceRunId?: string | null }>
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface MissionEvent {
|
|
85
|
+
id: string
|
|
86
|
+
missionId: string
|
|
87
|
+
at: number
|
|
88
|
+
kind: string
|
|
89
|
+
payload: Record<string, unknown>
|
|
90
|
+
sessionId?: string | null
|
|
91
|
+
runId?: string | null
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface Mission {
|
|
95
|
+
id: string
|
|
96
|
+
title: string
|
|
97
|
+
goal: string
|
|
98
|
+
successCriteria: string[]
|
|
99
|
+
rootSessionId: string
|
|
100
|
+
agentIds: string[]
|
|
101
|
+
status: MissionStatus
|
|
102
|
+
budget: MissionBudget
|
|
103
|
+
usage: MissionUsage
|
|
104
|
+
milestones: MissionMilestone[]
|
|
105
|
+
reportSchedule?: MissionReportSchedule | null
|
|
106
|
+
reportConnectorIds: string[]
|
|
107
|
+
createdAt: number
|
|
108
|
+
updatedAt: number
|
|
109
|
+
startedAt?: number | null
|
|
110
|
+
endedAt?: number | null
|
|
111
|
+
endReason?: string | null
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export const DEFAULT_MISSION_WARN_FRACTIONS = [0.5, 0.8, 0.95]
|
|
115
|
+
export const MISSION_MILESTONE_TAIL_CAP = 200
|
package/src/types/session.ts
CHANGED
|
@@ -173,6 +173,8 @@ export interface Session {
|
|
|
173
173
|
file?: string | null
|
|
174
174
|
queuedCount?: number
|
|
175
175
|
currentRunId?: string | null
|
|
176
|
+
/** Optional link to an autonomous Mission that drives this session. */
|
|
177
|
+
missionId?: string | null
|
|
176
178
|
conversationTone?: string
|
|
177
179
|
emoji?: string
|
|
178
180
|
creature?: string
|
|
@@ -227,4 +229,4 @@ export type SessionTool =
|
|
|
227
229
|
| 'crawl'
|
|
228
230
|
|
|
229
231
|
export type SessionType = 'human'
|
|
230
|
-
export type AppView = 'home' | 'agents' | 'org_chart' | 'inbox' | 'chatrooms' | 'protocols' | 'schedules' | 'memory' | 'tasks' | 'secrets' | 'wallets' | 'providers' | 'skills' | 'connectors' | 'webhooks' | 'mcp_servers' | 'knowledge' | 'extensions' | 'usage' | 'runs' | 'autonomy' | 'logs' | 'settings' | 'projects' | 'activity' | 'swarmfeed' | 'marketplace'
|
|
232
|
+
export type AppView = 'home' | 'agents' | 'org_chart' | 'inbox' | 'chatrooms' | 'protocols' | 'schedules' | 'memory' | 'tasks' | 'secrets' | 'wallets' | 'providers' | 'skills' | 'connectors' | 'webhooks' | 'mcp_servers' | 'knowledge' | 'extensions' | 'usage' | 'runs' | 'autonomy' | 'logs' | 'settings' | 'projects' | 'activity' | 'swarmfeed' | 'marketplace' | 'missions'
|