@mrpatronz/nexusflow 2.8.1 → 2.10.0
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 +172 -113
- package/dist/agent/AcpCliAdapter.d.ts +2 -0
- package/dist/agent/AcpCliAdapter.d.ts.map +1 -1
- package/dist/agent/AcpCliAdapter.js +50 -4
- package/dist/agent/AcpCliAdapter.js.map +1 -1
- package/dist/agent/AntigravityCliAdapter.d.ts +44 -2
- package/dist/agent/AntigravityCliAdapter.d.ts.map +1 -1
- package/dist/agent/AntigravityCliAdapter.js +363 -16
- package/dist/agent/AntigravityCliAdapter.js.map +1 -1
- package/dist/agent/ClaudeCliAdapter.d.ts +3 -0
- package/dist/agent/ClaudeCliAdapter.d.ts.map +1 -1
- package/dist/agent/ClaudeCliAdapter.js +37 -10
- package/dist/agent/ClaudeCliAdapter.js.map +1 -1
- package/dist/agent/ClaudeSdkAdapter.d.ts +1 -0
- package/dist/agent/ClaudeSdkAdapter.d.ts.map +1 -1
- package/dist/agent/ClaudeSdkAdapter.js +38 -11
- package/dist/agent/ClaudeSdkAdapter.js.map +1 -1
- package/dist/agent/CliAdapterBase.d.ts +2 -0
- package/dist/agent/CliAdapterBase.d.ts.map +1 -1
- package/dist/agent/CliAdapterBase.js +5 -1
- package/dist/agent/CliAdapterBase.js.map +1 -1
- package/dist/agent/CodexCliAdapter.d.ts +1 -1
- package/dist/agent/CodexCliAdapter.d.ts.map +1 -1
- package/dist/agent/CodexCliAdapter.js +5 -4
- package/dist/agent/CodexCliAdapter.js.map +1 -1
- package/dist/agent/CodexSdkAdapter.d.ts.map +1 -1
- package/dist/agent/CodexSdkAdapter.js +4 -1
- package/dist/agent/CodexSdkAdapter.js.map +1 -1
- package/dist/agent/CopilotAcpAdapter.d.ts.map +1 -1
- package/dist/agent/CopilotAcpAdapter.js +2 -2
- package/dist/agent/CopilotAcpAdapter.js.map +1 -1
- package/dist/agent/ProviderRegistry.d.ts +8 -1
- package/dist/agent/ProviderRegistry.d.ts.map +1 -1
- package/dist/agent/ProviderRegistry.js.map +1 -1
- package/dist/agent/TurnSessionManager.d.ts +65 -0
- package/dist/agent/TurnSessionManager.d.ts.map +1 -0
- package/dist/agent/TurnSessionManager.js +330 -0
- package/dist/agent/TurnSessionManager.js.map +1 -0
- package/dist/agent/adapters.d.ts.map +1 -1
- package/dist/agent/adapters.js +23 -6
- package/dist/agent/adapters.js.map +1 -1
- package/dist/agent/cliAvailability.d.ts.map +1 -1
- package/dist/agent/cliAvailability.js +12 -0
- package/dist/agent/cliAvailability.js.map +1 -1
- package/dist/agent/mcp-config.d.ts +0 -5
- package/dist/agent/mcp-config.d.ts.map +1 -1
- package/dist/agent/mcp-config.js +2 -1
- package/dist/agent/mcp-config.js.map +1 -1
- package/dist/agent/models.d.ts +13 -0
- package/dist/agent/models.d.ts.map +1 -1
- package/dist/agent/models.js +262 -8
- package/dist/agent/models.js.map +1 -1
- package/dist/agent/nativeTools.d.ts.map +1 -1
- package/dist/agent/nativeTools.js +1 -0
- package/dist/agent/nativeTools.js.map +1 -1
- package/dist/agent/orchestrator.d.ts.map +1 -1
- package/dist/agent/orchestrator.js +3 -2
- package/dist/agent/orchestrator.js.map +1 -1
- package/dist/agent/session.d.ts +1 -0
- package/dist/agent/session.d.ts.map +1 -1
- package/dist/agent/session.js.map +1 -1
- package/dist/analyzers/detect-deps.js +16 -1
- package/dist/analyzers/detect-deps.js.map +1 -1
- package/dist/analyzers/run-analyzer.d.ts.map +1 -1
- package/dist/analyzers/run-analyzer.js.map +1 -1
- package/dist/commands/adapter.d.ts.map +1 -1
- package/dist/commands/adapter.js +15 -14
- package/dist/commands/adapter.js.map +1 -1
- package/dist/commands/add-repo.d.ts.map +1 -1
- package/dist/commands/add-repo.js +6 -5
- package/dist/commands/add-repo.js.map +1 -1
- package/dist/commands/commit.js +6 -8
- package/dist/commands/commit.js.map +1 -1
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +2 -1
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/create.d.ts +4 -4
- package/dist/commands/create.d.ts.map +1 -1
- package/dist/commands/create.js +8 -7
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/desktop.d.ts +44 -3
- package/dist/commands/desktop.d.ts.map +1 -1
- package/dist/commands/desktop.js +246 -7
- package/dist/commands/desktop.js.map +1 -1
- package/dist/commands/diff.d.ts.map +1 -1
- package/dist/commands/diff.js +3 -2
- package/dist/commands/diff.js.map +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +3 -2
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/finish.js +2 -1
- package/dist/commands/finish.js.map +1 -1
- package/dist/commands/handoff.d.ts.map +1 -1
- package/dist/commands/handoff.js +12 -12
- package/dist/commands/handoff.js.map +1 -1
- package/dist/commands/init.d.ts +2 -2
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +8 -7
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/isolate.d.ts.map +1 -1
- package/dist/commands/isolate.js +2 -1
- package/dist/commands/isolate.js.map +1 -1
- package/dist/commands/knowledge.d.ts.map +1 -1
- package/dist/commands/knowledge.js +3 -2
- package/dist/commands/knowledge.js.map +1 -1
- package/dist/commands/list.d.ts.map +1 -1
- package/dist/commands/list.js +3 -2
- package/dist/commands/list.js.map +1 -1
- package/dist/commands/logs.js +3 -3
- package/dist/commands/logs.js.map +1 -1
- package/dist/commands/mcp.d.ts +1 -2
- package/dist/commands/mcp.d.ts.map +1 -1
- package/dist/commands/mcp.js +19 -12
- package/dist/commands/mcp.js.map +1 -1
- package/dist/commands/migrate.d.ts +12 -0
- package/dist/commands/migrate.d.ts.map +1 -0
- package/dist/commands/migrate.js +128 -0
- package/dist/commands/migrate.js.map +1 -0
- package/dist/commands/open.d.ts.map +1 -1
- package/dist/commands/open.js +2 -1
- package/dist/commands/open.js.map +1 -1
- package/dist/commands/progress.d.ts.map +1 -1
- package/dist/commands/progress.js +2 -1
- package/dist/commands/progress.js.map +1 -1
- package/dist/commands/project.d.ts.map +1 -1
- package/dist/commands/project.js +7 -6
- package/dist/commands/project.js.map +1 -1
- package/dist/commands/refresh.d.ts.map +1 -1
- package/dist/commands/refresh.js +8 -11
- package/dist/commands/refresh.js.map +1 -1
- package/dist/commands/remove.d.ts.map +1 -1
- package/dist/commands/remove.js +2 -1
- package/dist/commands/remove.js.map +1 -1
- package/dist/commands/schedule.js +16 -18
- package/dist/commands/schedule.js.map +1 -1
- package/dist/commands/start.d.ts.map +1 -1
- package/dist/commands/start.js +9 -8
- package/dist/commands/start.js.map +1 -1
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +2 -1
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/stop.d.ts.map +1 -1
- package/dist/commands/stop.js +2 -1
- package/dist/commands/stop.js.map +1 -1
- package/dist/commands/strategy.d.ts.map +1 -1
- package/dist/commands/strategy.js +6 -5
- package/dist/commands/strategy.js.map +1 -1
- package/dist/commands/sync.js +8 -10
- package/dist/commands/sync.js.map +1 -1
- package/dist/commands/tui.d.ts.map +1 -1
- package/dist/commands/tui.js +11 -10
- package/dist/commands/tui.js.map +1 -1
- package/dist/commands/ui.d.ts.map +1 -1
- package/dist/commands/ui.js +4 -3
- package/dist/commands/ui.js.map +1 -1
- package/dist/core/adapters/local-storage.d.ts +3 -1
- package/dist/core/adapters/local-storage.d.ts.map +1 -1
- package/dist/core/adapters/local-storage.js +62 -4
- package/dist/core/adapters/local-storage.js.map +1 -1
- package/dist/core/analysis-cache.d.ts.map +1 -1
- package/dist/core/analysis-cache.js +8 -7
- package/dist/core/analysis-cache.js.map +1 -1
- package/dist/core/brand-config.d.ts +261 -0
- package/dist/core/brand-config.d.ts.map +1 -0
- package/dist/core/brand-config.js +514 -0
- package/dist/core/brand-config.js.map +1 -0
- package/dist/core/config.d.ts +1 -1
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +18 -8
- package/dist/core/config.js.map +1 -1
- package/dist/core/constants.d.ts +88 -0
- package/dist/core/constants.d.ts.map +1 -0
- package/dist/core/constants.js +103 -0
- package/dist/core/constants.js.map +1 -0
- package/dist/core/doctor.d.ts.map +1 -1
- package/dist/core/doctor.js +11 -3
- package/dist/core/doctor.js.map +1 -1
- package/dist/core/generation-lock.d.ts +2 -2
- package/dist/core/generation-lock.d.ts.map +1 -1
- package/dist/core/generation-lock.js +49 -22
- package/dist/core/generation-lock.js.map +1 -1
- package/dist/core/knowledge.d.ts +19 -0
- package/dist/core/knowledge.d.ts.map +1 -1
- package/dist/core/knowledge.js +150 -24
- package/dist/core/knowledge.js.map +1 -1
- package/dist/core/migrate.d.ts +53 -0
- package/dist/core/migrate.d.ts.map +1 -0
- package/dist/core/migrate.js +607 -0
- package/dist/core/migrate.js.map +1 -0
- package/dist/core/new-repo.js +1 -1
- package/dist/core/new-repo.js.map +1 -1
- package/dist/core/projects.d.ts.map +1 -1
- package/dist/core/projects.js +13 -4
- package/dist/core/projects.js.map +1 -1
- package/dist/core/refresh.d.ts.map +1 -1
- package/dist/core/refresh.js +6 -4
- package/dist/core/refresh.js.map +1 -1
- package/dist/core/scheduler.d.ts +1 -1
- package/dist/core/scheduler.d.ts.map +1 -1
- package/dist/core/scheduler.js +11 -9
- package/dist/core/scheduler.js.map +1 -1
- package/dist/core/sync.js +1 -1
- package/dist/core/sync.js.map +1 -1
- package/dist/core/workspace-git.d.ts.map +1 -1
- package/dist/core/workspace-git.js +34 -10
- package/dist/core/workspace-git.js.map +1 -1
- package/dist/core/workspace-state.d.ts +4 -0
- package/dist/core/workspace-state.d.ts.map +1 -1
- package/dist/core/workspace-state.js +8 -8
- package/dist/core/workspace-state.js.map +1 -1
- package/dist/core/workspace.d.ts +3 -4
- package/dist/core/workspace.d.ts.map +1 -1
- package/dist/core/workspace.js +62 -30
- package/dist/core/workspace.js.map +1 -1
- package/dist/desktop-server.js +6 -3
- package/dist/desktop-server.js.map +1 -1
- package/dist/generators/base.d.ts.map +1 -1
- package/dist/generators/base.js +7 -5
- package/dist/generators/base.js.map +1 -1
- package/dist/generators/claude.d.ts.map +1 -1
- package/dist/generators/claude.js +2 -1
- package/dist/generators/claude.js.map +1 -1
- package/dist/generators/cursor.d.ts +1 -1
- package/dist/generators/cursor.d.ts.map +1 -1
- package/dist/generators/cursor.js +9 -4
- package/dist/generators/cursor.js.map +1 -1
- package/dist/generators/index.d.ts +4 -8
- package/dist/generators/index.d.ts.map +1 -1
- package/dist/generators/index.js +34 -69
- package/dist/generators/index.js.map +1 -1
- package/dist/generators/plan-generator.d.ts.map +1 -1
- package/dist/generators/plan-generator.js +20 -11
- package/dist/generators/plan-generator.js.map +1 -1
- package/dist/generators/skills-generator.d.ts.map +1 -1
- package/dist/generators/skills-generator.js +26 -11
- package/dist/generators/skills-generator.js.map +1 -1
- package/dist/gui/app_logo.png +0 -0
- package/dist/gui/assets/AgentsPage-CnXzZFo1.js +3 -0
- package/dist/gui/assets/DashboardPage-rm3hKgsw.js +1 -0
- package/dist/gui/assets/GettingStartedPage-BKeuSHZQ.js +1 -0
- package/dist/gui/assets/NullStore-DuSDhGOe.js +1 -0
- package/dist/gui/assets/ProjectsPage-CioaA4uf.js +1 -0
- package/dist/gui/assets/ScaffoldRepoInline-DKZ1rPmE.js +1 -0
- package/dist/gui/assets/Separator-BgWyHDtg.js +1 -0
- package/dist/gui/assets/SettingsPage-BevO8oge.js +1 -0
- package/dist/gui/assets/SkillsPage-D2stJRWy.js +6 -0
- package/dist/gui/assets/StartWorkPage-C8a6eN90.js +1 -0
- package/dist/gui/assets/{StrategiesPage-C36TZgDX.js → StrategiesPage-BRQMly_d.js} +2 -2
- package/dist/gui/assets/WorkroomsPage-KqBkRD-x.js +4 -0
- package/dist/gui/assets/WorkspacesPage-DcA2i5HN.js +2 -0
- package/dist/gui/assets/alert-QnMXzwpU.js +1 -0
- package/dist/gui/assets/alert-dialog-BHEUQVyS.js +1 -0
- package/dist/gui/assets/badge-DTBzXOWn.js +1 -0
- package/dist/gui/assets/brand-hHByyyzq.js +1 -0
- package/dist/gui/assets/button-CREn1mup.js +1 -0
- package/dist/gui/assets/card-yrg6ov8B.js +1 -0
- package/dist/gui/assets/chevron-down-B_Mwbim8.js +1 -0
- package/dist/gui/assets/{circle-plus-lmU1yNJd.js → circle-plus--zM3-3r3.js} +1 -1
- package/dist/gui/assets/{code-xml-jFAa3-TT.js → code-xml-DH-38Tt7.js} +1 -1
- package/dist/gui/assets/{git-branch-Cfj9D8yW.js → git-branch-Ce0I2663.js} +1 -1
- package/dist/gui/assets/index-BB4BuHBQ.css +2 -0
- package/dist/gui/assets/index-BTe-_1Mu.js +57 -0
- package/dist/gui/assets/isElementDisabled-BrwmCB1d.js +1 -0
- package/dist/gui/assets/label-DC730X8s.js +1 -0
- package/dist/gui/assets/layers-j_brOuoa.js +1 -0
- package/dist/gui/assets/menu-d_G9km1-.js +1 -0
- package/dist/gui/assets/shield-check-Dd1Yv3l2.js +1 -0
- package/dist/gui/assets/square-check-big-BSIiYXPI.js +1 -0
- package/dist/gui/assets/tabs-BHwTt4t1.js +1 -0
- package/dist/gui/assets/{trash-2-CtR35_eF.js → trash-2-CMdv3eD3.js} +1 -1
- package/dist/gui/assets/useAriaLabelledBy-BmJErI5a.js +1 -0
- package/dist/gui/assets/useValueChanged-WS99hTmm.js +1 -0
- package/dist/gui/assets/{vsc-DFh0-53h.js → vsc-eMRaEwN3.js} +1 -1
- package/dist/gui/favicon-aurora.svg +8 -0
- package/dist/gui/favicon.ico +0 -0
- package/dist/gui/favicon.svg +8 -1
- package/dist/gui/index.html +21 -12
- package/dist/harness/claude.d.ts.map +1 -1
- package/dist/harness/claude.js +97 -7
- package/dist/harness/claude.js.map +1 -1
- package/dist/harness/codex.d.ts +1 -0
- package/dist/harness/codex.d.ts.map +1 -1
- package/dist/harness/codex.js +51 -34
- package/dist/harness/codex.js.map +1 -1
- package/dist/index.js +57 -27
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.d.ts +12 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +24 -6
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/tools.d.ts +4 -2
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +372 -17
- package/dist/mcp/tools.js.map +1 -1
- package/dist/orchestration/detect.d.ts.map +1 -1
- package/dist/orchestration/detect.js +12 -5
- package/dist/orchestration/detect.js.map +1 -1
- package/dist/orchestration/log-tail.js +17 -17
- package/dist/orchestration/log-tail.js.map +1 -1
- package/dist/orchestration/runner.d.ts +1 -1
- package/dist/orchestration/runner.d.ts.map +1 -1
- package/dist/orchestration/runner.js +13 -8
- package/dist/orchestration/runner.js.map +1 -1
- package/dist/resources/agents-catalog.d.ts +3 -1
- package/dist/resources/agents-catalog.d.ts.map +1 -1
- package/dist/resources/agents-catalog.js +12 -6
- package/dist/resources/agents-catalog.js.map +1 -1
- package/dist/resources/contracts.d.ts +7 -0
- package/dist/resources/contracts.d.ts.map +1 -1
- package/dist/resources/contracts.js +8 -1
- package/dist/resources/contracts.js.map +1 -1
- package/dist/resources/fs-safety.d.ts +7 -1
- package/dist/resources/fs-safety.d.ts.map +1 -1
- package/dist/resources/fs-safety.js +44 -1
- package/dist/resources/fs-safety.js.map +1 -1
- package/dist/resources/materializer.d.ts +1 -0
- package/dist/resources/materializer.d.ts.map +1 -1
- package/dist/resources/materializer.js +48 -17
- package/dist/resources/materializer.js.map +1 -1
- package/dist/resources/service.d.ts.map +1 -1
- package/dist/resources/service.js +3 -2
- package/dist/resources/service.js.map +1 -1
- package/dist/server.d.ts +2 -19
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +1158 -318
- package/dist/server.js.map +1 -1
- package/dist/static-resources/workflows/cost-aware-sol-luna.md +95 -0
- package/dist/storage/db.d.ts +94 -0
- package/dist/storage/db.d.ts.map +1 -0
- package/dist/storage/db.js +609 -0
- package/dist/storage/db.js.map +1 -0
- package/dist/types.d.ts +19 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/detect-ai.d.ts.map +1 -1
- package/dist/utils/detect-ai.js +1 -3
- package/dist/utils/detect-ai.js.map +1 -1
- package/dist/utils/multi-git.d.ts +0 -8
- package/dist/utils/multi-git.d.ts.map +1 -1
- package/dist/utils/multi-git.js +12 -4
- package/dist/utils/multi-git.js.map +1 -1
- package/dist/utils/pr.d.ts.map +1 -1
- package/dist/utils/pr.js +10 -5
- package/dist/utils/pr.js.map +1 -1
- package/dist/utils/resolve-workspace.d.ts +1 -1
- package/dist/utils/resolve-workspace.d.ts.map +1 -1
- package/dist/utils/resolve-workspace.js +10 -3
- package/dist/utils/resolve-workspace.js.map +1 -1
- package/dist/utils/session-finder.d.ts +12 -1
- package/dist/utils/session-finder.d.ts.map +1 -1
- package/dist/utils/session-finder.js +183 -0
- package/dist/utils/session-finder.js.map +1 -1
- package/dist/utils/skills-catalog.d.ts +5 -1
- package/dist/utils/skills-catalog.d.ts.map +1 -1
- package/dist/utils/skills-catalog.js +131 -75
- package/dist/utils/skills-catalog.js.map +1 -1
- package/dist/utils/terminal-launch.d.ts +4 -0
- package/dist/utils/terminal-launch.d.ts.map +1 -1
- package/dist/utils/terminal-launch.js +71 -12
- package/dist/utils/terminal-launch.js.map +1 -1
- package/dist/utils/update-check.d.ts +4 -1
- package/dist/utils/update-check.d.ts.map +1 -1
- package/dist/utils/update-check.js +66 -19
- package/dist/utils/update-check.js.map +1 -1
- package/dist/utils/workflows.d.ts +4 -1
- package/dist/utils/workflows.d.ts.map +1 -1
- package/dist/utils/workflows.js +51 -21
- package/dist/utils/workflows.js.map +1 -1
- package/dist/workrooms/client.d.ts +54 -0
- package/dist/workrooms/client.d.ts.map +1 -0
- package/dist/workrooms/client.js +191 -0
- package/dist/workrooms/client.js.map +1 -0
- package/dist/workrooms/contracts.d.ts +579 -0
- package/dist/workrooms/contracts.d.ts.map +1 -0
- package/dist/workrooms/contracts.js +231 -0
- package/dist/workrooms/contracts.js.map +1 -0
- package/dist/workrooms/crypto.d.ts +30 -0
- package/dist/workrooms/crypto.d.ts.map +1 -0
- package/dist/workrooms/crypto.js +72 -0
- package/dist/workrooms/crypto.js.map +1 -0
- package/dist/workrooms/host.d.ts +61 -0
- package/dist/workrooms/host.d.ts.map +1 -0
- package/dist/workrooms/host.js +747 -0
- package/dist/workrooms/host.js.map +1 -0
- package/dist/workrooms/local-resources.d.ts +26 -0
- package/dist/workrooms/local-resources.d.ts.map +1 -0
- package/dist/workrooms/local-resources.js +367 -0
- package/dist/workrooms/local-resources.js.map +1 -0
- package/dist/workrooms/manager.d.ts +228 -0
- package/dist/workrooms/manager.d.ts.map +1 -0
- package/dist/workrooms/manager.js +768 -0
- package/dist/workrooms/manager.js.map +1 -0
- package/dist/workrooms/milestones.d.ts +42 -0
- package/dist/workrooms/milestones.d.ts.map +1 -0
- package/dist/workrooms/milestones.js +94 -0
- package/dist/workrooms/milestones.js.map +1 -0
- package/dist/workrooms/portable.d.ts +21 -0
- package/dist/workrooms/portable.d.ts.map +1 -0
- package/dist/workrooms/portable.js +201 -0
- package/dist/workrooms/portable.js.map +1 -0
- package/dist/workrooms/resource-package.d.ts +7 -0
- package/dist/workrooms/resource-package.d.ts.map +1 -0
- package/dist/workrooms/resource-package.js +260 -0
- package/dist/workrooms/resource-package.js.map +1 -0
- package/dist/workrooms/service.d.ts +92 -0
- package/dist/workrooms/service.d.ts.map +1 -0
- package/dist/workrooms/service.js +788 -0
- package/dist/workrooms/service.js.map +1 -0
- package/dist/workrooms/sqlite-store.d.ts +31 -0
- package/dist/workrooms/sqlite-store.d.ts.map +1 -0
- package/dist/workrooms/sqlite-store.js +511 -0
- package/dist/workrooms/sqlite-store.js.map +1 -0
- package/dist/workrooms/store.d.ts +542 -0
- package/dist/workrooms/store.d.ts.map +1 -0
- package/dist/workrooms/store.js +71 -0
- package/dist/workrooms/store.js.map +1 -0
- package/package.json +16 -10
- package/resources/workflows/cost-aware-sol-luna.md +95 -0
- package/dist/agent/AcpCliAdapter.test.d.ts +0 -2
- package/dist/agent/AcpCliAdapter.test.d.ts.map +0 -1
- package/dist/agent/AcpCliAdapter.test.js +0 -235
- package/dist/agent/AcpCliAdapter.test.js.map +0 -1
- package/dist/agent/AntigravityCliAdapter.test.d.ts +0 -2
- package/dist/agent/AntigravityCliAdapter.test.d.ts.map +0 -1
- package/dist/agent/AntigravityCliAdapter.test.js +0 -222
- package/dist/agent/AntigravityCliAdapter.test.js.map +0 -1
- package/dist/agent/ClaudeCliAdapter.test.d.ts +0 -2
- package/dist/agent/ClaudeCliAdapter.test.d.ts.map +0 -1
- package/dist/agent/ClaudeCliAdapter.test.js +0 -320
- package/dist/agent/ClaudeCliAdapter.test.js.map +0 -1
- package/dist/agent/ClaudeSdkAdapter.test.d.ts +0 -2
- package/dist/agent/ClaudeSdkAdapter.test.d.ts.map +0 -1
- package/dist/agent/ClaudeSdkAdapter.test.js +0 -155
- package/dist/agent/ClaudeSdkAdapter.test.js.map +0 -1
- package/dist/agent/CliAdapterBase.test.d.ts +0 -2
- package/dist/agent/CliAdapterBase.test.d.ts.map +0 -1
- package/dist/agent/CliAdapterBase.test.js +0 -240
- package/dist/agent/CliAdapterBase.test.js.map +0 -1
- package/dist/agent/CodexCliAdapter.test.d.ts +0 -2
- package/dist/agent/CodexCliAdapter.test.d.ts.map +0 -1
- package/dist/agent/CodexCliAdapter.test.js +0 -331
- package/dist/agent/CodexCliAdapter.test.js.map +0 -1
- package/dist/agent/CodexSdkAdapter.test.d.ts +0 -2
- package/dist/agent/CodexSdkAdapter.test.d.ts.map +0 -1
- package/dist/agent/CodexSdkAdapter.test.js +0 -76
- package/dist/agent/CodexSdkAdapter.test.js.map +0 -1
- package/dist/agent/ProviderRegistry.test.d.ts +0 -2
- package/dist/agent/ProviderRegistry.test.d.ts.map +0 -1
- package/dist/agent/ProviderRegistry.test.js +0 -129
- package/dist/agent/ProviderRegistry.test.js.map +0 -1
- package/dist/agent/adapters.test.d.ts +0 -2
- package/dist/agent/adapters.test.d.ts.map +0 -1
- package/dist/agent/adapters.test.js +0 -71
- package/dist/agent/adapters.test.js.map +0 -1
- package/dist/agent/cliAvailability.test.d.ts +0 -2
- package/dist/agent/cliAvailability.test.d.ts.map +0 -1
- package/dist/agent/cliAvailability.test.js +0 -217
- package/dist/agent/cliAvailability.test.js.map +0 -1
- package/dist/agent/mcp-config.test.d.ts +0 -2
- package/dist/agent/mcp-config.test.d.ts.map +0 -1
- package/dist/agent/mcp-config.test.js +0 -22
- package/dist/agent/mcp-config.test.js.map +0 -1
- package/dist/agent/models.test.d.ts +0 -2
- package/dist/agent/models.test.d.ts.map +0 -1
- package/dist/agent/models.test.js +0 -40
- package/dist/agent/models.test.js.map +0 -1
- package/dist/agent/nativeAgent.test.d.ts +0 -2
- package/dist/agent/nativeAgent.test.d.ts.map +0 -1
- package/dist/agent/nativeAgent.test.js +0 -35
- package/dist/agent/nativeAgent.test.js.map +0 -1
- package/dist/agent/nativeTools.test.d.ts +0 -2
- package/dist/agent/nativeTools.test.d.ts.map +0 -1
- package/dist/agent/nativeTools.test.js +0 -67
- package/dist/agent/nativeTools.test.js.map +0 -1
- package/dist/agent/orchestrator.test.d.ts +0 -2
- package/dist/agent/orchestrator.test.d.ts.map +0 -1
- package/dist/agent/orchestrator.test.js +0 -284
- package/dist/agent/orchestrator.test.js.map +0 -1
- package/dist/agent/session.test.d.ts +0 -2
- package/dist/agent/session.test.d.ts.map +0 -1
- package/dist/agent/session.test.js +0 -74
- package/dist/agent/session.test.js.map +0 -1
- package/dist/agent/smoke-chat.test.d.ts +0 -2
- package/dist/agent/smoke-chat.test.d.ts.map +0 -1
- package/dist/agent/smoke-chat.test.js +0 -166
- package/dist/agent/smoke-chat.test.js.map +0 -1
- package/dist/commands/add-repo.test.d.ts +0 -2
- package/dist/commands/add-repo.test.d.ts.map +0 -1
- package/dist/commands/add-repo.test.js +0 -30
- package/dist/commands/add-repo.test.js.map +0 -1
- package/dist/commands/commands.test.d.ts +0 -2
- package/dist/commands/commands.test.d.ts.map +0 -1
- package/dist/commands/commands.test.js +0 -156
- package/dist/commands/commands.test.js.map +0 -1
- package/dist/commands/commit.test.d.ts +0 -2
- package/dist/commands/commit.test.d.ts.map +0 -1
- package/dist/commands/commit.test.js +0 -54
- package/dist/commands/commit.test.js.map +0 -1
- package/dist/commands/isolate.test.d.ts +0 -2
- package/dist/commands/isolate.test.d.ts.map +0 -1
- package/dist/commands/isolate.test.js +0 -59
- package/dist/commands/isolate.test.js.map +0 -1
- package/dist/commands/knowledge.test.d.ts +0 -2
- package/dist/commands/knowledge.test.d.ts.map +0 -1
- package/dist/commands/knowledge.test.js +0 -90
- package/dist/commands/knowledge.test.js.map +0 -1
- package/dist/commands/status.test.d.ts +0 -2
- package/dist/commands/status.test.d.ts.map +0 -1
- package/dist/commands/status.test.js +0 -31
- package/dist/commands/status.test.js.map +0 -1
- package/dist/commands/ui.test.d.ts +0 -2
- package/dist/commands/ui.test.d.ts.map +0 -1
- package/dist/commands/ui.test.js +0 -58
- package/dist/commands/ui.test.js.map +0 -1
- package/dist/core/adapters/storage.test.d.ts +0 -2
- package/dist/core/adapters/storage.test.d.ts.map +0 -1
- package/dist/core/adapters/storage.test.js +0 -65
- package/dist/core/adapters/storage.test.js.map +0 -1
- package/dist/core/analysis-cache.test.d.ts +0 -2
- package/dist/core/analysis-cache.test.d.ts.map +0 -1
- package/dist/core/analysis-cache.test.js +0 -163
- package/dist/core/analysis-cache.test.js.map +0 -1
- package/dist/core/config.test.d.ts +0 -2
- package/dist/core/config.test.d.ts.map +0 -1
- package/dist/core/config.test.js +0 -90
- package/dist/core/config.test.js.map +0 -1
- package/dist/core/finish.test.d.ts +0 -2
- package/dist/core/finish.test.d.ts.map +0 -1
- package/dist/core/finish.test.js +0 -110
- package/dist/core/finish.test.js.map +0 -1
- package/dist/core/generation-lock.test.d.ts +0 -2
- package/dist/core/generation-lock.test.d.ts.map +0 -1
- package/dist/core/generation-lock.test.js +0 -134
- package/dist/core/generation-lock.test.js.map +0 -1
- package/dist/core/isolate.test.d.ts +0 -2
- package/dist/core/isolate.test.d.ts.map +0 -1
- package/dist/core/isolate.test.js +0 -320
- package/dist/core/isolate.test.js.map +0 -1
- package/dist/core/knowledge.test.d.ts +0 -2
- package/dist/core/knowledge.test.d.ts.map +0 -1
- package/dist/core/knowledge.test.js +0 -287
- package/dist/core/knowledge.test.js.map +0 -1
- package/dist/core/locks.test.d.ts +0 -2
- package/dist/core/locks.test.d.ts.map +0 -1
- package/dist/core/locks.test.js +0 -143
- package/dist/core/locks.test.js.map +0 -1
- package/dist/core/new-repo.test.d.ts +0 -2
- package/dist/core/new-repo.test.d.ts.map +0 -1
- package/dist/core/new-repo.test.js +0 -78
- package/dist/core/new-repo.test.js.map +0 -1
- package/dist/core/progress.test.d.ts +0 -2
- package/dist/core/progress.test.d.ts.map +0 -1
- package/dist/core/progress.test.js +0 -52
- package/dist/core/progress.test.js.map +0 -1
- package/dist/core/projects.test.d.ts +0 -2
- package/dist/core/projects.test.d.ts.map +0 -1
- package/dist/core/projects.test.js +0 -165
- package/dist/core/projects.test.js.map +0 -1
- package/dist/core/refresh.test.d.ts +0 -2
- package/dist/core/refresh.test.d.ts.map +0 -1
- package/dist/core/refresh.test.js +0 -93
- package/dist/core/refresh.test.js.map +0 -1
- package/dist/core/scanner.test.d.ts +0 -2
- package/dist/core/scanner.test.d.ts.map +0 -1
- package/dist/core/scanner.test.js +0 -53
- package/dist/core/scanner.test.js.map +0 -1
- package/dist/core/scheduler.test.d.ts +0 -2
- package/dist/core/scheduler.test.d.ts.map +0 -1
- package/dist/core/scheduler.test.js +0 -169
- package/dist/core/scheduler.test.js.map +0 -1
- package/dist/core/sync.test.d.ts +0 -2
- package/dist/core/sync.test.d.ts.map +0 -1
- package/dist/core/sync.test.js +0 -128
- package/dist/core/sync.test.js.map +0 -1
- package/dist/core/workspace-git.test.d.ts +0 -2
- package/dist/core/workspace-git.test.d.ts.map +0 -1
- package/dist/core/workspace-git.test.js +0 -52
- package/dist/core/workspace-git.test.js.map +0 -1
- package/dist/core/workspace-skills-workflow.integration.test.d.ts +0 -2
- package/dist/core/workspace-skills-workflow.integration.test.d.ts.map +0 -1
- package/dist/core/workspace-skills-workflow.integration.test.js +0 -177
- package/dist/core/workspace-skills-workflow.integration.test.js.map +0 -1
- package/dist/core/workspace-state.test.d.ts +0 -2
- package/dist/core/workspace-state.test.d.ts.map +0 -1
- package/dist/core/workspace-state.test.js +0 -84
- package/dist/core/workspace-state.test.js.map +0 -1
- package/dist/core/workspace.create.test.d.ts +0 -2
- package/dist/core/workspace.create.test.d.ts.map +0 -1
- package/dist/core/workspace.create.test.js +0 -224
- package/dist/core/workspace.create.test.js.map +0 -1
- package/dist/core/workspace.test.d.ts +0 -2
- package/dist/core/workspace.test.d.ts.map +0 -1
- package/dist/core/workspace.test.js +0 -53
- package/dist/core/workspace.test.js.map +0 -1
- package/dist/core/worktree.test.d.ts +0 -2
- package/dist/core/worktree.test.d.ts.map +0 -1
- package/dist/core/worktree.test.js +0 -153
- package/dist/core/worktree.test.js.map +0 -1
- package/dist/generators/base.test.d.ts +0 -2
- package/dist/generators/base.test.d.ts.map +0 -1
- package/dist/generators/base.test.js +0 -287
- package/dist/generators/base.test.js.map +0 -1
- package/dist/generators/knowledge-migration.test.d.ts +0 -2
- package/dist/generators/knowledge-migration.test.d.ts.map +0 -1
- package/dist/generators/knowledge-migration.test.js +0 -22
- package/dist/generators/knowledge-migration.test.js.map +0 -1
- package/dist/generators/plan-generator.test.d.ts +0 -2
- package/dist/generators/plan-generator.test.d.ts.map +0 -1
- package/dist/generators/plan-generator.test.js +0 -220
- package/dist/generators/plan-generator.test.js.map +0 -1
- package/dist/generators/skills-generator.test.d.ts +0 -2
- package/dist/generators/skills-generator.test.d.ts.map +0 -1
- package/dist/generators/skills-generator.test.js +0 -79
- package/dist/generators/skills-generator.test.js.map +0 -1
- package/dist/gui/assets/AgentsPage-B4fx43rd.js +0 -3
- package/dist/gui/assets/CSPContext-DBUu9tE0.js +0 -1
- package/dist/gui/assets/DashboardPage-DH52G8IG.js +0 -1
- package/dist/gui/assets/GettingStartedPage-BlTrQhvi.js +0 -1
- package/dist/gui/assets/ProjectsPage-BPVSLPXv.js +0 -1
- package/dist/gui/assets/ScaffoldRepoInline-ywcpq9PX.js +0 -1
- package/dist/gui/assets/SettingsPage-RQr8SalV.js +0 -1
- package/dist/gui/assets/SkillsPage-CYoSRrwx.js +0 -6
- package/dist/gui/assets/StartWorkPage-DBKK4lFi.js +0 -1
- package/dist/gui/assets/WorkspacesPage-DxEONvSq.js +0 -6
- package/dist/gui/assets/alert-dialog-CQAX2yRx.js +0 -1
- package/dist/gui/assets/badge-CAohJGj6.js +0 -1
- package/dist/gui/assets/bot-C2HWrAft.js +0 -1
- package/dist/gui/assets/button-BDDZnIHx.js +0 -1
- package/dist/gui/assets/card-BKXzd_5L.js +0 -1
- package/dist/gui/assets/chevron-down-qTL8HPug.js +0 -1
- package/dist/gui/assets/cpu-Cdwd84Jz.js +0 -1
- package/dist/gui/assets/empty-BdlgbifD.js +0 -1
- package/dist/gui/assets/index-BF6pre6G.css +0 -2
- package/dist/gui/assets/index-C_rwnBzF.js +0 -52
- package/dist/gui/assets/isElementDisabled-uy_EOitH.js +0 -1
- package/dist/gui/assets/label-CG5exjag.js +0 -1
- package/dist/gui/assets/square-DlIYnlSq.js +0 -1
- package/dist/gui/assets/textarea-ByfoCb54.js +0 -1
- package/dist/gui/assets/useAriaLabelledBy-ClesgjMe.js +0 -1
- package/dist/gui/assets/useValueChanged-DeYvGnKs.js +0 -1
- package/dist/gui/assets/zap-CWG_Lvki.js +0 -1
- package/dist/harness/contract.test.d.ts +0 -2
- package/dist/harness/contract.test.d.ts.map +0 -1
- package/dist/harness/contract.test.js +0 -426
- package/dist/harness/contract.test.js.map +0 -1
- package/dist/harness/harness.test.d.ts +0 -2
- package/dist/harness/harness.test.d.ts.map +0 -1
- package/dist/harness/harness.test.js +0 -215
- package/dist/harness/harness.test.js.map +0 -1
- package/dist/mcp/tools.test.d.ts +0 -2
- package/dist/mcp/tools.test.d.ts.map +0 -1
- package/dist/mcp/tools.test.js +0 -194
- package/dist/mcp/tools.test.js.map +0 -1
- package/dist/orchestration/log-tail.test.d.ts +0 -2
- package/dist/orchestration/log-tail.test.d.ts.map +0 -1
- package/dist/orchestration/log-tail.test.js +0 -72
- package/dist/orchestration/log-tail.test.js.map +0 -1
- package/dist/orchestration/orchestrator.test.d.ts +0 -2
- package/dist/orchestration/orchestrator.test.d.ts.map +0 -1
- package/dist/orchestration/orchestrator.test.js +0 -85
- package/dist/orchestration/orchestrator.test.js.map +0 -1
- package/dist/orchestration/runner.test.d.ts +0 -2
- package/dist/orchestration/runner.test.d.ts.map +0 -1
- package/dist/orchestration/runner.test.js +0 -233
- package/dist/orchestration/runner.test.js.map +0 -1
- package/dist/resources/agents-catalog.test.d.ts +0 -2
- package/dist/resources/agents-catalog.test.d.ts.map +0 -1
- package/dist/resources/agents-catalog.test.js +0 -50
- package/dist/resources/agents-catalog.test.js.map +0 -1
- package/dist/resources/codex-agent.test.d.ts +0 -2
- package/dist/resources/codex-agent.test.d.ts.map +0 -1
- package/dist/resources/codex-agent.test.js +0 -27
- package/dist/resources/codex-agent.test.js.map +0 -1
- package/dist/resources/materializer.test.d.ts +0 -2
- package/dist/resources/materializer.test.d.ts.map +0 -1
- package/dist/resources/materializer.test.js +0 -149
- package/dist/resources/materializer.test.js.map +0 -1
- package/dist/resources/service.test.d.ts +0 -2
- package/dist/resources/service.test.d.ts.map +0 -1
- package/dist/resources/service.test.js +0 -58
- package/dist/resources/service.test.js.map +0 -1
- package/dist/server.test.d.ts +0 -2
- package/dist/server.test.d.ts.map +0 -1
- package/dist/server.test.js +0 -1697
- package/dist/server.test.js.map +0 -1
- package/dist/utils/detect-ai.test.d.ts +0 -2
- package/dist/utils/detect-ai.test.d.ts.map +0 -1
- package/dist/utils/detect-ai.test.js +0 -70
- package/dist/utils/detect-ai.test.js.map +0 -1
- package/dist/utils/detect-editors.test.d.ts +0 -2
- package/dist/utils/detect-editors.test.d.ts.map +0 -1
- package/dist/utils/detect-editors.test.js +0 -85
- package/dist/utils/detect-editors.test.js.map +0 -1
- package/dist/utils/git.test.d.ts +0 -2
- package/dist/utils/git.test.d.ts.map +0 -1
- package/dist/utils/git.test.js +0 -125
- package/dist/utils/git.test.js.map +0 -1
- package/dist/utils/multi-git.test.d.ts +0 -2
- package/dist/utils/multi-git.test.d.ts.map +0 -1
- package/dist/utils/multi-git.test.js +0 -330
- package/dist/utils/multi-git.test.js.map +0 -1
- package/dist/utils/pr.test.d.ts +0 -2
- package/dist/utils/pr.test.d.ts.map +0 -1
- package/dist/utils/pr.test.js +0 -47
- package/dist/utils/pr.test.js.map +0 -1
- package/dist/utils/session-finder.test.d.ts +0 -2
- package/dist/utils/session-finder.test.d.ts.map +0 -1
- package/dist/utils/session-finder.test.js +0 -304
- package/dist/utils/session-finder.test.js.map +0 -1
- package/dist/utils/skills-catalog.test.d.ts +0 -2
- package/dist/utils/skills-catalog.test.d.ts.map +0 -1
- package/dist/utils/skills-catalog.test.js +0 -281
- package/dist/utils/skills-catalog.test.js.map +0 -1
- package/dist/utils/system-scanner.test.d.ts +0 -2
- package/dist/utils/system-scanner.test.d.ts.map +0 -1
- package/dist/utils/system-scanner.test.js +0 -73
- package/dist/utils/system-scanner.test.js.map +0 -1
- package/dist/utils/terminal-launch.test.d.ts +0 -2
- package/dist/utils/terminal-launch.test.d.ts.map +0 -1
- package/dist/utils/terminal-launch.test.js +0 -117
- package/dist/utils/terminal-launch.test.js.map +0 -1
- package/dist/utils/update-check.test.d.ts +0 -2
- package/dist/utils/update-check.test.d.ts.map +0 -1
- package/dist/utils/update-check.test.js +0 -30
- package/dist/utils/update-check.test.js.map +0 -1
- package/dist/utils/workflows.test.d.ts +0 -2
- package/dist/utils/workflows.test.d.ts.map +0 -1
- package/dist/utils/workflows.test.js +0 -86
- package/dist/utils/workflows.test.js.map +0 -1
- package/dist/utils/workspace-launch.test.d.ts +0 -2
- package/dist/utils/workspace-launch.test.d.ts.map +0 -1
- package/dist/utils/workspace-launch.test.js +0 -128
- package/dist/utils/workspace-launch.test.js.map +0 -1
package/dist/server.js
CHANGED
|
@@ -6,19 +6,19 @@ import { Hono } from 'hono';
|
|
|
6
6
|
import { serve } from '@hono/node-server';
|
|
7
7
|
import { serveStatic } from '@hono/node-server/serve-static';
|
|
8
8
|
import { cors } from 'hono/cors';
|
|
9
|
+
import { deleteCookie, getCookie, setCookie } from 'hono/cookie';
|
|
9
10
|
import { streamSSE } from 'hono/streaming';
|
|
10
11
|
import { createNodeWebSocket } from '@hono/node-ws';
|
|
11
12
|
import * as fs from 'node:fs/promises';
|
|
12
|
-
import {
|
|
13
|
+
import { existsSync } from 'node:fs';
|
|
13
14
|
import * as path from 'node:path';
|
|
14
15
|
import { fileURLToPath } from 'node:url';
|
|
15
16
|
import { execa } from 'execa';
|
|
16
17
|
import * as os from 'node:os';
|
|
17
18
|
import { createHash } from 'node:crypto';
|
|
18
|
-
import { Readable } from 'node:stream';
|
|
19
|
-
import { pipeline } from 'node:stream/promises';
|
|
20
|
-
import { spawn } from 'node:child_process';
|
|
21
19
|
import { loadConfig, saveConfig, getConfigDir } from './core/config.js';
|
|
20
|
+
import { saveChatThread, loadChatThread, clearChatThread } from './storage/db.js';
|
|
21
|
+
import { PRIMARY_LOGS_DIR, LEGACY_LOGS_DIR, WORKROOM_BOOTSTRAP_HEADERS, WORKROOM_BOOTSTRAP_COOKIES, ENGINE_ID, LEGACY_ENGINE_ID, ENGINE_NPM_PACKAGE, LEGACY_ENGINE_NPM_PACKAGE, resolveWorkspaceChatLedger, readWorkspaceChatMessages, } from './core/constants.js';
|
|
22
22
|
import { configPatchSchema } from './core/config-schema.js';
|
|
23
23
|
import { listStorageProviders } from './core/adapters/registry.js';
|
|
24
24
|
import { scanForRepos } from './core/scanner.js';
|
|
@@ -35,9 +35,10 @@ import { detectEditors } from './utils/detect-editors.js';
|
|
|
35
35
|
import { buildWorkspaceLaunchPrompt, detectWorkspaceLaunchTargets, launchTargetIdForEditorCommand, launchWorkspaceTarget, } from './utils/workspace-launch.js';
|
|
36
36
|
import { isBinaryOnPath, launchWorkspaceTerminal, SUPPORTED_ASSISTANTS } from './utils/terminal-launch.js';
|
|
37
37
|
import { openInEditor } from './utils/open-editor.js';
|
|
38
|
-
import { canOpenCodexSessionInWorkspace, canTransferClaudeSessionInWorkspace, findSessions, getSessionTranscript, } from './utils/session-finder.js';
|
|
38
|
+
import { canOpenCodexSessionInWorkspace, canTransferClaudeSessionInWorkspace, findActiveAssistants, findSessions, getSessionTranscript, } from './utils/session-finder.js';
|
|
39
39
|
import { ProviderRegistry } from './agent/adapters.js';
|
|
40
40
|
import { isValidSessionUuid } from './agent/session.js';
|
|
41
|
+
import { defaultTurnSessionManager, AgentTurnGate, dispatchAgentInput } from './agent/TurnSessionManager.js';
|
|
41
42
|
import { getRepoStatus } from './utils/multi-git.js';
|
|
42
43
|
import { syncWorkspace } from './core/sync.js';
|
|
43
44
|
import { commitWorkspace } from './core/commit.js';
|
|
@@ -54,6 +55,12 @@ import { deleteAgent, getAllAgents, importAgentToml, saveAgent, } from './resour
|
|
|
54
55
|
import { ResourceConflictError } from './resources/materializer.js';
|
|
55
56
|
import { ResourceSelectionError, validateResourceSelections, withResourceAdministrationLock, } from './resources/service.js';
|
|
56
57
|
import { suggestWorkflow } from './utils/workflow-advisor.js';
|
|
58
|
+
import { WorkroomAuthorizationError, WorkroomRevisionError, WorkroomValidationError, documentNameSchema, workflowStepSchema, } from './workrooms/contracts.js';
|
|
59
|
+
import { listWorkroomNetworkInterfaces } from './workrooms/host.js';
|
|
60
|
+
import * as workroomManagerModule from './workrooms/manager.js';
|
|
61
|
+
const { workroomManager } = workroomManagerModule;
|
|
62
|
+
import { buildPortableWorkroomPreview, digestPortableWorkroomContext } from './workrooms/portable.js';
|
|
63
|
+
import { randomToken, tokenDigest } from './workrooms/crypto.js';
|
|
57
64
|
// Resolve static files directory
|
|
58
65
|
const __filename = fileURLToPath(import.meta.url);
|
|
59
66
|
const __dirname = path.dirname(__filename);
|
|
@@ -73,34 +80,7 @@ function hasTrustedLocalOrigin(origin) {
|
|
|
73
80
|
return false;
|
|
74
81
|
}
|
|
75
82
|
}
|
|
76
|
-
|
|
77
|
-
export function dispatchAgentInput(agent, provider, payload) {
|
|
78
|
-
if (typeof payload.input !== 'string' || !payload.input.trim()) {
|
|
79
|
-
return 'A non-empty input message is required.';
|
|
80
|
-
}
|
|
81
|
-
const executionProfile = ProviderRegistry.resolveExecutionProfile(provider, payload.executionProfile);
|
|
82
|
-
if (executionProfile === null) {
|
|
83
|
-
return 'Select a supported execution profile before sending this turn.';
|
|
84
|
-
}
|
|
85
|
-
void agent.send(payload.input, executionProfile);
|
|
86
|
-
return null;
|
|
87
|
-
}
|
|
88
|
-
/** Per-socket admission guard so a second prompt is rejected, never dropped. */
|
|
89
|
-
export class AgentTurnGate {
|
|
90
|
-
active = false;
|
|
91
|
-
tryBegin() {
|
|
92
|
-
if (this.active)
|
|
93
|
-
return false;
|
|
94
|
-
this.active = true;
|
|
95
|
-
return true;
|
|
96
|
-
}
|
|
97
|
-
settle() {
|
|
98
|
-
this.active = false;
|
|
99
|
-
}
|
|
100
|
-
isActive() {
|
|
101
|
-
return this.active;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
83
|
+
export { dispatchAgentInput, AgentTurnGate };
|
|
104
84
|
app.get('/ws', async (c, next) => {
|
|
105
85
|
// Prevent Cross-Site WebSocket Hijacking (CSWSH)
|
|
106
86
|
const origin = c.req.header('origin');
|
|
@@ -123,11 +103,11 @@ app.get('/ws', async (c, next) => {
|
|
|
123
103
|
// | {type:'rejected', reason:'busy', message, turnId?}
|
|
124
104
|
// | {type:'close', code} | {type:'pong'}
|
|
125
105
|
return upgradeWebSocket((c) => {
|
|
126
|
-
let
|
|
127
|
-
let
|
|
128
|
-
const turnGate = new AgentTurnGate();
|
|
106
|
+
let currentCwd = null;
|
|
107
|
+
let currentWs = null;
|
|
129
108
|
return {
|
|
130
109
|
async onMessage(event, ws) {
|
|
110
|
+
currentWs = ws;
|
|
131
111
|
if (typeof event.data === 'string') {
|
|
132
112
|
if (event.data === 'ping') {
|
|
133
113
|
ws.send(JSON.stringify({ type: 'pong' }));
|
|
@@ -139,35 +119,24 @@ app.get('/ws', async (c, next) => {
|
|
|
139
119
|
ws.send(JSON.stringify({ type: 'pong' }));
|
|
140
120
|
}
|
|
141
121
|
else if (payload.type === 'start') {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
previousAgent.stop();
|
|
122
|
+
const command = payload.command;
|
|
123
|
+
const cwd = payload.cwd;
|
|
124
|
+
if (typeof cwd !== 'string' || !cwd.trim()) {
|
|
125
|
+
ws.send(JSON.stringify({ type: 'error', message: 'Workspace directory cwd is required.' }));
|
|
126
|
+
return;
|
|
148
127
|
}
|
|
149
|
-
const provider = ProviderRegistry.getProvider(
|
|
128
|
+
const provider = ProviderRegistry.getProvider(command);
|
|
150
129
|
if (!provider) {
|
|
151
|
-
ws.send(JSON.stringify({ type: 'error', message: `No provider found for ${
|
|
130
|
+
ws.send(JSON.stringify({ type: 'error', message: `No provider found for ${command}. Please create a dedicated adapter.` }));
|
|
152
131
|
return;
|
|
153
132
|
}
|
|
154
133
|
const config = await loadConfig();
|
|
155
|
-
|
|
156
|
-
ws.send(JSON.stringify({ type: 'error', message: 'Workspace directory cwd is required.' }));
|
|
157
|
-
return;
|
|
158
|
-
}
|
|
159
|
-
const safeCwd = await resolveExactLaunchWorkspace(config.workspacesDir, payload.cwd);
|
|
134
|
+
const safeCwd = await resolveExactLaunchWorkspace(config.workspacesDir, cwd);
|
|
160
135
|
if (!safeCwd) {
|
|
161
136
|
ws.send(JSON.stringify({ type: 'error', message: 'Invalid or uncontained workspace directory.' }));
|
|
162
137
|
return;
|
|
163
138
|
}
|
|
164
|
-
|
|
165
|
-
if (!freshness.fresh) {
|
|
166
|
-
ws.send(JSON.stringify({
|
|
167
|
-
type: 'system',
|
|
168
|
-
message: `⚠ NexusFlow generated context is stale or drifted. ${freshness.drift.map((item) => item.message).join(' ')}`,
|
|
169
|
-
}));
|
|
170
|
-
}
|
|
139
|
+
currentCwd = safeCwd;
|
|
171
140
|
let session;
|
|
172
141
|
const model = ProviderRegistry.resolveModel(provider, payload.model);
|
|
173
142
|
if (model === null) {
|
|
@@ -177,120 +146,95 @@ app.get('/ws', async (c, next) => {
|
|
|
177
146
|
}));
|
|
178
147
|
return;
|
|
179
148
|
}
|
|
149
|
+
const effort = typeof payload.effort === 'string' && payload.effort.trim() ? payload.effort.trim() : undefined;
|
|
180
150
|
if (payload.sessionId !== undefined && payload.sessionId !== null) {
|
|
181
151
|
if (!isValidSessionUuid(payload.sessionId)) {
|
|
182
152
|
ws.send(JSON.stringify({ type: 'error', message: 'Invalid session id.' }));
|
|
183
153
|
return;
|
|
184
154
|
}
|
|
185
|
-
session = { id: payload.sessionId, resume: Boolean(payload.resume), model };
|
|
155
|
+
session = { id: payload.sessionId, resume: Boolean(payload.resume), model, effort };
|
|
186
156
|
}
|
|
187
|
-
else if (model) {
|
|
188
|
-
session = { id: crypto.randomUUID(), resume: false, model };
|
|
157
|
+
else if (model || effort) {
|
|
158
|
+
session = { id: crypto.randomUUID(), resume: false, model, effort };
|
|
189
159
|
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
return;
|
|
197
|
-
ws.send(JSON.stringify({ type: 'stream', text }));
|
|
160
|
+
await defaultTurnSessionManager.startSession({
|
|
161
|
+
workspaceCwd: safeCwd,
|
|
162
|
+
command,
|
|
163
|
+
client: ws,
|
|
164
|
+
provider,
|
|
165
|
+
session,
|
|
198
166
|
});
|
|
199
|
-
startedAgent.on('system', (message) => {
|
|
200
|
-
if (!isCurrentAgent())
|
|
201
|
-
return;
|
|
202
|
-
ws.send(JSON.stringify({ type: 'system', message }));
|
|
203
|
-
});
|
|
204
|
-
startedAgent.on('session', (id) => {
|
|
205
|
-
if (!isCurrentAgent())
|
|
206
|
-
return;
|
|
207
|
-
// Provider output is still boundary input. Only inert UUIDs
|
|
208
|
-
// may be persisted by the renderer and sent back in argv.
|
|
209
|
-
if (isValidSessionUuid(id)) {
|
|
210
|
-
ws.send(JSON.stringify({ type: 'session', id }));
|
|
211
|
-
}
|
|
212
|
-
});
|
|
213
|
-
startedAgent.on('idle', () => {
|
|
214
|
-
if (!isCurrentAgent())
|
|
215
|
-
return;
|
|
216
|
-
turnGate.settle();
|
|
217
|
-
ws.send(JSON.stringify({ type: 'status', state: 'idle' }));
|
|
218
|
-
});
|
|
219
|
-
startedAgent.on('close', (code) => {
|
|
220
|
-
if (!isCurrentAgent())
|
|
221
|
-
return;
|
|
222
|
-
turnGate.settle();
|
|
223
|
-
ws.send(JSON.stringify({ type: 'close', code }));
|
|
224
|
-
});
|
|
225
|
-
startedAgent.on('usage', (usage) => {
|
|
226
|
-
if (!isCurrentAgent())
|
|
227
|
-
return;
|
|
228
|
-
ws.send(JSON.stringify({ type: 'usage', usage }));
|
|
229
|
-
});
|
|
230
|
-
startedAgent.on('error', (error) => {
|
|
231
|
-
if (!isCurrentAgent())
|
|
232
|
-
return;
|
|
233
|
-
ws.send(JSON.stringify({ type: 'error', message: error?.message ?? String(error) }));
|
|
234
|
-
});
|
|
235
|
-
startedAgent.start(safeCwd, session);
|
|
236
167
|
}
|
|
237
168
|
else if (payload.type === 'input') {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
169
|
+
const cwd = typeof payload.cwd === 'string' ? payload.cwd : currentCwd;
|
|
170
|
+
if (!cwd) {
|
|
171
|
+
ws.send(JSON.stringify({
|
|
172
|
+
type: 'error',
|
|
173
|
+
message: 'No active agent session. Please start or reconnect the agent.',
|
|
174
|
+
}));
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
const config = await loadConfig();
|
|
178
|
+
const safeCwd = await resolveExactLaunchWorkspace(config.workspacesDir, cwd);
|
|
179
|
+
if (!safeCwd) {
|
|
180
|
+
ws.send(JSON.stringify({ type: 'error', message: 'Invalid workspace directory.' }));
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
currentCwd = safeCwd;
|
|
184
|
+
const existingSession = defaultTurnSessionManager.getSession(safeCwd);
|
|
185
|
+
if (!existingSession) {
|
|
186
|
+
const command = typeof payload.command === 'string' ? payload.command : 'antigravity-cli';
|
|
187
|
+
const provider = ProviderRegistry.getProvider(command);
|
|
188
|
+
if (!provider) {
|
|
189
|
+
ws.send(JSON.stringify({ type: 'error', message: `No provider found for ${command}.` }));
|
|
253
190
|
return;
|
|
254
191
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
192
|
+
await defaultTurnSessionManager.startSession({
|
|
193
|
+
workspaceCwd: safeCwd,
|
|
194
|
+
command,
|
|
195
|
+
client: ws,
|
|
196
|
+
provider,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
const result = defaultTurnSessionManager.dispatchInput(safeCwd, payload);
|
|
200
|
+
if (result.error) {
|
|
201
|
+
ws.send(JSON.stringify({ type: 'error', message: result.error }));
|
|
202
|
+
}
|
|
203
|
+
else if (result.rejected) {
|
|
204
|
+
const turnId = isValidSessionUuid(payload.turnId) ? payload.turnId : undefined;
|
|
205
|
+
ws.send(JSON.stringify({
|
|
206
|
+
type: 'rejected',
|
|
207
|
+
reason: 'busy',
|
|
208
|
+
message: 'The agent is still processing the current turn.',
|
|
209
|
+
...(turnId ? { turnId } : {}),
|
|
210
|
+
}));
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
else if (payload.type === 'approval_response') {
|
|
214
|
+
if (currentCwd) {
|
|
215
|
+
const requestId = typeof payload.requestId === 'string' ? payload.requestId : '';
|
|
216
|
+
const decision = payload.decision === 'allow' ? 'allow' : 'deny';
|
|
217
|
+
const message = typeof payload.message === 'string' ? payload.message : undefined;
|
|
218
|
+
defaultTurnSessionManager.respondToApproval(currentCwd, requestId, decision, message);
|
|
266
219
|
}
|
|
267
220
|
}
|
|
268
221
|
else if (payload.type === 'stop') {
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
const stoppedAgent = agent;
|
|
272
|
-
agent = null;
|
|
273
|
-
activeProvider = null;
|
|
274
|
-
stoppedAgent.stop();
|
|
222
|
+
if (currentCwd) {
|
|
223
|
+
defaultTurnSessionManager.stopSession(currentCwd);
|
|
275
224
|
}
|
|
276
225
|
}
|
|
277
226
|
}
|
|
278
227
|
catch (err) {
|
|
279
|
-
console.error('
|
|
228
|
+
console.error('Error handling WebSocket message:', err);
|
|
280
229
|
}
|
|
281
230
|
}
|
|
282
231
|
},
|
|
283
|
-
onOpen(_event,
|
|
284
|
-
|
|
232
|
+
onOpen(_event, ws) {
|
|
233
|
+
currentWs = ws;
|
|
285
234
|
},
|
|
286
|
-
onClose(_event,
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
if (agent) {
|
|
290
|
-
const disconnectedAgent = agent;
|
|
291
|
-
agent = null;
|
|
292
|
-
activeProvider = null;
|
|
293
|
-
disconnectedAgent.stop();
|
|
235
|
+
onClose(_event, ws) {
|
|
236
|
+
if (currentCwd && ws) {
|
|
237
|
+
defaultTurnSessionManager.unregisterClient(currentCwd, ws);
|
|
294
238
|
}
|
|
295
239
|
},
|
|
296
240
|
};
|
|
@@ -401,6 +345,19 @@ function errorResponse(c, error) {
|
|
|
401
345
|
missingAgents: error.missingAgents,
|
|
402
346
|
}, 400);
|
|
403
347
|
}
|
|
348
|
+
if (error instanceof WorkroomAuthorizationError) {
|
|
349
|
+
return c.json({ error: error.message }, 401);
|
|
350
|
+
}
|
|
351
|
+
if (error instanceof WorkroomRevisionError) {
|
|
352
|
+
return c.json({
|
|
353
|
+
error: error.message,
|
|
354
|
+
expectedRevision: error.expected,
|
|
355
|
+
actualRevision: error.actual,
|
|
356
|
+
}, 409);
|
|
357
|
+
}
|
|
358
|
+
if (error instanceof WorkroomValidationError) {
|
|
359
|
+
return c.json({ error: error.message }, 400);
|
|
360
|
+
}
|
|
404
361
|
const msg = error instanceof Error ? error.message : String(error);
|
|
405
362
|
return c.json({ error: msg }, 500);
|
|
406
363
|
}
|
|
@@ -431,35 +388,18 @@ async function findResourceAssignments(workspacesDir, resourceId, kind) {
|
|
|
431
388
|
}
|
|
432
389
|
return assignments;
|
|
433
390
|
}
|
|
434
|
-
/**
|
|
435
|
-
* Restrict the self-update download to GitHub release hosts over HTTPS so a
|
|
436
|
-
* cross-origin caller cannot make the server fetch and later execute an
|
|
437
|
-
* arbitrary binary.
|
|
438
|
-
*/
|
|
439
|
-
export function isAllowedUpdateUrl(candidate) {
|
|
440
|
-
try {
|
|
441
|
-
const url = new URL(candidate);
|
|
442
|
-
if (url.protocol !== 'https:')
|
|
443
|
-
return false;
|
|
444
|
-
const host = url.hostname.toLowerCase();
|
|
445
|
-
if (host === 'github.com') {
|
|
446
|
-
return url.pathname.includes('/releases/download/');
|
|
447
|
-
}
|
|
448
|
-
return (host === 'objects.githubusercontent.com' ||
|
|
449
|
-
host === 'github-releases.githubusercontent.com');
|
|
450
|
-
}
|
|
451
|
-
catch {
|
|
452
|
-
return false;
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
391
|
// Enable CORS for the local GUI only. This server can spawn processes, run
|
|
456
392
|
// package installs and delete worktrees, so a wildcard origin would let any
|
|
457
393
|
// web page the developer has open drive it cross-origin.
|
|
458
394
|
app.use('/api/*', cors({
|
|
459
|
-
|
|
395
|
+
credentials: true,
|
|
396
|
+
origin: (origin, c) => {
|
|
460
397
|
// No Origin header → same-origin or a non-browser client (CLI/desktop).
|
|
461
398
|
if (!origin)
|
|
462
399
|
return origin;
|
|
400
|
+
if (c.req.path.startsWith('/api/workrooms/')) {
|
|
401
|
+
return hasExactDashboardOrigin(origin, c.req.url) ? origin : null;
|
|
402
|
+
}
|
|
463
403
|
try {
|
|
464
404
|
const { hostname } = new URL(origin);
|
|
465
405
|
const isLocal = hostname === 'localhost' ||
|
|
@@ -493,10 +433,18 @@ app.use('*', async (c, next) => {
|
|
|
493
433
|
// cross-site request forgery and browser form posts from untrusted web pages.
|
|
494
434
|
app.use('/api/*', async (c, next) => {
|
|
495
435
|
if (['POST', 'PUT', 'DELETE', 'PATCH'].includes(c.req.method)) {
|
|
436
|
+
const secFetchSite = c.req.header('sec-fetch-site');
|
|
437
|
+
if (secFetchSite === 'cross-site') {
|
|
438
|
+
return c.json({ error: 'Forbidden cross-site request.' }, 403);
|
|
439
|
+
}
|
|
496
440
|
const origin = c.req.header('origin');
|
|
497
441
|
if (origin && !hasTrustedLocalOrigin(origin)) {
|
|
498
442
|
return c.json({ error: 'Forbidden cross-origin request.' }, 403);
|
|
499
443
|
}
|
|
444
|
+
const referer = c.req.header('referer');
|
|
445
|
+
if (referer && !hasTrustedLocalOrigin(referer)) {
|
|
446
|
+
return c.json({ error: 'Forbidden cross-origin referer.' }, 403);
|
|
447
|
+
}
|
|
500
448
|
}
|
|
501
449
|
await next();
|
|
502
450
|
});
|
|
@@ -525,6 +473,116 @@ app.post('/api/adapters/status/refresh', async (c) => {
|
|
|
525
473
|
}
|
|
526
474
|
return c.json(ProviderRegistry.getAllStatus({ refreshProviderId: providerId }));
|
|
527
475
|
});
|
|
476
|
+
app.post('/api/chat/upload-attachment', async (c) => {
|
|
477
|
+
if (!hasTrustedLocalOrigin(c.req.header('origin'))) {
|
|
478
|
+
return c.json({ error: 'A local browser origin is required.' }, 403);
|
|
479
|
+
}
|
|
480
|
+
try {
|
|
481
|
+
const body = await c.req.json().catch(() => null);
|
|
482
|
+
if (!body || typeof body.dataUrl !== 'string' || !body.dataUrl.startsWith('data:image/')) {
|
|
483
|
+
return c.json({ error: 'A valid image dataUrl is required.' }, 400);
|
|
484
|
+
}
|
|
485
|
+
const matches = body.dataUrl.match(/^data:image\/([a-zA-Z0-9+.-]+);base64,(.+)$/);
|
|
486
|
+
if (!matches) {
|
|
487
|
+
return c.json({ error: 'Malformed image dataUrl format.' }, 400);
|
|
488
|
+
}
|
|
489
|
+
const MAX_ATTACHMENT_BASE64_CHARS = Math.ceil((20 * 1024 * 1024 * 4) / 3);
|
|
490
|
+
if (matches[2].length > MAX_ATTACHMENT_BASE64_CHARS) {
|
|
491
|
+
return c.json({ error: 'Image attachment exceeds maximum allowed size of 20MB.' }, 413);
|
|
492
|
+
}
|
|
493
|
+
const rawExt = matches[1].toLowerCase();
|
|
494
|
+
const ext = rawExt === 'jpeg' ? 'jpg' : rawExt.replace(/[^a-zA-Z0-9]/g, '') || 'png';
|
|
495
|
+
const buffer = Buffer.from(matches[2], 'base64');
|
|
496
|
+
const imageId = crypto.randomUUID();
|
|
497
|
+
const config = await loadConfig();
|
|
498
|
+
const workspaceDir = typeof body.workspacePath === 'string'
|
|
499
|
+
? await resolveExactLaunchWorkspace(config.workspacesDir, body.workspacePath)
|
|
500
|
+
: null;
|
|
501
|
+
const targetDir = workspaceDir
|
|
502
|
+
? path.join(workspaceDir, '.nexusflow', 'attachments')
|
|
503
|
+
: path.join(getConfigDir(), 'attachments');
|
|
504
|
+
await fs.mkdir(targetDir, { recursive: true });
|
|
505
|
+
const targetFile = path.join(targetDir, `${imageId}.${ext}`);
|
|
506
|
+
await fs.writeFile(targetFile, buffer);
|
|
507
|
+
return c.json({
|
|
508
|
+
id: imageId,
|
|
509
|
+
filename: typeof body.filename === 'string' ? body.filename : `image-${imageId}.${ext}`,
|
|
510
|
+
path: targetFile,
|
|
511
|
+
size: buffer.length,
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
catch (error) {
|
|
515
|
+
return errorResponse(c, error);
|
|
516
|
+
}
|
|
517
|
+
});
|
|
518
|
+
app.get('/api/chat/thread/:workspaceId', async (c) => {
|
|
519
|
+
const workspaceId = c.req.param('workspaceId');
|
|
520
|
+
if (!workspaceId) {
|
|
521
|
+
return c.json({ error: 'workspaceId is required' }, 400);
|
|
522
|
+
}
|
|
523
|
+
try {
|
|
524
|
+
const thread = loadChatThread(workspaceId);
|
|
525
|
+
const config = await loadConfig().catch(() => null);
|
|
526
|
+
let isBusy = false;
|
|
527
|
+
if (config) {
|
|
528
|
+
try {
|
|
529
|
+
const workspacePath = resolveWorkspacePath(config.workspacesDir, workspaceId);
|
|
530
|
+
isBusy = defaultTurnSessionManager.hasActiveTurn(workspacePath);
|
|
531
|
+
}
|
|
532
|
+
catch {
|
|
533
|
+
// Workspace not found on disk or invalid
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
return c.json({ thread, isBusy });
|
|
537
|
+
}
|
|
538
|
+
catch (error) {
|
|
539
|
+
return errorResponse(c, error);
|
|
540
|
+
}
|
|
541
|
+
});
|
|
542
|
+
app.post('/api/chat/thread/:workspaceId', async (c) => {
|
|
543
|
+
if (!hasTrustedLocalOrigin(c.req.header('origin'))) {
|
|
544
|
+
return c.json({ error: 'A local browser origin is required.' }, 403);
|
|
545
|
+
}
|
|
546
|
+
const workspaceId = c.req.param('workspaceId');
|
|
547
|
+
if (!workspaceId) {
|
|
548
|
+
return c.json({ error: 'workspaceId is required' }, 400);
|
|
549
|
+
}
|
|
550
|
+
try {
|
|
551
|
+
const body = await c.req.json().catch(() => null);
|
|
552
|
+
if (!body || typeof body !== 'object') {
|
|
553
|
+
return c.json({ error: 'Valid chat thread payload is required.' }, 400);
|
|
554
|
+
}
|
|
555
|
+
saveChatThread({
|
|
556
|
+
workspaceId,
|
|
557
|
+
providerId: typeof body.providerId === 'string' ? body.providerId : null,
|
|
558
|
+
sessions: typeof body.sessions === 'object' && body.sessions !== null ? body.sessions : {},
|
|
559
|
+
profilesByProvider: typeof body.profilesByProvider === 'object' && body.profilesByProvider !== null ? body.profilesByProvider : {},
|
|
560
|
+
modelsByProvider: typeof body.modelsByProvider === 'object' && body.modelsByProvider !== null ? body.modelsByProvider : {},
|
|
561
|
+
effortsByProvider: typeof body.effortsByProvider === 'object' && body.effortsByProvider !== null ? body.effortsByProvider : {},
|
|
562
|
+
messages: Array.isArray(body.messages) ? body.messages : [],
|
|
563
|
+
});
|
|
564
|
+
return c.json({ success: true });
|
|
565
|
+
}
|
|
566
|
+
catch (error) {
|
|
567
|
+
return errorResponse(c, error);
|
|
568
|
+
}
|
|
569
|
+
});
|
|
570
|
+
app.delete('/api/chat/thread/:workspaceId', async (c) => {
|
|
571
|
+
if (!hasTrustedLocalOrigin(c.req.header('origin'))) {
|
|
572
|
+
return c.json({ error: 'A local browser origin is required.' }, 403);
|
|
573
|
+
}
|
|
574
|
+
const workspaceId = c.req.param('workspaceId');
|
|
575
|
+
if (!workspaceId) {
|
|
576
|
+
return c.json({ error: 'workspaceId is required' }, 400);
|
|
577
|
+
}
|
|
578
|
+
try {
|
|
579
|
+
clearChatThread(workspaceId);
|
|
580
|
+
return c.json({ success: true });
|
|
581
|
+
}
|
|
582
|
+
catch (error) {
|
|
583
|
+
return errorResponse(c, error);
|
|
584
|
+
}
|
|
585
|
+
});
|
|
528
586
|
// 1. Get current configuration
|
|
529
587
|
app.get('/api/config', async (c) => {
|
|
530
588
|
try {
|
|
@@ -736,6 +794,7 @@ app.get('/api/workspaces/status', async (c) => {
|
|
|
736
794
|
runningServices: 0,
|
|
737
795
|
syncStatus: 'unknown',
|
|
738
796
|
pendingValidation: false,
|
|
797
|
+
activeAssistants: [],
|
|
739
798
|
};
|
|
740
799
|
try {
|
|
741
800
|
// Uncommitted changes across the workspace's repos: worktrees inside
|
|
@@ -758,6 +817,8 @@ app.get('/api/workspaces/status', async (c) => {
|
|
|
758
817
|
const repoStates = Object.values(wsState.repos);
|
|
759
818
|
status.syncStatus = worstSyncStatus(repoStates);
|
|
760
819
|
status.pendingValidation = repoStates.some((r) => r.pendingValidation);
|
|
820
|
+
// Active AI assistant sessions
|
|
821
|
+
status.activeAssistants = await findActiveAssistants(workspacePath, ws.repos);
|
|
761
822
|
}
|
|
762
823
|
catch {
|
|
763
824
|
// Leave defaults on any per-workspace failure so one bad repo doesn't
|
|
@@ -1126,7 +1187,6 @@ app.post('/api/workspace/:id/isolate', async (c) => {
|
|
|
1126
1187
|
app.post('/api/workspace/suggest-workflow', async (c) => {
|
|
1127
1188
|
try {
|
|
1128
1189
|
const { description, repos } = await c.req.json();
|
|
1129
|
-
const config = await loadConfig();
|
|
1130
1190
|
const suggestion = await suggestWorkflow(description, repos);
|
|
1131
1191
|
return c.json({
|
|
1132
1192
|
success: true,
|
|
@@ -1332,6 +1392,15 @@ app.get('/api/workspace/:id/services', async (c) => {
|
|
|
1332
1392
|
return errorResponse(c, error);
|
|
1333
1393
|
}
|
|
1334
1394
|
});
|
|
1395
|
+
function getWorkspaceLogDir(workspacePath) {
|
|
1396
|
+
const primary = path.join(workspacePath, PRIMARY_LOGS_DIR);
|
|
1397
|
+
const legacy = path.join(workspacePath, LEGACY_LOGS_DIR);
|
|
1398
|
+
if (existsSync(primary))
|
|
1399
|
+
return primary;
|
|
1400
|
+
if (existsSync(legacy))
|
|
1401
|
+
return legacy;
|
|
1402
|
+
return primary;
|
|
1403
|
+
}
|
|
1335
1404
|
// 10. Start services in workspace. Configs are re-detected server-side —
|
|
1336
1405
|
// the client only says "start", never what to execute.
|
|
1337
1406
|
app.post('/api/workspace/:id/services/start', async (c) => {
|
|
@@ -1339,7 +1408,7 @@ app.post('/api/workspace/:id/services/start', async (c) => {
|
|
|
1339
1408
|
const id = c.req.param('id');
|
|
1340
1409
|
const config = await loadConfig();
|
|
1341
1410
|
const workspacePath = resolveWorkspacePath(config.workspacesDir, id);
|
|
1342
|
-
const logDir =
|
|
1411
|
+
const logDir = getWorkspaceLogDir(workspacePath);
|
|
1343
1412
|
const services = await detectAllServices(workspacePath);
|
|
1344
1413
|
await startServices(services, workspacePath, logDir);
|
|
1345
1414
|
return c.json({ success: true });
|
|
@@ -1370,7 +1439,7 @@ app.post('/api/workspace/:id/services/:serviceName/:action{start|stop|restart}',
|
|
|
1370
1439
|
const action = c.req.param('action');
|
|
1371
1440
|
const config = await loadConfig();
|
|
1372
1441
|
const workspacePath = resolveWorkspacePath(config.workspacesDir, id);
|
|
1373
|
-
const logDir =
|
|
1442
|
+
const logDir = getWorkspaceLogDir(workspacePath);
|
|
1374
1443
|
if (action === 'stop') {
|
|
1375
1444
|
const stopped = await stopService(workspacePath, serviceName);
|
|
1376
1445
|
return c.json({ success: true, stopped });
|
|
@@ -1391,7 +1460,7 @@ app.post('/api/workspace/:id/services/:serviceName/:action{start|stop|restart}',
|
|
|
1391
1460
|
});
|
|
1392
1461
|
/** Resolve + contain a service log path; names may contain '/' (repo/sub). */
|
|
1393
1462
|
function resolveServiceLogFile(workspacePath, serviceName) {
|
|
1394
|
-
const logDir =
|
|
1463
|
+
const logDir = getWorkspaceLogDir(workspacePath);
|
|
1395
1464
|
return assertWithin(logDir, path.join(logDir, `${serviceName}.log`));
|
|
1396
1465
|
}
|
|
1397
1466
|
// 12. Get service logs (backfill). Returns the trailing 50KB and the byte
|
|
@@ -1406,14 +1475,18 @@ app.get('/api/workspace/:id/services/logs/:serviceName', async (c) => {
|
|
|
1406
1475
|
let content = '';
|
|
1407
1476
|
let size = 0;
|
|
1408
1477
|
try {
|
|
1409
|
-
const stats = await fs.stat(logFile);
|
|
1410
|
-
size = stats.size;
|
|
1411
|
-
const start = Math.max(0, size - 50000);
|
|
1412
1478
|
const fd = await fs.open(logFile, 'r');
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1479
|
+
try {
|
|
1480
|
+
const stats = await fd.stat();
|
|
1481
|
+
size = stats.size;
|
|
1482
|
+
const start = Math.max(0, size - 50000);
|
|
1483
|
+
const buffer = Buffer.alloc(size - start);
|
|
1484
|
+
await fd.read(buffer, 0, buffer.length, start);
|
|
1485
|
+
content = buffer.toString('utf-8');
|
|
1486
|
+
}
|
|
1487
|
+
finally {
|
|
1488
|
+
await fd.close();
|
|
1489
|
+
}
|
|
1417
1490
|
}
|
|
1418
1491
|
catch {
|
|
1419
1492
|
content = 'No logs available yet.';
|
|
@@ -1482,7 +1555,7 @@ app.post('/api/workspace/:id/orchestrators/:action{start|stop}', async (c) => {
|
|
|
1482
1555
|
}
|
|
1483
1556
|
const config = await loadConfig();
|
|
1484
1557
|
const workspacePath = resolveWorkspacePath(config.workspacesDir, id);
|
|
1485
|
-
const logDir =
|
|
1558
|
+
const logDir = getWorkspaceLogDir(workspacePath);
|
|
1486
1559
|
const tools = await detectOrchestrationTools(workspacePath);
|
|
1487
1560
|
const detection = tools.find((t) => t.id === body.id);
|
|
1488
1561
|
if (!detection) {
|
|
@@ -1641,6 +1714,214 @@ app.get('/api/workspace/:id/changes/diff', async (c) => {
|
|
|
1641
1714
|
return errorResponse(c, error);
|
|
1642
1715
|
}
|
|
1643
1716
|
});
|
|
1717
|
+
// 13b. Revert changes to specific files in workspace sub-repositories
|
|
1718
|
+
app.post('/api/workspace/:id/changes/revert', async (c) => {
|
|
1719
|
+
if (!hasTrustedLocalOrigin(c.req.header('origin'))) {
|
|
1720
|
+
return c.json({ error: 'A local browser origin is required.' }, 403);
|
|
1721
|
+
}
|
|
1722
|
+
try {
|
|
1723
|
+
const id = c.req.param('id');
|
|
1724
|
+
const body = await c.req.json().catch(() => null);
|
|
1725
|
+
const files = Array.isArray(body?.files) ? body.files.filter((f) => typeof f === 'string' && f.trim().length > 0) : [];
|
|
1726
|
+
if (files.length === 0) {
|
|
1727
|
+
return c.json({ error: 'Missing files array in request body.' }, 400);
|
|
1728
|
+
}
|
|
1729
|
+
const config = await loadConfig();
|
|
1730
|
+
const workspacePath = resolveWorkspacePath(config.workspacesDir, id);
|
|
1731
|
+
const feature = await loadFeatureConfig(workspacePath);
|
|
1732
|
+
const repoName = typeof body?.repo === 'string' && body.repo ? body.repo : (feature?.repos[0] ? path.basename(feature.repos[0]) : null);
|
|
1733
|
+
let worktreePath;
|
|
1734
|
+
if (feature && isInPlace(feature)) {
|
|
1735
|
+
const matches = repoName ? feature.repos.filter((r) => path.basename(r) === repoName) : feature.repos;
|
|
1736
|
+
if (matches.length === 0) {
|
|
1737
|
+
return c.json({ error: `Unknown repo in this workspace.` }, 404);
|
|
1738
|
+
}
|
|
1739
|
+
if (matches.length > 1) {
|
|
1740
|
+
return c.json({ error: `Ambiguous repository '${repoName}' in this workspace. Multiple repositories match this name.` }, 400);
|
|
1741
|
+
}
|
|
1742
|
+
worktreePath = matches[0];
|
|
1743
|
+
}
|
|
1744
|
+
else {
|
|
1745
|
+
worktreePath = repoName ? resolveRepoPath(workspacePath, repoName) : workspacePath;
|
|
1746
|
+
}
|
|
1747
|
+
// Snapshot git index so staged changes can be restored with 100% fidelity on rollback
|
|
1748
|
+
let indexPath = null;
|
|
1749
|
+
let originalIndexBuf = null;
|
|
1750
|
+
try {
|
|
1751
|
+
const { stdout: gitPathOut } = await execa('git', ['rev-parse', '--git-path', 'index'], { cwd: worktreePath });
|
|
1752
|
+
const relIndexPath = gitPathOut.trim();
|
|
1753
|
+
if (relIndexPath) {
|
|
1754
|
+
indexPath = path.resolve(worktreePath, relIndexPath);
|
|
1755
|
+
try {
|
|
1756
|
+
originalIndexBuf = await fs.readFile(indexPath);
|
|
1757
|
+
}
|
|
1758
|
+
catch (readErr) {
|
|
1759
|
+
if (readErr?.code !== 'ENOENT') {
|
|
1760
|
+
return c.json({
|
|
1761
|
+
error: `Failed to snapshot Git index before revert: ${readErr?.message ?? String(readErr)}`,
|
|
1762
|
+
}, 500);
|
|
1763
|
+
}
|
|
1764
|
+
originalIndexBuf = null;
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
catch (gitErr) {
|
|
1769
|
+
return c.json({
|
|
1770
|
+
error: `Failed to resolve Git index path before revert: ${gitErr?.message ?? String(gitErr)}`,
|
|
1771
|
+
}, 500);
|
|
1772
|
+
}
|
|
1773
|
+
const preflightItems = [];
|
|
1774
|
+
for (const file of files) {
|
|
1775
|
+
const resolvedFile = assertWithin(worktreePath, path.join(worktreePath, file));
|
|
1776
|
+
const relFile = path.relative(worktreePath, resolvedFile);
|
|
1777
|
+
const { stdout: statusOut } = await execa('git', ['status', '--porcelain', '--', relFile], { cwd: worktreePath });
|
|
1778
|
+
const statusLine = statusOut.trim();
|
|
1779
|
+
if (!statusLine) {
|
|
1780
|
+
continue;
|
|
1781
|
+
}
|
|
1782
|
+
// Preflight unsupported statuses: conflicts (U, AA, DD), submodules (S), typechanges (T)
|
|
1783
|
+
const code = statusLine.slice(0, 2);
|
|
1784
|
+
if (code.includes('U') || code === 'AA' || code === 'DD') {
|
|
1785
|
+
return c.json({
|
|
1786
|
+
error: `Cannot revert '${relFile}': file has unmerged git conflicts ('${statusLine}'). Resolve conflicts first.`,
|
|
1787
|
+
}, 400);
|
|
1788
|
+
}
|
|
1789
|
+
if (code.includes('S') || code.includes('T')) {
|
|
1790
|
+
return c.json({
|
|
1791
|
+
error: `Cannot revert '${relFile}': file has unsupported git status ('${statusLine}').`,
|
|
1792
|
+
}, 400);
|
|
1793
|
+
}
|
|
1794
|
+
const isUntracked = statusLine.startsWith('??');
|
|
1795
|
+
let exists = false;
|
|
1796
|
+
let originalContent;
|
|
1797
|
+
let originalMode;
|
|
1798
|
+
try {
|
|
1799
|
+
let handle = null;
|
|
1800
|
+
try {
|
|
1801
|
+
if (typeof fs.open === 'function') {
|
|
1802
|
+
handle = await fs.open(resolvedFile, 'r');
|
|
1803
|
+
}
|
|
1804
|
+
if (handle && typeof handle.readFile === 'function') {
|
|
1805
|
+
const [content, stat] = await Promise.all([
|
|
1806
|
+
handle.readFile(),
|
|
1807
|
+
typeof handle.stat === 'function' ? handle.stat() : Promise.resolve(undefined),
|
|
1808
|
+
]);
|
|
1809
|
+
originalContent = content;
|
|
1810
|
+
originalMode = stat?.mode;
|
|
1811
|
+
exists = true;
|
|
1812
|
+
}
|
|
1813
|
+
else {
|
|
1814
|
+
originalContent = await fs.readFile(resolvedFile);
|
|
1815
|
+
exists = true;
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
finally {
|
|
1819
|
+
if (handle && typeof handle.close === 'function') {
|
|
1820
|
+
await handle.close();
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
}
|
|
1824
|
+
catch (err) {
|
|
1825
|
+
if (err?.code !== 'ENOENT') {
|
|
1826
|
+
throw err;
|
|
1827
|
+
}
|
|
1828
|
+
exists = false;
|
|
1829
|
+
}
|
|
1830
|
+
preflightItems.push({
|
|
1831
|
+
resolvedFile,
|
|
1832
|
+
relFile,
|
|
1833
|
+
isUntracked,
|
|
1834
|
+
exists,
|
|
1835
|
+
originalContent,
|
|
1836
|
+
originalMode,
|
|
1837
|
+
});
|
|
1838
|
+
}
|
|
1839
|
+
// Execution with rollback protection: if any operation fails, restore previous state
|
|
1840
|
+
const executedReverts = [];
|
|
1841
|
+
try {
|
|
1842
|
+
for (const item of preflightItems) {
|
|
1843
|
+
if (item.isUntracked) {
|
|
1844
|
+
if (item.exists) {
|
|
1845
|
+
await fs.unlink(item.resolvedFile);
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
else {
|
|
1849
|
+
await execa('git', ['checkout', 'HEAD', '--', item.relFile], { cwd: worktreePath });
|
|
1850
|
+
}
|
|
1851
|
+
executedReverts.push(item);
|
|
1852
|
+
}
|
|
1853
|
+
}
|
|
1854
|
+
catch (revertErr) {
|
|
1855
|
+
const rollbackErrors = [];
|
|
1856
|
+
// 1. Restore the Git index first if it was snapshotted
|
|
1857
|
+
if (indexPath && originalIndexBuf) {
|
|
1858
|
+
try {
|
|
1859
|
+
await fs.writeFile(indexPath, originalIndexBuf);
|
|
1860
|
+
}
|
|
1861
|
+
catch (indexErr) {
|
|
1862
|
+
rollbackErrors.push(`Failed to restore git index: ${indexErr?.message ?? String(indexErr)}`);
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
// 2. Restore modified working tree files and metadata
|
|
1866
|
+
for (const item of executedReverts) {
|
|
1867
|
+
try {
|
|
1868
|
+
if (item.isUntracked) {
|
|
1869
|
+
if (item.exists && item.originalContent) {
|
|
1870
|
+
await fs.writeFile(item.resolvedFile, item.originalContent);
|
|
1871
|
+
if (item.originalMode !== undefined && typeof fs.chmod === 'function') {
|
|
1872
|
+
try {
|
|
1873
|
+
await fs.chmod(item.resolvedFile, item.originalMode);
|
|
1874
|
+
}
|
|
1875
|
+
catch (chmodErr) {
|
|
1876
|
+
rollbackErrors.push(`Failed to restore mode on ${item.relFile}: ${chmodErr?.message ?? String(chmodErr)}`);
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
else {
|
|
1882
|
+
if (item.exists && item.originalContent) {
|
|
1883
|
+
await fs.writeFile(item.resolvedFile, item.originalContent);
|
|
1884
|
+
if (item.originalMode !== undefined && typeof fs.chmod === 'function') {
|
|
1885
|
+
try {
|
|
1886
|
+
await fs.chmod(item.resolvedFile, item.originalMode);
|
|
1887
|
+
}
|
|
1888
|
+
catch (chmodErr) {
|
|
1889
|
+
rollbackErrors.push(`Failed to restore mode on ${item.relFile}: ${chmodErr?.message ?? String(chmodErr)}`);
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
}
|
|
1893
|
+
else if (!item.exists) {
|
|
1894
|
+
try {
|
|
1895
|
+
await fs.unlink(item.resolvedFile);
|
|
1896
|
+
}
|
|
1897
|
+
catch (unlinkErr) {
|
|
1898
|
+
if (unlinkErr?.code !== 'ENOENT') {
|
|
1899
|
+
rollbackErrors.push(`Failed to remove checked-out file ${item.relFile}: ${unlinkErr?.message ?? String(unlinkErr)}`);
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
catch (restoreErr) {
|
|
1906
|
+
rollbackErrors.push(`Failed to restore ${item.relFile}: ${restoreErr?.message ?? String(restoreErr)}`);
|
|
1907
|
+
}
|
|
1908
|
+
}
|
|
1909
|
+
if (rollbackErrors.length > 0) {
|
|
1910
|
+
return c.json({
|
|
1911
|
+
error: `Failed to revert files: ${revertErr?.message ?? String(revertErr)}. Warning: Rollback could not fully restore state: ${rollbackErrors.join('; ')}`,
|
|
1912
|
+
}, 500);
|
|
1913
|
+
}
|
|
1914
|
+
return c.json({
|
|
1915
|
+
error: `Failed to revert files: ${revertErr?.message ?? String(revertErr)}. Any modified files were rolled back.`,
|
|
1916
|
+
}, 500);
|
|
1917
|
+
}
|
|
1918
|
+
const reverted = preflightItems.map((item) => item.relFile);
|
|
1919
|
+
return c.json({ success: true, reverted });
|
|
1920
|
+
}
|
|
1921
|
+
catch (error) {
|
|
1922
|
+
return errorResponse(c, error);
|
|
1923
|
+
}
|
|
1924
|
+
});
|
|
1644
1925
|
// 13a. Get workspace knowledge (nexusflow-knowledge.md)
|
|
1645
1926
|
// Routed through the active storage adapter so the GUI edits the same file the
|
|
1646
1927
|
// generators write, whichever backend is active.
|
|
@@ -1665,7 +1946,14 @@ app.put('/api/workspace/:id/knowledge', async (c) => {
|
|
|
1665
1946
|
const workspacePath = resolveWorkspacePath(config.workspacesDir, id);
|
|
1666
1947
|
const feature = await loadFeatureConfig(workspacePath);
|
|
1667
1948
|
const featureId = feature?.id ?? path.basename(workspacePath);
|
|
1668
|
-
|
|
1949
|
+
const { PRIMARY_KNOWLEDGE_FILE, LEGACY_KNOWLEDGE_FILE } = await import('./core/constants.js');
|
|
1950
|
+
const { workspaceFileExists } = await import('./core/storage.js');
|
|
1951
|
+
const filename = (await workspaceFileExists(workspacePath, featureId, PRIMARY_KNOWLEDGE_FILE))
|
|
1952
|
+
? PRIMARY_KNOWLEDGE_FILE
|
|
1953
|
+
: (await workspaceFileExists(workspacePath, featureId, LEGACY_KNOWLEDGE_FILE))
|
|
1954
|
+
? LEGACY_KNOWLEDGE_FILE
|
|
1955
|
+
: PRIMARY_KNOWLEDGE_FILE;
|
|
1956
|
+
await writeWorkspaceFile(workspacePath, featureId, filename, content);
|
|
1669
1957
|
return c.json({ success: true });
|
|
1670
1958
|
}
|
|
1671
1959
|
catch (error) {
|
|
@@ -1706,16 +1994,17 @@ app.post('/api/workspace/:id/knowledge/entry', async (c) => {
|
|
|
1706
1994
|
return errorResponse(c, error);
|
|
1707
1995
|
}
|
|
1708
1996
|
});
|
|
1709
|
-
// 13c. Get workspace plan (nexusflow-plan.md)
|
|
1997
|
+
// 13c. Get workspace plan (contextspace-plan.md / nexusflow-plan.md)
|
|
1710
1998
|
app.get('/api/workspace/:id/plan', async (c) => {
|
|
1711
1999
|
try {
|
|
1712
2000
|
const id = c.req.param('id');
|
|
1713
2001
|
const config = await loadConfig();
|
|
1714
2002
|
const workspacePath = resolveWorkspacePath(config.workspacesDir, id);
|
|
1715
|
-
const
|
|
2003
|
+
const { resolveWorkspaceFilePath } = await import('./core/brand-config.js');
|
|
2004
|
+
const resolved = await resolveWorkspaceFilePath(workspacePath, 'plan');
|
|
1716
2005
|
let content = '';
|
|
1717
2006
|
try {
|
|
1718
|
-
content = await fs.readFile(
|
|
2007
|
+
content = await fs.readFile(resolved.path, 'utf-8');
|
|
1719
2008
|
}
|
|
1720
2009
|
catch {
|
|
1721
2010
|
content = '# Workspace Plan\n\nNo implementation plan file yet.';
|
|
@@ -1726,70 +2015,206 @@ app.get('/api/workspace/:id/plan', async (c) => {
|
|
|
1726
2015
|
return errorResponse(c, error);
|
|
1727
2016
|
}
|
|
1728
2017
|
});
|
|
1729
|
-
//
|
|
1730
|
-
app.
|
|
2018
|
+
// 13c-2. Check workspace migration status (legacy NexusFlow files detected)
|
|
2019
|
+
app.get('/api/workspace/:id/migration-status', async (c) => {
|
|
1731
2020
|
try {
|
|
1732
2021
|
const id = c.req.param('id');
|
|
1733
2022
|
const config = await loadConfig();
|
|
1734
2023
|
const workspacePath = resolveWorkspacePath(config.workspacesDir, id);
|
|
1735
|
-
const
|
|
1736
|
-
const
|
|
1737
|
-
|
|
1738
|
-
success: repo.status !== 'conflict' && repo.status !== 'error',
|
|
1739
|
-
status: repo.status,
|
|
1740
|
-
message: repo.message,
|
|
1741
|
-
conflict: repo.conflict,
|
|
1742
|
-
}));
|
|
1743
|
-
return c.json({
|
|
1744
|
-
results,
|
|
1745
|
-
syncedCount: report.syncedCount,
|
|
1746
|
-
conflictCount: report.conflictCount,
|
|
1747
|
-
errorCount: report.errorCount,
|
|
1748
|
-
});
|
|
2024
|
+
const { isLegacyWorkspace } = await import('./core/migrate.js');
|
|
2025
|
+
const isLegacy = await isLegacyWorkspace(workspacePath);
|
|
2026
|
+
return c.json({ isLegacy });
|
|
1749
2027
|
}
|
|
1750
2028
|
catch (error) {
|
|
1751
2029
|
return errorResponse(c, error);
|
|
1752
2030
|
}
|
|
1753
2031
|
});
|
|
1754
|
-
//
|
|
1755
|
-
app.post('/api/workspace/:id/
|
|
2032
|
+
// 13c-3. Migrate legacy workspace to native ContextSpace
|
|
2033
|
+
app.post('/api/workspace/:id/migrate', async (c) => {
|
|
1756
2034
|
try {
|
|
1757
2035
|
const id = c.req.param('id');
|
|
1758
|
-
const { message } = await c.req.json();
|
|
1759
2036
|
const config = await loadConfig();
|
|
1760
2037
|
const workspacePath = resolveWorkspacePath(config.workspacesDir, id);
|
|
1761
|
-
const
|
|
1762
|
-
const
|
|
1763
|
-
|
|
1764
|
-
success: repo.success,
|
|
1765
|
-
commitHash: repo.commitHash,
|
|
1766
|
-
filesChanged: repo.filesChanged,
|
|
1767
|
-
message: repo.message,
|
|
1768
|
-
}));
|
|
1769
|
-
return c.json({ results });
|
|
2038
|
+
const { migrateWorkspace } = await import('./core/migrate.js');
|
|
2039
|
+
const report = await migrateWorkspace(workspacePath, { dryRun: false, refresh: true });
|
|
2040
|
+
return c.json(report);
|
|
1770
2041
|
}
|
|
1771
2042
|
catch (error) {
|
|
1772
2043
|
return errorResponse(c, error);
|
|
1773
2044
|
}
|
|
1774
2045
|
});
|
|
1775
|
-
//
|
|
1776
|
-
app.
|
|
1777
|
-
const origin = c.req.header('origin');
|
|
1778
|
-
if (origin && !hasTrustedLocalOrigin(origin)) {
|
|
1779
|
-
return c.json({ error: 'Forbidden cross-origin request.' }, 403);
|
|
1780
|
-
}
|
|
2046
|
+
// 13c-4. Get workspace collaboration stream (.nexusflow/chat.jsonl or .contextspace/chat.jsonl)
|
|
2047
|
+
app.get('/api/workspace/:id/stream', async (c) => {
|
|
1781
2048
|
try {
|
|
1782
|
-
const id =
|
|
2049
|
+
const id = c.req.param('id');
|
|
1783
2050
|
const config = await loadConfig();
|
|
1784
2051
|
const workspacePath = resolveWorkspacePath(config.workspacesDir, id);
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
2052
|
+
const { messages, ledgerInfo } = await readWorkspaceChatMessages(workspacePath);
|
|
2053
|
+
let isRemoteActive = false;
|
|
2054
|
+
let remoteStatus = null;
|
|
2055
|
+
let workflowProgress = null;
|
|
2056
|
+
try {
|
|
2057
|
+
const client = await workroomManagerModule.loadPinnedWorkroomClientForWorkspace(id);
|
|
2058
|
+
const snapshot = await client.snapshot();
|
|
2059
|
+
isRemoteActive = true;
|
|
2060
|
+
workflowProgress = snapshot.workflowProgress ?? null;
|
|
2061
|
+
remoteStatus = {
|
|
2062
|
+
roomId: snapshot.roomId,
|
|
2063
|
+
name: snapshot.name,
|
|
2064
|
+
};
|
|
1789
2065
|
}
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
2066
|
+
catch {
|
|
2067
|
+
// Fallback to in-process workroomManager
|
|
2068
|
+
try {
|
|
2069
|
+
if (workroomManagerModule.workroomManager.hasActiveRoom()) {
|
|
2070
|
+
const status = await workroomManagerModule.workroomManager.status();
|
|
2071
|
+
if ((status.mode === 'host' || status.mode === 'guest') && status.localWorkspaceId === id) {
|
|
2072
|
+
isRemoteActive = true;
|
|
2073
|
+
workflowProgress = status.snapshot?.workflowProgress ?? null;
|
|
2074
|
+
remoteStatus = {
|
|
2075
|
+
roomId: status.roomId,
|
|
2076
|
+
url: status.url,
|
|
2077
|
+
name: status.name,
|
|
2078
|
+
};
|
|
2079
|
+
}
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
catch {
|
|
2083
|
+
// ignore
|
|
2084
|
+
}
|
|
2085
|
+
}
|
|
2086
|
+
// Reconcile messages with live authoritative workflow progress by stepId
|
|
2087
|
+
const stepMap = new Map();
|
|
2088
|
+
if (workflowProgress?.steps) {
|
|
2089
|
+
for (const step of workflowProgress.steps) {
|
|
2090
|
+
stepMap.set(step.stepId, step);
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2093
|
+
const reconciledMessages = messages.map((msg) => {
|
|
2094
|
+
if (!msg.stepId)
|
|
2095
|
+
return msg;
|
|
2096
|
+
const liveStep = stepMap.get(msg.stepId);
|
|
2097
|
+
if (!liveStep)
|
|
2098
|
+
return msg;
|
|
2099
|
+
return {
|
|
2100
|
+
...msg,
|
|
2101
|
+
stepProposal: liveStep,
|
|
2102
|
+
};
|
|
2103
|
+
});
|
|
2104
|
+
const relLedgerPath = ledgerInfo.isLegacy ? '.nexusflow/chat.jsonl' : '.contextspace/chat.jsonl';
|
|
2105
|
+
return c.json({
|
|
2106
|
+
workspaceId: id,
|
|
2107
|
+
messages: reconciledMessages,
|
|
2108
|
+
workflowProgress,
|
|
2109
|
+
isRemoteActive,
|
|
2110
|
+
remoteStatus,
|
|
2111
|
+
isLegacy: ledgerInfo.isLegacy,
|
|
2112
|
+
ledgerPath: relLedgerPath,
|
|
2113
|
+
});
|
|
2114
|
+
}
|
|
2115
|
+
catch (error) {
|
|
2116
|
+
return errorResponse(c, error);
|
|
2117
|
+
}
|
|
2118
|
+
});
|
|
2119
|
+
// 13c-5. Post a message to workspace collaboration stream
|
|
2120
|
+
app.post('/api/workspace/:id/stream', async (c) => {
|
|
2121
|
+
try {
|
|
2122
|
+
const id = c.req.param('id');
|
|
2123
|
+
const body = (await c.req.json());
|
|
2124
|
+
const message = String(body.message ?? '').trim();
|
|
2125
|
+
if (!message) {
|
|
2126
|
+
return c.json({ error: 'Message cannot be empty.' }, 400);
|
|
2127
|
+
}
|
|
2128
|
+
const config = await loadConfig();
|
|
2129
|
+
const workspacePath = resolveWorkspacePath(config.workspacesDir, id);
|
|
2130
|
+
const ledger = await resolveWorkspaceChatLedger(workspacePath);
|
|
2131
|
+
await fs.mkdir(ledger.chatDir, { recursive: true });
|
|
2132
|
+
const chatFile = ledger.chatPath;
|
|
2133
|
+
const isHuman = body.author === 'human' || body.harness === 'developer' || body.harness === 'human';
|
|
2134
|
+
const status = body.status ? String(body.status).trim() : (body.stepId ? 'proposed' : undefined);
|
|
2135
|
+
const effectiveStatus = (!isHuman && status === 'completed') ? 'proposed' : status;
|
|
2136
|
+
const entry = {
|
|
2137
|
+
id: createHash('sha256').update(Date.now() + message).digest('hex').slice(0, 12),
|
|
2138
|
+
timestamp: new Date().toISOString(),
|
|
2139
|
+
harness: body.harness || (isHuman ? 'developer' : 'agent'),
|
|
2140
|
+
author: isHuman ? 'human' : 'agent',
|
|
2141
|
+
status: effectiveStatus,
|
|
2142
|
+
message,
|
|
2143
|
+
...(body.stepId ? { stepId: body.stepId } : {}),
|
|
2144
|
+
...(body.evidence ? { evidence: body.evidence } : {}),
|
|
2145
|
+
...(isHuman && effectiveStatus === 'completed' ? { confirmedBy: 'human', confirmedAt: new Date().toISOString() } : {}),
|
|
2146
|
+
};
|
|
2147
|
+
await fs.appendFile(chatFile, JSON.stringify(entry) + '\n', 'utf-8');
|
|
2148
|
+
return c.json({ success: true, entry });
|
|
2149
|
+
}
|
|
2150
|
+
catch (error) {
|
|
2151
|
+
return errorResponse(c, error);
|
|
2152
|
+
}
|
|
2153
|
+
});
|
|
2154
|
+
// 13d. Sync all repositories in workspace
|
|
2155
|
+
app.post('/api/workspace/:id/sync', async (c) => {
|
|
2156
|
+
try {
|
|
2157
|
+
const id = c.req.param('id');
|
|
2158
|
+
const config = await loadConfig();
|
|
2159
|
+
const workspacePath = resolveWorkspacePath(config.workspacesDir, id);
|
|
2160
|
+
const report = await syncWorkspace(workspacePath);
|
|
2161
|
+
const results = report.repos.map((repo) => ({
|
|
2162
|
+
repoName: repo.name,
|
|
2163
|
+
success: repo.status !== 'conflict' && repo.status !== 'error',
|
|
2164
|
+
status: repo.status,
|
|
2165
|
+
message: repo.message,
|
|
2166
|
+
conflict: repo.conflict,
|
|
2167
|
+
}));
|
|
2168
|
+
return c.json({
|
|
2169
|
+
results,
|
|
2170
|
+
syncedCount: report.syncedCount,
|
|
2171
|
+
conflictCount: report.conflictCount,
|
|
2172
|
+
errorCount: report.errorCount,
|
|
2173
|
+
});
|
|
2174
|
+
}
|
|
2175
|
+
catch (error) {
|
|
2176
|
+
return errorResponse(c, error);
|
|
2177
|
+
}
|
|
2178
|
+
});
|
|
2179
|
+
// 13e. Commit changes in all repositories in workspace
|
|
2180
|
+
app.post('/api/workspace/:id/commit', async (c) => {
|
|
2181
|
+
try {
|
|
2182
|
+
const id = c.req.param('id');
|
|
2183
|
+
const { message } = await c.req.json();
|
|
2184
|
+
const config = await loadConfig();
|
|
2185
|
+
const workspacePath = resolveWorkspacePath(config.workspacesDir, id);
|
|
2186
|
+
const report = await commitWorkspace(workspacePath, message);
|
|
2187
|
+
const results = report.repos.map((repo) => ({
|
|
2188
|
+
repoName: repo.name,
|
|
2189
|
+
success: repo.success,
|
|
2190
|
+
commitHash: repo.commitHash,
|
|
2191
|
+
filesChanged: repo.filesChanged,
|
|
2192
|
+
message: repo.message,
|
|
2193
|
+
}));
|
|
2194
|
+
return c.json({ results });
|
|
2195
|
+
}
|
|
2196
|
+
catch (error) {
|
|
2197
|
+
return errorResponse(c, error);
|
|
2198
|
+
}
|
|
2199
|
+
});
|
|
2200
|
+
// 14. Resume session in workspace (copies CLI resume command and opens editor)
|
|
2201
|
+
app.post('/api/workspace/:id/resume', async (c) => {
|
|
2202
|
+
const origin = c.req.header('origin');
|
|
2203
|
+
if (origin && !hasTrustedLocalOrigin(origin)) {
|
|
2204
|
+
return c.json({ error: 'Forbidden cross-origin request.' }, 403);
|
|
2205
|
+
}
|
|
2206
|
+
try {
|
|
2207
|
+
const id = decodeURIComponent(c.req.param('id'));
|
|
2208
|
+
const config = await loadConfig();
|
|
2209
|
+
const workspacePath = resolveWorkspacePath(config.workspacesDir, id);
|
|
2210
|
+
// Load feature config to get repo paths
|
|
2211
|
+
const feature = await loadFeatureConfig(workspacePath);
|
|
2212
|
+
if (!feature) {
|
|
2213
|
+
return c.json({ error: 'Workspace configuration not found.' }, 404);
|
|
2214
|
+
}
|
|
2215
|
+
const body = await c.req.json().catch(() => ({}));
|
|
2216
|
+
const targetSessionId = typeof body.sessionId === 'string' && isValidSessionUuid(body.sessionId) ? body.sessionId : undefined;
|
|
2217
|
+
if (body.sessionId !== undefined && targetSessionId === undefined) {
|
|
1793
2218
|
return c.json({ error: 'Invalid session UUID format.' }, 400);
|
|
1794
2219
|
}
|
|
1795
2220
|
if (body.command !== undefined && (typeof body.command !== 'string' || !ALLOWED_EDITORS.has(body.command))) {
|
|
@@ -1981,7 +2406,8 @@ app.post('/api/updates/install', async (c) => {
|
|
|
1981
2406
|
try {
|
|
1982
2407
|
const { toolId } = await c.req.json();
|
|
1983
2408
|
const tools = [
|
|
1984
|
-
{ id:
|
|
2409
|
+
{ id: ENGINE_ID, cmd: 'npm', args: ['install', '-g', ENGINE_NPM_PACKAGE] },
|
|
2410
|
+
{ id: LEGACY_ENGINE_ID, cmd: 'npm', args: ['install', '-g', LEGACY_ENGINE_NPM_PACKAGE] },
|
|
1985
2411
|
{ id: 'antigravity', cmd: 'agy', args: ['update'] },
|
|
1986
2412
|
{ id: 'claude', cmd: 'npm', args: ['install', '-g', '@anthropic-ai/claude-code'] },
|
|
1987
2413
|
];
|
|
@@ -2004,96 +2430,6 @@ app.post('/api/updates/install', async (c) => {
|
|
|
2004
2430
|
return errorResponse(c, error);
|
|
2005
2431
|
}
|
|
2006
2432
|
});
|
|
2007
|
-
let downloadedInstallerPath = null;
|
|
2008
|
-
// 17.7. Download matching GitHub Release installer to temporary folder
|
|
2009
|
-
app.post('/api/updates/download', async (c) => {
|
|
2010
|
-
try {
|
|
2011
|
-
const { downloadUrl } = await c.req.json();
|
|
2012
|
-
if (!downloadUrl) {
|
|
2013
|
-
return c.json({ error: 'Download URL is required' }, 400);
|
|
2014
|
-
}
|
|
2015
|
-
if (!isAllowedUpdateUrl(downloadUrl)) {
|
|
2016
|
-
return c.json({ error: 'Download URL is not an allowed update host' }, 400);
|
|
2017
|
-
}
|
|
2018
|
-
const tempDir = os.tmpdir();
|
|
2019
|
-
const fileName = 'NexusFlowSetup_Update.exe';
|
|
2020
|
-
const targetPath = path.join(tempDir, fileName);
|
|
2021
|
-
const response = await fetch(downloadUrl);
|
|
2022
|
-
if (!response.ok) {
|
|
2023
|
-
throw new Error(`HTTP error ${response.status}`);
|
|
2024
|
-
}
|
|
2025
|
-
// Node fetch body stream download and SHA-256 computation
|
|
2026
|
-
const hash = createHash('sha256');
|
|
2027
|
-
const fileStream = createWriteStream(targetPath);
|
|
2028
|
-
if (!response.body) {
|
|
2029
|
-
throw new Error('Response body is null');
|
|
2030
|
-
}
|
|
2031
|
-
const nodeStream = Readable.fromWeb(response.body);
|
|
2032
|
-
nodeStream.on('data', (chunk) => hash.update(chunk));
|
|
2033
|
-
await pipeline(nodeStream, fileStream);
|
|
2034
|
-
const computedHash = hash.digest('hex').toLowerCase();
|
|
2035
|
-
// Verify SHA-256 against release checksum file if published
|
|
2036
|
-
try {
|
|
2037
|
-
const shaUrl = `${downloadUrl}.sha256`;
|
|
2038
|
-
if (isAllowedUpdateUrl(shaUrl)) {
|
|
2039
|
-
const shaRes = await fetch(shaUrl);
|
|
2040
|
-
if (shaRes.ok) {
|
|
2041
|
-
const shaText = await shaRes.text();
|
|
2042
|
-
const expectedHash = shaText.trim().split(/\s+/)[0]?.toLowerCase();
|
|
2043
|
-
if (expectedHash && expectedHash.length === 64 && expectedHash !== computedHash) {
|
|
2044
|
-
await fs.unlink(targetPath).catch(() => { });
|
|
2045
|
-
throw new Error(`SHA-256 checksum mismatch (expected ${expectedHash}, got ${computedHash})`);
|
|
2046
|
-
}
|
|
2047
|
-
}
|
|
2048
|
-
}
|
|
2049
|
-
}
|
|
2050
|
-
catch (shaErr) {
|
|
2051
|
-
if (shaErr instanceof Error && shaErr.message.includes('SHA-256 checksum mismatch')) {
|
|
2052
|
-
throw shaErr;
|
|
2053
|
-
}
|
|
2054
|
-
// Non-fatal if sha256 sidecar does not exist on older releases
|
|
2055
|
-
}
|
|
2056
|
-
downloadedInstallerPath = targetPath;
|
|
2057
|
-
return c.json({ success: true, path: targetPath, sha256: computedHash });
|
|
2058
|
-
}
|
|
2059
|
-
catch (error) {
|
|
2060
|
-
const msg = error instanceof Error ? error.message : String(error);
|
|
2061
|
-
return c.json({ error: `Download failed: ${msg}` }, 500);
|
|
2062
|
-
}
|
|
2063
|
-
});
|
|
2064
|
-
// 17.8. Launch the silent installer detached and exit server
|
|
2065
|
-
app.post('/api/updates/apply', async (c) => {
|
|
2066
|
-
if (!downloadedInstallerPath || !existsSync(downloadedInstallerPath)) {
|
|
2067
|
-
return c.json({ error: 'No downloaded installer found on disk' }, 400);
|
|
2068
|
-
}
|
|
2069
|
-
try {
|
|
2070
|
-
const isWin = process.platform === 'win32';
|
|
2071
|
-
if (isWin) {
|
|
2072
|
-
// The desktop app ships an electron-builder NSIS installer (build target
|
|
2073
|
-
// 'nsis'), whose silent-install switch is `/S` — NOT Inno Setup's
|
|
2074
|
-
// /VERYSILENT. With the wrong flags the one-click installer waits for UI
|
|
2075
|
-
// that a detached (stdio: 'ignore') process can never provide, so the
|
|
2076
|
-
// update silently fails to apply. `/S` runs it unattended and relaunches
|
|
2077
|
-
// the app on finish (electron-builder default).
|
|
2078
|
-
console.log(`Applying update: Spawning detached installer at: ${downloadedInstallerPath}`);
|
|
2079
|
-
const child = spawn(downloadedInstallerPath, ['/S'], {
|
|
2080
|
-
detached: true,
|
|
2081
|
-
stdio: 'ignore',
|
|
2082
|
-
});
|
|
2083
|
-
child.unref();
|
|
2084
|
-
}
|
|
2085
|
-
// Gracefully exit server process in 1 second to release file locks
|
|
2086
|
-
setTimeout(() => {
|
|
2087
|
-
console.log('Update installer successfully spawned. Exiting Hono server process...');
|
|
2088
|
-
process.exit(0);
|
|
2089
|
-
}, 1000);
|
|
2090
|
-
return c.json({ success: true, message: 'Installer spawned, app shutting down...' });
|
|
2091
|
-
}
|
|
2092
|
-
catch (error) {
|
|
2093
|
-
const msg = error instanceof Error ? error.message : String(error);
|
|
2094
|
-
return c.json({ error: `Failed to execute update: ${msg}` }, 500);
|
|
2095
|
-
}
|
|
2096
|
-
});
|
|
2097
2433
|
// 17.9. Get available workflow templates
|
|
2098
2434
|
app.get('/api/workflows/templates', async (c) => {
|
|
2099
2435
|
try {
|
|
@@ -2399,6 +2735,505 @@ app.post('/api/skills/workspace/:id/assign', async (c) => {
|
|
|
2399
2735
|
return errorResponse(c, error);
|
|
2400
2736
|
}
|
|
2401
2737
|
});
|
|
2738
|
+
// ─── Workrooms: opt-in LAN/VPN collaboration ─────────────────────────────
|
|
2739
|
+
const WORKROOM_HUMAN_SESSION_COOKIES = [
|
|
2740
|
+
'contextspace_workroom_human',
|
|
2741
|
+
'nexusflow_workroom_human',
|
|
2742
|
+
];
|
|
2743
|
+
const WORKROOM_BOOTSTRAP_TOKEN = randomToken();
|
|
2744
|
+
function establishWorkroomBootstrap(c) {
|
|
2745
|
+
for (const cookieName of WORKROOM_BOOTSTRAP_COOKIES) {
|
|
2746
|
+
setCookie(c, cookieName, WORKROOM_BOOTSTRAP_TOKEN, {
|
|
2747
|
+
httpOnly: true,
|
|
2748
|
+
sameSite: 'Strict',
|
|
2749
|
+
path: '/api/workrooms',
|
|
2750
|
+
});
|
|
2751
|
+
}
|
|
2752
|
+
}
|
|
2753
|
+
function hasValidWorkroomBootstrap(c) {
|
|
2754
|
+
const cookieToken = WORKROOM_BOOTSTRAP_COOKIES
|
|
2755
|
+
.map((name) => getCookie(c, name))
|
|
2756
|
+
.find((val) => typeof val === 'string' && val);
|
|
2757
|
+
const headerToken = WORKROOM_BOOTSTRAP_HEADERS
|
|
2758
|
+
.map((header) => c.req.header(header))
|
|
2759
|
+
.find((val) => typeof val === 'string' && val);
|
|
2760
|
+
return Boolean(cookieToken && headerToken
|
|
2761
|
+
&& tokenDigest(cookieToken) === tokenDigest(WORKROOM_BOOTSTRAP_TOKEN)
|
|
2762
|
+
&& tokenDigest(headerToken) === tokenDigest(WORKROOM_BOOTSTRAP_TOKEN));
|
|
2763
|
+
}
|
|
2764
|
+
function establishWorkroomHumanSession(c, token = workroomManager.beginHumanSession()) {
|
|
2765
|
+
for (const cookieName of WORKROOM_HUMAN_SESSION_COOKIES) {
|
|
2766
|
+
setCookie(c, cookieName, token, {
|
|
2767
|
+
httpOnly: true,
|
|
2768
|
+
sameSite: 'Strict',
|
|
2769
|
+
path: '/api/workrooms',
|
|
2770
|
+
});
|
|
2771
|
+
}
|
|
2772
|
+
}
|
|
2773
|
+
function getWorkroomHumanSessionToken(c) {
|
|
2774
|
+
return WORKROOM_HUMAN_SESSION_COOKIES
|
|
2775
|
+
.map((name) => getCookie(c, name))
|
|
2776
|
+
.find((val) => typeof val === 'string' && val);
|
|
2777
|
+
}
|
|
2778
|
+
function clearWorkroomHumanSession(c) {
|
|
2779
|
+
for (const cookieName of WORKROOM_HUMAN_SESSION_COOKIES) {
|
|
2780
|
+
deleteCookie(c, cookieName, { path: '/api/workrooms' });
|
|
2781
|
+
}
|
|
2782
|
+
}
|
|
2783
|
+
function hasExactDashboardOrigin(origin, requestUrl) {
|
|
2784
|
+
if (!origin)
|
|
2785
|
+
return false;
|
|
2786
|
+
try {
|
|
2787
|
+
const normalizedOrigin = new URL(origin).origin;
|
|
2788
|
+
const requestOrigin = new URL(requestUrl).origin;
|
|
2789
|
+
const devOrigin = process.env.CONTEXTSPACE_DASHBOARD_ORIGIN || process.env.NEXUSFLOW_DASHBOARD_ORIGIN;
|
|
2790
|
+
const configuredDevelopmentOrigin = devOrigin ? new URL(devOrigin).origin : undefined;
|
|
2791
|
+
return normalizedOrigin === requestOrigin || normalizedOrigin === configuredDevelopmentOrigin;
|
|
2792
|
+
}
|
|
2793
|
+
catch {
|
|
2794
|
+
return false;
|
|
2795
|
+
}
|
|
2796
|
+
}
|
|
2797
|
+
function hasExactWorkroomBrowserBoundary(c) {
|
|
2798
|
+
const origin = c.req.header('origin');
|
|
2799
|
+
if (origin)
|
|
2800
|
+
return hasExactDashboardOrigin(origin, c.req.url);
|
|
2801
|
+
const referer = c.req.header('referer');
|
|
2802
|
+
if (referer)
|
|
2803
|
+
return hasExactDashboardOrigin(referer, c.req.url);
|
|
2804
|
+
return c.req.header('sec-fetch-site') === 'same-origin';
|
|
2805
|
+
}
|
|
2806
|
+
app.use('/api/workrooms/*', async (c, next) => {
|
|
2807
|
+
const pathname = new URL(c.req.url).pathname;
|
|
2808
|
+
c.header('Cache-Control', 'no-store');
|
|
2809
|
+
if (!hasExactWorkroomBrowserBoundary(c)) {
|
|
2810
|
+
return c.json({ error: 'Workroom access requires the local browser dashboard.' }, 403);
|
|
2811
|
+
}
|
|
2812
|
+
if (pathname === '/api/workrooms/bootstrap')
|
|
2813
|
+
return next();
|
|
2814
|
+
if (!hasValidWorkroomBootstrap(c)) {
|
|
2815
|
+
return c.json({ error: 'Workroom access requires a same-origin dashboard bootstrap.' }, 403);
|
|
2816
|
+
}
|
|
2817
|
+
if (pathname === '/api/workrooms/session' || pathname === '/api/workrooms/session/reclaim'
|
|
2818
|
+
|| pathname === '/api/workrooms/session/abandon')
|
|
2819
|
+
return next();
|
|
2820
|
+
const isMutation = ['POST', 'PUT', 'PATCH', 'DELETE'].includes(c.req.method);
|
|
2821
|
+
const needsHumanReadSession = c.req.method === 'GET' && (pathname === '/api/workrooms/snapshot'
|
|
2822
|
+
|| pathname === '/api/workrooms/local-resources'
|
|
2823
|
+
|| (pathname === '/api/workrooms/status' && workroomManager.hasActiveRoom()));
|
|
2824
|
+
if (!isMutation && !needsHumanReadSession)
|
|
2825
|
+
return next();
|
|
2826
|
+
const doesNotRequireExistingHumanSession = pathname === '/api/workrooms/start'
|
|
2827
|
+
|| pathname === '/api/workrooms/join'
|
|
2828
|
+
|| pathname === '/api/workrooms/import'
|
|
2829
|
+
|| /^\/api\/workrooms\/quarantined\/room-[a-f0-9]{32}\/discard$/.test(pathname)
|
|
2830
|
+
|| /^\/api\/workrooms\/room-[a-f0-9]{32}\/resume$/.test(pathname);
|
|
2831
|
+
if (needsHumanReadSession || (isMutation && !doesNotRequireExistingHumanSession)) {
|
|
2832
|
+
try {
|
|
2833
|
+
const authority = workroomManager.assertHumanSession(getWorkroomHumanSessionToken(c));
|
|
2834
|
+
if (pathname === '/api/workrooms/stop')
|
|
2835
|
+
return next();
|
|
2836
|
+
await workroomManager.runWithHumanAuthority(authority, () => next());
|
|
2837
|
+
return;
|
|
2838
|
+
}
|
|
2839
|
+
catch (error) {
|
|
2840
|
+
return errorResponse(c, error);
|
|
2841
|
+
}
|
|
2842
|
+
}
|
|
2843
|
+
await next();
|
|
2844
|
+
});
|
|
2845
|
+
app.post('/api/workrooms/bootstrap', (c) => {
|
|
2846
|
+
establishWorkroomBootstrap(c);
|
|
2847
|
+
return c.json({ token: WORKROOM_BOOTSTRAP_TOKEN });
|
|
2848
|
+
});
|
|
2849
|
+
app.get('/api/workrooms/session', (c) => {
|
|
2850
|
+
const active = workroomManager.hasActiveRoom();
|
|
2851
|
+
if (!active)
|
|
2852
|
+
return c.json({ active: false, locked: false });
|
|
2853
|
+
const roomType = workroomManager.activeRoomType();
|
|
2854
|
+
if (!roomType)
|
|
2855
|
+
return c.json({ active: false, locked: false });
|
|
2856
|
+
try {
|
|
2857
|
+
workroomManager.assertHumanSession(getWorkroomHumanSessionToken(c));
|
|
2858
|
+
return c.json({ active: true, locked: false, roomType });
|
|
2859
|
+
}
|
|
2860
|
+
catch {
|
|
2861
|
+
return c.json({ active: true, locked: true, roomType });
|
|
2862
|
+
}
|
|
2863
|
+
});
|
|
2864
|
+
app.post('/api/workrooms/session/reclaim', async (c) => {
|
|
2865
|
+
try {
|
|
2866
|
+
const body = await c.req.json();
|
|
2867
|
+
if (!workroomManager.hasActiveRoom())
|
|
2868
|
+
return c.json({ error: 'No active Workroom can be reconnected.' }, 409);
|
|
2869
|
+
const token = await workroomManager.reclaimHostHumanSession(String(body.password ?? ''));
|
|
2870
|
+
establishWorkroomHumanSession(c, token);
|
|
2871
|
+
return c.json({ success: true });
|
|
2872
|
+
}
|
|
2873
|
+
catch (error) {
|
|
2874
|
+
return errorResponse(c, error);
|
|
2875
|
+
}
|
|
2876
|
+
});
|
|
2877
|
+
app.post('/api/workrooms/session/abandon', async (c) => {
|
|
2878
|
+
try {
|
|
2879
|
+
const body = await c.req.json();
|
|
2880
|
+
if (body.confirm !== true)
|
|
2881
|
+
return c.json({ error: 'Confirm leaving this locked guest connection.' }, 400);
|
|
2882
|
+
await workroomManager.abandonLockedGuest();
|
|
2883
|
+
clearWorkroomHumanSession(c);
|
|
2884
|
+
return c.json({ success: true });
|
|
2885
|
+
}
|
|
2886
|
+
catch (error) {
|
|
2887
|
+
return errorResponse(c, error);
|
|
2888
|
+
}
|
|
2889
|
+
});
|
|
2890
|
+
app.get('/api/workrooms/interfaces', (c) => {
|
|
2891
|
+
return c.json({ interfaces: listWorkroomNetworkInterfaces() });
|
|
2892
|
+
});
|
|
2893
|
+
app.get('/api/workrooms/status', async (c) => {
|
|
2894
|
+
try {
|
|
2895
|
+
const status = await workroomManager.status();
|
|
2896
|
+
if (status.mode !== 'idle') {
|
|
2897
|
+
workroomManager.assertHumanSession(getWorkroomHumanSessionToken(c));
|
|
2898
|
+
}
|
|
2899
|
+
return c.json({ status });
|
|
2900
|
+
}
|
|
2901
|
+
catch (error) {
|
|
2902
|
+
return errorResponse(c, error);
|
|
2903
|
+
}
|
|
2904
|
+
});
|
|
2905
|
+
app.get('/api/workrooms/paused', async (c) => {
|
|
2906
|
+
try {
|
|
2907
|
+
return c.json({ rooms: await workroomManager.listPaused() });
|
|
2908
|
+
}
|
|
2909
|
+
catch (error) {
|
|
2910
|
+
return errorResponse(c, error);
|
|
2911
|
+
}
|
|
2912
|
+
});
|
|
2913
|
+
app.get('/api/workrooms/quarantined', async (c) => {
|
|
2914
|
+
try {
|
|
2915
|
+
return c.json({ rooms: await workroomManager.listQuarantined() });
|
|
2916
|
+
}
|
|
2917
|
+
catch (error) {
|
|
2918
|
+
return errorResponse(c, error);
|
|
2919
|
+
}
|
|
2920
|
+
});
|
|
2921
|
+
app.post('/api/workrooms/quarantined/:roomId/discard', async (c) => {
|
|
2922
|
+
try {
|
|
2923
|
+
const roomId = c.req.param('roomId');
|
|
2924
|
+
const body = await c.req.json();
|
|
2925
|
+
if (body.confirmRoomId !== roomId) {
|
|
2926
|
+
return c.json({ error: 'Confirm the exact quarantined Workroom ID before discarding it.' }, 400);
|
|
2927
|
+
}
|
|
2928
|
+
await workroomManager.discardQuarantined(roomId);
|
|
2929
|
+
return c.json({ success: true });
|
|
2930
|
+
}
|
|
2931
|
+
catch (error) {
|
|
2932
|
+
return errorResponse(c, error);
|
|
2933
|
+
}
|
|
2934
|
+
});
|
|
2935
|
+
app.post('/api/workrooms/:roomId/resume', async (c) => {
|
|
2936
|
+
try {
|
|
2937
|
+
const body = await c.req.json();
|
|
2938
|
+
const status = await workroomManager.resumeHost(c.req.param('roomId'), String(body.password ?? ''));
|
|
2939
|
+
establishWorkroomHumanSession(c);
|
|
2940
|
+
return c.json({ status });
|
|
2941
|
+
}
|
|
2942
|
+
catch (error) {
|
|
2943
|
+
return errorResponse(c, error);
|
|
2944
|
+
}
|
|
2945
|
+
});
|
|
2946
|
+
app.get('/api/workrooms/preview/:workspaceId', async (c) => {
|
|
2947
|
+
try {
|
|
2948
|
+
const workspaceId = decodeURIComponent(c.req.param('workspaceId'));
|
|
2949
|
+
const config = await loadConfig();
|
|
2950
|
+
const workspacePath = await resolveExactWorkspaceById(config.workspacesDir, workspaceId);
|
|
2951
|
+
if (!workspacePath)
|
|
2952
|
+
return c.json({ error: 'Workspace not found.' }, 404);
|
|
2953
|
+
return c.json({ preview: await buildPortableWorkroomPreview(workspacePath) });
|
|
2954
|
+
}
|
|
2955
|
+
catch (error) {
|
|
2956
|
+
return errorResponse(c, error);
|
|
2957
|
+
}
|
|
2958
|
+
});
|
|
2959
|
+
app.post('/api/workrooms/start', async (c) => {
|
|
2960
|
+
try {
|
|
2961
|
+
const body = await c.req.json();
|
|
2962
|
+
const workspaceId = typeof body.workspaceId === 'string' ? body.workspaceId : '';
|
|
2963
|
+
const config = await loadConfig();
|
|
2964
|
+
const workspacePath = await resolveExactWorkspaceById(config.workspacesDir, workspaceId);
|
|
2965
|
+
if (!workspacePath)
|
|
2966
|
+
return c.json({ error: 'Workspace not found.' }, 404);
|
|
2967
|
+
const preview = await buildPortableWorkroomPreview(workspacePath);
|
|
2968
|
+
if (body.contextConfirmed !== true) {
|
|
2969
|
+
return c.json({ error: 'Review the exact shared context and confirm it before starting the Workroom.' }, 400);
|
|
2970
|
+
}
|
|
2971
|
+
const documentOverrides = typeof body.documents === 'object' && body.documents !== null
|
|
2972
|
+
? body.documents
|
|
2973
|
+
: {};
|
|
2974
|
+
const documents = { plan: '', decisions: '', handoff: '' };
|
|
2975
|
+
for (const name of ['plan', 'decisions', 'handoff']) {
|
|
2976
|
+
if (typeof documentOverrides[name] === 'string')
|
|
2977
|
+
documents[name] = documentOverrides[name];
|
|
2978
|
+
}
|
|
2979
|
+
if (body.contextDigest !== digestPortableWorkroomContext(preview.bundle, documents)) {
|
|
2980
|
+
return c.json({ error: 'The exact selected context changed after review. Refresh the sharing preview and confirm it again.' }, 409);
|
|
2981
|
+
}
|
|
2982
|
+
const status = await workroomManager.startHost({
|
|
2983
|
+
...preview,
|
|
2984
|
+
documents,
|
|
2985
|
+
name: String(body.name ?? '').trim() || `${preview.bundle.feature.id} workroom`,
|
|
2986
|
+
address: String(body.address ?? ''),
|
|
2987
|
+
port: Number(body.port ?? 4242),
|
|
2988
|
+
password: String(body.password ?? ''),
|
|
2989
|
+
hostDisplayName: String(body.hostDisplayName ?? '').trim() || os.userInfo().username || 'Host',
|
|
2990
|
+
});
|
|
2991
|
+
establishWorkroomHumanSession(c);
|
|
2992
|
+
return c.json({ status }, 201);
|
|
2993
|
+
}
|
|
2994
|
+
catch (error) {
|
|
2995
|
+
return errorResponse(c, error);
|
|
2996
|
+
}
|
|
2997
|
+
});
|
|
2998
|
+
app.post('/api/workrooms/stop', async (c) => {
|
|
2999
|
+
try {
|
|
3000
|
+
const authority = workroomManager.assertHumanSession(getWorkroomHumanSessionToken(c));
|
|
3001
|
+
await workroomManager.stopOrLeave(authority);
|
|
3002
|
+
clearWorkroomHumanSession(c);
|
|
3003
|
+
return c.json({ success: true });
|
|
3004
|
+
}
|
|
3005
|
+
catch (error) {
|
|
3006
|
+
return errorResponse(c, error);
|
|
3007
|
+
}
|
|
3008
|
+
});
|
|
3009
|
+
app.post('/api/workrooms/invites', async (c) => {
|
|
3010
|
+
try {
|
|
3011
|
+
return c.json(await workroomManager.createInvite(), 201);
|
|
3012
|
+
}
|
|
3013
|
+
catch (error) {
|
|
3014
|
+
return errorResponse(c, error);
|
|
3015
|
+
}
|
|
3016
|
+
});
|
|
3017
|
+
app.post('/api/workrooms/join', async (c) => {
|
|
3018
|
+
try {
|
|
3019
|
+
const body = await c.req.json();
|
|
3020
|
+
let localWorkspaceId;
|
|
3021
|
+
if (typeof body.workspaceId === 'string' && body.workspaceId) {
|
|
3022
|
+
const config = await loadConfig();
|
|
3023
|
+
const workspacePath = await resolveExactWorkspaceById(config.workspacesDir, body.workspaceId);
|
|
3024
|
+
if (!workspacePath)
|
|
3025
|
+
return c.json({ error: 'Select an existing local workspace for the Workroom mapping.' }, 404);
|
|
3026
|
+
localWorkspaceId = body.workspaceId;
|
|
3027
|
+
}
|
|
3028
|
+
const status = await workroomManager.join(String(body.invite ?? ''), String(body.password ?? ''), String(body.displayName ?? '').trim() || os.userInfo().username || 'Developer', localWorkspaceId);
|
|
3029
|
+
establishWorkroomHumanSession(c);
|
|
3030
|
+
return c.json({ status }, 202);
|
|
3031
|
+
}
|
|
3032
|
+
catch (error) {
|
|
3033
|
+
return errorResponse(c, error);
|
|
3034
|
+
}
|
|
3035
|
+
});
|
|
3036
|
+
app.post('/api/workrooms/join/poll', async (c) => {
|
|
3037
|
+
try {
|
|
3038
|
+
return c.json({ status: await workroomManager.pollJoin() });
|
|
3039
|
+
}
|
|
3040
|
+
catch (error) {
|
|
3041
|
+
return errorResponse(c, error);
|
|
3042
|
+
}
|
|
3043
|
+
});
|
|
3044
|
+
app.get('/api/workrooms/snapshot', async (c) => {
|
|
3045
|
+
try {
|
|
3046
|
+
return c.json({ snapshot: await workroomManager.snapshot() });
|
|
3047
|
+
}
|
|
3048
|
+
catch (error) {
|
|
3049
|
+
return errorResponse(c, error);
|
|
3050
|
+
}
|
|
3051
|
+
});
|
|
3052
|
+
app.put('/api/workrooms/documents/:name', async (c) => {
|
|
3053
|
+
try {
|
|
3054
|
+
const name = documentNameSchema.parse(c.req.param('name'));
|
|
3055
|
+
const body = await c.req.json();
|
|
3056
|
+
if (typeof body.content !== 'string' || !Number.isInteger(body.expectedRevision)) {
|
|
3057
|
+
return c.json({ error: 'content and an integer expectedRevision are required.' }, 400);
|
|
3058
|
+
}
|
|
3059
|
+
return c.json({ document: await workroomManager.updateDocument(name, body.content, body.expectedRevision) });
|
|
3060
|
+
}
|
|
3061
|
+
catch (error) {
|
|
3062
|
+
return errorResponse(c, error);
|
|
3063
|
+
}
|
|
3064
|
+
});
|
|
3065
|
+
app.post('/api/workrooms/handoff', async (c) => {
|
|
3066
|
+
try {
|
|
3067
|
+
const body = await c.req.json();
|
|
3068
|
+
const workspaceId = typeof body.workspaceId === 'string' ? body.workspaceId : '';
|
|
3069
|
+
const config = await loadConfig();
|
|
3070
|
+
const workspacePath = await resolveExactWorkspaceById(config.workspacesDir, workspaceId);
|
|
3071
|
+
if (!workspacePath)
|
|
3072
|
+
return c.json({ error: 'Select a local workspace before publishing a handoff.' }, 404);
|
|
3073
|
+
return c.json({ document: await workroomManager.publishHandoff(workspacePath) });
|
|
3074
|
+
}
|
|
3075
|
+
catch (error) {
|
|
3076
|
+
return errorResponse(c, error);
|
|
3077
|
+
}
|
|
3078
|
+
});
|
|
3079
|
+
app.post('/api/workrooms/members/:requestId/decision', async (c) => {
|
|
3080
|
+
try {
|
|
3081
|
+
const body = await c.req.json();
|
|
3082
|
+
if (typeof body.accept !== 'boolean')
|
|
3083
|
+
return c.json({ error: 'accept must be a boolean.' }, 400);
|
|
3084
|
+
await workroomManager.decideJoin(c.req.param('requestId'), body.accept);
|
|
3085
|
+
return c.json({ success: true });
|
|
3086
|
+
}
|
|
3087
|
+
catch (error) {
|
|
3088
|
+
return errorResponse(c, error);
|
|
3089
|
+
}
|
|
3090
|
+
});
|
|
3091
|
+
app.put('/api/workrooms/members/:memberId/role', async (c) => {
|
|
3092
|
+
try {
|
|
3093
|
+
const body = await c.req.json();
|
|
3094
|
+
if (body.role !== 'publisher' && body.role !== 'member')
|
|
3095
|
+
return c.json({ error: 'role must be publisher or member.' }, 400);
|
|
3096
|
+
await workroomManager.setRole(c.req.param('memberId'), body.role);
|
|
3097
|
+
return c.json({ success: true });
|
|
3098
|
+
}
|
|
3099
|
+
catch (error) {
|
|
3100
|
+
return errorResponse(c, error);
|
|
3101
|
+
}
|
|
3102
|
+
});
|
|
3103
|
+
app.delete('/api/workrooms/members/:memberId', async (c) => {
|
|
3104
|
+
try {
|
|
3105
|
+
await workroomManager.revokeMember(c.req.param('memberId'));
|
|
3106
|
+
return c.json({ success: true });
|
|
3107
|
+
}
|
|
3108
|
+
catch (error) {
|
|
3109
|
+
return errorResponse(c, error);
|
|
3110
|
+
}
|
|
3111
|
+
});
|
|
3112
|
+
app.post('/api/workrooms/password/rotate', async (c) => {
|
|
3113
|
+
try {
|
|
3114
|
+
const body = await c.req.json();
|
|
3115
|
+
await workroomManager.rotatePassword(String(body.password ?? ''), body.revokeDevices !== false);
|
|
3116
|
+
return c.json({ success: true });
|
|
3117
|
+
}
|
|
3118
|
+
catch (error) {
|
|
3119
|
+
return errorResponse(c, error);
|
|
3120
|
+
}
|
|
3121
|
+
});
|
|
3122
|
+
app.get('/api/workrooms/local-resources', async (c) => {
|
|
3123
|
+
try {
|
|
3124
|
+
return c.json({ resources: await workroomManager.listLocalResources() });
|
|
3125
|
+
}
|
|
3126
|
+
catch (error) {
|
|
3127
|
+
return errorResponse(c, error);
|
|
3128
|
+
}
|
|
3129
|
+
});
|
|
3130
|
+
app.post('/api/workrooms/resources/publish', async (c) => {
|
|
3131
|
+
try {
|
|
3132
|
+
const body = await c.req.json();
|
|
3133
|
+
if (!['skill', 'agent', 'workflow'].includes(body.kind) || typeof body.id !== 'string' || typeof body.version !== 'string') {
|
|
3134
|
+
return c.json({ error: 'kind, id, and semantic version are required.' }, 400);
|
|
3135
|
+
}
|
|
3136
|
+
return c.json({ package: await workroomManager.publishLocalResource(body.kind, body.id, body.version) }, 201);
|
|
3137
|
+
}
|
|
3138
|
+
catch (error) {
|
|
3139
|
+
return errorResponse(c, error);
|
|
3140
|
+
}
|
|
3141
|
+
});
|
|
3142
|
+
app.post('/api/workrooms/resources/:digest/download', async (c) => {
|
|
3143
|
+
try {
|
|
3144
|
+
return c.json(await workroomManager.downloadResource(c.req.param('digest')));
|
|
3145
|
+
}
|
|
3146
|
+
catch (error) {
|
|
3147
|
+
return errorResponse(c, error);
|
|
3148
|
+
}
|
|
3149
|
+
});
|
|
3150
|
+
app.post('/api/workrooms/resources/:digest/apply', async (c) => {
|
|
3151
|
+
try {
|
|
3152
|
+
const body = await c.req.json();
|
|
3153
|
+
return c.json({ applied: await workroomManager.applyResource(c.req.param('digest'), String(body.approvedDigest ?? ''), String(body.approvedLocalDigest ?? '')) });
|
|
3154
|
+
}
|
|
3155
|
+
catch (error) {
|
|
3156
|
+
return errorResponse(c, error);
|
|
3157
|
+
}
|
|
3158
|
+
});
|
|
3159
|
+
app.post('/api/workrooms/resources/:digest/quarantine', async (c) => {
|
|
3160
|
+
try {
|
|
3161
|
+
await workroomManager.quarantineResource(c.req.param('digest'));
|
|
3162
|
+
return c.json({ success: true });
|
|
3163
|
+
}
|
|
3164
|
+
catch (error) {
|
|
3165
|
+
return errorResponse(c, error);
|
|
3166
|
+
}
|
|
3167
|
+
});
|
|
3168
|
+
app.post('/api/workrooms/resources/:digest/purge', async (c) => {
|
|
3169
|
+
try {
|
|
3170
|
+
const body = await c.req.json();
|
|
3171
|
+
if (body.confirmDigest !== c.req.param('digest')) {
|
|
3172
|
+
return c.json({ error: 'Confirm the exact quarantined resource digest before purging it.' }, 400);
|
|
3173
|
+
}
|
|
3174
|
+
await workroomManager.purgeResource(c.req.param('digest'));
|
|
3175
|
+
return c.json({ success: true });
|
|
3176
|
+
}
|
|
3177
|
+
catch (error) {
|
|
3178
|
+
return errorResponse(c, error);
|
|
3179
|
+
}
|
|
3180
|
+
});
|
|
3181
|
+
app.post('/api/workrooms/workflow/select', async (c) => {
|
|
3182
|
+
try {
|
|
3183
|
+
const body = await c.req.json();
|
|
3184
|
+
const steps = Array.isArray(body.steps)
|
|
3185
|
+
? body.steps.map((step) => workflowStepSchema.parse(step))
|
|
3186
|
+
: undefined;
|
|
3187
|
+
if (typeof body.workflowId !== 'string' || typeof body.version !== 'string' || !steps
|
|
3188
|
+
|| !Number.isInteger(body.expectedRevision) || body.expectedRevision < 0) {
|
|
3189
|
+
return c.json({ error: 'workflowId, version, steps, and a nonnegative integer expectedRevision are required.' }, 400);
|
|
3190
|
+
}
|
|
3191
|
+
await workroomManager.selectLocalWorkflow(body.workflowId, body.version, steps, body.expectedRevision);
|
|
3192
|
+
return c.json({ success: true });
|
|
3193
|
+
}
|
|
3194
|
+
catch (error) {
|
|
3195
|
+
return errorResponse(c, error);
|
|
3196
|
+
}
|
|
3197
|
+
});
|
|
3198
|
+
app.post('/api/workrooms/workflow/steps/:stepId/transition', async (c) => {
|
|
3199
|
+
try {
|
|
3200
|
+
const body = await c.req.json();
|
|
3201
|
+
const allowed = ['pending', 'in_progress', 'completion_proposed', 'completed', 'skipped'];
|
|
3202
|
+
if (!allowed.includes(body.status) || !Number.isInteger(body.expectedRevision)) {
|
|
3203
|
+
return c.json({ error: 'A valid status and integer expectedRevision are required.' }, 400);
|
|
3204
|
+
}
|
|
3205
|
+
return c.json({ step: await workroomManager.transitionWorkflowStep(c.req.param('stepId'), body.status, body.expectedRevision, typeof body.evidence === 'string' ? body.evidence : undefined) });
|
|
3206
|
+
}
|
|
3207
|
+
catch (error) {
|
|
3208
|
+
return errorResponse(c, error);
|
|
3209
|
+
}
|
|
3210
|
+
});
|
|
3211
|
+
app.post('/api/workrooms/export', async (c) => {
|
|
3212
|
+
try {
|
|
3213
|
+
const body = await c.req.json();
|
|
3214
|
+
return c.json({ export: await workroomManager.exportRoom(String(body.passphrase ?? '')) });
|
|
3215
|
+
}
|
|
3216
|
+
catch (error) {
|
|
3217
|
+
return errorResponse(c, error);
|
|
3218
|
+
}
|
|
3219
|
+
});
|
|
3220
|
+
app.post('/api/workrooms/import', async (c) => {
|
|
3221
|
+
try {
|
|
3222
|
+
const body = await c.req.json();
|
|
3223
|
+
const status = await workroomManager.importRoom(body.export, String(body.exportPassphrase ?? ''), {
|
|
3224
|
+
name: typeof body.name === 'string' ? body.name : undefined,
|
|
3225
|
+
address: String(body.address ?? ''),
|
|
3226
|
+
port: Number(body.port ?? 4242),
|
|
3227
|
+
password: String(body.password ?? ''),
|
|
3228
|
+
hostDisplayName: String(body.hostDisplayName ?? '').trim() || os.userInfo().username || 'Host',
|
|
3229
|
+
});
|
|
3230
|
+
establishWorkroomHumanSession(c);
|
|
3231
|
+
return c.json({ status }, 201);
|
|
3232
|
+
}
|
|
3233
|
+
catch (error) {
|
|
3234
|
+
return errorResponse(c, error);
|
|
3235
|
+
}
|
|
3236
|
+
});
|
|
2402
3237
|
// ─── Schedules: recurring workspace jobs (sync/refresh) ─────────────────
|
|
2403
3238
|
// List all scheduled jobs (with computed next-due time)
|
|
2404
3239
|
app.get('/api/schedules', async (c) => {
|
|
@@ -2500,6 +3335,11 @@ export function startServer(port = 3000, opts = {}) {
|
|
|
2500
3335
|
resolve({ port: info.port, server });
|
|
2501
3336
|
});
|
|
2502
3337
|
injectWebSocket(server);
|
|
3338
|
+
server.on('close', () => {
|
|
3339
|
+
void workroomManager.stopOrLeave().catch((error) => {
|
|
3340
|
+
console.error('[workroom] failed to stop with the dashboard:', error instanceof Error ? error.message : String(error));
|
|
3341
|
+
});
|
|
3342
|
+
});
|
|
2503
3343
|
server.on('error', (e) => {
|
|
2504
3344
|
if (e.code === 'EADDRINUSE') {
|
|
2505
3345
|
// Callers that own their backend (desktop/extension) pass strictPort so
|