@aiworker/web 1.22.0 → 1.23.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 +4 -4
- package/bin/cli.js +14 -4
- package/bin/cli.test.js +165 -73
- package/bin/lib/cli-api-target.js +2 -2
- package/bin/lib/cli-args.js +119 -119
- package/bin/lib/cli-lifecycle.js +9 -9
- package/bin/lib/cli-network.js +3 -3
- package/bin/lib/cli-paths.js +3 -3
- package/bin/lib/cli-ports.js +2 -2
- package/bin/lib/cli-process.js +4 -4
- package/bin/lib/cli-startup.js +64 -15
- package/bin/lib/cli-tunnel-profiles.js +1 -1
- package/bin/lib/cli-tunnel-utils.js +2 -2
- package/bin/lib/commands-connect-url.js +19 -19
- package/bin/lib/commands-lifecycle.js +27 -27
- package/bin/lib/commands-logs.js +5 -5
- package/bin/lib/commands-models.js +1 -1
- package/bin/lib/commands-projects.js +1 -1
- package/bin/lib/commands-schedule.js +1 -1
- package/bin/lib/commands-serve.js +29 -29
- package/bin/lib/commands-session.js +1 -1
- package/bin/lib/commands-startup.js +45 -10
- package/bin/lib/commands-status.js +1 -1
- package/bin/lib/commands-tunnel.js +40 -40
- package/bin/lib/commands-update.js +1 -1
- package/bin/lib/commands-update.test.js +6 -6
- package/dist/assets/{CodeMirrorCommentWidgets-B3u7UIcE.js → CodeMirrorCommentWidgets-B1Gizi3a.js} +1 -1
- package/dist/assets/DiffView-DXjVsEOw.js +4 -0
- package/dist/assets/DirtyBranchSwitchDialog-aig6zJjg.js +1 -0
- package/dist/assets/FilesView-CJdG0-wk.js +17 -0
- package/dist/assets/GitView-BXsS3jG6.js +6 -0
- package/dist/assets/{JsonTreeView-B0BdZEHG.js → JsonTreeView-D_hpXBnF.js} +1 -1
- package/dist/assets/LinearIssuesView-BoUiLQG1.js +1 -0
- package/dist/assets/MarkdownImageGallery-B_B_a9KS.js +1 -0
- package/dist/assets/MarkdownRendererImpl-xgyyLiBM.js +12 -0
- package/dist/assets/OnboardingScreen-DIp84OWt.js +1 -0
- package/dist/assets/{PatchDiff-DvfqDql7.js → PatchDiff-8p6nLJaU.js} +1 -1
- package/dist/assets/PierreDiffViewer-DjC8nTk9.js +161 -0
- package/dist/assets/PlanView-CJTmOF6I.js +5 -0
- package/dist/assets/SettingsView-DD-JqIJx.js +28 -0
- package/dist/assets/{SettingsWindow-i45U1Cy1.js → SettingsWindow-Cpb4hM6q.js} +1 -1
- package/dist/assets/SymbolsNerdFontMono-Regular-aK5vsLov.woff2 +0 -0
- package/dist/assets/{ToolOutputDialog-BTwyh5OF.js → ToolOutputDialog-DfaKZXNU.js} +6 -6
- package/dist/assets/ToolPartDiffPreview-DtZLzrSK.js +8 -0
- package/dist/assets/{VirtualizedFileDiff-DD8lDVej.js → VirtualizedFileDiff-C8i9HpTB.js} +1 -1
- package/dist/assets/WalkthroughView-DeKMvZlL.js +4 -0
- package/dist/assets/{computeVirtualFileMetrics-BMIRgMHv.js → computeVirtualFileMetrics-BBI7ZwrC.js} +2 -2
- package/dist/assets/contextPanelEmbeddedChat-BdA6bdky.js +1 -0
- package/dist/assets/de-DCrsQ5zS.js +10 -0
- package/dist/assets/es-Dwv4YfH6.js +16 -0
- package/dist/assets/fr-BCWg4Cnx.js +14 -0
- package/dist/assets/ghostty-vt-DdA0Zryv.wasm +0 -0
- package/dist/assets/index-B68h5S5B.css +1 -0
- package/dist/assets/index-DVwMarHI.js +39 -0
- package/dist/assets/{index-CODwDsU_.js → index-jXSK5RrB.js} +1 -1
- package/dist/assets/{isDiffPlainText-BqayB8d3.js → isDiffPlainText-B_x_rVon.js} +1 -1
- package/dist/assets/{isFilePlainText-QfoiKJNY.js → isFilePlainText-DW_xMer1.js} +1 -1
- package/dist/assets/ja-Bruw3RYC.js +16 -0
- package/dist/assets/ko-idhXGF8j.js +16 -0
- package/dist/assets/main-BkyaQhJX.js +930 -0
- package/dist/assets/{main-C5DVOxc6.js → main-CzdPuL0s.js} +2 -2
- package/dist/assets/markdownCore-Cn4Tyejo.js +5 -0
- package/dist/assets/{miniChat-DUJ4irZc.js → miniChat-CCkqKOLp.js} +2 -2
- package/dist/assets/{mobile-CcaxB0n4.js → mobile-mvxTo055.js} +2 -2
- package/dist/assets/patchFileDiff-Csk4iNUi.js +13 -0
- package/dist/assets/pl-CGZWUqZJ.js +16 -0
- package/dist/assets/pt-BR-B8V7u7tA.js +16 -0
- package/dist/assets/renderElectronMiniChatApp-BxoyujA5.js +2 -0
- package/dist/assets/renderMobileApp-B48CsP4r.js +25 -0
- package/dist/assets/runtimeEndpointReset-Bg5gx18H.js +17 -0
- package/dist/assets/scheduledTasksApi-BW0Etzwz.js +1 -0
- package/dist/assets/{shikiHighlight-DYOWuY65.js → shikiHighlight-CRl0SUC9.js} +1 -1
- package/dist/assets/surface-oMvhsog3.js +1 -0
- package/dist/assets/tr-BK_KiO2V.js +7 -0
- package/dist/assets/uk-CG74xV8N.js +16 -0
- package/dist/assets/useAppFontEffects-CPKeOpUo.js +7284 -0
- package/dist/assets/useGitComparison-DH220WmO.js +1 -0
- package/dist/assets/useInlineCommentController-BiWz4AVb.js +2 -0
- package/dist/assets/useRootScrollLock-BS4-kr7P.js +1 -0
- package/dist/assets/{vendor-base-ui-BeMD8X_w.js → vendor-base-ui-DdV8vhHn.js} +1 -1
- package/dist/assets/{vendor-codemirror-language-data-DFZik9R2.js → vendor-codemirror-language-data-Bg4AEeCr.js} +2 -2
- package/dist/assets/vendor-zod-BPAANqvH.js +39 -0
- package/dist/assets/{web-components-Bhg83tjf.js → web-components-z7hfnS8n.js} +1 -1
- package/dist/assets/zh-CN-T4AXofZI.js +16 -0
- package/dist/assets/zh-TW-Dte40sxp.js +16 -0
- package/dist/index.html +21 -59
- package/dist/mini-chat.html +7 -7
- package/dist/mobile.html +5 -5
- package/package.json +3 -4
- package/server/index.d.ts +15 -0
- package/server/index.js +158 -89
- package/server/lib/agent-memory/actions.js +2 -2
- package/server/lib/agent-memory/feature-flag.js +1 -1
- package/server/lib/agent-memory/feature-flag.test.js +8 -8
- package/server/lib/agent-memory/routes.js +2 -2
- package/server/lib/agent-tool/runtime.js +37 -54
- package/server/lib/agent-tool/runtime.test.js +59 -59
- package/server/lib/browser-control/broker.js +3 -3
- package/server/lib/browser-control/broker.test.js +1 -1
- package/server/lib/changelog/update-notes.js +82 -0
- package/server/lib/changelog/update-notes.test.js +59 -0
- package/server/lib/client-auth/remote-clients.test.js +5 -5
- package/server/lib/context-obligatory/runtime.js +7 -7
- package/server/lib/context-obligatory/runtime.test.js +5 -5
- package/server/lib/data-dir-migration.js +40 -0
- package/server/lib/data-dir-migration.test.js +59 -0
- package/server/lib/dev-servers/parse.js +2 -2
- package/server/lib/dev-servers/parse.test.js +2 -2
- package/server/lib/dev-servers/routes.js +1 -1
- package/server/lib/dev-tunnel/DOCUMENTATION.md +13 -4
- package/server/lib/dev-tunnel/client.js +1 -1
- package/server/lib/dev-tunnel/runtime.js +5 -5
- package/server/lib/dev-tunnel/tunnel.test.js +21 -1
- package/server/lib/dictation/local/model-catalog.js +1 -1
- package/server/lib/dictation/local/worker-process.js +2 -2
- package/server/lib/event-stream/directory-ws-bridge.js +1 -1
- package/server/lib/event-stream/global-ws-bridge.js +1 -1
- package/server/lib/event-stream/protocol.test.js +4 -4
- package/server/lib/event-stream/runtime.test.js +18 -18
- package/server/lib/fs/DOCUMENTATION.md +5 -0
- package/server/lib/fs/routes.js +68 -26
- package/server/lib/fs/routes.test.js +208 -22
- package/server/lib/git/DOCUMENTATION.md +32 -6
- package/server/lib/git/identity-storage.js +1 -1
- package/server/lib/git/issue-2746-longpaths.test.js +4 -4
- package/server/lib/git/routes.js +41 -5
- package/server/lib/git/service.js +300 -224
- package/server/lib/git/service.test.js +596 -28
- package/server/lib/github/auth.js +8 -8
- package/server/lib/github/pr-status.js +1 -1
- package/server/lib/github/routes.js +2 -2
- package/server/lib/inherited-env.js +17 -10
- package/server/lib/inherited-env.test.js +10 -11
- package/server/lib/linear/auth.js +8 -8
- package/server/lib/linear/auth.test.js +24 -24
- package/server/lib/linear/issues.test.js +6 -6
- package/server/lib/linear/mapping.test.js +4 -4
- package/server/lib/linear/oauth.js +2 -2
- package/server/lib/linear/oauth.test.js +14 -14
- package/server/lib/linear/routes.js +5 -5
- package/server/lib/linear/routes.test.js +16 -16
- package/server/lib/linear/status-runtime.test.js +9 -9
- package/server/lib/linear/status.js +1 -1
- package/server/lib/linear/status.test.js +15 -15
- package/server/lib/linear/teams.test.js +4 -4
- package/server/lib/magic-prompts/routes.js +2 -2
- package/server/lib/mcp-reconnect/DOCUMENTATION.md +55 -0
- package/server/lib/mcp-reconnect/runtime.js +122 -0
- package/server/lib/mcp-reconnect/runtime.test.js +140 -0
- package/server/lib/message-queue/DOCUMENTATION.md +161 -0
- package/server/lib/message-queue/runtime.js +870 -0
- package/server/lib/message-queue/runtime.test.js +482 -0
- package/server/lib/network-defaults.js +23 -0
- package/server/lib/network-defaults.test.js +52 -0
- package/server/lib/notifications/apns-runtime.js +17 -17
- package/server/lib/notifications/apns-runtime.test.js +11 -11
- package/server/lib/notifications/emitter-runtime.js +1 -1
- package/server/lib/notifications/emitter-runtime.test.js +1 -1
- package/server/lib/notifications/push-runtime.js +7 -7
- package/server/lib/notifications/routes.js +1 -1
- package/server/lib/notifications/runtime.js +1 -1
- package/server/lib/openchamber-control/action-resolution.test.js +10 -10
- package/server/lib/openchamber-control/actions.js +24 -24
- package/server/lib/openchamber-control/error.js +4 -4
- package/server/lib/openchamber-control/routes.js +2 -2
- package/server/lib/openchamber-control/routes.test.js +5 -5
- package/server/lib/openchamber-control/screenshots.js +1 -1
- package/server/lib/openchamber-control/screenshots.test.js +1 -1
- package/server/lib/openchamber-control/service.js +50 -50
- package/server/lib/openchamber-control/service.test.js +5 -5
- package/server/lib/openchamber-sessions/routes.js +124 -22
- package/server/lib/openchamber-sessions/routes.test.js +99 -11
- package/server/lib/opencode/DOCUMENTATION.md +30 -6
- package/server/lib/opencode/auth.js +1 -1
- package/server/lib/opencode/bootstrap-runtime.js +8 -6
- package/server/lib/opencode/cli-options.js +8 -8
- package/server/lib/opencode/config-paths.test.js +116 -0
- package/server/lib/opencode/core-routes.js +8 -7
- package/server/lib/opencode/core-routes.test.js +11 -11
- package/server/lib/opencode/env-config.js +5 -5
- package/server/lib/opencode/env-config.test.js +7 -7
- package/server/lib/opencode/env-runtime.js +10 -10
- package/server/lib/opencode/env-runtime.test.js +18 -18
- package/server/lib/opencode/feature-routes-runtime.js +19 -12
- package/server/lib/opencode/hmr-state-runtime.js +2 -2
- package/server/lib/opencode/hmr-state-runtime.test.js +1 -1
- package/server/lib/opencode/input-history-scope.js +14 -0
- package/server/lib/opencode/lifecycle.js +8 -8
- package/server/lib/opencode/lifecycle.test.js +4 -4
- package/server/lib/opencode/managed-plugin-config.js +35 -0
- package/server/lib/opencode/managed-process-registry.js +3 -3
- package/server/lib/opencode/mcp-oauth-callback.test.js +2 -2
- package/server/lib/opencode/openchamber-routes.js +79 -19
- package/server/lib/opencode/openchamber-routes.test.js +139 -12
- package/server/lib/opencode/plugins.js +2 -2
- package/server/lib/opencode/plugins.test.js +1 -1
- package/server/lib/opencode/project-icon-routes.js +2 -2
- package/server/lib/opencode/project-icon-routes.test.js +1 -1
- package/server/lib/opencode/providers.js +44 -2
- package/server/lib/opencode/providers.test.js +131 -4
- package/server/lib/opencode/proxy.js +2 -2
- package/server/lib/opencode/pwa-manifest-routes.js +1 -1
- package/server/lib/opencode/restart-session-recovery.test.js +1 -1
- package/server/lib/opencode/routes-upgrade.test.js +4 -4
- package/server/lib/opencode/routes.js +19 -17
- package/server/lib/opencode/server-startup-runtime.js +6 -6
- package/server/lib/opencode/session-runtime.js +3 -3
- package/server/lib/opencode/session-runtime.test.js +6 -6
- package/server/lib/opencode/settings-files.js +214 -0
- package/server/lib/opencode/settings-helpers.js +116 -44
- package/server/lib/opencode/settings-helpers.test.js +354 -12
- package/server/lib/opencode/settings-registry.json +747 -0
- package/server/lib/opencode/settings-runtime.js +141 -20
- package/server/lib/opencode/settings-runtime.test.js +191 -4
- package/server/lib/opencode/shared.js +5 -2
- package/server/lib/opencode/shared.test.js +7 -7
- package/server/lib/opencode/shutdown-runtime.js +2 -0
- package/server/lib/opencode/skill-routes.js +2 -1
- package/server/lib/opencode/snippets.js +1 -2
- package/server/lib/opencode/startup-performance.js +1 -1
- package/server/lib/opencode/startup-performance.test.js +6 -6
- package/server/lib/opencode/static-routes-runtime.js +8 -8
- package/server/lib/opencode/static-routes-runtime.test.js +7 -7
- package/server/lib/opencode/upgrade-capability.js +2 -2
- package/server/lib/opencode/upgrade-capability.test.js +3 -3
- package/server/lib/package-manager.js +17 -42
- package/server/lib/package-manager.test.js +19 -19
- package/server/lib/permission-auto-accept/runtime.js +1 -1
- package/server/lib/project-context/DOCUMENTATION.md +25 -1
- package/server/lib/project-context/routes.http.test.js +18 -0
- package/server/lib/project-context/routes.js +16 -1
- package/server/lib/project-context/runtime.js +248 -23
- package/server/lib/project-context/runtime.test.js +106 -3
- package/server/lib/projects/DOCUMENTATION.md +76 -0
- package/server/lib/projects/project-config.js +144 -11
- package/server/lib/projects/project-id.js +17 -0
- package/server/lib/projects/project-setup.js +409 -0
- package/server/lib/projects/project-setup.test.js +557 -0
- package/server/lib/projects/routes.js +59 -0
- package/server/lib/quota/DOCUMENTATION.md +40 -2
- package/server/lib/quota/credentials/providers.js +4 -0
- package/server/lib/quota/credentials/store.js +4 -4
- package/server/lib/quota/credentials/store.test.js +8 -8
- package/server/lib/quota/index.js +1 -0
- package/server/lib/quota/providers/cline-pass.js +125 -0
- package/server/lib/quota/providers/cline-pass.test.js +103 -0
- package/server/lib/quota/providers/exe-dev.js +75 -0
- package/server/lib/quota/providers/exe-dev.test.js +47 -0
- package/server/lib/quota/providers/hyper.js +118 -0
- package/server/lib/quota/providers/hyper.test.js +126 -0
- package/server/lib/quota/providers/index.js +22 -0
- package/server/lib/quota/providers/neuralwatt.js +3 -6
- package/server/lib/quota/providers/neuralwatt.test.js +7 -7
- package/server/lib/quota/providers/ollama-cloud.js +32 -1
- package/server/lib/quota/providers/ollama-cloud.test.js +33 -1
- package/server/lib/quota/providers/opencode-go.js +3 -2
- package/server/lib/quota/providers/opencode-go.test.js +35 -6
- package/server/lib/quota/providers/openrouter.js +119 -24
- package/server/lib/quota/providers/openrouter.test.js +330 -0
- package/server/lib/quota/providers/xai.js +1 -1
- package/server/lib/quota/routes.js +2 -0
- package/server/lib/quota/utils/auth.js +1 -1
- package/server/lib/relay/DOCUMENTATION.md +56 -1
- package/server/lib/relay/cross-compat.test.js +26 -0
- package/server/lib/relay/downstream-scheduler.js +154 -0
- package/server/lib/relay/downstream-scheduler.test.js +162 -0
- package/server/lib/relay/e2ee.js +10 -8
- package/server/lib/relay/flow-control.test.js +177 -0
- package/server/lib/relay/host-client.js +38 -9
- package/server/lib/relay/host-client.test.js +9 -1
- package/server/lib/relay/host-lock.js +1 -1
- package/server/lib/relay/service.js +5 -5
- package/server/lib/relay/tunnel-codec.js +9 -0
- package/server/lib/relay/tunnel-host.js +69 -18
- package/server/lib/relay/tunnel-host.test.js +9 -1
- package/server/lib/scheduled-tasks/issue-2710-double-execution.test.js +2 -2
- package/server/lib/scheduled-tasks/routes.js +5 -5
- package/server/lib/scheduled-tasks/service.js +25 -25
- package/server/lib/scheduled-tasks/service.test.js +1 -1
- package/server/lib/security/bind-host.js +4 -4
- package/server/lib/security/bind-host.test.js +1 -1
- package/server/lib/security/request-security.js +11 -5
- package/server/lib/security/request-security.test.js +14 -14
- package/server/lib/session-assist/runtime.js +15 -18
- package/server/lib/session-folders/routes.js +2 -2
- package/server/lib/session-folders/routes.test.js +8 -8
- package/server/lib/session-goal/DOCUMENTATION.md +20 -7
- package/server/lib/session-goal/create.js +4 -2
- package/server/lib/session-goal/create.test.js +1 -1
- package/server/lib/session-goal/objectives.js +4 -4
- package/server/lib/session-goal/routes.js +1 -1
- package/server/lib/session-goal/runtime.js +91 -30
- package/server/lib/session-goal/runtime.test.js +428 -2
- package/server/lib/session-knowledge/runtime.js +11 -11
- package/server/lib/session-knowledge/runtime.test.js +5 -5
- package/server/lib/skills-catalog/DOCUMENTATION.md +1 -1
- package/server/lib/skills-catalog/cache.test.js +2 -2
- package/server/lib/skills-catalog/disk-cache.js +5 -5
- package/server/lib/skills-catalog/github-meta.test.js +2 -2
- package/server/lib/skills-catalog/install.js +3 -2
- package/server/lib/small-model/DOCUMENTATION.md +7 -2
- package/server/lib/small-model/call.js +18 -7
- package/server/lib/small-model/call.test.js +58 -6
- package/server/lib/small-model/index.js +12 -16
- package/server/lib/small-model/index.test.js +1 -1
- package/server/lib/small-model/resolve.js +1 -1
- package/server/lib/small-model/resolve.test.js +1 -1
- package/server/lib/small-model/routes.js +2 -1
- package/server/lib/small-model/runtime-providers.js +15 -7
- package/server/lib/small-model/runtime-providers.test.js +7 -1
- package/server/lib/system-prompt/runtime.js +3 -22
- package/server/lib/system-prompt/runtime.test.js +3 -3
- package/server/lib/terminal/DOCUMENTATION.md +14 -10
- package/server/lib/terminal/runtime.js +190 -31
- package/server/lib/terminal/runtime.test.js +572 -38
- package/server/lib/terminal/shells.js +20 -3
- package/server/lib/terminal/shells.test.js +33 -2
- package/server/lib/tts/base-url.js +3 -3
- package/server/lib/tts/routes.test.js +20 -20
- package/server/lib/ui-auth/ui-auth.js +12 -9
- package/server/lib/ui-auth/ui-auth.test.js +19 -3
- package/server/lib/ui-auth/ui-passkeys.js +7 -7
- package/server/lib/walkthrough/DOCUMENTATION.md +33 -14
- package/server/lib/walkthrough/index.js +1 -0
- package/server/lib/walkthrough/jobs.test.js +1 -1
- package/server/lib/walkthrough/language.test.js +1 -1
- package/server/lib/walkthrough/model-settings.js +11 -15
- package/server/lib/walkthrough/model-settings.test.js +1 -1
- package/server/lib/walkthrough/prompt.js +3 -1
- package/server/lib/walkthrough/pull-request.test.js +5 -5
- package/server/lib/walkthrough/reproduce-2607.test.js +2 -2
- package/server/lib/walkthrough/routes.test.js +26 -5
- package/server/lib/walkthrough/sources.js +23 -2
- package/server/lib/walkthrough/store.js +3 -3
- package/server/lib/walkthrough/store.test.js +1 -1
- package/server/opencode-proxy.test.js +2 -2
- package/server/proxy-headers.js +1 -1
- package/server/sse-routes.test.js +4 -4
- package/README.md.bak +0 -250
- package/bin/lib/DOCUMENTATION.md.bak +0 -163
- package/dist/assets/CommitSection-SGBCAW0w.js +0 -1
- package/dist/assets/DiffView-DluiRSla.js +0 -4
- package/dist/assets/FilesView-Bdq8ppva.js +0 -17
- package/dist/assets/GitView-Znn7VJcP.js +0 -6
- package/dist/assets/LinearIssuesView-jP-hXyac.js +0 -1
- package/dist/assets/MarkdownImageGallery-ClOvuZxC.js +0 -1
- package/dist/assets/MarkdownRendererImpl-Dt9mjTRN.js +0 -12
- package/dist/assets/OnboardingScreen-D2DG7K6b.js +0 -1
- package/dist/assets/PierreDiffViewer-B7m3f0d7.js +0 -157
- package/dist/assets/PlanView-B0F3BtPZ.js +0 -5
- package/dist/assets/SettingsView-BBW-S-gh.js +0 -28
- package/dist/assets/ToolPartDiffPreview-CUNe28U1.js +0 -8
- package/dist/assets/WalkthroughView-egQgxel3.js +0 -4
- package/dist/assets/contextPanelEmbeddedChat-C3fjDbdg.js +0 -1
- package/dist/assets/de-C1P6X5Rs.js +0 -9
- package/dist/assets/es-CDgaF1_t.js +0 -15
- package/dist/assets/fr-Bgxqvvz_.js +0 -13
- package/dist/assets/index-C57jT0Sa.css +0 -1
- package/dist/assets/index-DW_E3rnL.js +0 -7
- package/dist/assets/ja-BDRrEcNP.js +0 -15
- package/dist/assets/ko-Ba8sQV79.js +0 -15
- package/dist/assets/main-Bv5Pkngh.js +0 -928
- package/dist/assets/markdownCore-CZwzz5gw.js +0 -3
- package/dist/assets/patchFileDiff-yNfWXN4E.js +0 -10
- package/dist/assets/pl-Dioz29BI.js +0 -15
- package/dist/assets/pt-BR-DIs6ElwV.js +0 -15
- package/dist/assets/renderElectronMiniChatApp-BcKLHXKe.js +0 -2
- package/dist/assets/renderMobileApp-D9yQJy0L.js +0 -25
- package/dist/assets/runtimeEndpointReset-CjhvxLH0.js +0 -16
- package/dist/assets/scheduledTasksApi-DLW0mkQK.js +0 -1
- package/dist/assets/tr-DFnMlPxY.js +0 -6
- package/dist/assets/uk-ZrCNjM7Y.js +0 -15
- package/dist/assets/useAppFontEffects-CjrntdL0.js +0 -7310
- package/dist/assets/useInlineCommentController-CDEuSlPH.js +0 -2
- package/dist/assets/useRootScrollLock-BHYPkrLv.js +0 -1
- package/dist/assets/vendor-ghostty-web-CrxinGGH.js +0 -13
- package/dist/assets/vendor-zod-BLfBSgBh.js +0 -39
- package/dist/assets/zh-CN-iv_AV4EC.js +0 -15
- package/dist/assets/zh-TW-CzEUk38j.js +0 -15
- package/server/index.d.ts.bak +0 -39
- package/server/lib/agent-tool/DOCUMENTATION.md.bak +0 -119
- package/server/lib/browser-control/DOCUMENTATION.md.bak +0 -50
- package/server/lib/context-obligatory/DOCUMENTATION.md.bak +0 -19
- package/server/lib/dev-tunnel/DOCUMENTATION.md.bak +0 -57
- package/server/lib/dictation/DOCUMENTATION.md.bak +0 -100
- package/server/lib/event-stream/DOCUMENTATION.md.bak +0 -62
- package/server/lib/fs/DOCUMENTATION.md.bak +0 -47
- package/server/lib/git/DOCUMENTATION.md.bak +0 -193
- package/server/lib/github/DOCUMENTATION.md.bak +0 -183
- package/server/lib/linear/DOCUMENTATION.md.bak +0 -97
- package/server/lib/linear/OAUTH-BROKER-HANDOFF.md.bak +0 -91
- package/server/lib/markdown-image-grants/DOCUMENTATION.md.bak +0 -40
- package/server/lib/notifications/APNS.md.bak +0 -135
- package/server/lib/notifications/DOCUMENTATION.md.bak +0 -138
- package/server/lib/openchamber-control/DOCUMENTATION.md.bak +0 -64
- package/server/lib/opencode/DOCUMENTATION.md.bak +0 -431
- package/server/lib/opencode/managed-process-registry.d.ts.bak +0 -13
- package/server/lib/opencode/provider-env-aliases.d.ts.bak +0 -3
- package/server/lib/permission-auto-accept/DOCUMENTATION.md.bak +0 -34
- package/server/lib/project-context/DOCUMENTATION.md.bak +0 -160
- package/server/lib/quota/DOCUMENTATION.md.bak +0 -123
- package/server/lib/relay/DOCUMENTATION.md.bak +0 -100
- package/server/lib/scheduled-tasks/DOCUMENTATION.md.bak +0 -182
- package/server/lib/session-assist/DOCUMENTATION.md.bak +0 -66
- package/server/lib/session-goal/DOCUMENTATION.md.bak +0 -213
- package/server/lib/session-knowledge/DOCUMENTATION.md.bak +0 -89
- package/server/lib/skills-catalog/DOCUMENTATION.md.bak +0 -151
- package/server/lib/small-model/DOCUMENTATION.md.bak +0 -212
- package/server/lib/system-prompt/DOCUMENTATION.md.bak +0 -36
- package/server/lib/terminal/DOCUMENTATION.md.bak +0 -50
- package/server/lib/text/DOCUMENTATION.md.bak +0 -35
- package/server/lib/tts/DOCUMENTATION.md.bak +0 -147
- package/server/lib/tunnels/DOCUMENTATION.md.bak +0 -21
- package/server/lib/ui-auth/DOCUMENTATION.md.bak +0 -44
- package/server/lib/walkthrough/DOCUMENTATION.md.bak +0 -411
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// Release notes for the update dialog.
|
|
2
|
+
//
|
|
3
|
+
// The repo publishes `changelog/index.json` on `main`: one object per release,
|
|
4
|
+
// newest first, with the app notes grouped as New / Improvements / Fixes /
|
|
5
|
+
// Misc. This module fetches it and renders the releases between the installed
|
|
6
|
+
// version (exclusive) and the offered one (inclusive) as the Markdown the
|
|
7
|
+
// dialog already understands: a `## [x.y.z] - YYYY-MM-DD` header per release,
|
|
8
|
+
// the release title in bold, the intro, then the groups.
|
|
9
|
+
//
|
|
10
|
+
// Any failure (network, 404, unexpected shape) yields null: the update is
|
|
11
|
+
// still offered, only without notes.
|
|
12
|
+
|
|
13
|
+
export const CHANGELOG_INDEX_URL = 'https://raw.githubusercontent.com/openchamber/openchamber/main/changelog/index.json';
|
|
14
|
+
|
|
15
|
+
const GROUPS = [
|
|
16
|
+
['new', 'New'],
|
|
17
|
+
['improvements', 'Improvements'],
|
|
18
|
+
['fixes', 'Fixes'],
|
|
19
|
+
['misc', 'Misc'],
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
const VERSION_PATTERN = /^\d+\.\d+\.\d+$/;
|
|
23
|
+
const DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
|
|
24
|
+
|
|
25
|
+
// The file crosses a network boundary with no schema library on this side, so
|
|
26
|
+
// each field is coerced into its domain shape here; a release without a valid
|
|
27
|
+
// version and date is dropped.
|
|
28
|
+
const text = (value) => (value === null || value === undefined ? null : String(value).trim() || null);
|
|
29
|
+
const bulletList = (value) => (Array.isArray(value) ? value.map((item) => String(item)) : []);
|
|
30
|
+
|
|
31
|
+
const parseRelease = (entry) => {
|
|
32
|
+
if (entry === null || entry === undefined) return null;
|
|
33
|
+
const version = text(entry.version);
|
|
34
|
+
const date = text(entry.date);
|
|
35
|
+
if (!version || !VERSION_PATTERN.test(version) || !date || !DATE_PATTERN.test(date)) return null;
|
|
36
|
+
const app = entry.app ?? {};
|
|
37
|
+
return {
|
|
38
|
+
version,
|
|
39
|
+
date,
|
|
40
|
+
title: text(entry.title),
|
|
41
|
+
intro: text(entry.intro),
|
|
42
|
+
groups: GROUPS.map(([key, heading]) => ({ heading, bullets: bulletList(app[key]) })),
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const renderRelease = (release) => {
|
|
47
|
+
const lines = [`## [${release.version}] - ${release.date}`, ''];
|
|
48
|
+
if (release.title) lines.push(`**${release.title}**`, '');
|
|
49
|
+
if (release.intro) lines.push(release.intro, '');
|
|
50
|
+
for (const { heading, bullets } of release.groups) {
|
|
51
|
+
if (bullets.length === 0) continue;
|
|
52
|
+
lines.push(`### ${heading}`, '', ...bullets.map((bullet) => `- ${bullet}`), '');
|
|
53
|
+
}
|
|
54
|
+
return lines.join('\n').trim();
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Markdown for the releases in (fromVersion, toVersion], newest first, or
|
|
59
|
+
* null when the index holds none of them.
|
|
60
|
+
*/
|
|
61
|
+
export function renderUpdateNotes(index, fromVersion, toVersion, compareVersions) {
|
|
62
|
+
if (!Array.isArray(index)) return null;
|
|
63
|
+
const relevant = index
|
|
64
|
+
.map(parseRelease)
|
|
65
|
+
.filter((release) => release !== null)
|
|
66
|
+
.filter((release) => compareVersions(release.version, fromVersion) > 0 && compareVersions(release.version, toVersion) <= 0)
|
|
67
|
+
.sort((a, b) => compareVersions(b.version, a.version));
|
|
68
|
+
if (relevant.length === 0) return null;
|
|
69
|
+
return relevant.map(renderRelease).join('\n\n');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Fetch the index and render the notes for one update; null on any failure. */
|
|
73
|
+
export async function fetchUpdateNotes(fromVersion, toVersion, compareVersions, options = {}) {
|
|
74
|
+
const fetchImpl = options.fetch ?? fetch;
|
|
75
|
+
try {
|
|
76
|
+
const response = await fetchImpl(CHANGELOG_INDEX_URL, { signal: AbortSignal.timeout(options.timeoutMs ?? 10_000) });
|
|
77
|
+
if (!response.ok) return null;
|
|
78
|
+
return renderUpdateNotes(await response.json(), fromVersion, toVersion, compareVersions);
|
|
79
|
+
} catch {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { fetchUpdateNotes, renderUpdateNotes } from './update-notes.js';
|
|
3
|
+
|
|
4
|
+
const compare = (a, b) => {
|
|
5
|
+
const pa = a.split('.').map(Number);
|
|
6
|
+
const pb = b.split('.').map(Number);
|
|
7
|
+
for (let i = 0; i < 3; i += 1) if (pa[i] !== pb[i]) return pa[i] - pb[i];
|
|
8
|
+
return 0;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const index = [
|
|
12
|
+
{ version: '1.2.3', date: '2026-03-03', title: 'Comments everywhere', intro: 'A short intro.', app: { new: ['**Comments:** on code.'], improvements: [], fixes: ['Chat: no freeze.'], misc: [] }, vscode: null },
|
|
13
|
+
{ version: '1.2.2', date: '2026-03-02', title: null, intro: null, app: { new: [], improvements: ['Faster.'], fixes: [], misc: [] }, vscode: null },
|
|
14
|
+
{ version: '1.2.1', date: '2026-03-01', title: 'Old', intro: null, app: { new: ['Older.'], improvements: [], fixes: [], misc: [] }, vscode: null },
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
describe('renderUpdateNotes', () => {
|
|
18
|
+
it('renders the releases after the installed version up to the offered one, newest first', () => {
|
|
19
|
+
expect(renderUpdateNotes(index, '1.2.1', '1.2.3', compare)).toBe(`## [1.2.3] - 2026-03-03
|
|
20
|
+
|
|
21
|
+
**Comments everywhere**
|
|
22
|
+
|
|
23
|
+
A short intro.
|
|
24
|
+
|
|
25
|
+
### New
|
|
26
|
+
|
|
27
|
+
- **Comments:** on code.
|
|
28
|
+
|
|
29
|
+
### Fixes
|
|
30
|
+
|
|
31
|
+
- Chat: no freeze.
|
|
32
|
+
|
|
33
|
+
## [1.2.2] - 2026-03-02
|
|
34
|
+
|
|
35
|
+
### Improvements
|
|
36
|
+
|
|
37
|
+
- Faster.`);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('returns null when nothing lies in the range or the payload is not an index', () => {
|
|
41
|
+
expect(renderUpdateNotes(index, '1.2.3', '1.2.3', compare)).toBe(null);
|
|
42
|
+
expect(renderUpdateNotes({ entries: [] }, '1.0.0', '9.9.9', compare)).toBe(null);
|
|
43
|
+
expect(renderUpdateNotes([{ version: 'nope' }, { version: '1.2.2', date: '2026' }], '1.0.0', '9.9.9', compare)).toBe(null);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe('fetchUpdateNotes', () => {
|
|
48
|
+
it('turns a failed request or a thrown fetch into null', async () => {
|
|
49
|
+
const notFound = async () => ({ ok: false });
|
|
50
|
+
expect(await fetchUpdateNotes('1.0.0', '2.0.0', compare, { fetch: notFound })).toBe(null);
|
|
51
|
+
const throwing = async () => { throw new Error('offline'); };
|
|
52
|
+
expect(await fetchUpdateNotes('1.0.0', '2.0.0', compare, { fetch: throwing })).toBe(null);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('renders a successful response', async () => {
|
|
56
|
+
const ok = async () => ({ ok: true, json: async () => index });
|
|
57
|
+
expect(await fetchUpdateNotes('1.2.2', '1.2.3', compare, { fetch: ok })).toMatch(/^## \[1\.2\.3\] - 2026-03-03\n\n\*\*Comments everywhere\*\*/);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -86,15 +86,15 @@ describe('remote client auth runtime', () => {
|
|
|
86
86
|
it('keeps the replaced record label on a dedupe re-mint without an explicit label', async () => {
|
|
87
87
|
const { dir, runtime } = await createRuntime();
|
|
88
88
|
try {
|
|
89
|
-
await runtime.createClient({ label: 'Iryna iPhone', dedupeKey: 'mobile:device-1', fallbackLabel: '
|
|
90
|
-
const remint = await runtime.createClient({ dedupeKey: 'mobile:device-1', fallbackLabel: '
|
|
89
|
+
await runtime.createClient({ label: 'Iryna iPhone', dedupeKey: 'mobile:device-1', fallbackLabel: 'AiWorker Mobile' });
|
|
90
|
+
const remint = await runtime.createClient({ dedupeKey: 'mobile:device-1', fallbackLabel: 'AiWorker Mobile' });
|
|
91
91
|
expect(remint.client.label).toBe('Iryna iPhone');
|
|
92
92
|
|
|
93
|
-
const renamed = await runtime.createClient({ label: 'Work phone', dedupeKey: 'mobile:device-1', fallbackLabel: '
|
|
93
|
+
const renamed = await runtime.createClient({ label: 'Work phone', dedupeKey: 'mobile:device-1', fallbackLabel: 'AiWorker Mobile' });
|
|
94
94
|
expect(renamed.client.label).toBe('Work phone');
|
|
95
95
|
|
|
96
|
-
const fresh = await runtime.createClient({ dedupeKey: 'mobile:device-2', fallbackLabel: '
|
|
97
|
-
expect(fresh.client.label).toBe('
|
|
96
|
+
const fresh = await runtime.createClient({ dedupeKey: 'mobile:device-2', fallbackLabel: 'AiWorker Mobile' });
|
|
97
|
+
expect(fresh.client.label).toBe('AiWorker Mobile');
|
|
98
98
|
} finally {
|
|
99
99
|
await fs.rm(dir, { recursive: true, force: true });
|
|
100
100
|
}
|
|
@@ -5,15 +5,15 @@ const isRecord = (value) => Boolean(value && typeof value === 'object' && !Array
|
|
|
5
5
|
|
|
6
6
|
const readContextState = (session) => {
|
|
7
7
|
const metadata = isRecord(session?.metadata) ? session.metadata : {};
|
|
8
|
-
const
|
|
9
|
-
const messages = Array.isArray(
|
|
10
|
-
?
|
|
8
|
+
const aiworker = isRecord(metadata.aiworker) ? metadata.aiworker : {};
|
|
9
|
+
const messages = Array.isArray(aiworker.context_obligatory_messages)
|
|
10
|
+
? aiworker.context_obligatory_messages.filter((item) =>
|
|
11
11
|
isRecord(item)
|
|
12
12
|
&& typeof item.id === 'string'
|
|
13
13
|
&& typeof item.createdAt === 'number'
|
|
14
14
|
&& (item.role === 'user' || item.role === 'assistant'))
|
|
15
15
|
: [];
|
|
16
|
-
return { metadata,
|
|
16
|
+
return { metadata, aiworker, messages };
|
|
17
17
|
};
|
|
18
18
|
|
|
19
19
|
const buildContextPrompt = (entries) => {
|
|
@@ -89,7 +89,7 @@ export const createContextObligatoryRuntime = ({
|
|
|
89
89
|
const summary = recent.toReversed().find((message) =>
|
|
90
90
|
message?.info?.role === 'assistant' && message.info.summary === true)?.info;
|
|
91
91
|
if (!summary?.id || !summary?.time?.completed) return;
|
|
92
|
-
if (state.
|
|
92
|
+
if (state.aiworker.context_obligatory_last_compaction_message_id === summary.id) return;
|
|
93
93
|
|
|
94
94
|
const fetched = await Promise.allSettled(state.messages.map(async (pinned) => {
|
|
95
95
|
const message = await openCodeFetch(
|
|
@@ -138,8 +138,8 @@ export const createContextObligatoryRuntime = ({
|
|
|
138
138
|
body: {
|
|
139
139
|
metadata: {
|
|
140
140
|
...freshState.metadata,
|
|
141
|
-
|
|
142
|
-
...freshState.
|
|
141
|
+
aiworker: {
|
|
142
|
+
...freshState.aiworker,
|
|
143
143
|
context_obligatory_last_compaction_message_id: summary.id,
|
|
144
144
|
// Recorded together with the cursor: the session now carries this
|
|
145
145
|
// knowledge again, so the next send must not repeat it.
|
|
@@ -23,7 +23,7 @@ describe('context obligatory runtime', () => {
|
|
|
23
23
|
sessionReads += 1;
|
|
24
24
|
return json({
|
|
25
25
|
id: 'ses_1',
|
|
26
|
-
metadata: {
|
|
26
|
+
metadata: { aiworker: { context_obligatory_messages: [
|
|
27
27
|
{ id: 'msg_2', createdAt: 20, role: 'assistant' },
|
|
28
28
|
{ id: 'msg_1', createdAt: 10, role: 'user' },
|
|
29
29
|
] } },
|
|
@@ -58,7 +58,7 @@ describe('context obligatory runtime', () => {
|
|
|
58
58
|
expect(payload.parts[0].text).toContain('Only if no tasks or next steps remain');
|
|
59
59
|
expect(payload.parts[0].text).toContain('no more than one short paragraph');
|
|
60
60
|
const patch = requests.find((request) => request.method === 'PATCH');
|
|
61
|
-
expect(JSON.parse(patch.body).metadata.
|
|
61
|
+
expect(JSON.parse(patch.body).metadata.aiworker.context_obligatory_last_compaction_message_id).toBe('msg_summary');
|
|
62
62
|
expect(sessionReads).toBe(2);
|
|
63
63
|
runtime.stop();
|
|
64
64
|
});
|
|
@@ -75,7 +75,7 @@ describe('context obligatory runtime', () => {
|
|
|
75
75
|
if (url.pathname === '/session/ses_1' && init.method === 'PATCH') return json({});
|
|
76
76
|
if (url.pathname === '/session/ses_1') return json({
|
|
77
77
|
id: 'ses_1',
|
|
78
|
-
metadata: {
|
|
78
|
+
metadata: { aiworker: { knowledge_context_delivered: 'sig-before-compaction' } },
|
|
79
79
|
});
|
|
80
80
|
if (url.pathname === '/session/ses_1/message') return json([
|
|
81
81
|
{ info: { id: 'msg_agent', role: 'assistant', providerID: 'provider', modelID: 'model', agent: 'build' } },
|
|
@@ -112,7 +112,7 @@ describe('context obligatory runtime', () => {
|
|
|
112
112
|
expect(JSON.parse(prompt.body).parts[0].text).toContain('Remember this.');
|
|
113
113
|
const patch = requests.find((request) => request.method === 'PATCH');
|
|
114
114
|
// Recorded with the cursor, so the next ordinary send does not repeat it.
|
|
115
|
-
expect(JSON.parse(patch.body).metadata.
|
|
115
|
+
expect(JSON.parse(patch.body).metadata.aiworker.knowledge_context_delivered).toBe('sig-1');
|
|
116
116
|
});
|
|
117
117
|
|
|
118
118
|
it('sends pinned messages and project knowledge as one message', async () => {
|
|
@@ -123,7 +123,7 @@ describe('context obligatory runtime', () => {
|
|
|
123
123
|
if (url.pathname === '/session/ses_1' && init.method === 'PATCH') return json({});
|
|
124
124
|
if (url.pathname === '/session/ses_1') return json({
|
|
125
125
|
id: 'ses_1',
|
|
126
|
-
metadata: {
|
|
126
|
+
metadata: { aiworker: { context_obligatory_messages: [{ id: 'msg_1', createdAt: 10, role: 'user' }] } },
|
|
127
127
|
});
|
|
128
128
|
if (url.pathname === '/session/ses_1/message') return json([
|
|
129
129
|
{ info: { id: 'msg_agent', role: 'assistant', providerID: 'provider', modelID: 'model', agent: 'build' } },
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// One-time copy of the user-authored folders into a custom data directory.
|
|
2
|
+
//
|
|
3
|
+
// `AIWORKER_DATA_DIR` is documented as "the AiWorker data directory",
|
|
4
|
+
// but for a long time only the flat files (settings, auth, push tokens)
|
|
5
|
+
// followed it while `projects/`, `themes/`, and `speech-models/` stayed under
|
|
6
|
+
// `~/.config/openchamber`. Now every folder hangs off the data directory, so an
|
|
7
|
+
// instance that already ran with a custom directory finds those folders in
|
|
8
|
+
// the old place. This copies each one over once: only when the new location
|
|
9
|
+
// does not exist yet and the old one does. It copies rather than moves
|
|
10
|
+
// because a second instance on the same machine (a custom directory next to
|
|
11
|
+
// the default one) must not strip the default instance of its projects.
|
|
12
|
+
|
|
13
|
+
const USER_DIR_ENTRIES = Object.freeze(['projects', 'themes', 'speech-models']);
|
|
14
|
+
|
|
15
|
+
const exists = async (fsPromises, target) => fsPromises.access(target).then(() => true, () => false);
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Copy the user folders from `legacyRoot` into `dataDir`. Returns the entries
|
|
19
|
+
* copied. A no-op when the two roots are the same directory. A folder whose
|
|
20
|
+
* copy fails is reported through `warn` and its partial copy removed; the
|
|
21
|
+
* server keeps starting, reading the (empty) new location.
|
|
22
|
+
*/
|
|
23
|
+
export const migrateLegacyUserDirs = async ({ fsPromises, path, dataDir, legacyRoot, warn = () => {}, entries = USER_DIR_ENTRIES }) => {
|
|
24
|
+
if (path.resolve(dataDir) === path.resolve(legacyRoot)) return [];
|
|
25
|
+
const moved = [];
|
|
26
|
+
for (const entry of entries) {
|
|
27
|
+
const from = path.join(legacyRoot, entry);
|
|
28
|
+
const to = path.join(dataDir, entry);
|
|
29
|
+
if (await exists(fsPromises, to) || !(await exists(fsPromises, from))) continue;
|
|
30
|
+
try {
|
|
31
|
+
await fsPromises.mkdir(dataDir, { recursive: true });
|
|
32
|
+
await fsPromises.cp(from, to, { recursive: true, errorOnExist: true, force: false });
|
|
33
|
+
moved.push(entry);
|
|
34
|
+
} catch (error) {
|
|
35
|
+
await fsPromises.rm(to, { recursive: true, force: true }).catch(() => {});
|
|
36
|
+
warn(`Failed to copy ${from} to ${to}: ${error instanceof Error ? error.message : String(error)}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return moved;
|
|
40
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import fsPromises from 'fs/promises';
|
|
5
|
+
|
|
6
|
+
import { migrateLegacyUserDirs } from './data-dir-migration.js';
|
|
7
|
+
|
|
8
|
+
const setup = async () => {
|
|
9
|
+
const root = await fsPromises.mkdtemp(path.join(os.tmpdir(), 'oc-data-dir-'));
|
|
10
|
+
const legacyRoot = path.join(root, 'legacy');
|
|
11
|
+
const dataDir = path.join(root, 'custom');
|
|
12
|
+
await fsPromises.mkdir(path.join(legacyRoot, 'projects'), { recursive: true });
|
|
13
|
+
await fsPromises.writeFile(path.join(legacyRoot, 'projects', 'p.json'), '{"a":1}');
|
|
14
|
+
await fsPromises.mkdir(path.join(legacyRoot, 'themes'), { recursive: true });
|
|
15
|
+
return { root, legacyRoot, dataDir, cleanup: () => fsPromises.rm(root, { recursive: true, force: true }) };
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
describe('migrateLegacyUserDirs', () => {
|
|
19
|
+
it('copies the user folders once into a custom data dir and leaves the originals', async () => {
|
|
20
|
+
const { legacyRoot, dataDir, cleanup } = await setup();
|
|
21
|
+
try {
|
|
22
|
+
const warnings = [];
|
|
23
|
+
const moved = await migrateLegacyUserDirs({ fsPromises, path, dataDir, legacyRoot, warn: (message) => warnings.push(message) });
|
|
24
|
+
expect(moved).toEqual(['projects', 'themes']);
|
|
25
|
+
expect(warnings).toEqual([]);
|
|
26
|
+
expect(await fsPromises.readFile(path.join(dataDir, 'projects', 'p.json'), 'utf8')).toBe('{"a":1}');
|
|
27
|
+
// The default instance keeps its own copy: a second instance must not strip it.
|
|
28
|
+
expect(await fsPromises.readFile(path.join(legacyRoot, 'projects', 'p.json'), 'utf8')).toBe('{"a":1}');
|
|
29
|
+
// A second start copies nothing more.
|
|
30
|
+
expect(await migrateLegacyUserDirs({ fsPromises, path, dataDir, legacyRoot })).toEqual([]);
|
|
31
|
+
} finally {
|
|
32
|
+
await cleanup();
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('never merges into a folder that already exists in the data dir', async () => {
|
|
37
|
+
const { legacyRoot, dataDir, cleanup } = await setup();
|
|
38
|
+
try {
|
|
39
|
+
await fsPromises.mkdir(path.join(dataDir, 'projects'), { recursive: true });
|
|
40
|
+
await fsPromises.writeFile(path.join(dataDir, 'projects', 'q.json'), '{}');
|
|
41
|
+
const moved = await migrateLegacyUserDirs({ fsPromises, path, dataDir, legacyRoot });
|
|
42
|
+
expect(moved).toEqual(['themes']);
|
|
43
|
+
expect(await fsPromises.readdir(path.join(dataDir, 'projects'))).toEqual(['q.json']);
|
|
44
|
+
expect(await fsPromises.readdir(path.join(legacyRoot, 'projects'))).toEqual(['p.json']);
|
|
45
|
+
} finally {
|
|
46
|
+
await cleanup();
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('is a no-op when the data dir is the default root', async () => {
|
|
51
|
+
const { legacyRoot, cleanup } = await setup();
|
|
52
|
+
try {
|
|
53
|
+
expect(await migrateLegacyUserDirs({ fsPromises, path, dataDir: legacyRoot, legacyRoot })).toEqual([]);
|
|
54
|
+
expect(await fsPromises.readdir(path.join(legacyRoot, 'projects'))).toEqual(['p.json']);
|
|
55
|
+
} finally {
|
|
56
|
+
await cleanup();
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -143,7 +143,7 @@ export const parseNetstatListeners = (output) => {
|
|
|
143
143
|
/**
|
|
144
144
|
* Narrows raw listeners to the ones worth offering as a preview target.
|
|
145
145
|
*
|
|
146
|
-
* `ownPorts` removes
|
|
146
|
+
* `ownPorts` removes AiWorker's own listeners — offering the user a preview
|
|
147
147
|
* of the app they are already looking at is pure noise.
|
|
148
148
|
*/
|
|
149
149
|
export const selectDevServerCandidates = (listeners, { ownPorts = [], ownPids = [] } = {}) => {
|
|
@@ -169,7 +169,7 @@ const PROC_LOOPBACK_ADDRESSES = new Set(['0100007F', '00000000000000000000000001
|
|
|
169
169
|
* Parses `/proc/net/tcp` and `/proc/net/tcp6`.
|
|
170
170
|
*
|
|
171
171
|
* The fallback for hosts without `lsof`, which is most containers — and a
|
|
172
|
-
* deployed
|
|
172
|
+
* deployed AiWorker is exactly where a dev server needs discovering. Reads a
|
|
173
173
|
* kernel file rather than shelling out, so it cannot be defeated by a missing
|
|
174
174
|
* binary or a stripped PATH.
|
|
175
175
|
*
|
|
@@ -100,11 +100,11 @@ describe('candidate selection', () => {
|
|
|
100
100
|
const listeners = [
|
|
101
101
|
{ port: 5173, pid: 10, command: 'node' },
|
|
102
102
|
{ port: 5432, pid: 11, command: 'postgres' },
|
|
103
|
-
{ port: 4096, pid: 12, command: '
|
|
103
|
+
{ port: 4096, pid: 12, command: 'aiworker' },
|
|
104
104
|
{ port: 3000, pid: 13, command: 'node' },
|
|
105
105
|
];
|
|
106
106
|
|
|
107
|
-
test('drops
|
|
107
|
+
test('drops AiWorker own ports so the app never offers itself', () => {
|
|
108
108
|
const ports = selectDevServerCandidates(listeners, { ownPorts: [4096] }).map((entry) => entry.port);
|
|
109
109
|
expect(ports).toEqual([5173, 3000]);
|
|
110
110
|
});
|
|
@@ -50,7 +50,7 @@ const runCommand = (spawn, command, args, timeoutMs) => new Promise((resolve) =>
|
|
|
50
50
|
|
|
51
51
|
/**
|
|
52
52
|
* Reads the kernel's socket tables. Containers routinely ship without `lsof`,
|
|
53
|
-
* and a deployed
|
|
53
|
+
* and a deployed AiWorker is precisely where discovery has to work, so this
|
|
54
54
|
* is tried whenever the command is unavailable.
|
|
55
55
|
*/
|
|
56
56
|
const readProcListeners = async (readFile) => {
|
|
@@ -20,7 +20,13 @@ and what made it fragile per framework.
|
|
|
20
20
|
- `client.js` is the local end: it binds a loopback listener on the user's
|
|
21
21
|
machine and pipes each accepted connection through one WebSocket. It lives in
|
|
22
22
|
this package because it needs a WebSocket client the package already depends
|
|
23
|
-
on; the desktop shell drives it over IPC
|
|
23
|
+
on; the desktop shell drives it over IPC for directly reachable HTTP(S)
|
|
24
|
+
runtimes.
|
|
25
|
+
- Relay-only runtimes use `packages/electron/relay-dev-tunnel.mjs` for the local
|
|
26
|
+
listener. Each accepted connection gets an Electron `MessagePort`; the trusted
|
|
27
|
+
renderer carries its bytes through the active E2EE relay. This keeps relay
|
|
28
|
+
credentials and encryption in their existing renderer owner instead of
|
|
29
|
+
duplicating them in Electron main.
|
|
24
30
|
- Port discovery is not owned here. `runtime.js` is given the reachable set by
|
|
25
31
|
the same dev-server discovery the user's own list is built from.
|
|
26
32
|
- The browser panel decides when to tunnel; this module never chooses a target.
|
|
@@ -40,9 +46,12 @@ and what made it fragile per framework.
|
|
|
40
46
|
usual origin allowlist applies unchanged. That check is a CSRF defence: a
|
|
41
47
|
hostile page can make a browser open a WebSocket carrying ambient cookies,
|
|
42
48
|
and the origin is what exposes it.
|
|
43
|
-
- With no `Origin` the request must carry client-token auth
|
|
44
|
-
|
|
45
|
-
|
|
49
|
+
- With no `Origin` the request must carry client-token auth or a short-lived
|
|
50
|
+
URL token. The bearer case is the desktop main process. The URL-token case
|
|
51
|
+
is the trusted renderer carrying the socket through the E2EE relay.
|
|
52
|
+
- Through the E2EE relay, the trusted renderer mints a short-lived URL token
|
|
53
|
+
and includes it in the virtual WebSocket URL. The relay host and URL-token
|
|
54
|
+
allowlists accept exactly `/api/dev-tunnel`, not subpaths.
|
|
46
55
|
- Concurrency is capped per host, not per page, because one page load opens
|
|
47
56
|
many sockets.
|
|
48
57
|
- A connection that cannot be established fails the socket rather than holding
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Local end of the dev-server tunnel.
|
|
3
3
|
*
|
|
4
4
|
* Binds a loopback listener on this machine and pipes every connection to a
|
|
5
|
-
* dev server on the
|
|
5
|
+
* dev server on the AiWorker host. The point of binding a real local port —
|
|
6
6
|
* rather than serving the remote page under a path on some other origin — is
|
|
7
7
|
* that the page then has its own origin at the root of its own host. Absolute
|
|
8
8
|
* URLs resolve, cookies scope correctly, HMR sockets connect, and nothing has
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Raw byte tunnel to a dev server running on the
|
|
2
|
+
* Raw byte tunnel to a dev server running on the AiWorker host.
|
|
3
3
|
*
|
|
4
4
|
* This is what lets a desktop client preview a dev server that lives on another
|
|
5
5
|
* machine without rewriting anything. The client binds its own local port and
|
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
*
|
|
21
21
|
* - With an `Origin` header, the request came from a browser context and the
|
|
22
22
|
* usual origin check applies unchanged.
|
|
23
|
-
* - With no `Origin`, the request must carry client-token auth
|
|
24
|
-
*
|
|
25
|
-
*
|
|
23
|
+
* - With no `Origin`, the request must carry client-token auth or a short-lived
|
|
24
|
+
* URL token. The URL-token case is used only by the trusted renderer through
|
|
25
|
+
* the E2EE relay; the UI-auth allowlist limits it to this exact path.
|
|
26
26
|
*/
|
|
27
27
|
import net from 'node:net';
|
|
28
28
|
import { WebSocketServer } from 'ws';
|
|
@@ -133,7 +133,7 @@ export function createDevTunnelRuntime({
|
|
|
133
133
|
void (async () => {
|
|
134
134
|
try {
|
|
135
135
|
if (uiAuthController?.enabled) {
|
|
136
|
-
const auth = await uiAuthController.resolveAuthContext(req, null, { allowUrlToken:
|
|
136
|
+
const auth = await uiAuthController.resolveAuthContext(req, null, { allowUrlToken: true });
|
|
137
137
|
if (!auth) {
|
|
138
138
|
rejectWebSocketUpgrade(socket, 401, 'UI authentication required');
|
|
139
139
|
return;
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { afterEach, describe, expect, test } from 'bun:test';
|
|
2
2
|
import http from 'node:http';
|
|
3
3
|
import net from 'node:net';
|
|
4
|
+
import WebSocket from 'ws';
|
|
4
5
|
|
|
5
6
|
import { createDevTunnelClient } from './client.js';
|
|
6
7
|
import { createDevTunnelRuntime, isDevTunnelPath } from './runtime.js';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
|
-
* These exercise the real socket path end to end: a dev server, an
|
|
10
|
+
* These exercise the real socket path end to end: a dev server, an AiWorker
|
|
10
11
|
* host tunnelling to it, and a client binding a local port. Anything less would
|
|
11
12
|
* not prove the thing that matters — that a page loads over the tunnel exactly
|
|
12
13
|
* as it does locally.
|
|
@@ -228,6 +229,12 @@ describe('dev tunnel authentication', () => {
|
|
|
228
229
|
enabled: true,
|
|
229
230
|
resolveAuthContext: async () => ({ type: 'session' }),
|
|
230
231
|
};
|
|
232
|
+
const urlTokenAuth = {
|
|
233
|
+
enabled: true,
|
|
234
|
+
resolveAuthContext: async (req, _res, options) => (
|
|
235
|
+
options?.allowUrlToken === true && req.url.includes('oc_url_token=good') ? { type: 'client', token: 'url:authenticated' } : null
|
|
236
|
+
),
|
|
237
|
+
};
|
|
231
238
|
|
|
232
239
|
test('accepts a bearer-authenticated client that sends no origin', async () => {
|
|
233
240
|
const devPort = await startDevServer((_req, res) => res.end('ok'));
|
|
@@ -243,6 +250,19 @@ describe('dev tunnel authentication', () => {
|
|
|
243
250
|
expect((await httpGet(localPort, '/')).body).toBe('ok');
|
|
244
251
|
});
|
|
245
252
|
|
|
253
|
+
test('accepts a URL-token client carried by the E2EE relay', async () => {
|
|
254
|
+
const devPort = await startDevServer((_req, res) => res.end('relay-ok'));
|
|
255
|
+
const host = await startHost({ allowedPorts: [devPort], auth: urlTokenAuth });
|
|
256
|
+
|
|
257
|
+
const body = await new Promise((resolve, reject) => {
|
|
258
|
+
const socket = new WebSocket(`ws://127.0.0.1:${host.port}/api/dev-tunnel?port=${devPort}&oc_url_token=good`);
|
|
259
|
+
socket.on('open', () => socket.send('GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n'));
|
|
260
|
+
socket.on('message', (data) => resolve(Buffer.from(data).toString()));
|
|
261
|
+
socket.on('error', reject);
|
|
262
|
+
});
|
|
263
|
+
expect(body).toContain('relay-ok');
|
|
264
|
+
});
|
|
265
|
+
|
|
246
266
|
test('rejects a client with no credentials', async () => {
|
|
247
267
|
const devPort = await startDevServer((_req, res) => res.end('ok'));
|
|
248
268
|
const host = await startHost({ allowedPorts: [devPort], auth: clientAuth });
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Catalog of local sherpa-onnx STT models available for dictation.
|
|
3
3
|
* Models are downloaded on demand from the k2-fsa GitHub releases and
|
|
4
|
-
* extracted under the
|
|
4
|
+
* extracted under the AiWorker speech-models directory.
|
|
5
5
|
*
|
|
6
6
|
* `type` selects the recognizer construction path in the worker:
|
|
7
7
|
* - 'nemo_transducer': encoder/decoder/joiner transducer (Parakeet)
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Dictation local-speech worker process.
|
|
3
3
|
*
|
|
4
4
|
* Hosts the sherpa-onnx native inference (Parakeet STT) in a separate process
|
|
5
|
-
* so ONNX decoding never blocks the main
|
|
5
|
+
* so ONNX decoding never blocks the main AiWorker server. Communicates
|
|
6
6
|
* with the parent over child_process IPC (advanced serialization, so Buffers
|
|
7
7
|
* survive the trip as Uint8Array).
|
|
8
8
|
*
|
|
@@ -24,7 +24,7 @@ import { getLocalSttModelDir, getLocalSttModelSpec } from './model-catalog.js';
|
|
|
24
24
|
import { pcm16ToWav } from '../audio.js';
|
|
25
25
|
import path from 'path';
|
|
26
26
|
|
|
27
|
-
process.title = '
|
|
27
|
+
process.title = 'AiWorker Dictation';
|
|
28
28
|
|
|
29
29
|
const engines = new Map();
|
|
30
30
|
const ttsEngines = new Map();
|
|
@@ -56,7 +56,7 @@ export function acceptDirectoryMessageStreamWsConnection({
|
|
|
56
56
|
return;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
sendMessageStreamWsEvent(socket, { type: '
|
|
59
|
+
sendMessageStreamWsEvent(socket, { type: 'aiworker:heartbeat', timestamp: Date.now() }, { directory: 'global' });
|
|
60
60
|
}, heartbeatIntervalMs);
|
|
61
61
|
|
|
62
62
|
socket.on('close', () => {
|
|
@@ -177,7 +177,7 @@ export function createGlobalMessageStreamWsBridge({
|
|
|
177
177
|
return;
|
|
178
178
|
}
|
|
179
179
|
|
|
180
|
-
sendMessageStreamWsEvent(socket, { type: '
|
|
180
|
+
sendMessageStreamWsEvent(socket, { type: 'aiworker:heartbeat', timestamp: Date.now() }, { directory: 'global' });
|
|
181
181
|
}, heartbeatIntervalMs);
|
|
182
182
|
|
|
183
183
|
socket.on('close', () => {
|
|
@@ -32,14 +32,14 @@ describe('event stream protocol helpers', () => {
|
|
|
32
32
|
|
|
33
33
|
it('derives directory from payload properties when not wrapped', () => {
|
|
34
34
|
const envelope = parseSseEventEnvelope(
|
|
35
|
-
'data: {"type":"
|
|
35
|
+
'data: {"type":"aiworker:notification","properties":{"directory":"/tmp/project"}}\n'
|
|
36
36
|
);
|
|
37
37
|
|
|
38
38
|
expect(envelope).toEqual({
|
|
39
39
|
eventId: null,
|
|
40
40
|
directory: '/tmp/project',
|
|
41
41
|
payload: {
|
|
42
|
-
type: '
|
|
42
|
+
type: 'aiworker:notification',
|
|
43
43
|
properties: { directory: '/tmp/project' },
|
|
44
44
|
},
|
|
45
45
|
});
|
|
@@ -167,14 +167,14 @@ describe('event stream protocol helpers', () => {
|
|
|
167
167
|
|
|
168
168
|
const sent = sendMessageStreamWsEvent(
|
|
169
169
|
socket,
|
|
170
|
-
{ type: '
|
|
170
|
+
{ type: 'aiworker:heartbeat', timestamp: 1 },
|
|
171
171
|
{ eventId: 'evt-2', directory: '/tmp/project' }
|
|
172
172
|
);
|
|
173
173
|
|
|
174
174
|
expect(sent).toBe(true);
|
|
175
175
|
expect(JSON.parse(rawPayload)).toEqual({
|
|
176
176
|
type: 'event',
|
|
177
|
-
payload: { type: '
|
|
177
|
+
payload: { type: 'aiworker:heartbeat', timestamp: 1 },
|
|
178
178
|
eventId: 'evt-2',
|
|
179
179
|
directory: '/tmp/project',
|
|
180
180
|
});
|