@swarmclawai/swarmclaw 1.9.3 → 1.9.5
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 +25 -5
- package/package.json +2 -2
- package/src/app/api/gateways/[id]/environments/[environmentId]/route.ts +16 -0
- package/src/app/api/gateways/[id]/environments/route.ts +13 -0
- package/src/app/api/gateways/topology-route.test.ts +30 -0
- package/src/app/api/portability/export/route.test.ts +225 -0
- package/src/app/api/portability/export/route.ts +18 -9
- package/src/app/api/portability/import/route.test.ts +232 -31
- package/src/app/api/portability/import/route.ts +2 -2
- package/src/app/api/tasks/task-workspace-route.test.ts +4 -0
- package/src/cli/index.js +2 -0
- package/src/cli/spec.js +2 -0
- package/src/components/providers/provider-list.tsx +34 -1
- package/src/components/tasks/task-sheet.tsx +50 -0
- package/src/features/gateways/queries.ts +3 -0
- package/src/lib/server/gateways/gateway-profile-service.ts +2 -0
- package/src/lib/server/gateways/gateway-topology.test.ts +59 -3
- package/src/lib/server/gateways/gateway-topology.ts +129 -3
- package/src/lib/server/operations/operation-pulse.test.ts +29 -0
- package/src/lib/server/operations/operation-pulse.ts +9 -0
- package/src/lib/server/portability/export.ts +244 -38
- package/src/lib/server/portability/import.ts +148 -98
- package/src/lib/server/tasks/task-execution-workspace.test.ts +14 -0
- package/src/lib/server/tasks/task-execution-workspace.ts +133 -6
- package/src/lib/validation/schemas.ts +54 -1
- package/src/types/misc.ts +31 -0
- package/src/types/task.ts +30 -0
package/README.md
CHANGED
|
@@ -399,19 +399,39 @@ Operational docs: https://swarmclaw.ai/docs/observability
|
|
|
399
399
|
|
|
400
400
|
## Releases
|
|
401
401
|
|
|
402
|
+
### v1.9.5 Highlights
|
|
403
|
+
|
|
404
|
+
Bundled portability release: project-scoped workspace bundles, safer v2 imports, and preserved internal relationships for reusable teams.
|
|
405
|
+
|
|
406
|
+
- **Project bundle export.** `/api/portability/export?projectId=...` now emits a scoped workspace template with the selected project, active agents, pinned skills, schedules, chatrooms, connectors, MCP servers, and goals.
|
|
407
|
+
- **Downloadable project templates.** Project exports include a `scope` block and use readable `swarmclaw-project-...json` filenames for portable team handoff.
|
|
408
|
+
- **v2 import preservation.** The import route now validates and preserves v2 resources instead of dropping connectors, chatrooms, MCP servers, projects, goals, extensions, or scope metadata.
|
|
409
|
+
- **Reference remapping.** Imports now remap project, skill, MCP server, schedule, chatroom, connector, and goal relationships so restored bundles remain internally linked.
|
|
410
|
+
- **Credential-safe bundles.** Connector credentials, MCP env values, and sensitive config keys stay scrubbed while non-secret setup hints are retained.
|
|
411
|
+
|
|
412
|
+
### v1.9.4 Highlights
|
|
413
|
+
|
|
414
|
+
Bundled runtime-environment release: gateway execution visibility, task context handoff, and operator triage in one release cycle.
|
|
415
|
+
|
|
416
|
+
- **OpenClaw environments.** Gateway topology now calls `environments.list`, stores available environment counts, exposes `/api/gateways/:id/environments`, and adds CLI commands for list/status checks.
|
|
417
|
+
- **Provider dashboard visibility.** The Providers screen now shows fleet-wide and per-gateway execution environment availability alongside nodes, sessions, presence, and pairings.
|
|
418
|
+
- **Task context packets.** Prepared task workspaces now write `context.json` with task, preview, runtime, blocker, tag, and upstream-result context for external workers.
|
|
419
|
+
- **Runtime env handoff.** Workspaces now include `.env.swarmclaw` plus SwarmClaw, portable task/workspace, and `AGENT_HOME` env hints without embedding secrets.
|
|
420
|
+
- **Operations Pulse triage.** Gateway actions now surface zero-available-environment states as high-priority operator work.
|
|
421
|
+
|
|
402
422
|
### v1.9.3 Highlights
|
|
403
423
|
|
|
404
|
-
Bundled extension-orchestration release:
|
|
424
|
+
Bundled extension-orchestration release: managed plugin resources, gateway/setup declarations, and safer local folder access in one release cycle.
|
|
405
425
|
|
|
406
|
-
- **Managed extension resources.** Extensions can now declare provisionable agents, schedules/routines, local folders, gateway platforms, and setup checks through `managedResources` or
|
|
426
|
+
- **Managed extension resources.** Extensions can now declare provisionable agents, schedules/routines, local folders, gateway platforms, and setup checks through `managedResources` or top-level manifest aliases.
|
|
407
427
|
- **Deterministic reconciliation.** `/api/extensions/managed-resources` can preview and reconcile extension-owned agents and routines with stable IDs and `managedByExtension` markers.
|
|
408
428
|
- **Trusted local folders.** Extension-declared local folders support root-bounded inspection and recursive listing with traversal and symlink-escape protection.
|
|
409
429
|
- **Operator UI.** The Extensions screen now shows managed-resource badges and a Managed tab with totals plus per-extension reconcile controls.
|
|
410
|
-
- **Extension authoring spec.** `extension_creator` now documents managed resources, gateway declarations, setup checks, and
|
|
430
|
+
- **Extension authoring spec.** `extension_creator` now documents managed resources, gateway declarations, setup checks, and manifest aliases.
|
|
411
431
|
|
|
412
432
|
### v1.9.2 Highlights
|
|
413
433
|
|
|
414
|
-
Bundled
|
|
434
|
+
Bundled runtime-polish release: reasoning hygiene, deterministic delegation routing, task workflow polish, OpenClaw export hardening, and timeout hygiene.
|
|
415
435
|
|
|
416
436
|
- **Stateful reasoning tag scrubber.** String-streamed `<think>`, `<thinking>`, `<reasoning>`, `<thought>`, and `<REASONING_SCRATCHPAD>` blocks are removed across split deltas and routed into SwarmClaw's thinking stream instead of leaking into visible answers.
|
|
417
437
|
- **Deterministic delegation profiles.** `manage_tasks` now accepts explicit `workType` and `requiredCapabilities` routing hints, returns a stable `routeKey`, and can auto-assign unowned work without a classifier call when the profile is explicit.
|
|
@@ -421,7 +441,7 @@ Bundled competitor-parity release: Hermes-style reasoning hygiene, deterministic
|
|
|
421
441
|
|
|
422
442
|
### v1.9.1 Highlights
|
|
423
443
|
|
|
424
|
-
Task execution workspace release:
|
|
444
|
+
Task execution workspace release: task-scoped workspaces, preview handoffs, and liveness evidence.
|
|
425
445
|
|
|
426
446
|
- **Task-scoped execution workspaces.** Tasks can now provision a deterministic workspace under the SwarmClaw workspace root, preserving source cwd and project context while creating a task-local README for artifacts and handoffs.
|
|
427
447
|
- **Preview and runtime metadata.** Tasks can carry preview links and runtime services, and the task board surfaces those links directly on task cards and sheets.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@swarmclawai/swarmclaw",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.5",
|
|
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/ensure-sandbox-browser-image.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/gateways/gateway-topology.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/gateways/topology-route.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/agents/delegation-advisory.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/extension-managed-resources.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/chat-execution/reasoning-tag-scrubber.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/runtime/queue-retry-policy.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/server/tasks/task-execution-workspace.test.ts src/lib/server/tasks/task-service.test.ts src/lib/server/session-tools/execute.test.ts src/lib/server/session-tools/manage-tasks.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/tasks/task-workspace-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/extensions/managed-resources/route.test.ts src/app/api/healthz/route.test.ts src/app/api/logs/route.test.ts src/app/api/portability/export/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/agents/delegation-advisory.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/extension-managed-resources.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/chat-execution/reasoning-tag-scrubber.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/runtime/queue-retry-policy.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/server/tasks/task-execution-workspace.test.ts src/lib/server/tasks/task-service.test.ts src/lib/server/session-tools/execute.test.ts src/lib/server/session-tools/manage-tasks.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/tasks/task-workspace-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/extensions/managed-resources/route.test.ts src/app/api/healthz/route.test.ts src/app/api/logs/route.test.ts src/app/api/portability/export/route.test.ts src/app/api/portability/import/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",
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
|
|
3
|
+
import { notFound } from '@/lib/server/collection-helpers'
|
|
4
|
+
import { getOpenClawGatewayEnvironmentStatus } from '@/lib/server/gateways/gateway-topology'
|
|
5
|
+
|
|
6
|
+
export const dynamic = 'force-dynamic'
|
|
7
|
+
|
|
8
|
+
export async function GET(
|
|
9
|
+
_req: Request,
|
|
10
|
+
{ params }: { params: Promise<{ id: string; environmentId: string }> },
|
|
11
|
+
) {
|
|
12
|
+
const { id, environmentId } = await params
|
|
13
|
+
const snapshot = await getOpenClawGatewayEnvironmentStatus(id, decodeURIComponent(environmentId))
|
|
14
|
+
if (!snapshot) return notFound()
|
|
15
|
+
return NextResponse.json(snapshot, { status: snapshot.errors.length > 0 ? 502 : 200 })
|
|
16
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
|
|
3
|
+
import { notFound } from '@/lib/server/collection-helpers'
|
|
4
|
+
import { listOpenClawGatewayEnvironments } from '@/lib/server/gateways/gateway-topology'
|
|
5
|
+
|
|
6
|
+
export const dynamic = 'force-dynamic'
|
|
7
|
+
|
|
8
|
+
export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
9
|
+
const { id } = await params
|
|
10
|
+
const snapshot = await listOpenClawGatewayEnvironments(id)
|
|
11
|
+
if (!snapshot) return notFound()
|
|
12
|
+
return NextResponse.json(snapshot)
|
|
13
|
+
}
|
|
@@ -18,6 +18,36 @@ test('gateway topology route returns 404 for unknown profiles', () => {
|
|
|
18
18
|
assert.equal(output.body.error, 'Not found')
|
|
19
19
|
})
|
|
20
20
|
|
|
21
|
+
test('gateway environments route returns 404 for unknown profiles', () => {
|
|
22
|
+
const output = runWithTempDataDir<{ status: number; body: { error: string } }>(`
|
|
23
|
+
const routeMod = await import('./src/app/api/gateways/[id]/environments/route')
|
|
24
|
+
const route = routeMod.default || routeMod
|
|
25
|
+
const response = await route.GET(
|
|
26
|
+
new Request('http://local/api/gateways/missing/environments'),
|
|
27
|
+
{ params: Promise.resolve({ id: 'missing' }) },
|
|
28
|
+
)
|
|
29
|
+
console.log(JSON.stringify({ status: response.status, body: await response.json() }))
|
|
30
|
+
`, { prefix: 'swarmclaw-gateway-environments-route-test-' })
|
|
31
|
+
|
|
32
|
+
assert.equal(output.status, 404)
|
|
33
|
+
assert.equal(output.body.error, 'Not found')
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
test('gateway environment status route returns 404 for unknown profiles', () => {
|
|
37
|
+
const output = runWithTempDataDir<{ status: number; body: { error: string } }>(`
|
|
38
|
+
const routeMod = await import('./src/app/api/gateways/[id]/environments/[environmentId]/route')
|
|
39
|
+
const route = routeMod.default || routeMod
|
|
40
|
+
const response = await route.GET(
|
|
41
|
+
new Request('http://local/api/gateways/missing/environments/gateway'),
|
|
42
|
+
{ params: Promise.resolve({ id: 'missing', environmentId: 'gateway' }) },
|
|
43
|
+
)
|
|
44
|
+
console.log(JSON.stringify({ status: response.status, body: await response.json() }))
|
|
45
|
+
`, { prefix: 'swarmclaw-gateway-environment-status-route-test-' })
|
|
46
|
+
|
|
47
|
+
assert.equal(output.status, 404)
|
|
48
|
+
assert.equal(output.body.error, 'Not found')
|
|
49
|
+
})
|
|
50
|
+
|
|
21
51
|
test('gateway fleet route reports empty totals when no OpenClaw profiles exist', () => {
|
|
22
52
|
const output = runWithTempDataDir<{
|
|
23
53
|
status: number
|
|
@@ -3,6 +3,7 @@ import { describe, it } from 'node:test'
|
|
|
3
3
|
|
|
4
4
|
import { GET } from './route'
|
|
5
5
|
import { buildPortableExportFilename } from '@/lib/server/portability/export'
|
|
6
|
+
import { runWithTempDataDir } from '@/lib/server/test-utils/run-with-temp-data-dir'
|
|
6
7
|
|
|
7
8
|
describe('GET /api/portability/export', () => {
|
|
8
9
|
it('returns a collision-resistant attachment filename for downloads', async () => {
|
|
@@ -14,4 +15,228 @@ describe('GET /api/portability/export', () => {
|
|
|
14
15
|
const body = await response.json()
|
|
15
16
|
assert.equal(disposition, `attachment; filename="${buildPortableExportFilename(body)}"`)
|
|
16
17
|
})
|
|
18
|
+
|
|
19
|
+
it('exports a scoped project bundle with scrubbed integrations', () => {
|
|
20
|
+
const output = runWithTempDataDir<{
|
|
21
|
+
status: number
|
|
22
|
+
disposition: string
|
|
23
|
+
scopeKind: string
|
|
24
|
+
projectNames: string[]
|
|
25
|
+
agentNames: string[]
|
|
26
|
+
skillNames: string[]
|
|
27
|
+
scheduleNames: string[]
|
|
28
|
+
chatroomNames: string[]
|
|
29
|
+
connectorNames: string[]
|
|
30
|
+
mcpServerNames: string[]
|
|
31
|
+
connectorConfig: Record<string, string>
|
|
32
|
+
connectorEnabled: boolean
|
|
33
|
+
missingStatus: number
|
|
34
|
+
missingError: string
|
|
35
|
+
}>(`
|
|
36
|
+
const storageMod = await import('./src/lib/server/storage')
|
|
37
|
+
const agentRepoMod = await import('./src/lib/server/agents/agent-repository')
|
|
38
|
+
const skillRepoMod = await import('./src/lib/server/skills/skill-repository')
|
|
39
|
+
const scheduleRepoMod = await import('./src/lib/server/schedules/schedule-repository')
|
|
40
|
+
const chatroomRepoMod = await import('./src/lib/server/chatrooms/chatroom-repository')
|
|
41
|
+
const connectorRepoMod = await import('./src/lib/server/connectors/connector-repository')
|
|
42
|
+
const routeMod = await import('./src/app/api/portability/export/route')
|
|
43
|
+
const storage = storageMod.default || storageMod
|
|
44
|
+
const agentRepo = agentRepoMod.default || agentRepoMod
|
|
45
|
+
const skillRepo = skillRepoMod.default || skillRepoMod
|
|
46
|
+
const scheduleRepo = scheduleRepoMod.default || scheduleRepoMod
|
|
47
|
+
const chatroomRepo = chatroomRepoMod.default || chatroomRepoMod
|
|
48
|
+
const connectorRepo = connectorRepoMod.default || connectorRepoMod
|
|
49
|
+
const route = routeMod.default || routeMod
|
|
50
|
+
const { saveProjects, saveMcpServers } = storage
|
|
51
|
+
const { saveAgents } = agentRepo
|
|
52
|
+
const { saveSkills } = skillRepo
|
|
53
|
+
const { saveSchedules } = scheduleRepo
|
|
54
|
+
const { upsertChatroom } = chatroomRepo
|
|
55
|
+
const { upsertConnector } = connectorRepo
|
|
56
|
+
const now = 1780000000000
|
|
57
|
+
|
|
58
|
+
saveProjects({
|
|
59
|
+
'project-a': {
|
|
60
|
+
id: 'project-a',
|
|
61
|
+
name: 'Launch Room',
|
|
62
|
+
description: 'Shipping workspace',
|
|
63
|
+
color: '#5b8def',
|
|
64
|
+
objective: 'Ship the next release',
|
|
65
|
+
createdAt: now,
|
|
66
|
+
updatedAt: now,
|
|
67
|
+
},
|
|
68
|
+
'project-b': {
|
|
69
|
+
id: 'project-b',
|
|
70
|
+
name: 'Backlog',
|
|
71
|
+
description: 'Separate workspace',
|
|
72
|
+
createdAt: now,
|
|
73
|
+
updatedAt: now,
|
|
74
|
+
},
|
|
75
|
+
})
|
|
76
|
+
saveSkills({
|
|
77
|
+
'skill-a': {
|
|
78
|
+
id: 'skill-a',
|
|
79
|
+
name: 'Release Notes',
|
|
80
|
+
filename: 'release-notes.md',
|
|
81
|
+
content: 'Summarize shipped changes',
|
|
82
|
+
projectId: 'project-a',
|
|
83
|
+
scope: 'global',
|
|
84
|
+
createdAt: now,
|
|
85
|
+
updatedAt: now,
|
|
86
|
+
},
|
|
87
|
+
'global-skill': {
|
|
88
|
+
id: 'global-skill',
|
|
89
|
+
name: 'Risk Scan',
|
|
90
|
+
filename: 'risk-scan.md',
|
|
91
|
+
content: 'Find release risks',
|
|
92
|
+
scope: 'global',
|
|
93
|
+
createdAt: now,
|
|
94
|
+
updatedAt: now,
|
|
95
|
+
},
|
|
96
|
+
'skill-b': {
|
|
97
|
+
id: 'skill-b',
|
|
98
|
+
name: 'Backlog Grooming',
|
|
99
|
+
filename: 'backlog-grooming.md',
|
|
100
|
+
content: 'Sort the backlog',
|
|
101
|
+
projectId: 'project-b',
|
|
102
|
+
scope: 'global',
|
|
103
|
+
createdAt: now,
|
|
104
|
+
updatedAt: now,
|
|
105
|
+
},
|
|
106
|
+
})
|
|
107
|
+
saveMcpServers({
|
|
108
|
+
'mcp-a': {
|
|
109
|
+
id: 'mcp-a',
|
|
110
|
+
name: 'Local Tools',
|
|
111
|
+
transport: 'stdio',
|
|
112
|
+
command: 'node',
|
|
113
|
+
args: ['tool.js'],
|
|
114
|
+
env: { API_TOKEN: 'secret-token' },
|
|
115
|
+
createdAt: now,
|
|
116
|
+
updatedAt: now,
|
|
117
|
+
},
|
|
118
|
+
'mcp-b': {
|
|
119
|
+
id: 'mcp-b',
|
|
120
|
+
name: 'Backlog Tools',
|
|
121
|
+
transport: 'stdio',
|
|
122
|
+
command: 'node',
|
|
123
|
+
createdAt: now,
|
|
124
|
+
updatedAt: now,
|
|
125
|
+
},
|
|
126
|
+
})
|
|
127
|
+
saveAgents({
|
|
128
|
+
'agent-a': {
|
|
129
|
+
id: 'agent-a',
|
|
130
|
+
name: 'Release Lead',
|
|
131
|
+
description: 'Owns launch execution',
|
|
132
|
+
systemPrompt: 'Ship safely',
|
|
133
|
+
provider: 'openai',
|
|
134
|
+
model: 'gpt-4o-mini',
|
|
135
|
+
projectId: 'project-a',
|
|
136
|
+
skillIds: ['skill-a', 'global-skill'],
|
|
137
|
+
mcpServerIds: ['mcp-a'],
|
|
138
|
+
createdAt: now,
|
|
139
|
+
updatedAt: now,
|
|
140
|
+
},
|
|
141
|
+
'agent-b': {
|
|
142
|
+
id: 'agent-b',
|
|
143
|
+
name: 'Backlog Lead',
|
|
144
|
+
description: 'Owns backlog',
|
|
145
|
+
systemPrompt: 'Plan later',
|
|
146
|
+
provider: 'openai',
|
|
147
|
+
model: 'gpt-4o-mini',
|
|
148
|
+
projectId: 'project-b',
|
|
149
|
+
skillIds: ['skill-b'],
|
|
150
|
+
mcpServerIds: ['mcp-b'],
|
|
151
|
+
createdAt: now,
|
|
152
|
+
updatedAt: now,
|
|
153
|
+
},
|
|
154
|
+
})
|
|
155
|
+
saveSchedules({
|
|
156
|
+
'schedule-a': {
|
|
157
|
+
id: 'schedule-a',
|
|
158
|
+
name: 'Daily Launch Check',
|
|
159
|
+
agentId: 'agent-a',
|
|
160
|
+
projectId: 'project-a',
|
|
161
|
+
taskPrompt: 'Check release readiness',
|
|
162
|
+
scheduleType: 'interval',
|
|
163
|
+
intervalMs: 60000,
|
|
164
|
+
status: 'active',
|
|
165
|
+
createdAt: now,
|
|
166
|
+
updatedAt: now,
|
|
167
|
+
},
|
|
168
|
+
'schedule-b': {
|
|
169
|
+
id: 'schedule-b',
|
|
170
|
+
name: 'Backlog Check',
|
|
171
|
+
agentId: 'agent-b',
|
|
172
|
+
projectId: 'project-b',
|
|
173
|
+
taskPrompt: 'Review backlog',
|
|
174
|
+
scheduleType: 'interval',
|
|
175
|
+
intervalMs: 60000,
|
|
176
|
+
status: 'active',
|
|
177
|
+
createdAt: now,
|
|
178
|
+
updatedAt: now,
|
|
179
|
+
},
|
|
180
|
+
})
|
|
181
|
+
upsertChatroom('room-a', {
|
|
182
|
+
id: 'room-a',
|
|
183
|
+
name: 'Launch Room Chat',
|
|
184
|
+
agentIds: ['agent-a'],
|
|
185
|
+
messages: [],
|
|
186
|
+
chatMode: 'parallel',
|
|
187
|
+
temporary: false,
|
|
188
|
+
createdAt: now,
|
|
189
|
+
updatedAt: now,
|
|
190
|
+
})
|
|
191
|
+
upsertConnector('connector-a', {
|
|
192
|
+
id: 'connector-a',
|
|
193
|
+
name: 'Launch Slack',
|
|
194
|
+
platform: 'slack',
|
|
195
|
+
agentId: 'agent-a',
|
|
196
|
+
chatroomId: 'room-a',
|
|
197
|
+
credentialId: 'credential-a',
|
|
198
|
+
config: { channel: 'launch', botToken: 'secret-token' },
|
|
199
|
+
isEnabled: true,
|
|
200
|
+
status: 'running',
|
|
201
|
+
createdAt: now,
|
|
202
|
+
updatedAt: now,
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
const response = await route.GET(new Request('http://local/api/portability/export?projectId=project-a&download=true'))
|
|
206
|
+
const body = await response.json()
|
|
207
|
+
const missingResponse = await route.GET(new Request('http://local/api/portability/export?projectId=missing-project'))
|
|
208
|
+
const missingPayload = await missingResponse.json()
|
|
209
|
+
console.log(JSON.stringify({
|
|
210
|
+
status: response.status,
|
|
211
|
+
disposition: response.headers.get('content-disposition') || '',
|
|
212
|
+
scopeKind: body.scope?.kind || null,
|
|
213
|
+
projectNames: (body.projects || []).map((project) => project.name),
|
|
214
|
+
agentNames: body.agents.map((agent) => agent.name),
|
|
215
|
+
skillNames: body.skills.map((skill) => skill.name).sort(),
|
|
216
|
+
scheduleNames: body.schedules.map((schedule) => schedule.name),
|
|
217
|
+
chatroomNames: (body.chatrooms || []).map((chatroom) => chatroom.name),
|
|
218
|
+
connectorNames: (body.connectors || []).map((connector) => connector.name),
|
|
219
|
+
mcpServerNames: (body.mcpServers || []).map((server) => server.name),
|
|
220
|
+
connectorConfig: body.connectors?.[0]?.config || {},
|
|
221
|
+
connectorEnabled: body.connectors?.[0]?.isEnabled ?? null,
|
|
222
|
+
missingStatus: missingResponse.status,
|
|
223
|
+
missingError: missingPayload.error,
|
|
224
|
+
}))
|
|
225
|
+
`)
|
|
226
|
+
|
|
227
|
+
assert.equal(output.status, 200)
|
|
228
|
+
assert.match(output.disposition, /^attachment; filename="swarmclaw-project-launch-room-\d{8}-\d{6}\d{3}Z\.json"$/)
|
|
229
|
+
assert.equal(output.scopeKind, 'project')
|
|
230
|
+
assert.deepEqual(output.projectNames, ['Launch Room'])
|
|
231
|
+
assert.deepEqual(output.agentNames, ['Release Lead'])
|
|
232
|
+
assert.deepEqual(output.skillNames, ['Release Notes', 'Risk Scan'])
|
|
233
|
+
assert.deepEqual(output.scheduleNames, ['Daily Launch Check'])
|
|
234
|
+
assert.deepEqual(output.chatroomNames, ['Launch Room Chat'])
|
|
235
|
+
assert.deepEqual(output.connectorNames, ['Launch Slack'])
|
|
236
|
+
assert.deepEqual(output.mcpServerNames, ['Local Tools'])
|
|
237
|
+
assert.deepEqual(output.connectorConfig, { channel: 'launch' })
|
|
238
|
+
assert.equal(output.connectorEnabled, false)
|
|
239
|
+
assert.equal(output.missingStatus, 404)
|
|
240
|
+
assert.equal(output.missingError, 'Project not found: missing-project')
|
|
241
|
+
})
|
|
17
242
|
})
|
|
@@ -3,15 +3,24 @@ import { buildPortableExportFilename, exportConfig } from '@/lib/server/portabil
|
|
|
3
3
|
export const dynamic = 'force-dynamic'
|
|
4
4
|
|
|
5
5
|
export async function GET(req: Request) {
|
|
6
|
-
const manifest = exportConfig()
|
|
7
6
|
const { searchParams } = new URL(req.url)
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
7
|
+
const projectId = searchParams.get('projectId')?.trim() || null
|
|
8
|
+
try {
|
|
9
|
+
const manifest = exportConfig({ projectId })
|
|
10
|
+
if (searchParams.get('download') === 'true') {
|
|
11
|
+
return new NextResponse(JSON.stringify(manifest, null, 2), {
|
|
12
|
+
headers: {
|
|
13
|
+
'content-type': 'application/json; charset=utf-8',
|
|
14
|
+
'content-disposition': `attachment; filename="${buildPortableExportFilename(manifest)}"`,
|
|
15
|
+
},
|
|
16
|
+
})
|
|
17
|
+
}
|
|
18
|
+
return NextResponse.json(manifest)
|
|
19
|
+
} catch (err) {
|
|
20
|
+
const message = err instanceof Error ? err.message : 'Failed to export manifest'
|
|
21
|
+
if (message.startsWith('Project not found: ')) {
|
|
22
|
+
return NextResponse.json({ error: message }, { status: 404 })
|
|
23
|
+
}
|
|
24
|
+
return NextResponse.json({ error: message }, { status: 500 })
|
|
15
25
|
}
|
|
16
|
-
return NextResponse.json(manifest)
|
|
17
26
|
}
|