@swarmclawai/swarmclaw 1.8.0 → 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 +10 -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/home/page.tsx +3 -0
- package/src/app/missions/page.tsx +37 -4
- 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 +3 -0
- package/src/components/operations/operations-pulse-panel.tsx +184 -0
- package/src/components/quality/quality-workspace.tsx +3 -0
- 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/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
package/README.md
CHANGED
|
@@ -399,6 +399,16 @@ Operational docs: https://swarmclaw.ai/docs/observability
|
|
|
399
399
|
|
|
400
400
|
## Releases
|
|
401
401
|
|
|
402
|
+
### v1.8.1 Highlights
|
|
403
|
+
|
|
404
|
+
Operator evidence release: a focused follow-up that makes release and mission review easier to scan.
|
|
405
|
+
|
|
406
|
+
- **Operations Pulse.** Home and Quality now share a live triage panel that rolls missions, runs, approvals, connector readiness, and budget pressure into one next-action queue.
|
|
407
|
+
- **Run Briefs.** Run detail sheets now open with a deterministic brief: objective, owner, timeline, warnings, usage, and evidence before the raw replay log.
|
|
408
|
+
- **Evidence Shelf.** Runs and missions now expose linked artifacts, task outputs, protocol outputs, mission reports, public share links, and knowledge citations through a shared artifact resolver.
|
|
409
|
+
- **Connector readiness.** Connector cards now show credential, route, pairing, gateway, connection, and doctor hints so setup gaps are visible before a platform bridge is started.
|
|
410
|
+
- **API surface.** Added `GET /api/operations/pulse`, `GET /api/runs/:id/brief`, and `GET /api/artifacts` for external operator dashboards and release tooling.
|
|
411
|
+
|
|
402
412
|
### v1.8.0 Highlights
|
|
403
413
|
|
|
404
414
|
Mission Command release: a bigger operator update that makes autonomous missions easier to launch, inspect, and share.
|
package/next.config.ts
CHANGED
|
@@ -6,9 +6,39 @@ import path from "path";
|
|
|
6
6
|
import { fileURLToPath } from "url";
|
|
7
7
|
|
|
8
8
|
const PROJECT_ROOT = path.dirname(fileURLToPath(import.meta.url))
|
|
9
|
-
const
|
|
10
|
-
'
|
|
11
|
-
'
|
|
9
|
+
const OUTPUT_TRACE_EXCLUDE_GLOBS = [
|
|
10
|
+
'./.env*',
|
|
11
|
+
'./.git/**/*',
|
|
12
|
+
'./.next/cache/**/*',
|
|
13
|
+
'./.tmp-swarmclaw-build/**/*',
|
|
14
|
+
'./AGENTS.md',
|
|
15
|
+
'./CLAUDE.md',
|
|
16
|
+
'./CONTRIBUTING.md',
|
|
17
|
+
'./Dockerfile',
|
|
18
|
+
'./Dockerfile.*',
|
|
19
|
+
'./README.md',
|
|
20
|
+
'./SWARMDOCK.md',
|
|
21
|
+
'./artifacts/**/*',
|
|
22
|
+
'./components.json',
|
|
23
|
+
'./coverage/**/*',
|
|
24
|
+
'./data/**/*',
|
|
25
|
+
'./daemon.log',
|
|
26
|
+
'./docker-compose.yml',
|
|
27
|
+
'./electron-builder.yml',
|
|
28
|
+
'./electron-dist/**/*',
|
|
29
|
+
'./eslint.config.mjs',
|
|
30
|
+
'./fly.toml',
|
|
31
|
+
'./install.sh',
|
|
32
|
+
'./next.config.ts',
|
|
33
|
+
'./package-lock.json',
|
|
34
|
+
'./postcss.config.mjs',
|
|
35
|
+
'./railway.json',
|
|
36
|
+
'./release/**/*',
|
|
37
|
+
'./render.yaml',
|
|
38
|
+
'./research.md',
|
|
39
|
+
'./swarmclaw-skill.md',
|
|
40
|
+
'./test-results/**/*',
|
|
41
|
+
'./tsconfig.json',
|
|
12
42
|
]
|
|
13
43
|
|
|
14
44
|
function getGitSha(): string {
|
|
@@ -50,11 +80,11 @@ function getAllowedDevOrigins(): string[] {
|
|
|
50
80
|
const nextConfig: NextConfig = {
|
|
51
81
|
output: 'standalone',
|
|
52
82
|
outputFileTracingExcludes: {
|
|
53
|
-
'/*':
|
|
54
|
-
'/api/**':
|
|
55
|
-
instrumentation:
|
|
56
|
-
'/instrumentation':
|
|
57
|
-
'next-server':
|
|
83
|
+
'/*': OUTPUT_TRACE_EXCLUDE_GLOBS,
|
|
84
|
+
'/api/**': OUTPUT_TRACE_EXCLUDE_GLOBS,
|
|
85
|
+
instrumentation: OUTPUT_TRACE_EXCLUDE_GLOBS,
|
|
86
|
+
'/instrumentation': OUTPUT_TRACE_EXCLUDE_GLOBS,
|
|
87
|
+
'next-server': OUTPUT_TRACE_EXCLUDE_GLOBS,
|
|
58
88
|
},
|
|
59
89
|
turbopack: {
|
|
60
90
|
// Pin workspace root to the project directory so a stale lockfile
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@swarmclawai/swarmclaw",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.1",
|
|
4
4
|
"description": "Build and run autonomous AI agents with OpenClaw, Hermes, multiple model providers, orchestration, delegation, memory, skills, schedules, and chat connectors.",
|
|
5
5
|
"main": "electron-dist/main.js",
|
|
6
6
|
"license": "MIT",
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
"test:cli": "node --test src/cli/*.test.js bin/*.test.js scripts/electron-after-pack.test.mjs scripts/postinstall.test.mjs scripts/run-next-build.test.mjs scripts/run-next-typegen.test.mjs",
|
|
88
88
|
"test:setup": "tsx --test src/app/api/setup/check-provider/route.test.ts src/lib/server/provider-model-discovery.test.ts src/components/auth/setup-wizard/utils.test.ts src/components/auth/setup-wizard/types.test.ts src/hooks/setup-done-detection.test.ts src/lib/setup-defaults.test.ts src/lib/server/storage-auth.test.ts src/lib/server/storage-auth-docker.test.ts",
|
|
89
89
|
"test:openclaw": "tsx --test src/lib/openclaw/openclaw-agent-id.test.ts src/lib/openclaw/openclaw-endpoint.test.ts src/lib/server/agents/agent-runtime-config.test.ts src/lib/server/build-llm.test.ts src/lib/server/connectors/connector-routing.test.ts src/lib/server/connectors/openclaw.test.ts src/lib/server/connectors/swarmdock.test.ts src/lib/server/gateway/protocol.test.ts src/lib/server/llm-response-cache.test.ts src/lib/server/mcp-conformance.test.ts src/lib/server/openclaw/agent-resolver.test.ts src/lib/server/openclaw/deploy.test.ts src/lib/server/openclaw/skills-normalize.test.ts src/lib/server/session-tools/openclaw-nodes.test.ts src/lib/server/session-tools/swarmdock.test.ts src/lib/server/tasks/task-quality-gate.test.ts src/lib/server/tasks/task-validation.test.ts src/lib/server/tool-capability-policy.test.ts src/lib/providers/openai.test.ts src/lib/providers/openclaw-exports.test.ts src/app/api/openclaw/dashboard-url/route.test.ts",
|
|
90
|
-
"test:runtime": "tsx --test src/lib/a2a/agent-card.test.ts src/lib/strip-internal-metadata.test.ts src/lib/provider-sets.test.ts src/lib/providers/opencode-cli.test.ts src/lib/providers/cli-provider-metadata.test.ts src/lib/providers/cli-utils.test.ts src/lib/providers/generic-cli.test.ts src/lib/server/cli-provider-readiness.test.ts src/lib/server/provider-health.test.ts src/lib/server/mcp-gateway-runtime.test.ts src/lib/server/mcp-connection-pool.test.ts src/lib/server/knowledge-sources.test.ts src/lib/server/chat-execution/chat-execution-grounding.test.ts src/lib/server/chat-execution/chat-turn-preparation.test.ts src/lib/server/chat-execution/iteration-timers.test.ts src/lib/server/chat-execution/post-stream-finalization.test.ts src/lib/server/chats/clear-undo-snapshots.test.ts src/lib/server/connectors/email.test.ts src/lib/server/protocols/protocol-service.test.ts src/lib/server/runtime/run-ledger.test.ts src/lib/server/observability/otel-config.test.ts src/lib/server/safe-parse-body.test.ts src/lib/server/missions/mission-templates.test.ts src/lib/server/sharing/share-link-repository.test.ts src/lib/server/sharing/share-resolver.test.ts src/lib/app/view-constants.test.ts src/lib/quality/quality-summary.test.ts src/app/api/approvals/route.test.ts src/app/api/agents/agents-route.test.ts src/app/api/tasks/tasks-route.test.ts src/app/api/chats/chat-route.test.ts src/app/api/chats/clear-route.test.ts src/app/api/chats/compact-route.test.ts src/app/api/chats/context-status-route.test.ts src/app/api/connectors/connector-doctor-route.test.ts src/app/api/healthz/route.test.ts src/app/api/logs/route.test.ts src/app/api/providers/[id]/route.test.ts src/app/api/tts/route.test.ts",
|
|
90
|
+
"test:runtime": "tsx --test src/lib/a2a/agent-card.test.ts src/lib/strip-internal-metadata.test.ts src/lib/provider-sets.test.ts src/lib/providers/opencode-cli.test.ts src/lib/providers/cli-provider-metadata.test.ts src/lib/providers/cli-utils.test.ts src/lib/providers/generic-cli.test.ts src/lib/server/cli-provider-readiness.test.ts src/lib/server/provider-health.test.ts src/lib/server/mcp-gateway-runtime.test.ts src/lib/server/mcp-connection-pool.test.ts src/lib/server/knowledge-sources.test.ts src/lib/server/chat-execution/chat-execution-grounding.test.ts src/lib/server/chat-execution/chat-turn-preparation.test.ts src/lib/server/chat-execution/iteration-timers.test.ts src/lib/server/chat-execution/post-stream-finalization.test.ts src/lib/server/chats/clear-undo-snapshots.test.ts src/lib/server/connectors/email.test.ts src/lib/server/protocols/protocol-service.test.ts src/lib/server/runtime/run-ledger.test.ts src/lib/server/runs/run-brief.test.ts src/lib/server/operations/operation-pulse.test.ts src/lib/server/artifacts/artifact-resolver.test.ts src/lib/server/observability/otel-config.test.ts src/lib/server/safe-parse-body.test.ts src/lib/server/missions/mission-templates.test.ts src/lib/server/sharing/share-link-repository.test.ts src/lib/server/sharing/share-resolver.test.ts src/lib/app/view-constants.test.ts src/lib/quality/quality-summary.test.ts src/app/api/approvals/route.test.ts src/app/api/agents/agents-route.test.ts src/app/api/tasks/tasks-route.test.ts src/app/api/chats/chat-route.test.ts src/app/api/chats/clear-route.test.ts src/app/api/chats/compact-route.test.ts src/app/api/chats/context-status-route.test.ts src/app/api/connectors/connector-doctor-route.test.ts src/app/api/healthz/route.test.ts src/app/api/logs/route.test.ts src/app/api/providers/[id]/route.test.ts src/app/api/tts/route.test.ts",
|
|
91
91
|
"test:builder": "tsx --test src/features/protocols/builder/utils/nodes-to-template.test.ts src/features/protocols/builder/utils/template-to-nodes.test.ts src/features/protocols/builder/validators/dag-validator.test.ts",
|
|
92
92
|
"test:e2e": "node --import tsx scripts/browser-e2e-smoke.ts",
|
|
93
93
|
"test:mcp:conformance": "node --import tsx ./scripts/mcp-conformance-check.ts",
|
|
@@ -11,13 +11,15 @@ import { ensureBuildBootstrapPaths } from './build-bootstrap-env.mjs'
|
|
|
11
11
|
|
|
12
12
|
const require = createRequire(import.meta.url)
|
|
13
13
|
|
|
14
|
-
export const DEFAULT_MAX_OLD_SPACE_SIZE_MB = '
|
|
14
|
+
export const DEFAULT_MAX_OLD_SPACE_SIZE_MB = '24576'
|
|
15
15
|
export const MIN_MAX_OLD_SPACE_SIZE_MB = 1024
|
|
16
16
|
export const FALLBACK_MIN_MAX_OLD_SPACE_SIZE_MB = 512
|
|
17
17
|
export const RESERVED_BUILD_MEMORY_MB = 768
|
|
18
|
-
export const MAX_OLD_SPACE_RATIO = 0.
|
|
18
|
+
export const MAX_OLD_SPACE_RATIO = 0.85
|
|
19
19
|
export const LOW_MEMORY_RATIO = 0.6
|
|
20
20
|
export const BUILD_MAX_OLD_SPACE_SIZE_ENV = 'SWARMCLAW_BUILD_MAX_OLD_SPACE_SIZE_MB'
|
|
21
|
+
export const BUILD_BUNDLER_ENV = 'SWARMCLAW_BUILD_BUNDLER'
|
|
22
|
+
export const DEFAULT_BUILD_BUNDLER = 'turbopack'
|
|
21
23
|
export const CGROUP_MEMORY_LIMIT_PATHS = [
|
|
22
24
|
'/sys/fs/cgroup/memory.max',
|
|
23
25
|
'/sys/fs/cgroup/memory/memory.limit_in_bytes',
|
|
@@ -40,6 +42,15 @@ export const REQUIRED_STANDALONE_BROWSER_PACKAGES = [
|
|
|
40
42
|
'playwright',
|
|
41
43
|
'playwright-core',
|
|
42
44
|
]
|
|
45
|
+
export const STANDALONE_LOCAL_STATE_ENTRIES = [
|
|
46
|
+
'.git',
|
|
47
|
+
'.tmp-swarmclaw-build',
|
|
48
|
+
'artifacts',
|
|
49
|
+
'coverage',
|
|
50
|
+
'data',
|
|
51
|
+
'release',
|
|
52
|
+
'test-results',
|
|
53
|
+
]
|
|
43
54
|
|
|
44
55
|
function parsePositiveInteger(value) {
|
|
45
56
|
const parsed = Number.parseInt(String(value ?? '').trim(), 10)
|
|
@@ -133,6 +144,18 @@ export function buildNextBuildEnv(
|
|
|
133
144
|
}
|
|
134
145
|
}
|
|
135
146
|
|
|
147
|
+
export function resolveNextBuildBundlerFlag(args = [], env = process.env) {
|
|
148
|
+
if (args.includes('--webpack') || args.includes('--turbopack')) return null
|
|
149
|
+
|
|
150
|
+
const requested = String(env[BUILD_BUNDLER_ENV] || DEFAULT_BUILD_BUNDLER).trim().toLowerCase()
|
|
151
|
+
if (requested === 'webpack') return '--webpack'
|
|
152
|
+
if (requested === 'turbopack') return '--turbopack'
|
|
153
|
+
|
|
154
|
+
throw new Error(
|
|
155
|
+
`${BUILD_BUNDLER_ENV} must be "turbopack" or "webpack"; received ${JSON.stringify(requested)}.`,
|
|
156
|
+
)
|
|
157
|
+
}
|
|
158
|
+
|
|
136
159
|
export function hasTraceCopyWarning(output = '') {
|
|
137
160
|
return output.includes(TRACE_COPY_WARNING)
|
|
138
161
|
}
|
|
@@ -239,6 +262,27 @@ export function repairStandaloneNextMetadata(cwd = process.cwd()) {
|
|
|
239
262
|
return true
|
|
240
263
|
}
|
|
241
264
|
|
|
265
|
+
export function pruneStandaloneLocalState(cwd = process.cwd()) {
|
|
266
|
+
const standaloneDir = path.join(cwd, '.next', 'standalone')
|
|
267
|
+
if (!fs.existsSync(standaloneDir)) return false
|
|
268
|
+
|
|
269
|
+
let pruned = false
|
|
270
|
+
for (const entry of STANDALONE_LOCAL_STATE_ENTRIES) {
|
|
271
|
+
const target = path.join(standaloneDir, entry)
|
|
272
|
+
if (!fs.existsSync(target)) continue
|
|
273
|
+
fs.rmSync(target, { recursive: true, force: true })
|
|
274
|
+
pruned = true
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
for (const entry of fs.readdirSync(standaloneDir, { withFileTypes: true })) {
|
|
278
|
+
if (entry.name !== '.env' && !entry.name.startsWith('.env.')) continue
|
|
279
|
+
fs.rmSync(path.join(standaloneDir, entry.name), { recursive: entry.isDirectory(), force: true })
|
|
280
|
+
pruned = true
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return pruned
|
|
284
|
+
}
|
|
285
|
+
|
|
242
286
|
export function runNextBuild(
|
|
243
287
|
args = process.argv.slice(2),
|
|
244
288
|
env = process.env,
|
|
@@ -246,7 +290,8 @@ export function runNextBuild(
|
|
|
246
290
|
maxOldSpaceSizeMb = resolveNextBuildMaxOldSpaceSizeMb(env),
|
|
247
291
|
) {
|
|
248
292
|
const nextBin = require.resolve('next/dist/bin/next')
|
|
249
|
-
|
|
293
|
+
const bundlerFlag = resolveNextBuildBundlerFlag(args, env)
|
|
294
|
+
return spawnSync(process.execPath, [nextBin, 'build', ...(bundlerFlag ? [bundlerFlag] : []), ...args], {
|
|
250
295
|
stdio: 'pipe',
|
|
251
296
|
encoding: 'utf-8',
|
|
252
297
|
env: buildNextBuildEnv(env, maxOldSpaceSizeMb, cwd),
|
|
@@ -277,6 +322,9 @@ function main() {
|
|
|
277
322
|
if (result.status === 0 && repairStandaloneBrowserMcpRuntime(process.cwd())) {
|
|
278
323
|
console.error('Copied Playwright MCP runtime packages into standalone build output.')
|
|
279
324
|
}
|
|
325
|
+
if (result.status === 0 && pruneStandaloneLocalState(process.cwd())) {
|
|
326
|
+
console.error('Pruned local state and release artifacts from standalone build output.')
|
|
327
|
+
}
|
|
280
328
|
process.exit(result.status)
|
|
281
329
|
}
|
|
282
330
|
if (result.signal) {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { listEvidenceArtifacts } from '@/lib/server/artifacts/artifact-resolver'
|
|
3
|
+
|
|
4
|
+
export const dynamic = 'force-dynamic'
|
|
5
|
+
|
|
6
|
+
export async function GET(req: Request) {
|
|
7
|
+
const { searchParams } = new URL(req.url)
|
|
8
|
+
const runId = searchParams.get('runId')
|
|
9
|
+
const missionId = searchParams.get('missionId')
|
|
10
|
+
const taskId = searchParams.get('taskId')
|
|
11
|
+
if (!runId && !missionId && !taskId) {
|
|
12
|
+
return NextResponse.json({ error: 'runId, missionId, or taskId is required' }, { status: 400 })
|
|
13
|
+
}
|
|
14
|
+
return NextResponse.json(listEvidenceArtifacts({ runId, missionId, taskId }))
|
|
15
|
+
}
|
|
@@ -48,7 +48,7 @@ async function writeClawHubBundleToWorkspace(bundle: ClawHubSkillBundle): Promis
|
|
|
48
48
|
.filter((entry): entry is { file: ClawHubSkillBundle['files'][number], path: string } => Boolean(entry.path))
|
|
49
49
|
|
|
50
50
|
const workspaceSkillsDir = resolveWorkspaceSkillsDir()
|
|
51
|
-
const targetDir = path.join(workspaceSkillsDir, sanitizeSkillDirName(bundle.slug))
|
|
51
|
+
const targetDir = path.join(/*turbopackIgnore: true*/ workspaceSkillsDir, sanitizeSkillDirName(bundle.slug))
|
|
52
52
|
const normalizedPaths = stripSharedTopLevelDir(normalizedEntries.map((entry) => entry.path))
|
|
53
53
|
|
|
54
54
|
await fs.rm(targetDir, { recursive: true, force: true })
|
|
@@ -57,12 +57,12 @@ async function writeClawHubBundleToWorkspace(bundle: ClawHubSkillBundle): Promis
|
|
|
57
57
|
for (let index = 0; index < normalizedEntries.length; index += 1) {
|
|
58
58
|
const relativePath = normalizedPaths[index]
|
|
59
59
|
if (!relativePath) continue
|
|
60
|
-
const destination = path.join(targetDir, relativePath)
|
|
60
|
+
const destination = path.join(/*turbopackIgnore: true*/ targetDir, relativePath)
|
|
61
61
|
if (!destination.startsWith(targetDir + path.sep) && destination !== targetDir) {
|
|
62
62
|
throw new Error(`Refusing to write bundle file outside the target directory: ${relativePath}`)
|
|
63
63
|
}
|
|
64
|
-
await fs.mkdir(path.dirname(destination), { recursive: true })
|
|
65
|
-
await fs.writeFile(destination, normalizedEntries[index].file.content)
|
|
64
|
+
await fs.mkdir(/*turbopackIgnore: true*/ path.dirname(destination), { recursive: true })
|
|
65
|
+
await fs.writeFile(/*turbopackIgnore: true*/ destination, normalizedEntries[index].file.content)
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
clearDiscoveredSkillsCache()
|
|
@@ -9,18 +9,21 @@ export async function GET(req: NextRequest) {
|
|
|
9
9
|
|
|
10
10
|
// Resolve ~ to home dir
|
|
11
11
|
const resolved = targetDir.startsWith('~')
|
|
12
|
-
? path.join(os.homedir(), targetDir.slice(1))
|
|
13
|
-
: path.resolve(targetDir)
|
|
12
|
+
? path.join(/*turbopackIgnore: true*/ os.homedir(), targetDir.slice(1))
|
|
13
|
+
: path.resolve(/*turbopackIgnore: true*/ targetDir)
|
|
14
14
|
|
|
15
15
|
let dirs: Array<{ name: string; path: string }> = []
|
|
16
16
|
try {
|
|
17
|
-
dirs = fs.readdirSync(resolved)
|
|
17
|
+
dirs = fs.readdirSync(/*turbopackIgnore: true*/ resolved)
|
|
18
18
|
.filter(d => {
|
|
19
19
|
if (d.startsWith('.')) return false
|
|
20
|
-
try {
|
|
20
|
+
try {
|
|
21
|
+
const childPath = path.join(/*turbopackIgnore: true*/ resolved, d)
|
|
22
|
+
return fs.statSync(/*turbopackIgnore: true*/ childPath).isDirectory()
|
|
23
|
+
} catch { return false }
|
|
21
24
|
})
|
|
22
25
|
.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()))
|
|
23
|
-
.map(d => ({ name: d, path: path.join(resolved, d) }))
|
|
26
|
+
.map(d => ({ name: d, path: path.join(/*turbopackIgnore: true*/ resolved, d) }))
|
|
24
27
|
} catch {}
|
|
25
28
|
|
|
26
29
|
const parentPath = resolved === '/' ? null : path.dirname(resolved)
|
|
@@ -19,7 +19,7 @@ export async function POST(req: Request) {
|
|
|
19
19
|
return NextResponse.json({ error: 'Path does not exist' }, { status: 404 })
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
const isDir = fs.statSync(resolved).isDirectory()
|
|
22
|
+
const isDir = fs.statSync(/*turbopackIgnore: true*/ resolved).isDirectory()
|
|
23
23
|
const platform = process.platform
|
|
24
24
|
|
|
25
25
|
let command: string
|
|
@@ -32,11 +32,11 @@ export async function POST(req: Request) {
|
|
|
32
32
|
args = isDir ? [resolved] : [`/select,${resolved}`]
|
|
33
33
|
} else {
|
|
34
34
|
command = 'xdg-open'
|
|
35
|
-
args = [isDir ? resolved : path.dirname(resolved)]
|
|
35
|
+
args = [isDir ? resolved : path.dirname(/*turbopackIgnore: true*/ resolved)]
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
return new Promise<NextResponse>((resolve) => {
|
|
39
|
-
const child = spawn(command, args, { stdio: 'ignore' })
|
|
39
|
+
const child = spawn(/*turbopackIgnore: true*/ command, args, { stdio: 'ignore' })
|
|
40
40
|
child.once('error', (err) => {
|
|
41
41
|
resolve(NextResponse.json({ error: err.message }, { status: 500 }))
|
|
42
42
|
})
|
|
@@ -57,7 +57,7 @@ export async function GET(req: Request) {
|
|
|
57
57
|
return NextResponse.json({ error: 'Access denied' }, { status: 403 })
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
const stat = fs.statSync(resolved)
|
|
60
|
+
const stat = fs.statSync(/*turbopackIgnore: true*/ resolved)
|
|
61
61
|
if (!stat.isFile()) {
|
|
62
62
|
return NextResponse.json({ error: 'Not a file' }, { status: 400 })
|
|
63
63
|
}
|
|
@@ -67,7 +67,7 @@ export async function GET(req: Request) {
|
|
|
67
67
|
|
|
68
68
|
const ext = path.extname(resolved).toLowerCase()
|
|
69
69
|
const contentType = MIME_MAP[ext] || 'application/octet-stream'
|
|
70
|
-
const content = fs.readFileSync(resolved)
|
|
70
|
+
const content = fs.readFileSync(/*turbopackIgnore: true*/ resolved)
|
|
71
71
|
|
|
72
72
|
return new NextResponse(content, {
|
|
73
73
|
headers: {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { getOperationPulse, normalizeOperationPulseRange } from '@/lib/server/operations/operation-pulse'
|
|
3
|
+
|
|
4
|
+
export const dynamic = 'force-dynamic'
|
|
5
|
+
|
|
6
|
+
export async function GET(req: Request) {
|
|
7
|
+
const { searchParams } = new URL(req.url)
|
|
8
|
+
return NextResponse.json(getOperationPulse(normalizeOperationPulseRange(searchParams.get('range'))))
|
|
9
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { buildRunBrief } from '@/lib/server/runs/run-brief'
|
|
3
|
+
import { getUnifiedRunById, listUnifiedRunEvents } from '@/lib/server/runs/unified-run-queries'
|
|
4
|
+
|
|
5
|
+
export const dynamic = 'force-dynamic'
|
|
6
|
+
|
|
7
|
+
export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
8
|
+
const { id } = await params
|
|
9
|
+
const run = getUnifiedRunById(id)
|
|
10
|
+
if (!run) return NextResponse.json({ error: 'Run not found' }, { status: 404 })
|
|
11
|
+
return NextResponse.json(buildRunBrief(run, listUnifiedRunEvents(id, 300)))
|
|
12
|
+
}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
|
-
import {
|
|
3
|
-
import { listProtocolRunEventsForRun, loadProtocolRunById } from '@/lib/server/protocols/protocol-queries'
|
|
4
|
-
import { protocolEventToRunEventRecord } from '@/lib/server/runs/unified-run-records'
|
|
2
|
+
import { getUnifiedRunById, listUnifiedRunEvents } from '@/lib/server/runs/unified-run-queries'
|
|
5
3
|
|
|
6
4
|
export const dynamic = 'force-dynamic'
|
|
7
5
|
|
|
@@ -13,16 +11,9 @@ function parseLimit(value: string | null): number | undefined {
|
|
|
13
11
|
|
|
14
12
|
export async function GET(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
15
13
|
const { id } = await params
|
|
16
|
-
const run =
|
|
17
|
-
if (run) {
|
|
18
|
-
const url = new URL(req.url)
|
|
19
|
-
const limit = parseLimit(url.searchParams.get('limit'))
|
|
20
|
-
return NextResponse.json(listRunEvents(id, limit))
|
|
21
|
-
}
|
|
22
|
-
const protocolRun = loadProtocolRunById(id)
|
|
23
|
-
if (!protocolRun) return NextResponse.json({ error: 'Run not found' }, { status: 404 })
|
|
14
|
+
const run = getUnifiedRunById(id)
|
|
15
|
+
if (!run) return NextResponse.json({ error: 'Run not found' }, { status: 404 })
|
|
24
16
|
const url = new URL(req.url)
|
|
25
17
|
const limit = parseLimit(url.searchParams.get('limit'))
|
|
26
|
-
|
|
27
|
-
return NextResponse.json(events)
|
|
18
|
+
return NextResponse.json(listUnifiedRunEvents(id, limit || 200))
|
|
28
19
|
}
|
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
|
-
import {
|
|
3
|
-
import { loadProtocolRunById } from '@/lib/server/protocols/protocol-queries'
|
|
4
|
-
import { protocolRunToSessionRunRecord } from '@/lib/server/runs/unified-run-records'
|
|
2
|
+
import { getUnifiedRunById } from '@/lib/server/runs/unified-run-queries'
|
|
5
3
|
|
|
6
4
|
export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
7
5
|
const { id } = await params
|
|
8
|
-
const run =
|
|
6
|
+
const run = getUnifiedRunById(id)
|
|
9
7
|
if (run) return NextResponse.json(run)
|
|
10
|
-
const protocolRun = loadProtocolRunById(id)
|
|
11
|
-
if (protocolRun) return NextResponse.json(protocolRunToSessionRunRecord(protocolRun))
|
|
12
8
|
return NextResponse.json({ error: 'Run not found' }, { status: 404 })
|
|
13
9
|
}
|
|
@@ -1,28 +1,9 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { protocolRunToSessionRunRecord } from '@/lib/server/runs/unified-run-records'
|
|
5
|
-
import type { ProtocolRunStatus, SessionRunStatus } from '@/types'
|
|
2
|
+
import { listUnifiedRuns } from '@/lib/server/runs/unified-run-queries'
|
|
3
|
+
import type { SessionRunStatus } from '@/types'
|
|
6
4
|
|
|
7
5
|
export const dynamic = 'force-dynamic'
|
|
8
6
|
|
|
9
|
-
function protocolStatusesForRunStatus(status?: SessionRunStatus): ProtocolRunStatus[] {
|
|
10
|
-
switch (status) {
|
|
11
|
-
case 'queued':
|
|
12
|
-
return ['draft']
|
|
13
|
-
case 'running':
|
|
14
|
-
return ['running', 'waiting', 'paused']
|
|
15
|
-
case 'completed':
|
|
16
|
-
return ['completed']
|
|
17
|
-
case 'failed':
|
|
18
|
-
return ['failed']
|
|
19
|
-
case 'cancelled':
|
|
20
|
-
return ['cancelled', 'archived']
|
|
21
|
-
default:
|
|
22
|
-
return []
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
7
|
export async function GET(req: Request) {
|
|
27
8
|
const { searchParams } = new URL(req.url)
|
|
28
9
|
const sessionId = searchParams.get('sessionId') || undefined
|
|
@@ -30,26 +11,5 @@ export async function GET(req: Request) {
|
|
|
30
11
|
const limitRaw = searchParams.get('limit')
|
|
31
12
|
const limit = limitRaw ? Number.parseInt(limitRaw, 10) : undefined
|
|
32
13
|
|
|
33
|
-
|
|
34
|
-
const fetchLimit = limit || 200
|
|
35
|
-
const scopedProtocolRuns = status
|
|
36
|
-
? protocolStatusesForRunStatus(status).flatMap((protocolStatus) => listProtocolRuns({
|
|
37
|
-
includeSystemOwned: true,
|
|
38
|
-
sessionId,
|
|
39
|
-
status: protocolStatus,
|
|
40
|
-
limit: fetchLimit,
|
|
41
|
-
}))
|
|
42
|
-
: listProtocolRuns({
|
|
43
|
-
includeSystemOwned: true,
|
|
44
|
-
sessionId,
|
|
45
|
-
limit: fetchLimit,
|
|
46
|
-
})
|
|
47
|
-
const protocolRuns = Array.from(new Map(scopedProtocolRuns.map((run) => [run.id, run])).values())
|
|
48
|
-
.filter((run) => run.status !== 'archived')
|
|
49
|
-
.map(protocolRunToSessionRunRecord)
|
|
50
|
-
.filter((run) => !status || run.status === status)
|
|
51
|
-
const runs = [...sessionRuns, ...protocolRuns]
|
|
52
|
-
.sort((left, right) => (right.queuedAt || 0) - (left.queuedAt || 0))
|
|
53
|
-
.slice(0, fetchLimit)
|
|
54
|
-
return NextResponse.json(runs)
|
|
14
|
+
return NextResponse.json(listUnifiedRuns({ sessionId, status, limit }))
|
|
55
15
|
}
|
package/src/app/home/page.tsx
CHANGED
|
@@ -7,6 +7,7 @@ import { useAppStore } from '@/stores/use-app-store'
|
|
|
7
7
|
import { useChatStore } from '@/stores/use-chat-store'
|
|
8
8
|
import { AgentAvatar } from '@/components/agents/agent-avatar'
|
|
9
9
|
import { HomeLaunchpad } from '@/components/home/home-launchpad'
|
|
10
|
+
import { OperationsPulsePanel } from '@/components/operations/operations-pulse-panel'
|
|
10
11
|
import { useMountedRef } from '@/hooks/use-mounted-ref'
|
|
11
12
|
import { useNow } from '@/hooks/use-now'
|
|
12
13
|
import { api } from '@/lib/app/api-client'
|
|
@@ -314,6 +315,8 @@ export default function HomePage() {
|
|
|
314
315
|
</p>
|
|
315
316
|
</div>
|
|
316
317
|
|
|
318
|
+
<OperationsPulsePanel className="mb-8" compact />
|
|
319
|
+
|
|
317
320
|
{/* Quick actions / triage */}
|
|
318
321
|
<section className="mb-8" style={{ animation: 'fade-up 0.6s var(--ease-spring) 0.15s both' }}>
|
|
319
322
|
<SectionHeader
|
|
@@ -4,6 +4,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react'
|
|
|
4
4
|
import { useRouter, useSearchParams } from 'next/navigation'
|
|
5
5
|
import { api } from '@/lib/app/api-client'
|
|
6
6
|
import { MainContent } from '@/components/layout/main-content'
|
|
7
|
+
import { EvidenceShelf } from '@/components/evidence/evidence-shelf'
|
|
7
8
|
import { HintTip } from '@/components/shared/hint-tip'
|
|
8
9
|
import { inputClass } from '@/components/shared/form-styles'
|
|
9
10
|
import { MissionTemplateGallery } from '@/components/missions/mission-template-gallery'
|
|
@@ -12,7 +13,7 @@ import {
|
|
|
12
13
|
type InstantiateInput,
|
|
13
14
|
} from '@/components/missions/mission-template-install-dialog'
|
|
14
15
|
import { MissionEditSheet, isMissionEditable } from '@/components/missions/mission-edit-sheet'
|
|
15
|
-
import type { Mission, MissionReport, MissionEvent, MissionTemplate, Session } from '@/types'
|
|
16
|
+
import type { EvidenceArtifact, Mission, MissionReport, MissionEvent, MissionTemplate, Session } from '@/types'
|
|
16
17
|
import { toast } from 'sonner'
|
|
17
18
|
|
|
18
19
|
const POLL_MS = 4_000
|
|
@@ -412,6 +413,8 @@ interface DetailProps {
|
|
|
412
413
|
function MissionDetail({ mission, reports, events, busy, onAction, onForceReport, onEdit }: DetailProps) {
|
|
413
414
|
const [selectedReport, setSelectedReport] = useState<MissionReport | null>(null)
|
|
414
415
|
const [shareLinks, setShareLinks] = useState<ShareLink[]>([])
|
|
416
|
+
const [artifacts, setArtifacts] = useState<EvidenceArtifact[]>([])
|
|
417
|
+
const [artifactsLoading, setArtifactsLoading] = useState(false)
|
|
415
418
|
const [shareBusy, setShareBusy] = useState<string | null>(null)
|
|
416
419
|
const wallclockCapMs = mission.budget.maxWallclockSec != null ? mission.budget.maxWallclockSec * 1000 : null
|
|
417
420
|
const activeShare = useMemo(
|
|
@@ -428,9 +431,22 @@ function MissionDetail({ mission, reports, events, busy, onAction, onForceReport
|
|
|
428
431
|
}
|
|
429
432
|
}, [mission.id])
|
|
430
433
|
|
|
434
|
+
const loadArtifacts = useCallback(async () => {
|
|
435
|
+
setArtifactsLoading(true)
|
|
436
|
+
try {
|
|
437
|
+
const list = await api<EvidenceArtifact[]>('GET', `/artifacts?missionId=${encodeURIComponent(mission.id)}`)
|
|
438
|
+
setArtifacts(Array.isArray(list) ? list : [])
|
|
439
|
+
} catch {
|
|
440
|
+
setArtifacts([])
|
|
441
|
+
} finally {
|
|
442
|
+
setArtifactsLoading(false)
|
|
443
|
+
}
|
|
444
|
+
}, [mission.id])
|
|
445
|
+
|
|
431
446
|
useEffect(() => {
|
|
432
447
|
void loadShareLinks()
|
|
433
|
-
|
|
448
|
+
void loadArtifacts()
|
|
449
|
+
}, [loadArtifacts, loadShareLinks])
|
|
434
450
|
|
|
435
451
|
const shareUrl = activeShare && typeof window !== 'undefined'
|
|
436
452
|
? `${window.location.origin}/s/${activeShare.token}`
|
|
@@ -445,13 +461,14 @@ function MissionDetail({ mission, reports, events, busy, onAction, onForceReport
|
|
|
445
461
|
label: `${mission.title} public report`,
|
|
446
462
|
})
|
|
447
463
|
setShareLinks((prev) => [link, ...prev])
|
|
464
|
+
void loadArtifacts()
|
|
448
465
|
toast.success('Mission share link created')
|
|
449
466
|
} catch (error) {
|
|
450
467
|
toast.error(error instanceof Error ? error.message : 'Unable to create share link')
|
|
451
468
|
} finally {
|
|
452
469
|
setShareBusy(null)
|
|
453
470
|
}
|
|
454
|
-
}, [mission.id, mission.title])
|
|
471
|
+
}, [loadArtifacts, mission.id, mission.title])
|
|
455
472
|
|
|
456
473
|
const revokeShareLink = useCallback(async () => {
|
|
457
474
|
if (!activeShare) return
|
|
@@ -459,13 +476,14 @@ function MissionDetail({ mission, reports, events, busy, onAction, onForceReport
|
|
|
459
476
|
try {
|
|
460
477
|
const revoked = await api<ShareLink>('DELETE', `/share/${activeShare.id}`)
|
|
461
478
|
setShareLinks((prev) => prev.map((link) => (link.id === revoked.id ? revoked : link)))
|
|
479
|
+
void loadArtifacts()
|
|
462
480
|
toast.success('Mission share link revoked')
|
|
463
481
|
} catch (error) {
|
|
464
482
|
toast.error(error instanceof Error ? error.message : 'Unable to revoke share link')
|
|
465
483
|
} finally {
|
|
466
484
|
setShareBusy(null)
|
|
467
485
|
}
|
|
468
|
-
}, [activeShare])
|
|
486
|
+
}, [activeShare, loadArtifacts])
|
|
469
487
|
|
|
470
488
|
const copyShareUrl = useCallback(async () => {
|
|
471
489
|
if (!shareUrl) return
|
|
@@ -552,6 +570,13 @@ function MissionDetail({ mission, reports, events, busy, onAction, onForceReport
|
|
|
552
570
|
)}
|
|
553
571
|
</div>
|
|
554
572
|
|
|
573
|
+
<EvidenceShelf
|
|
574
|
+
artifacts={artifacts}
|
|
575
|
+
loading={artifactsLoading}
|
|
576
|
+
title="Evidence Shelf"
|
|
577
|
+
emptyLabel="No mission reports, public share links, or milestone evidence yet."
|
|
578
|
+
/>
|
|
579
|
+
|
|
555
580
|
{mission.successCriteria.length > 0 && (
|
|
556
581
|
<div>
|
|
557
582
|
<div className="text-[11px] font-600 uppercase tracking-wide text-text-3 mb-2">Success criteria</div>
|
|
@@ -706,6 +731,14 @@ export default function MissionsPage() {
|
|
|
706
731
|
router.replace('/missions', { scroll: false })
|
|
707
732
|
}, [router, searchParams, templates])
|
|
708
733
|
|
|
734
|
+
useEffect(() => {
|
|
735
|
+
const missionId = searchParams.get('mission')?.trim()
|
|
736
|
+
if (!missionId || missions.length === 0) return
|
|
737
|
+
if (!missions.some((mission) => mission.id === missionId)) return
|
|
738
|
+
setSelectedId(missionId)
|
|
739
|
+
router.replace('/missions', { scroll: false })
|
|
740
|
+
}, [missions, router, searchParams])
|
|
741
|
+
|
|
709
742
|
const handleAction = useCallback(async (action: string, reason?: string) => {
|
|
710
743
|
if (!selectedId) return
|
|
711
744
|
setBusy(true)
|
package/src/cli/index.js
CHANGED
|
@@ -67,6 +67,13 @@ const COMMAND_GROUPS = [
|
|
|
67
67
|
cmd('resolve', 'POST', '/approvals', 'Resolve a human-loop approval', { expectsJsonBody: true }),
|
|
68
68
|
],
|
|
69
69
|
},
|
|
70
|
+
{
|
|
71
|
+
name: 'artifacts',
|
|
72
|
+
description: 'Resolve evidence artifacts for runs, missions, and tasks',
|
|
73
|
+
commands: [
|
|
74
|
+
cmd('list', 'GET', '/artifacts', 'List evidence artifacts (use --query runId=, --query missionId=, or --query taskId=)'),
|
|
75
|
+
],
|
|
76
|
+
},
|
|
70
77
|
{
|
|
71
78
|
name: 'claude-skills',
|
|
72
79
|
description: 'Read local Claude skills directory metadata',
|
|
@@ -198,6 +205,13 @@ const COMMAND_GROUPS = [
|
|
|
198
205
|
}),
|
|
199
206
|
],
|
|
200
207
|
},
|
|
208
|
+
{
|
|
209
|
+
name: 'operations',
|
|
210
|
+
description: 'Operator triage and readiness summaries',
|
|
211
|
+
commands: [
|
|
212
|
+
cmd('pulse', 'GET', '/operations/pulse', 'Get Operations Pulse summary (use --query range=24h or --query range=7d)'),
|
|
213
|
+
],
|
|
214
|
+
},
|
|
201
215
|
{
|
|
202
216
|
name: 'documents',
|
|
203
217
|
description: 'Manage documents',
|
|
@@ -544,6 +558,7 @@ const COMMAND_GROUPS = [
|
|
|
544
558
|
cmd('list', 'GET', '/runs', 'List runs (use --query sessionId=, --query status=, --query limit=)'),
|
|
545
559
|
cmd('get', 'GET', '/runs/:id', 'Get run by id'),
|
|
546
560
|
cmd('events', 'GET', '/runs/:id/events', 'Get run event history by run id'),
|
|
561
|
+
cmd('brief', 'GET', '/runs/:id/brief', 'Get deterministic run brief by run id'),
|
|
547
562
|
],
|
|
548
563
|
},
|
|
549
564
|
{
|
package/src/cli/spec.js
CHANGED
|
@@ -184,6 +184,12 @@ const COMMAND_GROUPS = {
|
|
|
184
184
|
'task-status': { description: 'Check A2A task status', method: 'GET', path: '/a2a/tasks/:taskId/status', params: ['taskId'] },
|
|
185
185
|
},
|
|
186
186
|
},
|
|
187
|
+
artifacts: {
|
|
188
|
+
description: 'Resolve evidence artifacts for runs, missions, and tasks',
|
|
189
|
+
commands: {
|
|
190
|
+
list: { description: 'List evidence artifacts (supports --query runId=,missionId=,taskId=)', method: 'GET', path: '/artifacts' },
|
|
191
|
+
},
|
|
192
|
+
},
|
|
187
193
|
uploads: {
|
|
188
194
|
description: 'Manage uploaded artifacts',
|
|
189
195
|
commands: {
|
|
@@ -193,6 +199,12 @@ const COMMAND_GROUPS = {
|
|
|
193
199
|
'delete-many': { description: 'Delete uploads by filter/body (filenames, olderThanDays, category, or all)', method: 'DELETE', path: '/uploads' },
|
|
194
200
|
},
|
|
195
201
|
},
|
|
202
|
+
operations: {
|
|
203
|
+
description: 'Operator triage and readiness summaries',
|
|
204
|
+
commands: {
|
|
205
|
+
pulse: { description: 'Get Operations Pulse summary (supports --query range=24h|7d)', method: 'GET', path: '/operations/pulse' },
|
|
206
|
+
},
|
|
207
|
+
},
|
|
196
208
|
files: {
|
|
197
209
|
description: 'Serve/open local files',
|
|
198
210
|
commands: {
|
|
@@ -526,6 +538,7 @@ const COMMAND_GROUPS = {
|
|
|
526
538
|
list: { description: 'List runs (supports --query sessionId=,status=,limit=)', method: 'GET', path: '/runs' },
|
|
527
539
|
get: { description: 'Get run by id', method: 'GET', path: '/runs/:id', params: ['id'] },
|
|
528
540
|
events: { description: 'Get run event history by run id', method: 'GET', path: '/runs/:id/events', params: ['id'] },
|
|
541
|
+
brief: { description: 'Get deterministic run brief by run id', method: 'GET', path: '/runs/:id/brief', params: ['id'] },
|
|
529
542
|
},
|
|
530
543
|
},
|
|
531
544
|
webhooks: {
|