From 7562c8bdea874fd26f08bf721f850c18f9866f2b Mon Sep 17 00:00:00 2001 From: Damon <16412057+Modsofthenation@users.noreply.github.com> Date: Sun, 8 Nov 2026 19:30:32 +0800 Subject: [PATCH 1/7] Publish Locus 0.17.0. Bring current product sources across and retarget auto-updates at Modsofthenation/Locus. --- .github/ISSUE_TEMPLATE/bug.yml | 4 +- .github/pull_request_template.md | 1 + .github/workflows/ci.yml | 13 + .github/workflows/dist-linux.yml | 32 +- .github/workflows/dist-macos.yml | 30 +- .github/workflows/dist-windows.yml | 34 +- .gitignore | 4 + CHANGELOG.md | 12 + CONTEXT.md | 2 +- CONTRIBUTING.md | 15 +- README.md | 20 +- SECURITY.md | 2 +- apps/android/app/build.gradle.kts | 4 +- apps/chrome-extension/manifest.json | 2 +- apps/cli/package.json | 2 +- apps/desktop/e2e/visual.spec.ts | 173 ++++ .../appearance-contrast.png | Bin 0 -> 76763 bytes .../appearance-ember.png | Bin 0 -> 77482 bytes .../appearance-midnight.png | Bin 0 -> 78439 bytes .../appearance-paper.png | Bin 0 -> 76906 bytes .../visual.spec.ts-snapshots/chat-ember.png | Bin 0 -> 4169 bytes .../visual.spec.ts-snapshots/chat-paper.png | Bin 0 -> 4095 bytes .../confirm-contrast.png | Bin 0 -> 6262 bytes .../confirm-ember-phone.png | Bin 0 -> 6437 bytes .../confirm-ember.png | Bin 0 -> 6618 bytes .../confirm-midnight.png | Bin 0 -> 6604 bytes .../confirm-paper.png | Bin 0 -> 6807 bytes .../design-empty-ember.png | Bin 0 -> 9010 bytes .../design-empty-paper.png | Bin 0 -> 8889 bytes .../empty-hero-ember.png | Bin 0 -> 6556 bytes .../empty-hero-paper.png | Bin 0 -> 6420 bytes .../empty-hint-ember-phone.png | Bin 0 -> 6683 bytes .../empty-hint-ember.png | Bin 0 -> 7560 bytes .../empty-hint-paper.png | Bin 0 -> 7426 bytes .../feedback-ember-phone.png | Bin 0 -> 22298 bytes .../feedback-ember.png | Bin 0 -> 24196 bytes .../feedback-paper.png | Bin 0 -> 22567 bytes .../ide-empty-ember.png | Bin 0 -> 7731 bytes .../ide-empty-paper.png | Bin 0 -> 7572 bytes .../new-agent-ember.png | Bin 0 -> 53845 bytes .../new-agent-paper.png | Bin 0 -> 54174 bytes .../new-automation-ember.png | Bin 0 -> 50863 bytes .../new-automation-paper.png | Bin 0 -> 50831 bytes .../new-automation-schedule-ember.png | Bin 0 -> 63358 bytes .../new-automation-schedule-paper.png | Bin 0 -> 71090 bytes .../prompt-ember-phone.png | Bin 0 -> 7818 bytes .../visual.spec.ts-snapshots/prompt-ember.png | Bin 0 -> 8082 bytes .../visual.spec.ts-snapshots/prompt-paper.png | Bin 0 -> 8361 bytes .../visual.spec.ts-snapshots/rail-ember.png | Bin 0 -> 8669 bytes .../visual.spec.ts-snapshots/rail-paper.png | Bin 0 -> 8536 bytes .../settings-providers-ember.png | Bin 0 -> 37290 bytes .../settings-providers-paper.png | Bin 0 -> 37114 bytes .../shortcuts-ember-phone.png | Bin 0 -> 50970 bytes .../shortcuts-ember.png | Bin 0 -> 55494 bytes .../shortcuts-paper.png | Bin 0 -> 55573 bytes .../splitter-ember.png | Bin 0 -> 4154 bytes .../splitter-paper.png | Bin 0 -> 4029 bytes .../toast-ember-phone.png | Bin 0 -> 3130 bytes .../visual.spec.ts-snapshots/toast-ember.png | Bin 0 -> 3132 bytes .../visual.spec.ts-snapshots/toast-paper.png | Bin 0 -> 3216 bytes apps/desktop/package.json | 7 +- apps/desktop/playwright.config.ts | 40 + .../scripts/capture-readme-screens.mjs | 23 + apps/desktop/src/App.test.tsx | 13 +- apps/desktop/src/App.tsx | 10 +- .../src/components/assets/AssetEmpty.test.tsx | 15 +- .../components/assets/AssetExportModal.tsx | 26 +- .../components/assets/AssetStylePicker.tsx | 24 +- .../src/components/assets/AssetStyles.tsx | 311 +++--- .../automations/Automations.test.tsx | 4 +- .../components/automations/Automations.tsx | 81 +- .../automations/NewAutomation.test.tsx | 53 +- .../components/automations/NewAutomation.tsx | 904 ++++++++--------- .../src/components/board/BoardCanvas.test.tsx | 2 +- .../src/components/board/BoardCanvas.tsx | 72 +- .../src/components/board/BoardEmpty.tsx | 14 +- .../src/components/board/BoardPane.tsx | 75 +- .../src/components/board/BoardShapePicker.tsx | 13 +- .../components/board/BoardTimelineObject.tsx | 27 +- apps/desktop/src/components/brain/Brain.tsx | 12 +- .../src/components/brain/BrainGraph.tsx | 36 +- .../src/components/chat/ArtifactDrawer.tsx | 14 +- .../src/components/chat/ChatPane.test.tsx | 37 +- apps/desktop/src/components/chat/ChatPane.tsx | 915 ++---------------- .../src/components/chat/ChatThread.test.tsx | 99 ++ .../src/components/chat/ChatThread.tsx | 755 +++++++++++++++ .../src/components/chat/ChatToolActivity.tsx | 32 +- .../src/components/chat/CompareBanner.tsx | 42 +- .../src/components/chat/CompareModal.tsx | 75 +- .../src/components/chat/ComposerFiles.tsx | 18 +- .../components/chat/ComposerMoreOptions.tsx | 22 +- .../src/components/chat/ContextMeter.test.tsx | 19 + .../src/components/chat/ContextMeter.tsx | 2 + .../src/components/chat/NewSession.tsx | 758 ++++++++------- apps/desktop/src/components/chat/PlanCard.tsx | 9 +- .../src/components/chat/ReplayModal.tsx | 113 +-- .../src/components/chat/SessionSettings.tsx | 748 +++++++------- .../src/components/chat/SubagentPicker.tsx | 25 +- .../src/components/chat/SubscriptionCard.tsx | 4 +- .../src/components/chat/TraceModal.tsx | 89 +- .../components/codebase/ArchitecturePane.tsx | 51 +- .../components/codebase/ChangeStackPanel.tsx | 26 +- .../src/components/codebase/Codebase.test.tsx | 25 +- .../src/components/codebase/Codebase.tsx | 315 +++--- .../components/codebase/MobileCodebase.tsx | 11 +- .../components/codebase/RepoField.test.tsx | 13 +- .../src/components/codebase/RepoField.tsx | 46 +- .../components/design/DesignCanvas.test.tsx | 93 +- .../src/components/design/DesignCanvas.tsx | 231 +++-- .../components/design/DesignEmpty.test.tsx | 13 +- .../src/components/design/DesignEmpty.tsx | 35 +- .../components/design/DesignSystemPicker.tsx | 44 +- .../src/components/design/DesignSystems.tsx | 743 +++++++------- .../src/components/design/MobileDesign.tsx | 78 +- apps/desktop/src/components/docker/Docker.tsx | 30 +- .../src/components/docker/DockerExplorer.tsx | 2 +- .../src/components/docker/DockerSwarm.tsx | 58 +- .../docker/DockerWorkspace.test.tsx | 5 +- .../src/components/docker/DockerWorkspace.tsx | 40 +- .../src/components/game/GameEmpty.test.tsx | 10 +- .../src/components/game/GamePlayLink.tsx | 12 +- .../src/components/game/GameStudioPanel.tsx | 49 +- .../src/components/game/GameStylePicker.tsx | 24 +- .../src/components/game/GameStyles.tsx | 235 +++-- .../src/components/grafana/Grafana.test.tsx | 5 +- .../src/components/grafana/Grafana.tsx | 113 +-- .../src/components/guest/VmWorkspace.test.tsx | 2 +- .../src/components/guest/VmWorkspace.tsx | 2 +- .../src/components/layout/BootScreen.test.tsx | 2 +- .../src/components/layout/BootScreen.tsx | 7 +- .../components/layout/CloseConfirmModal.tsx | 41 +- .../src/components/layout/ConfirmModal.tsx | 52 +- .../src/components/layout/DialogHost.tsx | 92 ++ .../src/components/layout/EmptyHero.test.tsx | 34 + .../src/components/layout/EmptyHero.tsx | 59 ++ .../src/components/layout/EmptyState.test.tsx | 37 +- .../src/components/layout/EmptyState.tsx | 68 +- .../components/layout/FeatureChecklist.tsx | 4 +- .../layout/KeyboardShortcutsSheet.tsx | 14 + .../components/layout/MobileShell.test.tsx | 18 +- .../src/components/layout/MobileShell.tsx | 85 +- .../src/components/layout/Modal.test.tsx | 44 + apps/desktop/src/components/layout/Modal.tsx | 60 ++ .../src/components/layout/NoticeBanner.tsx | 56 +- .../components/layout/OverflowMenu.test.tsx | 56 ++ .../src/components/layout/OverflowMenu.tsx | 186 +++- .../components/layout/PromptModal.test.tsx | 47 + .../src/components/layout/PromptModal.tsx | 49 + .../src/components/layout/Select.test.tsx | 84 ++ apps/desktop/src/components/layout/Select.tsx | 100 ++ .../layout/SessionSwitcher.test.tsx | 15 +- .../src/components/layout/SessionSwitcher.tsx | 237 ++--- .../src/components/layout/Sidebar.test.tsx | 202 ++-- .../desktop/src/components/layout/Sidebar.tsx | 309 +++--- .../src/components/layout/Skeleton.test.tsx | 72 ++ .../src/components/layout/Skeleton.tsx | 103 ++ .../src/components/layout/Splitter.tsx | 38 + .../components/layout/SwarmComputerPanel.tsx | 3 +- .../src/components/layout/Tooltip.test.tsx | 50 + .../desktop/src/components/layout/Tooltip.tsx | 25 + apps/desktop/src/components/layout/Topbar.tsx | 218 +++-- .../src/components/projects/ProjectEmpty.tsx | 17 +- .../components/projects/ProjectPane.test.tsx | 8 +- .../src/components/projects/ProjectPane.tsx | 94 +- .../components/projects/TeamEditor.test.tsx | 19 +- .../src/components/projects/TeamEditor.tsx | 47 +- .../settings/AppearanceSettings.test.tsx | 53 + .../settings/AppearanceSettings.tsx | 72 +- .../components/settings/EnvironmentPicker.tsx | 44 +- .../components/settings/ModelPicker.test.tsx | 124 +-- .../src/components/settings/ModelPicker.tsx | 188 ++-- .../src/components/settings/Settings.test.tsx | 37 +- .../src/components/settings/Settings.tsx | 304 +++--- .../src/components/settings/SettingsCards.tsx | 197 ++-- .../src/components/settings/SetupWizard.tsx | 257 +++-- .../src/components/settings/ThemePreview.tsx | 14 +- .../src/components/settings/WorkersModal.tsx | 85 +- .../src/components/slack/MobileSlack.tsx | 11 +- .../src/components/slack/Slack.test.tsx | 38 +- apps/desktop/src/components/slack/Slack.tsx | 215 ++-- .../components/workspace/HostFolderField.tsx | 220 ++--- .../workspace/HostWorkspace.test.tsx | 31 +- .../components/workspace/HostWorkspace.tsx | 105 +- .../src/components/workspace/IdeChat.test.tsx | 2 +- .../components/workspace/IdeEmpty.test.tsx | 2 +- .../src/components/workspace/IdeEmpty.tsx | 26 +- .../workspace/WorkspaceEditor.test.tsx | 22 +- .../components/workspace/WorkspaceEditor.tsx | 286 +++--- .../workspace/WorkspaceEditorTabs.test.tsx | 53 + .../workspace/WorkspaceEditorTabs.tsx | 93 ++ .../workspace/WorkspaceFullscreenButton.tsx | 23 +- .../components/workspace/WorkspaceGitPane.tsx | 84 +- .../workspace/WorkspaceQuickOpen.tsx | 9 + apps/desktop/src/index.css | 244 +++-- apps/desktop/src/lib/configDraft.test.ts | 9 +- apps/desktop/src/lib/configDraft.ts | 12 + apps/desktop/src/lib/dialog.test.ts | 35 + apps/desktop/src/lib/dialog.ts | 56 ++ apps/desktop/src/lib/discoverability.test.ts | 24 +- apps/desktop/src/lib/discoverability.ts | 33 +- apps/desktop/src/lib/docker.test.ts | 23 +- apps/desktop/src/lib/docker.ts | 40 +- apps/desktop/src/lib/formOptions.ts | 76 ++ apps/desktop/src/lib/goldenName.test.ts | 10 +- apps/desktop/src/lib/goldenName.ts | 9 +- apps/desktop/src/lib/notice.test.ts | 55 ++ apps/desktop/src/lib/notice.ts | 109 ++- apps/desktop/src/lib/palette.test.ts | 39 + apps/desktop/src/lib/palette.ts | 133 ++- apps/desktop/src/lib/paletteRecents.test.ts | 22 + apps/desktop/src/lib/paletteRecents.ts | 36 + apps/desktop/src/lib/productMode.test.ts | 8 +- apps/desktop/src/lib/productMode.ts | 14 +- apps/desktop/src/lib/queryMark.test.tsx | 27 + apps/desktop/src/lib/queryMark.tsx | 23 + apps/desktop/src/lib/railNav.test.ts | 10 +- apps/desktop/src/lib/railNav.ts | 12 +- apps/desktop/src/lib/selectControl.ts | 60 ++ apps/desktop/src/lib/sessionStatus.test.ts | 12 + apps/desktop/src/lib/sessionStatus.ts | 25 + apps/desktop/src/lib/sessionUndo.test.ts | 56 ++ apps/desktop/src/lib/sessionUndo.ts | 77 ++ apps/desktop/src/lib/settingsQuery.test.ts | 10 + apps/desktop/src/lib/settingsQuery.ts | 12 + apps/desktop/src/lib/shortcuts.test.ts | 77 ++ apps/desktop/src/lib/shortcuts.ts | 100 ++ apps/desktop/src/lib/splitter.test.ts | 42 + apps/desktop/src/lib/splitter.ts | 28 + apps/desktop/src/lib/useDialogFocus.ts | 13 +- apps/desktop/src/lib/uxContract.test.ts | 63 ++ apps/desktop/src/lib/visualScreens.test.tsx | 182 ++++ apps/desktop/src/main.tsx | 1 + apps/desktop/src/state/store.test.ts | 6 +- apps/desktop/src/state/store.ts | 2 +- apps/desktop/src/styles/ux.css | 542 +++++++++++ apps/desktop/src/visual.tsx | 410 ++++++++ apps/desktop/test/pack.test.ts | 9 + apps/desktop/visual.html | 32 + apps/desktop/vite.config.ts | 11 + apps/server/package.json | 2 +- apps/server/src/app.test.ts | 18 +- apps/server/src/automations/automations.ts | 8 +- apps/server/src/git/azureDevopsForge.ts | 330 +++++++ apps/server/src/git/forge.test.ts | 60 ++ apps/server/src/git/forge.ts | 2 + apps/server/src/git/forgeHttp.ts | 23 + apps/server/src/git/githubForge.ts | 66 +- apps/server/src/git/repos.ts | 7 +- .../server/src/integrations/cursorCli.test.ts | 20 +- apps/server/src/integrations/locusMcp.ts | 2 +- apps/server/src/integrations/mcp.ts | 4 +- apps/server/src/media/media.ts | 4 +- .../server/src/session/claudeCodeTurn.test.ts | 22 +- apps/server/src/session/codexCliTurn.test.ts | 22 +- apps/server/src/session/hostCliTurn.test.ts | 51 +- apps/server/src/session/hostCliTurn.ts | 116 ++- apps/server/src/session/kiroCliTurn.test.ts | 22 +- apps/server/src/session/sessionCreate.ts | 13 +- apps/server/src/session/sessions.ts | 87 ++ apps/server/src/session/subscriptions.ts | 80 ++ .../src/session/workerPlacement.test.ts | 43 +- .../src/workspace/workspaceMeta.test.ts | 35 + apps/server/src/workspace/workspaceMeta.ts | 9 +- docs/agent-backends.md | 11 +- docs/codebase.md | 4 +- docs/public-release.md | 2 +- docs/screenshots/new_agent.png | Bin 67241 -> 53845 bytes docs/screenshots/new_automation.png | Bin 106206 -> 50863 bytes eslint.config.mjs | 5 +- images/agent/.dockerignore | 2 + images/agent/Dockerfile | 13 +- images/agent/vendor/.gitkeep | 0 package-lock.json | 97 +- package.json | 4 +- packages/alb/package.json | 2 +- packages/alb/src/index.test.ts | 12 +- packages/alb/src/schema/index.ts | 4 +- packages/alb/src/workflow.ts | 13 +- packages/compute/package.json | 2 +- packages/guest-agent/package.json | 2 +- packages/guest-agent/src/index.ts | 8 + packages/guest-agent/src/sshCli.test.ts | 50 + packages/guest-agent/src/sshCli.ts | 128 +++ packages/hypervisor/package.json | 2 +- packages/hypervisor/src/args.ts | 36 +- packages/hypervisor/src/detect.ts | 71 +- packages/hypervisor/src/docker.test.ts | 47 +- packages/hypervisor/src/docker.ts | 34 +- packages/hypervisor/src/index.ts | 10 + packages/hypervisor/src/mock.ts | 1 + packages/hypervisor/src/qemu.test.ts | 18 + packages/hypervisor/src/qemu.ts | 31 +- packages/hypervisor/src/qemuArch.test.ts | 46 + packages/hypervisor/src/qemuArch.ts | 62 ++ packages/providers/package.json | 2 +- packages/shared/package.json | 2 +- packages/shared/src/index.test.ts | 20 +- packages/shared/src/models/autonomy.test.ts | 4 + packages/shared/src/models/autonomy.ts | 12 + .../shared/src/models/environment.test.ts | 58 ++ packages/shared/src/models/environment.ts | 95 ++ packages/shared/src/models/git.ts | 66 +- packages/shared/src/models/images.ts | 33 + packages/shared/src/models/integration.ts | 18 + packages/shared/src/models/namedEnv.test.ts | 23 +- packages/shared/src/models/namedEnv.ts | 2 +- packages/shared/src/models/placement.test.ts | 4 +- packages/shared/src/models/placement.ts | 2 +- packages/shared/src/models/setup.ts | 11 +- packages/shared/src/models/subscribe.test.ts | 50 + packages/shared/src/models/subscribe.ts | 157 ++- packages/shared/src/models/theme.ts | 5 +- packages/shared/src/workspaceChanges.test.ts | 6 +- packages/worker/README.md | 4 +- packages/worker/package.json | 4 +- packages/worker/src/app.test.ts | 46 +- packages/worker/src/app.ts | 321 +++++- packages/worker/src/guests.ts | 59 +- packages/worker/src/index.ts | 2 +- packages/worker/src/main.ts | 27 +- packages/worker/src/vmMedia.test.ts | 46 + packages/worker/src/vmMedia.ts | 27 + scripts/prefetch-github-release-asset.mjs | 398 ++++++++ .../prefetch-github-release-asset.test.mjs | 260 +++++ scripts/retry-cmd.mjs | 72 ++ scripts/retry-cmd.test.mjs | 80 ++ 326 files changed, 13778 insertions(+), 6176 deletions(-) create mode 100644 apps/desktop/e2e/visual.spec.ts create mode 100644 apps/desktop/e2e/visual.spec.ts-snapshots/appearance-contrast.png create mode 100644 apps/desktop/e2e/visual.spec.ts-snapshots/appearance-ember.png create mode 100644 apps/desktop/e2e/visual.spec.ts-snapshots/appearance-midnight.png create mode 100644 apps/desktop/e2e/visual.spec.ts-snapshots/appearance-paper.png create mode 100644 apps/desktop/e2e/visual.spec.ts-snapshots/chat-ember.png create mode 100644 apps/desktop/e2e/visual.spec.ts-snapshots/chat-paper.png create mode 100644 apps/desktop/e2e/visual.spec.ts-snapshots/confirm-contrast.png create mode 100644 apps/desktop/e2e/visual.spec.ts-snapshots/confirm-ember-phone.png create mode 100644 apps/desktop/e2e/visual.spec.ts-snapshots/confirm-ember.png create mode 100644 apps/desktop/e2e/visual.spec.ts-snapshots/confirm-midnight.png create mode 100644 apps/desktop/e2e/visual.spec.ts-snapshots/confirm-paper.png create mode 100644 apps/desktop/e2e/visual.spec.ts-snapshots/design-empty-ember.png create mode 100644 apps/desktop/e2e/visual.spec.ts-snapshots/design-empty-paper.png create mode 100644 apps/desktop/e2e/visual.spec.ts-snapshots/empty-hero-ember.png create mode 100644 apps/desktop/e2e/visual.spec.ts-snapshots/empty-hero-paper.png create mode 100644 apps/desktop/e2e/visual.spec.ts-snapshots/empty-hint-ember-phone.png create mode 100644 apps/desktop/e2e/visual.spec.ts-snapshots/empty-hint-ember.png create mode 100644 apps/desktop/e2e/visual.spec.ts-snapshots/empty-hint-paper.png create mode 100644 apps/desktop/e2e/visual.spec.ts-snapshots/feedback-ember-phone.png create mode 100644 apps/desktop/e2e/visual.spec.ts-snapshots/feedback-ember.png create mode 100644 apps/desktop/e2e/visual.spec.ts-snapshots/feedback-paper.png create mode 100644 apps/desktop/e2e/visual.spec.ts-snapshots/ide-empty-ember.png create mode 100644 apps/desktop/e2e/visual.spec.ts-snapshots/ide-empty-paper.png create mode 100644 apps/desktop/e2e/visual.spec.ts-snapshots/new-agent-ember.png create mode 100644 apps/desktop/e2e/visual.spec.ts-snapshots/new-agent-paper.png create mode 100644 apps/desktop/e2e/visual.spec.ts-snapshots/new-automation-ember.png create mode 100644 apps/desktop/e2e/visual.spec.ts-snapshots/new-automation-paper.png create mode 100644 apps/desktop/e2e/visual.spec.ts-snapshots/new-automation-schedule-ember.png create mode 100644 apps/desktop/e2e/visual.spec.ts-snapshots/new-automation-schedule-paper.png create mode 100644 apps/desktop/e2e/visual.spec.ts-snapshots/prompt-ember-phone.png create mode 100644 apps/desktop/e2e/visual.spec.ts-snapshots/prompt-ember.png create mode 100644 apps/desktop/e2e/visual.spec.ts-snapshots/prompt-paper.png create mode 100644 apps/desktop/e2e/visual.spec.ts-snapshots/rail-ember.png create mode 100644 apps/desktop/e2e/visual.spec.ts-snapshots/rail-paper.png create mode 100644 apps/desktop/e2e/visual.spec.ts-snapshots/settings-providers-ember.png create mode 100644 apps/desktop/e2e/visual.spec.ts-snapshots/settings-providers-paper.png create mode 100644 apps/desktop/e2e/visual.spec.ts-snapshots/shortcuts-ember-phone.png create mode 100644 apps/desktop/e2e/visual.spec.ts-snapshots/shortcuts-ember.png create mode 100644 apps/desktop/e2e/visual.spec.ts-snapshots/shortcuts-paper.png create mode 100644 apps/desktop/e2e/visual.spec.ts-snapshots/splitter-ember.png create mode 100644 apps/desktop/e2e/visual.spec.ts-snapshots/splitter-paper.png create mode 100644 apps/desktop/e2e/visual.spec.ts-snapshots/toast-ember-phone.png create mode 100644 apps/desktop/e2e/visual.spec.ts-snapshots/toast-ember.png create mode 100644 apps/desktop/e2e/visual.spec.ts-snapshots/toast-paper.png create mode 100644 apps/desktop/playwright.config.ts create mode 100644 apps/desktop/scripts/capture-readme-screens.mjs create mode 100644 apps/desktop/src/components/chat/ChatThread.test.tsx create mode 100644 apps/desktop/src/components/chat/ChatThread.tsx create mode 100644 apps/desktop/src/components/layout/DialogHost.tsx create mode 100644 apps/desktop/src/components/layout/EmptyHero.test.tsx create mode 100644 apps/desktop/src/components/layout/EmptyHero.tsx create mode 100644 apps/desktop/src/components/layout/KeyboardShortcutsSheet.tsx create mode 100644 apps/desktop/src/components/layout/Modal.test.tsx create mode 100644 apps/desktop/src/components/layout/Modal.tsx create mode 100644 apps/desktop/src/components/layout/PromptModal.test.tsx create mode 100644 apps/desktop/src/components/layout/PromptModal.tsx create mode 100644 apps/desktop/src/components/layout/Select.test.tsx create mode 100644 apps/desktop/src/components/layout/Select.tsx create mode 100644 apps/desktop/src/components/layout/Skeleton.test.tsx create mode 100644 apps/desktop/src/components/layout/Skeleton.tsx create mode 100644 apps/desktop/src/components/layout/Splitter.tsx create mode 100644 apps/desktop/src/components/layout/Tooltip.test.tsx create mode 100644 apps/desktop/src/components/layout/Tooltip.tsx create mode 100644 apps/desktop/src/components/settings/AppearanceSettings.test.tsx create mode 100644 apps/desktop/src/components/workspace/WorkspaceEditorTabs.test.tsx create mode 100644 apps/desktop/src/components/workspace/WorkspaceEditorTabs.tsx create mode 100644 apps/desktop/src/lib/dialog.test.ts create mode 100644 apps/desktop/src/lib/dialog.ts create mode 100644 apps/desktop/src/lib/formOptions.ts create mode 100644 apps/desktop/src/lib/notice.test.ts create mode 100644 apps/desktop/src/lib/paletteRecents.test.ts create mode 100644 apps/desktop/src/lib/paletteRecents.ts create mode 100644 apps/desktop/src/lib/queryMark.test.tsx create mode 100644 apps/desktop/src/lib/queryMark.tsx create mode 100644 apps/desktop/src/lib/selectControl.ts create mode 100644 apps/desktop/src/lib/sessionStatus.test.ts create mode 100644 apps/desktop/src/lib/sessionStatus.ts create mode 100644 apps/desktop/src/lib/sessionUndo.test.ts create mode 100644 apps/desktop/src/lib/sessionUndo.ts create mode 100644 apps/desktop/src/lib/settingsQuery.test.ts create mode 100644 apps/desktop/src/lib/settingsQuery.ts create mode 100644 apps/desktop/src/lib/shortcuts.test.ts create mode 100644 apps/desktop/src/lib/shortcuts.ts create mode 100644 apps/desktop/src/lib/splitter.test.ts create mode 100644 apps/desktop/src/lib/splitter.ts create mode 100644 apps/desktop/src/lib/uxContract.test.ts create mode 100644 apps/desktop/src/lib/visualScreens.test.tsx create mode 100644 apps/desktop/src/styles/ux.css create mode 100644 apps/desktop/src/visual.tsx create mode 100644 apps/desktop/visual.html create mode 100644 apps/server/src/git/azureDevopsForge.ts create mode 100644 images/agent/vendor/.gitkeep create mode 100644 packages/guest-agent/src/sshCli.test.ts create mode 100644 packages/guest-agent/src/sshCli.ts create mode 100644 packages/hypervisor/src/qemuArch.test.ts create mode 100644 packages/hypervisor/src/qemuArch.ts create mode 100644 packages/worker/src/vmMedia.test.ts create mode 100644 packages/worker/src/vmMedia.ts create mode 100644 scripts/prefetch-github-release-asset.mjs create mode 100644 scripts/prefetch-github-release-asset.test.mjs create mode 100644 scripts/retry-cmd.mjs create mode 100644 scripts/retry-cmd.test.mjs diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index d296be6..88df005 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -12,8 +12,8 @@ body: id: version attributes: label: Version - description: Settings → About, or the GitHub Release tag (for example 0.16.1). - placeholder: 0.16.1 + description: Settings → About, or the GitHub Release tag (for example 0.17.0). + placeholder: 0.17.0 validations: required: true - type: dropdown diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 1392198..53aa476 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -12,6 +12,7 @@ private advisory: https://github.com/Modsofthenation/Locus/security/advisories/n Local (CI also runs these, then builds `@locus/desktop`, `locus/agent:0.1`, and the Android debug APK): - [ ] `npm test` +- [ ] `npm run test:visual` (or N/A if the desktop UI did not change) - [ ] `npm run typecheck` - [ ] `npm run lint` - [ ] `npm run format:check` diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4d44e8a..ebb043d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,6 +19,10 @@ jobs: ELECTRON_SKIP_BINARY_DOWNLOAD: "1" - run: sudo apt-get update && sudo apt-get install -y ripgrep - run: npm test + - name: Install Playwright Chromium + run: npx playwright install --with-deps chromium + working-directory: apps/desktop + - run: npm run test:visual - run: npm run typecheck - run: npm run lint - run: npm run format:check @@ -28,6 +32,15 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 + with: + node-version: "22.22" + - name: Prefetch code-server + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + mkdir -p images/agent/vendor + node scripts/prefetch-github-release-asset.mjs --owner coder --repo code-server --tag v4.96.4 --file code-server-4.96.4-linux-amd64.tar.gz --out images/agent/vendor/code-server.tar.gz - run: docker build -t locus/agent:0.1 images/agent android: diff --git a/.github/workflows/dist-linux.yml b/.github/workflows/dist-linux.yml index 48ff810..c1a84fa 100644 --- a/.github/workflows/dist-linux.yml +++ b/.github/workflows/dist-linux.yml @@ -25,7 +25,7 @@ jobs: # v0.4.1 is a Windows-only hotfix; skip Linux packaging for that tag. if: github.ref != 'refs/tags/v0.4.1' runs-on: ubuntu-latest - timeout-minutes: 45 + timeout-minutes: 60 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: @@ -34,8 +34,36 @@ jobs: with: node-version: 22 cache: npm + # github.com/releases/download 504s from Actions. Prefetch Electron (zip + + # SHASUMS256.txt) and 7zip / AppImage / icons through the GitHub API. + - uses: actions/cache/restore@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0 + id: electron-cache + with: + path: | + ~/.cache/electron + ~/.cache/electron-builder + key: electron-${{ runner.os }}-${{ hashFiles('package-lock.json') }}-${{ github.run_id }} + restore-keys: | + electron-${{ runner.os }}-${{ hashFiles('package-lock.json') }}- + electron-${{ runner.os }}- - run: npm ci - - run: npm run dist --workspace=@locus/desktop + - name: Prefetch Electron and electron-builder binaries + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: node scripts/prefetch-github-release-asset.mjs + - name: Package + env: + RETRY_ATTEMPTS: "3" + RETRY_DELAY_MS: "30000" + run: node scripts/retry-cmd.mjs npm run dist --workspace=@locus/desktop + - uses: actions/cache/save@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0 + if: always() + continue-on-error: true + with: + path: | + ~/.cache/electron + ~/.cache/electron-builder + key: ${{ steps.electron-cache.outputs.cache-primary-key }} - name: Checksums run: node scripts/release-checksums.mjs apps/desktop/dist-electron SHA256SUMS-linux - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 diff --git a/.github/workflows/dist-macos.yml b/.github/workflows/dist-macos.yml index 85c838a..c95a116 100644 --- a/.github/workflows/dist-macos.yml +++ b/.github/workflows/dist-macos.yml @@ -25,7 +25,7 @@ jobs: # v0.4.1 is a Windows-only hotfix; skip macOS packaging for that tag. if: github.ref != 'refs/tags/v0.4.1' runs-on: macos-latest - timeout-minutes: 45 + timeout-minutes: 60 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: @@ -34,11 +34,29 @@ jobs: with: node-version: 22 cache: npm + # github.com/releases/download 504s from Actions. Prefetch Electron (zip + + # SHASUMS256.txt) and 7zip / icons through the GitHub API. + - uses: actions/cache/restore@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0 + id: electron-cache + with: + path: | + ~/Library/Caches/electron + ~/Library/Caches/electron-builder + key: electron-${{ runner.os }}-${{ hashFiles('package-lock.json') }}-${{ github.run_id }} + restore-keys: | + electron-${{ runner.os }}-${{ hashFiles('package-lock.json') }}- + electron-${{ runner.os }}- - run: npm ci + - name: Prefetch Electron and electron-builder binaries + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: node scripts/prefetch-github-release-asset.mjs - name: Package env: CSC_LINK_RAW: ${{ secrets.CSC_LINK }} CSC_KEY_PASSWORD_RAW: ${{ secrets.CSC_KEY_PASSWORD }} + RETRY_ATTEMPTS: "3" + RETRY_DELAY_MS: "30000" run: | # GitHub interpolates missing secrets as "", and electron-builder # treats empty CSC_LINK as a certificate path (the desktop dir). @@ -49,7 +67,15 @@ jobs: else export CSC_IDENTITY_AUTO_DISCOVERY=false fi - npm run dist --workspace=@locus/desktop + node scripts/retry-cmd.mjs npm run dist --workspace=@locus/desktop + - uses: actions/cache/save@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0 + if: always() + continue-on-error: true + with: + path: | + ~/Library/Caches/electron + ~/Library/Caches/electron-builder + key: ${{ steps.electron-cache.outputs.cache-primary-key }} - name: Checksums run: node scripts/release-checksums.mjs apps/desktop/dist-electron SHA256SUMS-macos - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 diff --git a/.github/workflows/dist-windows.yml b/.github/workflows/dist-windows.yml index db9e6b5..a7391ed 100644 --- a/.github/workflows/dist-windows.yml +++ b/.github/workflows/dist-windows.yml @@ -23,7 +23,7 @@ concurrency: jobs: package: runs-on: windows-latest - timeout-minutes: 45 + timeout-minutes: 60 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: @@ -32,8 +32,38 @@ jobs: with: node-version: 22 cache: npm + # github.com/releases/download 504s from Actions. Prefetch Electron (zip + + # SHASUMS256.txt) and 7zip / NSIS / icons through the GitHub API so + # @electron/get does not re-fetch checksums after the zip (that 504 is + # what failed Windows after Linux/macOS already succeeded). + - uses: actions/cache/restore@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0 + id: electron-cache + with: + path: | + ~/AppData/Local/electron/Cache + ~/AppData/Local/electron-builder/Cache + key: electron-${{ runner.os }}-${{ hashFiles('package-lock.json') }}-${{ github.run_id }} + restore-keys: | + electron-${{ runner.os }}-${{ hashFiles('package-lock.json') }}- + electron-${{ runner.os }}- - run: npm ci - - run: npm run dist --workspace=@locus/desktop + - name: Prefetch Electron and electron-builder binaries + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: node scripts/prefetch-github-release-asset.mjs + - name: Package + env: + RETRY_ATTEMPTS: "3" + RETRY_DELAY_MS: "30000" + run: node scripts/retry-cmd.mjs npm run dist --workspace=@locus/desktop + - uses: actions/cache/save@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0 + if: always() + continue-on-error: true + with: + path: | + ~/AppData/Local/electron/Cache + ~/AppData/Local/electron-builder/Cache + key: ${{ steps.electron-cache.outputs.cache-primary-key }} - name: Checksums run: node scripts/release-checksums.mjs apps/desktop/dist-electron SHA256SUMS-windows - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 diff --git a/.gitignore b/.gitignore index 0cd7fda..55d5a12 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ node_modules/ +images/agent/vendor/*.tar.gz dist/ build/ .turbo/ @@ -17,6 +18,9 @@ apps/server/dist/ packages/*/dist/ .idea/ .vscode/ +playwright-report/ +blob-report/ +test-results/ *.qcow2 *.iso *.img diff --git a/CHANGELOG.md b/CHANGELOG.md index b34a878..78f655d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,18 @@ ## Unreleased +## 0.17.0 — 2026-11-08 + +Quieter desktop shell, more guest runtimes, extra forges, and more reliable installer builds. Settings → About is **0.17.0**. + +**Runtimes.** Dev Container files can define install, start, ports, and image when a Locus environment file is absent. Docker, Podman, and nerdctl are all valid container engines. Linux guests on Apple silicon use arm64 QEMU; Windows guests stay x86_64. Host coding CLIs can run over SSH inside a Linux VM. LAN workers can run QEMU guests (live display stays on the worker). + +**Forges and subscriptions.** Gitea, Forgejo, and Azure DevOps can be added as forges. Subscribe also understands Linear, Jira, Sentry, Grafana alerts, incident.io, and pull URLs from those extra forges. + +**Desktop.** Shared themed dialogs, empty states, shortcuts, undo toasts, and command-palette search. Failed actions surface in the notice stack with Retry. Confirm and prompt no longer use the browser’s native dialogs. The computer column stays collapsed until the agent opens a canvas. + +**Packaging.** Linux, macOS, and Windows installer jobs prefetch Electron and packaging tools through the GitHub Releases API and retry failed fetches. + ## 0.16.1 — 2026-11-08 First public release. diff --git a/CONTEXT.md b/CONTEXT.md index 7c135b9..46970dc 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -24,7 +24,7 @@ The `/workspace` tree the operator Files pane and the agent file tools share. Ho ## Forge -GitHub, GitLab, or Bitbucket as the remote for clone, push, pull requests, and issues. Locus stays the local catalog. `ForgeClient` selects one forge adapter per account. +GitHub, GitLab, Bitbucket, Gitea, Forgejo, or Azure DevOps as the remote for clone, push, pull requests, and issues. Locus stays the local catalog. `ForgeClient` selects one forge adapter per account. Gitea/Forgejo/Azure DevOps are PAT-only and are not auto-added as empty Settings cards. ## Design diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 39f3acd..d4b4607 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -28,7 +28,16 @@ npm run lint npm run format:check ``` -CI on `main` and pull requests runs those plus `npm run build --workspace=@locus/desktop`, a Docker image build, and `cd apps/android && ./gradlew assembleDebug`. When Docker is available, tests include a real container write/patch/commit eval. Installers are **Package Linux / macOS / Windows**; they also publish to a GitHub Release when you push a `v*` tag. **Actions → Package from main → Run workflow** builds all three platforms from current `main` and uploads artifacts without publishing a release. Only the Linux job sets `generate_release_notes: true` so the three parallel packagers do not concatenate the changelog. +Visual regression (Chromium, Linux baselines in `apps/desktop/e2e`): + +```bash +npx playwright install chromium +npm run test:visual +``` + +After an intentional UI change, recapture screenshots with `npm run test:visual:update`. CI Linux is the source of truth. Harness-backed README shots (New agent, New automation) copy with `npm run capture:readme`. Playwright also runs axe on confirm, prompt, feedback, empty-hero, shortcuts, and chat, plus phone-sized shots of confirm / prompt / toast / feedback / shortcuts. Visual runs with `prefers-reduced-motion: reduce`. + +CI on `main` and pull requests runs those plus `npm run test:visual`, `npm run build --workspace=@locus/desktop`, a Docker image build, and `cd apps/android && ./gradlew assembleDebug`. When Docker is available, tests include a real container write/patch/commit eval. Installers are **Package Linux / macOS / Windows**; they also publish to a GitHub Release when you push a `v*` tag. **Actions → Package from main → Run workflow** builds all three platforms from current `main` and uploads artifacts without publishing a release. Only the Linux job sets `generate_release_notes: true` so the three parallel packagers do not concatenate the changelog. Please run those four checks before you open a PR. Target `main`. @@ -39,8 +48,8 @@ First agent without Docker: Settings → Providers (Ollama needs no key) then Ne After merge to `main`: ```bash -git tag v0.16.1 -git push origin v0.16.1 +git tag v0.17.0 +git push origin v0.17.0 ``` GitHub Releases are the distribution channel (`v*` tags). Pushing a `v*` tag publishes Linux, macOS, and Windows installers, then **Prune GitHub Releases** deletes older published releases so only the last three remain (git tags are kept). See [docs/public-release.md](docs/public-release.md) for listing settings this tree cannot change (visibility, vulnerability reporting, unsigned macOS/Windows). Keep the repository description as the README tagline and topics `ai-agents`, `qemu`, `docker`, `computer-use`, `electron`, `local-first`. diff --git a/README.md b/README.md index 3ebc94f..e860041 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Locus is a desktop control plane for AI coding agents that run on hardware you own — a Docker container, a QEMU virtual machine, or a folder on disk. Chat on one side, the live guest on the other. Code, keys, sessions, and disks stay under `~/.locus`. -This is **v0.16.1** (MIT). The API binds to loopback. Linux, macOS, and Windows. Bring any LLM provider you have a key for. +This is **v0.17.0** (MIT). The API binds to loopback. Linux, macOS, and Windows. Bring any LLM provider you have a key for. ## Install @@ -38,15 +38,15 @@ You describe a task. Locus runs the agent in a guest you own. Review diffs, stee The runtime is chosen per agent and stays fixed. New agents default to Docker when the engine is present, otherwise Host. -**Where** defaults to this machine. Docker, VM, and Host are shapes of a guest; placement is which computer runs it. Other LAN machines running `npm run dev:worker` can host **Docker or Host** agents — paste the printed `locus-worker:add` line in Settings → Workers (or `locus config workers add`), then New agent → Where. VMs stay on this machine. Locus does not scan the LAN. Each app only lists workers it registered. Two apps can share one worker, or run isolated workers on the same host with different `LOCUS_WORKER_PORT` and `LOCUS_WORKER_HOME` — [docs/agent-backends.md](docs/agent-backends.md#multiple-locus-apps-one-worker-host). Cursor Cloud is a provider that skips the guest, not a placement. +**Where** defaults to this machine. Docker, VM, and Host are shapes of a guest; placement is which computer runs it. Other LAN machines running `npm run dev:worker` can host **Docker, Host, or QEMU VM** agents — paste the printed `locus-worker:add` line in Settings → Workers (or `locus config workers add`), then New agent → Where. Worker VMs resolve cloud images from `LOCUS_WORKER_HOME/media`; live VNC stays on the worker localhost in v1 (screenshots and tools still work). Locus does not scan the LAN. Each app only lists workers it registered. Two apps can share one worker, or run isolated workers on the same host with different `LOCUS_WORKER_PORT` and `LOCUS_WORKER_HOME` — [docs/agent-backends.md](docs/agent-backends.md#multiple-locus-apps-one-worker-host). Cursor Cloud is a provider that skips the guest, not a placement. ## Tour -The icon rail is **Agents, Design, Games, Assets, IDE, Automations**, then Board, Projects, Codebase, Brain, Slack, Grafana, Docker. Settings is a full page. Developer / Swarm in the sidebar switches the product shell: Developer is this control plane; Swarm hides the rail and is teammate-first (Teammates, Teams, Routines). +The icon rail is **Agents, Design, IDE, Codebase, Automations**, then **More** for Games, Assets, Board, Projects, Brain, Slack, Grafana, and Docker. Settings is a full page. Developer / Swarm lives in Appearance (and the command palette) — Developer is this control plane; Swarm hides the rail and is teammate-first (Teammates, Teams, Routines). ### Setup -First-run wizard: provider key, optional Docker, optional QEMU, then Developer or Swarm. **Skip for now** leaves a one-line Finish setup hint until you Continue or Dismiss it. The command palette **Finish setup** reopens the wizard. +First-run wizard: provider key, then Developer or Swarm. Docker and QEMU sit under **Set up later**. **Skip for now** leaves a one-line Finish setup hint until you Continue or Dismiss it. The command palette **Finish setup** reopens the wizard.  @@ -58,7 +58,7 @@ Chat sits on the left. The guest sits on the right (Docker opens on **Files**; H Consecutive reads, searches, and commands collapse into one summary (`Explored 8 files, 4 searches, ran 1 command`). Expand the row for the per-call chips. Edits, questions, sub-agents, and pending approvals stay on their own rows. -Ask an agent to **subscribe** to a Slack channel or thread, a GitHub/GitLab/Bitbucket pull request, or a timer. A Listening card stays in the chat, and Agent settings lists every live subscription. The Agents rail surfaces **Needs you** when a chat is waiting on Approve / Deny. **Compare models** and **Replay** sit on those rows. Name a golden image, or export the chat as HTML or PDF, from the agent menu. Multi-file `apply_patch` / `git_diff` cards open the Change Stack walkthrough. A `Locus.md` at the repo root loads ahead of `AGENTS.md`, `CLAUDE.md`, and `.locus/rules`. +Ask an agent to **subscribe** to a Slack channel or thread, a GitHub/GitLab/Bitbucket/Gitea/Forgejo/Azure DevOps pull request, a Linear/Jira/Sentry/incident.io issue, a Grafana alert, or a timer. A Listening card stays in the chat, and Agent settings lists every live subscription. The Agents rail surfaces **Needs you** when a chat is waiting on Approve / Deny. **Compare models** and **Replay** sit on those rows. Name a golden image, or export the chat as HTML or PDF, from the agent menu. Multi-file `apply_patch` / `git_diff` cards open the Change Stack walkthrough. A `Locus.md` at the repo root loads ahead of `AGENTS.md`, `CLAUDE.md`, and `.locus/rules`.