@swarmclawai/swarmclaw 1.7.3 → 1.8.1
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 +20 -0
- package/next.config.ts +38 -8
- package/package.json +2 -2
- package/scripts/run-next-build.mjs +51 -3
- package/src/app/api/artifacts/route.ts +15 -0
- package/src/app/api/clawhub/install/route.ts +4 -4
- package/src/app/api/dirs/route.ts +8 -5
- package/src/app/api/files/open/route.ts +3 -3
- package/src/app/api/files/serve/route.ts +2 -2
- package/src/app/api/operations/pulse/route.ts +9 -0
- package/src/app/api/runs/[id]/brief/route.ts +12 -0
- package/src/app/api/runs/[id]/events/route.ts +4 -13
- package/src/app/api/runs/[id]/route.ts +2 -6
- package/src/app/api/runs/route.ts +3 -43
- package/src/app/api/s/[token]/raw/route.ts +1 -1
- package/src/app/home/page.tsx +11 -1
- package/src/app/missions/page.tsx +182 -3
- package/src/app/s/[token]/page.tsx +173 -48
- package/src/cli/index.js +15 -0
- package/src/cli/spec.js +13 -0
- package/src/components/connectors/connector-list.tsx +36 -20
- package/src/components/evidence/evidence-shelf.tsx +97 -0
- package/src/components/home/home-launchpad.tsx +52 -2
- package/src/components/missions/mission-template-install-dialog.tsx +33 -1
- package/src/components/operations/operations-pulse-panel.tsx +184 -0
- package/src/components/quality/quality-workspace.tsx +34 -6
- package/src/components/runs/run-list.tsx +94 -12
- package/src/lib/connectors/connector-readiness.ts +127 -0
- package/src/lib/server/artifacts/artifact-resolver.test.ts +98 -0
- package/src/lib/server/artifacts/artifact-resolver.ts +241 -0
- package/src/lib/server/operations/operation-pulse.test.ts +108 -0
- package/src/lib/server/operations/operation-pulse.ts +197 -0
- package/src/lib/server/resolve-workspace-path.ts +10 -10
- package/src/lib/server/runs/run-brief.test.ts +92 -0
- package/src/lib/server/runs/run-brief.ts +107 -0
- package/src/lib/server/runs/unified-run-queries.ts +84 -0
- package/src/lib/server/sharing/share-resolver.test.ts +129 -0
- package/src/lib/server/sharing/share-resolver.ts +48 -3
- package/src/types/artifact.ts +28 -0
- package/src/types/index.ts +3 -0
- package/src/types/operations.ts +39 -0
- package/src/types/run-brief.ts +41 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import fs from 'node:fs'
|
|
3
|
+
import os from 'node:os'
|
|
4
|
+
import path from 'node:path'
|
|
5
|
+
import { after, before, describe, it } from 'node:test'
|
|
6
|
+
|
|
7
|
+
import type { Mission, MissionReport } from '@/types'
|
|
8
|
+
|
|
9
|
+
const originalEnv = {
|
|
10
|
+
DATA_DIR: process.env.DATA_DIR,
|
|
11
|
+
WORKSPACE_DIR: process.env.WORKSPACE_DIR,
|
|
12
|
+
SWARMCLAW_BUILD_MODE: process.env.SWARMCLAW_BUILD_MODE,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let tempDir = ''
|
|
16
|
+
let repo: typeof import('@/lib/server/missions/mission-repository')
|
|
17
|
+
let resolver: typeof import('./share-resolver')
|
|
18
|
+
|
|
19
|
+
function makeMission(overrides: Partial<Mission> = {}): Mission {
|
|
20
|
+
const now = Date.now()
|
|
21
|
+
return {
|
|
22
|
+
id: overrides.id ?? 'mi_share_1',
|
|
23
|
+
title: 'Shared mission',
|
|
24
|
+
goal: 'Produce a launch report',
|
|
25
|
+
successCriteria: ['Report exists', 'Evidence is cited'],
|
|
26
|
+
rootSessionId: 'share_session_1',
|
|
27
|
+
agentIds: ['agent_1'],
|
|
28
|
+
status: 'running',
|
|
29
|
+
budget: {
|
|
30
|
+
maxUsd: 2,
|
|
31
|
+
maxTokens: 100_000,
|
|
32
|
+
maxToolCalls: null,
|
|
33
|
+
maxWallclockSec: 86_400,
|
|
34
|
+
maxTurns: 120,
|
|
35
|
+
warnAtFractions: [0.5, 0.8, 0.95],
|
|
36
|
+
},
|
|
37
|
+
usage: {
|
|
38
|
+
usdSpent: 0.42,
|
|
39
|
+
tokensUsed: 12_345,
|
|
40
|
+
toolCallsUsed: 9,
|
|
41
|
+
turnsRun: 12,
|
|
42
|
+
wallclockMsElapsed: 900_000,
|
|
43
|
+
startedAt: now - 900_000,
|
|
44
|
+
lastUpdatedAt: now,
|
|
45
|
+
warnFractionsHit: [],
|
|
46
|
+
},
|
|
47
|
+
milestones: [
|
|
48
|
+
{
|
|
49
|
+
id: 'ms_1',
|
|
50
|
+
at: now - 1000,
|
|
51
|
+
kind: 'subgoal_done',
|
|
52
|
+
summary: 'Release evidence collected',
|
|
53
|
+
evidence: ['run_1'],
|
|
54
|
+
sessionId: 'share_session_1',
|
|
55
|
+
runId: 'run_1',
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
reportSchedule: null,
|
|
59
|
+
reportConnectorIds: [],
|
|
60
|
+
createdAt: now - 1_000_000,
|
|
61
|
+
updatedAt: now,
|
|
62
|
+
...overrides,
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function makeReport(missionId: string, overrides: Partial<MissionReport> = {}): MissionReport {
|
|
67
|
+
const now = Date.now()
|
|
68
|
+
return {
|
|
69
|
+
id: overrides.id ?? 'mrep_share_1',
|
|
70
|
+
missionId,
|
|
71
|
+
generatedAt: overrides.generatedAt ?? now,
|
|
72
|
+
format: 'markdown',
|
|
73
|
+
fromAt: now - 10_000,
|
|
74
|
+
toAt: now,
|
|
75
|
+
title: overrides.title ?? 'Shared mission: progress update',
|
|
76
|
+
body: overrides.body ?? '# Shared mission\n\nEvidence is ready.',
|
|
77
|
+
deliveredTo: [],
|
|
78
|
+
highlights: [],
|
|
79
|
+
...overrides,
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
before(async () => {
|
|
84
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'swarmclaw-share-resolver-'))
|
|
85
|
+
process.env.DATA_DIR = path.join(tempDir, 'data')
|
|
86
|
+
process.env.WORKSPACE_DIR = path.join(tempDir, 'workspace')
|
|
87
|
+
process.env.SWARMCLAW_BUILD_MODE = '1'
|
|
88
|
+
repo = await import('@/lib/server/missions/mission-repository')
|
|
89
|
+
resolver = await import('./share-resolver')
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
after(() => {
|
|
93
|
+
if (originalEnv.DATA_DIR === undefined) delete process.env.DATA_DIR
|
|
94
|
+
else process.env.DATA_DIR = originalEnv.DATA_DIR
|
|
95
|
+
if (originalEnv.WORKSPACE_DIR === undefined) delete process.env.WORKSPACE_DIR
|
|
96
|
+
else process.env.WORKSPACE_DIR = originalEnv.WORKSPACE_DIR
|
|
97
|
+
if (originalEnv.SWARMCLAW_BUILD_MODE === undefined) delete process.env.SWARMCLAW_BUILD_MODE
|
|
98
|
+
else process.env.SWARMCLAW_BUILD_MODE = originalEnv.SWARMCLAW_BUILD_MODE
|
|
99
|
+
fs.rmSync(tempDir, { recursive: true, force: true })
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
describe('share-resolver', () => {
|
|
103
|
+
it('resolves a public mission payload with summary milestones and safe report metadata', () => {
|
|
104
|
+
const mission = makeMission({ id: 'mi_public_share' })
|
|
105
|
+
repo.upsertMission(mission)
|
|
106
|
+
repo.saveMissionReport(makeReport(mission.id, { id: 'mrep_old', generatedAt: mission.createdAt + 1000, title: 'Old report' }))
|
|
107
|
+
repo.saveMissionReport(makeReport(mission.id, { id: 'mrep_new', generatedAt: mission.createdAt + 2000, title: 'Latest report' }))
|
|
108
|
+
|
|
109
|
+
const payload = resolver.resolveSharedEntity({
|
|
110
|
+
id: 'share_1',
|
|
111
|
+
token: 'tok',
|
|
112
|
+
entityType: 'mission',
|
|
113
|
+
entityId: mission.id,
|
|
114
|
+
label: null,
|
|
115
|
+
createdAt: Date.now(),
|
|
116
|
+
expiresAt: null,
|
|
117
|
+
revokedAt: null,
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
assert.equal(payload?.kind, 'mission')
|
|
121
|
+
if (payload?.kind !== 'mission') return
|
|
122
|
+
assert.equal(payload.milestones[0]?.summary, 'Release evidence collected')
|
|
123
|
+
assert.equal(Object.hasOwn(payload.milestones[0] ?? {}, 'note'), false)
|
|
124
|
+
assert.equal(payload.usage.turnsRun, 12)
|
|
125
|
+
assert.equal(payload.budget.maxUsd, 2)
|
|
126
|
+
assert.equal(payload.latestReport?.title, 'Latest report')
|
|
127
|
+
assert.deepEqual(payload.reports.map((r) => r.title), ['Latest report', 'Old report'])
|
|
128
|
+
})
|
|
129
|
+
})
|
|
@@ -10,8 +10,25 @@ export interface SharedMissionPayload {
|
|
|
10
10
|
successCriteria: string[]
|
|
11
11
|
status: string
|
|
12
12
|
createdAt: number
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
updatedAt: number | null
|
|
14
|
+
usage: {
|
|
15
|
+
usdSpent: number
|
|
16
|
+
tokensUsed: number
|
|
17
|
+
toolCallsUsed: number
|
|
18
|
+
turnsRun: number
|
|
19
|
+
wallclockMsElapsed: number
|
|
20
|
+
startedAt: number | null
|
|
21
|
+
}
|
|
22
|
+
budget: {
|
|
23
|
+
maxUsd: number | null
|
|
24
|
+
maxTokens: number | null
|
|
25
|
+
maxToolCalls: number | null
|
|
26
|
+
maxWallclockSec: number | null
|
|
27
|
+
maxTurns: number | null
|
|
28
|
+
}
|
|
29
|
+
milestones: Array<{ at: number; summary: string; kind: string; evidence: string[] }>
|
|
30
|
+
reports: Array<{ id: string; at: number; title: string; format: string; content: string }>
|
|
31
|
+
latestReport: { id: string; at: number; title: string; format: string; content: string } | null
|
|
15
32
|
}
|
|
16
33
|
|
|
17
34
|
export interface SharedSkillPayload {
|
|
@@ -56,6 +73,8 @@ export function resolveSharedEntity(link: ShareLink): SharedPayload | null {
|
|
|
56
73
|
function resolveMission(id: string): SharedMissionPayload | null {
|
|
57
74
|
const raw = loadStoredItem('agent_missions', id) as Record<string, unknown> | null
|
|
58
75
|
if (!raw) return null
|
|
76
|
+
const usageRaw = (raw.usage || {}) as Record<string, unknown>
|
|
77
|
+
const budgetRaw = (raw.budget || {}) as Record<string, unknown>
|
|
59
78
|
const milestonesRaw = Array.isArray(raw.milestones) ? raw.milestones : []
|
|
60
79
|
const milestones = milestonesRaw
|
|
61
80
|
.slice(-MAX_MILESTONES)
|
|
@@ -63,8 +82,15 @@ function resolveMission(id: string): SharedMissionPayload | null {
|
|
|
63
82
|
const entry = (m || {}) as Record<string, unknown>
|
|
64
83
|
return {
|
|
65
84
|
at: typeof entry.at === 'number' ? entry.at : 0,
|
|
66
|
-
|
|
85
|
+
summary: typeof entry.summary === 'string'
|
|
86
|
+
? entry.summary
|
|
87
|
+
: typeof entry.note === 'string'
|
|
88
|
+
? entry.note
|
|
89
|
+
: '',
|
|
67
90
|
kind: typeof entry.kind === 'string' ? entry.kind : 'note',
|
|
91
|
+
evidence: Array.isArray(entry.evidence)
|
|
92
|
+
? entry.evidence.filter((x): x is string => typeof x === 'string')
|
|
93
|
+
: [],
|
|
68
94
|
}
|
|
69
95
|
})
|
|
70
96
|
|
|
@@ -72,7 +98,9 @@ function resolveMission(id: string): SharedMissionPayload | null {
|
|
|
72
98
|
try {
|
|
73
99
|
const rows = listMissionReports(id, MAX_REPORTS)
|
|
74
100
|
reports = rows.map((r) => ({
|
|
101
|
+
id: r.id,
|
|
75
102
|
at: r.generatedAt,
|
|
103
|
+
title: r.title,
|
|
76
104
|
format: String(r.format),
|
|
77
105
|
content: r.body,
|
|
78
106
|
}))
|
|
@@ -90,8 +118,25 @@ function resolveMission(id: string): SharedMissionPayload | null {
|
|
|
90
118
|
: [],
|
|
91
119
|
status: typeof raw.status === 'string' ? raw.status : 'unknown',
|
|
92
120
|
createdAt: typeof raw.createdAt === 'number' ? raw.createdAt : 0,
|
|
121
|
+
updatedAt: typeof raw.updatedAt === 'number' ? raw.updatedAt : null,
|
|
122
|
+
usage: {
|
|
123
|
+
usdSpent: typeof usageRaw.usdSpent === 'number' ? usageRaw.usdSpent : 0,
|
|
124
|
+
tokensUsed: typeof usageRaw.tokensUsed === 'number' ? usageRaw.tokensUsed : 0,
|
|
125
|
+
toolCallsUsed: typeof usageRaw.toolCallsUsed === 'number' ? usageRaw.toolCallsUsed : 0,
|
|
126
|
+
turnsRun: typeof usageRaw.turnsRun === 'number' ? usageRaw.turnsRun : 0,
|
|
127
|
+
wallclockMsElapsed: typeof usageRaw.wallclockMsElapsed === 'number' ? usageRaw.wallclockMsElapsed : 0,
|
|
128
|
+
startedAt: typeof usageRaw.startedAt === 'number' ? usageRaw.startedAt : null,
|
|
129
|
+
},
|
|
130
|
+
budget: {
|
|
131
|
+
maxUsd: typeof budgetRaw.maxUsd === 'number' ? budgetRaw.maxUsd : null,
|
|
132
|
+
maxTokens: typeof budgetRaw.maxTokens === 'number' ? budgetRaw.maxTokens : null,
|
|
133
|
+
maxToolCalls: typeof budgetRaw.maxToolCalls === 'number' ? budgetRaw.maxToolCalls : null,
|
|
134
|
+
maxWallclockSec: typeof budgetRaw.maxWallclockSec === 'number' ? budgetRaw.maxWallclockSec : null,
|
|
135
|
+
maxTurns: typeof budgetRaw.maxTurns === 'number' ? budgetRaw.maxTurns : null,
|
|
136
|
+
},
|
|
93
137
|
milestones,
|
|
94
138
|
reports,
|
|
139
|
+
latestReport: reports[0] ?? null,
|
|
95
140
|
}
|
|
96
141
|
}
|
|
97
142
|
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export type EvidenceArtifactKind =
|
|
2
|
+
| 'task_artifact'
|
|
3
|
+
| 'task_output'
|
|
4
|
+
| 'completion_report'
|
|
5
|
+
| 'task_result'
|
|
6
|
+
| 'protocol_artifact'
|
|
7
|
+
| 'mission_report'
|
|
8
|
+
| 'share_link'
|
|
9
|
+
| 'mission_milestone'
|
|
10
|
+
| 'run_result'
|
|
11
|
+
| 'run_error'
|
|
12
|
+
| 'run_citation'
|
|
13
|
+
|
|
14
|
+
export interface EvidenceArtifact {
|
|
15
|
+
id: string
|
|
16
|
+
kind: EvidenceArtifactKind
|
|
17
|
+
title: string
|
|
18
|
+
description?: string | null
|
|
19
|
+
url?: string | null
|
|
20
|
+
href?: string | null
|
|
21
|
+
preview?: string | null
|
|
22
|
+
createdAt?: number | null
|
|
23
|
+
source: {
|
|
24
|
+
type: 'run' | 'mission' | 'task' | 'protocol' | 'share'
|
|
25
|
+
id: string
|
|
26
|
+
label?: string | null
|
|
27
|
+
}
|
|
28
|
+
}
|
package/src/types/index.ts
CHANGED
|
@@ -15,6 +15,9 @@ export * from './approval'
|
|
|
15
15
|
export * from './misc'
|
|
16
16
|
export * from './goal'
|
|
17
17
|
export * from './mission'
|
|
18
|
+
export * from './operations'
|
|
19
|
+
export * from './run-brief'
|
|
20
|
+
export * from './artifact'
|
|
18
21
|
export * from './swarmdock'
|
|
19
22
|
export * from './dream'
|
|
20
23
|
export * from './swarmfeed'
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export type OperationPulseRange = '24h' | '7d'
|
|
2
|
+
|
|
3
|
+
export type OperationPulseSeverity = 'low' | 'medium' | 'high'
|
|
4
|
+
|
|
5
|
+
export type OperationPulseActionKind =
|
|
6
|
+
| 'mission'
|
|
7
|
+
| 'run'
|
|
8
|
+
| 'approval'
|
|
9
|
+
| 'connector'
|
|
10
|
+
| 'budget'
|
|
11
|
+
| 'quality'
|
|
12
|
+
|
|
13
|
+
export interface OperationPulseKpis {
|
|
14
|
+
activeMissions: number
|
|
15
|
+
runningRuns: number
|
|
16
|
+
failedRuns: number
|
|
17
|
+
pendingApprovals: number
|
|
18
|
+
connectorAttention: number
|
|
19
|
+
budgetWarnings: number
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface OperationPulseAction {
|
|
23
|
+
id: string
|
|
24
|
+
kind: OperationPulseActionKind
|
|
25
|
+
severity: OperationPulseSeverity
|
|
26
|
+
title: string
|
|
27
|
+
summary: string
|
|
28
|
+
href: string
|
|
29
|
+
evidence: string[]
|
|
30
|
+
createdAt: number | null
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface OperationPulse {
|
|
34
|
+
generatedAt: number
|
|
35
|
+
range: OperationPulseRange
|
|
36
|
+
windowStart: number
|
|
37
|
+
kpis: OperationPulseKpis
|
|
38
|
+
actions: OperationPulseAction[]
|
|
39
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { ExecutionOwnerType, SessionRunStatus } from './run'
|
|
2
|
+
|
|
3
|
+
export interface RunBriefTimelineItem {
|
|
4
|
+
label: string
|
|
5
|
+
status?: SessionRunStatus
|
|
6
|
+
at: number
|
|
7
|
+
detail?: string | null
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface RunBriefEvidenceItem {
|
|
11
|
+
id: string
|
|
12
|
+
kind: 'citation' | 'retrieval' | 'event'
|
|
13
|
+
title: string
|
|
14
|
+
summary: string
|
|
15
|
+
url?: string | null
|
|
16
|
+
sourceId?: string | null
|
|
17
|
+
createdAt?: number | null
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface RunBrief {
|
|
21
|
+
runId: string
|
|
22
|
+
sessionId: string
|
|
23
|
+
title: string
|
|
24
|
+
objective: string
|
|
25
|
+
status: SessionRunStatus
|
|
26
|
+
source: string
|
|
27
|
+
owner: { type: ExecutionOwnerType; id: string } | null
|
|
28
|
+
timeline: RunBriefTimelineItem[]
|
|
29
|
+
result: string | null
|
|
30
|
+
error: string | null
|
|
31
|
+
warnings: string[]
|
|
32
|
+
usage: {
|
|
33
|
+
inputTokens: number | null
|
|
34
|
+
outputTokens: number | null
|
|
35
|
+
estimatedCost: number | null
|
|
36
|
+
citationCount: number
|
|
37
|
+
sourceIds: string[]
|
|
38
|
+
}
|
|
39
|
+
evidence: RunBriefEvidenceItem[]
|
|
40
|
+
generatedAt: number
|
|
41
|
+
}
|