Backup before continuing from Codex 5.2 session - User storage, compaction suggestions, streaming improvements
This commit is contained in:
@@ -1,10 +1,18 @@
|
||||
import { FastifyInstance, FastifyReply } from "fastify"
|
||||
import { spawnSync } from "child_process"
|
||||
import { z } from "zod"
|
||||
import { existsSync, mkdirSync } from "fs"
|
||||
import { cp, readFile, writeFile } from "fs/promises"
|
||||
import path from "path"
|
||||
import { WorkspaceManager } from "../../workspaces/manager"
|
||||
import { InstanceStore } from "../../storage/instance-store"
|
||||
import { ConfigStore } from "../../config/store"
|
||||
import { getWorkspaceOpencodeConfigDir } from "../../opencode-config"
|
||||
|
||||
interface RouteDeps {
|
||||
workspaceManager: WorkspaceManager
|
||||
instanceStore: InstanceStore
|
||||
configStore: ConfigStore
|
||||
}
|
||||
|
||||
const WorkspaceCreateSchema = z.object({
|
||||
@@ -163,6 +171,143 @@ export function registerWorkspaceRoutes(app: FastifyInstance, deps: RouteDeps) {
|
||||
|
||||
return { isRepo: true, branch, ahead, behind, changes }
|
||||
})
|
||||
|
||||
app.post<{
|
||||
Params: { id: string }
|
||||
Body: { destination: string; includeConfig?: boolean }
|
||||
}>("/api/workspaces/:id/export", async (request, reply) => {
|
||||
const workspace = deps.workspaceManager.get(request.params.id)
|
||||
if (!workspace) {
|
||||
reply.code(404)
|
||||
return { error: "Workspace not found" }
|
||||
}
|
||||
|
||||
const payload = request.body ?? { destination: "" }
|
||||
const destination = payload.destination?.trim()
|
||||
if (!destination) {
|
||||
reply.code(400)
|
||||
return { error: "Destination is required" }
|
||||
}
|
||||
|
||||
const exportRoot = path.join(destination, `nomadarch-export-${path.basename(workspace.path)}-${Date.now()}`)
|
||||
mkdirSync(exportRoot, { recursive: true })
|
||||
|
||||
const workspaceTarget = path.join(exportRoot, "workspace")
|
||||
await cp(workspace.path, workspaceTarget, { recursive: true, force: true })
|
||||
|
||||
const instanceData = await deps.instanceStore.read(workspace.path)
|
||||
await writeFile(path.join(exportRoot, "instance-data.json"), JSON.stringify(instanceData, null, 2), "utf-8")
|
||||
|
||||
const configDir = getWorkspaceOpencodeConfigDir(workspace.id)
|
||||
if (existsSync(configDir)) {
|
||||
await cp(configDir, path.join(exportRoot, "opencode-config"), { recursive: true, force: true })
|
||||
}
|
||||
|
||||
if (payload.includeConfig) {
|
||||
const config = deps.configStore.get()
|
||||
await writeFile(path.join(exportRoot, "user-config.json"), JSON.stringify(config, null, 2), "utf-8")
|
||||
}
|
||||
|
||||
const metadata = {
|
||||
exportedAt: new Date().toISOString(),
|
||||
workspacePath: workspace.path,
|
||||
workspaceId: workspace.id,
|
||||
}
|
||||
await writeFile(path.join(exportRoot, "metadata.json"), JSON.stringify(metadata, null, 2), "utf-8")
|
||||
|
||||
return { destination: exportRoot }
|
||||
})
|
||||
|
||||
app.get<{ Params: { id: string } }>("/api/workspaces/:id/mcp-config", async (request, reply) => {
|
||||
const workspace = deps.workspaceManager.get(request.params.id)
|
||||
if (!workspace) {
|
||||
reply.code(404)
|
||||
return { error: "Workspace not found" }
|
||||
}
|
||||
|
||||
const configPath = path.join(workspace.path, ".mcp.json")
|
||||
if (!existsSync(configPath)) {
|
||||
return { path: configPath, exists: false, config: { mcpServers: {} } }
|
||||
}
|
||||
|
||||
try {
|
||||
const raw = await readFile(configPath, "utf-8")
|
||||
const parsed = raw ? JSON.parse(raw) : {}
|
||||
return { path: configPath, exists: true, config: parsed }
|
||||
} catch (error) {
|
||||
request.log.error({ err: error }, "Failed to read MCP config")
|
||||
reply.code(500)
|
||||
return { error: "Failed to read MCP config" }
|
||||
}
|
||||
})
|
||||
|
||||
app.put<{ Params: { id: string } }>("/api/workspaces/:id/mcp-config", async (request, reply) => {
|
||||
const workspace = deps.workspaceManager.get(request.params.id)
|
||||
if (!workspace) {
|
||||
reply.code(404)
|
||||
return { error: "Workspace not found" }
|
||||
}
|
||||
|
||||
const body = request.body as { config?: unknown }
|
||||
if (!body || typeof body.config !== "object" || body.config === null) {
|
||||
reply.code(400)
|
||||
return { error: "Invalid MCP config payload" }
|
||||
}
|
||||
|
||||
const configPath = path.join(workspace.path, ".mcp.json")
|
||||
try {
|
||||
await writeFile(configPath, JSON.stringify(body.config, null, 2), "utf-8")
|
||||
return { path: configPath, exists: true, config: body.config }
|
||||
} catch (error) {
|
||||
request.log.error({ err: error }, "Failed to write MCP config")
|
||||
reply.code(500)
|
||||
return { error: "Failed to write MCP config" }
|
||||
}
|
||||
})
|
||||
|
||||
app.post<{
|
||||
Body: { source: string; destination: string; includeConfig?: boolean }
|
||||
}>("/api/workspaces/import", async (request, reply) => {
|
||||
const payload = request.body ?? { source: "", destination: "" }
|
||||
const source = payload.source?.trim()
|
||||
const destination = payload.destination?.trim()
|
||||
if (!source || !destination) {
|
||||
reply.code(400)
|
||||
return { error: "Source and destination are required" }
|
||||
}
|
||||
|
||||
const workspaceSource = path.join(source, "workspace")
|
||||
if (!existsSync(workspaceSource)) {
|
||||
reply.code(400)
|
||||
return { error: "Export workspace folder not found" }
|
||||
}
|
||||
|
||||
await cp(workspaceSource, destination, { recursive: true, force: true })
|
||||
|
||||
const workspace = await deps.workspaceManager.create(destination)
|
||||
|
||||
const instanceDataPath = path.join(source, "instance-data.json")
|
||||
if (existsSync(instanceDataPath)) {
|
||||
const raw = await readFile(instanceDataPath, "utf-8")
|
||||
await deps.instanceStore.write(workspace.path, JSON.parse(raw))
|
||||
}
|
||||
|
||||
const configSource = path.join(source, "opencode-config")
|
||||
if (existsSync(configSource)) {
|
||||
const configTarget = getWorkspaceOpencodeConfigDir(workspace.id)
|
||||
await cp(configSource, configTarget, { recursive: true, force: true })
|
||||
}
|
||||
|
||||
if (payload.includeConfig) {
|
||||
const userConfigPath = path.join(source, "user-config.json")
|
||||
if (existsSync(userConfigPath)) {
|
||||
const raw = await readFile(userConfigPath, "utf-8")
|
||||
deps.configStore.replace(JSON.parse(raw))
|
||||
}
|
||||
}
|
||||
|
||||
return workspace
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user