@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.
Files changed (42) hide show
  1. package/README.md +20 -0
  2. package/next.config.ts +38 -8
  3. package/package.json +2 -2
  4. package/scripts/run-next-build.mjs +51 -3
  5. package/src/app/api/artifacts/route.ts +15 -0
  6. package/src/app/api/clawhub/install/route.ts +4 -4
  7. package/src/app/api/dirs/route.ts +8 -5
  8. package/src/app/api/files/open/route.ts +3 -3
  9. package/src/app/api/files/serve/route.ts +2 -2
  10. package/src/app/api/operations/pulse/route.ts +9 -0
  11. package/src/app/api/runs/[id]/brief/route.ts +12 -0
  12. package/src/app/api/runs/[id]/events/route.ts +4 -13
  13. package/src/app/api/runs/[id]/route.ts +2 -6
  14. package/src/app/api/runs/route.ts +3 -43
  15. package/src/app/api/s/[token]/raw/route.ts +1 -1
  16. package/src/app/home/page.tsx +11 -1
  17. package/src/app/missions/page.tsx +182 -3
  18. package/src/app/s/[token]/page.tsx +173 -48
  19. package/src/cli/index.js +15 -0
  20. package/src/cli/spec.js +13 -0
  21. package/src/components/connectors/connector-list.tsx +36 -20
  22. package/src/components/evidence/evidence-shelf.tsx +97 -0
  23. package/src/components/home/home-launchpad.tsx +52 -2
  24. package/src/components/missions/mission-template-install-dialog.tsx +33 -1
  25. package/src/components/operations/operations-pulse-panel.tsx +184 -0
  26. package/src/components/quality/quality-workspace.tsx +34 -6
  27. package/src/components/runs/run-list.tsx +94 -12
  28. package/src/lib/connectors/connector-readiness.ts +127 -0
  29. package/src/lib/server/artifacts/artifact-resolver.test.ts +98 -0
  30. package/src/lib/server/artifacts/artifact-resolver.ts +241 -0
  31. package/src/lib/server/operations/operation-pulse.test.ts +108 -0
  32. package/src/lib/server/operations/operation-pulse.ts +197 -0
  33. package/src/lib/server/resolve-workspace-path.ts +10 -10
  34. package/src/lib/server/runs/run-brief.test.ts +92 -0
  35. package/src/lib/server/runs/run-brief.ts +107 -0
  36. package/src/lib/server/runs/unified-run-queries.ts +84 -0
  37. package/src/lib/server/sharing/share-resolver.test.ts +129 -0
  38. package/src/lib/server/sharing/share-resolver.ts +48 -3
  39. package/src/types/artifact.ts +28 -0
  40. package/src/types/index.ts +3 -0
  41. package/src/types/operations.ts +39 -0
  42. package/src/types/run-brief.ts +41 -0
package/README.md CHANGED
@@ -399,6 +399,26 @@ 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
+
412
+ ### v1.8.0 Highlights
413
+
414
+ Mission Command release: a bigger operator update that makes autonomous missions easier to launch, inspect, and share.
415
+
416
+ - **Mission Command launchpad.** The home launchpad now opens concrete mission starters for Release QA, Launch Sprint, Cost Audit, and Connector Smoke Test instead of dropping users into a generic mission list.
417
+ - **Deep-linked mission templates.** `/missions?template=<id>` opens the right starter template directly, and the template installer can create a mission-driver chat when no sessions exist yet.
418
+ - **Quality Center handoffs.** `/quality?tab=evals|approvals|runs` is shareable, and the Quality overview/Eval Lab can start a Release QA mission from current operator evidence.
419
+ - **Public mission reports.** Missions can mint, copy, and revoke public share links from the detail view. Shared pages render status, budgets, milestones, and generated reports using the existing allowlisted share resolver.
420
+ - **Safer share payloads.** Mission milestones now expose `summary` correctly in public HTML and raw markdown shares, with regression coverage in `npm run test:runtime`.
421
+
402
422
  ### v1.7.3 Highlights
403
423
 
404
424
  Desktop packaging fix for Linux AppImage and deb builds.
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 RUNTIME_STATE_GLOBS = [
10
- 'data/**/*',
11
- '.tmp-swarmclaw-build/**/*',
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
- '/*': RUNTIME_STATE_GLOBS,
54
- '/api/**': RUNTIME_STATE_GLOBS,
55
- instrumentation: RUNTIME_STATE_GLOBS,
56
- '/instrumentation': RUNTIME_STATE_GLOBS,
57
- 'next-server': RUNTIME_STATE_GLOBS,
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.7.3",
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/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 = '12288'
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.75
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
- return spawnSync(process.execPath, [nextBin, 'build', '--webpack', ...args], {
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 { return fs.statSync(path.join(resolved, d)).isDirectory() } catch { return false }
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 { getRunById, listRunEvents } from '@/lib/server/runtime/session-run-manager'
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 = getRunById(id)
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
- const events = listProtocolRunEventsForRun(id, limit || 200).map((event) => protocolEventToRunEventRecord(protocolRun, event))
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 { getRunById } from '@/lib/server/runtime/session-run-manager'
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 = getRunById(id)
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 { listRuns } from '@/lib/server/runtime/session-run-manager'
3
- import { listProtocolRuns } from '@/lib/server/protocols/protocol-queries'
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
- const sessionRuns = listRuns({ sessionId, status, limit })
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
  }
@@ -49,7 +49,7 @@ export async function GET(_req: Request, ctx: { params: Promise<{ token: string
49
49
  if (payload.milestones.length > 0) {
50
50
  lines.push('## Milestones', '')
51
51
  for (const m of payload.milestones) {
52
- lines.push(`- ${new Date(m.at).toISOString().slice(0, 19).replace('T', ' ')}: ${m.note}`)
52
+ lines.push(`- ${new Date(m.at).toISOString().slice(0, 19).replace('T', ' ')}: ${m.summary}`)
53
53
  }
54
54
  lines.push('')
55
55
  }
@@ -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'
@@ -266,6 +267,10 @@ export default function HomePage() {
266
267
  router.push(DEFAULT_BUILDER_ROUTE)
267
268
  }
268
269
 
270
+ const openMissionTemplate = (templateId: string) => {
271
+ router.push(`/missions?template=${encodeURIComponent(templateId)}`)
272
+ }
273
+
269
274
  if (homeMode === 'launchpad') {
270
275
  return (
271
276
  <MainContent>
@@ -286,7 +291,10 @@ export default function HomePage() {
286
291
  onRunEvalSuite={() => navigateTo('quality')}
287
292
  onReviewApprovals={() => navigateTo('quality')}
288
293
  onInspectFailedRuns={() => navigateTo('quality')}
289
- onStartReleaseQaMission={() => navigateTo('missions')}
294
+ onStartReleaseQaMission={() => openMissionTemplate('release-candidate-qa')}
295
+ onStartLaunchSprintMission={() => openMissionTemplate('launch-week-growth-sprint')}
296
+ onStartCostAuditMission={() => openMissionTemplate('agent-cost-audit')}
297
+ onStartConnectorSmokeMission={() => openMissionTemplate('connector-smoke-test')}
290
298
  />
291
299
  </div>
292
300
  </MainContent>
@@ -307,6 +315,8 @@ export default function HomePage() {
307
315
  </p>
308
316
  </div>
309
317
 
318
+ <OperationsPulsePanel className="mb-8" compact />
319
+
310
320
  {/* Quick actions / triage */}
311
321
  <section className="mb-8" style={{ animation: 'fade-up 0.6s var(--ease-spring) 0.15s both' }}>
312
322
  <SectionHeader