@aiworker/web 1.22.0 → 1.22.2
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 +7 -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-CSICpWQD.js} +1 -1
- package/dist/assets/{DiffView-DluiRSla.js → DiffView-hDBKesA_.js} +3 -3
- package/dist/assets/DirtyBranchSwitchDialog-C22HdWj4.js +1 -0
- package/dist/assets/FilesView-CdWzeOp9.js +17 -0
- package/dist/assets/GitView-VQQkYuUw.js +6 -0
- package/dist/assets/{JsonTreeView-B0BdZEHG.js → JsonTreeView-6lrhwD3q.js} +1 -1
- package/dist/assets/LinearIssuesView-Ba6Q9569.js +1 -0
- package/dist/assets/{MarkdownImageGallery-ClOvuZxC.js → MarkdownImageGallery-Czv_mhvS.js} +1 -1
- package/dist/assets/MarkdownRendererImpl-BrMcswiy.js +12 -0
- package/dist/assets/{OnboardingScreen-D2DG7K6b.js → OnboardingScreen-ChxQoAkx.js} +1 -1
- package/dist/assets/{PatchDiff-DvfqDql7.js → PatchDiff-BzcZPlOq.js} +1 -1
- package/dist/assets/{PierreDiffViewer-B7m3f0d7.js → PierreDiffViewer-BchBtZlp.js} +2 -2
- package/dist/assets/PlanView-DyDt_nZ_.js +5 -0
- package/dist/assets/SettingsView-BKf5xBsP.js +28 -0
- package/dist/assets/{SettingsWindow-i45U1Cy1.js → SettingsWindow-DDQYaKWQ.js} +1 -1
- package/dist/assets/{ToolOutputDialog-BTwyh5OF.js → ToolOutputDialog-iYF8Y2bz.js} +6 -6
- package/dist/assets/ToolPartDiffPreview-CdjbOVMM.js +8 -0
- package/dist/assets/{VirtualizedFileDiff-DD8lDVej.js → VirtualizedFileDiff-fFFm2QgH.js} +1 -1
- package/dist/assets/{WalkthroughView-egQgxel3.js → WalkthroughView-DOeCsMju.js} +2 -2
- package/dist/assets/{computeVirtualFileMetrics-BMIRgMHv.js → computeVirtualFileMetrics-CzgLum_K.js} +2 -2
- package/dist/assets/{contextPanelEmbeddedChat-C3fjDbdg.js → contextPanelEmbeddedChat-C0ZLAepL.js} +1 -1
- package/dist/assets/{de-C1P6X5Rs.js → de-B5JaA3SA.js} +6 -5
- package/dist/assets/{es-CDgaF1_t.js → es-EPIH88ii.js} +6 -5
- package/dist/assets/{fr-Bgxqvvz_.js → fr-CAMfxUdZ.js} +6 -5
- package/dist/assets/{index-CODwDsU_.js → index-Bl3LSbjn.js} +1 -1
- package/dist/assets/index-DF5kMP-i.js +39 -0
- package/dist/assets/index-Dl3xbAB_.css +1 -0
- package/dist/assets/{isDiffPlainText-BqayB8d3.js → isDiffPlainText-Dx-dB_8l.js} +1 -1
- package/dist/assets/{isFilePlainText-QfoiKJNY.js → isFilePlainText-CWFPwmnU.js} +1 -1
- package/dist/assets/{ja-BDRrEcNP.js → ja-CifOE1VN.js} +7 -6
- package/dist/assets/{ko-Ba8sQV79.js → ko-BBDyAyYQ.js} +7 -6
- package/dist/assets/{main-C5DVOxc6.js → main-DnBAoktx.js} +2 -2
- package/dist/assets/main-MhSaOZoB.js +930 -0
- package/dist/assets/{markdownCore-CZwzz5gw.js → markdownCore-BrBwOOMn.js} +2 -2
- package/dist/assets/{miniChat-DUJ4irZc.js → miniChat-BKyvLseN.js} +2 -2
- package/dist/assets/{mobile-CcaxB0n4.js → mobile-DKjeFgMN.js} +2 -2
- package/dist/assets/{patchFileDiff-yNfWXN4E.js → patchFileDiff-o8ms1me8.js} +1 -1
- package/dist/assets/pl-DgEHYfFM.js +16 -0
- package/dist/assets/{pt-BR-DIs6ElwV.js → pt-BR-DuWHojUw.js} +6 -5
- package/dist/assets/{renderElectronMiniChatApp-BcKLHXKe.js → renderElectronMiniChatApp-CDgBwwCS.js} +2 -2
- package/dist/assets/renderMobileApp-DJIKIK1r.js +25 -0
- package/dist/assets/runtimeEndpointReset-CYJ9O_xX.js +17 -0
- package/dist/assets/{scheduledTasksApi-DLW0mkQK.js → scheduledTasksApi-D8yamh1b.js} +1 -1
- package/dist/assets/{shikiHighlight-DYOWuY65.js → shikiHighlight-TEQCmzW8.js} +1 -1
- package/dist/assets/tr-D5m2yIWL.js +7 -0
- package/dist/assets/{uk-ZrCNjM7Y.js → uk-D_q60oog.js} +6 -5
- package/dist/assets/{useAppFontEffects-CjrntdL0.js → useAppFontEffects-D7sNpuL2.js} +161 -190
- package/dist/assets/useInlineCommentController-Brz_cLcQ.js +2 -0
- package/dist/assets/{useRootScrollLock-BHYPkrLv.js → useRootScrollLock-lXEnWlaU.js} +1 -1
- package/dist/assets/{vendor-codemirror-language-data-DFZik9R2.js → vendor-codemirror-language-data-Dp3KMRQG.js} +2 -2
- package/dist/assets/vendor-zod-CK21ihK-.js +39 -0
- package/dist/assets/{web-components-Bhg83tjf.js → web-components-_mNEMjFY.js} +1 -1
- package/dist/assets/{zh-CN-iv_AV4EC.js → zh-CN-D8QvsVeg.js} +6 -5
- package/dist/assets/{zh-TW-CzEUk38j.js → zh-TW-B9t7rQQO.js} +6 -5
- package/dist/index.html +22 -22
- package/dist/mini-chat.html +7 -7
- package/dist/mobile.html +5 -5
- package/package.json +3 -3
- package/server/index.js +118 -85
- 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/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 +6 -6
- package/server/lib/fs/DOCUMENTATION.md +2 -0
- package/server/lib/fs/routes.js +37 -26
- package/server/lib/fs/routes.test.js +98 -21
- package/server/lib/git/DOCUMENTATION.md +3 -1
- 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 +17 -1
- package/server/lib/git/service.js +116 -26
- package/server/lib/git/service.test.js +174 -3
- 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 +1 -1
- package/server/lib/inherited-env.test.js +2 -2
- 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 +158 -0
- package/server/lib/message-queue/runtime.js +870 -0
- package/server/lib/message-queue/runtime.test.js +482 -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 +13 -5
- package/server/lib/opencode/auth.js +1 -1
- package/server/lib/opencode/bootstrap-runtime.js +6 -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 +17 -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 +15 -15
- package/server/lib/opencode/openchamber-routes.test.js +11 -11
- 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 +14 -14
- 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-helpers.js +31 -3
- package/server/lib/opencode/settings-helpers.test.js +189 -12
- package/server/lib/opencode/settings-runtime.js +2 -2
- package/server/lib/opencode/settings-runtime.test.js +29 -0
- 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/routes.js +1 -1
- package/server/lib/projects/project-config.js +2 -2
- package/server/lib/quota/DOCUMENTATION.md +9 -1
- 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/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 +15 -0
- package/server/lib/quota/providers/ollama-cloud.js +1 -1
- package/server/lib/quota/providers/opencode-go.js +2 -1
- package/server/lib/quota/providers/opencode-go.test.js +9 -5
- 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 +1 -1
- package/server/lib/relay/host-client.js +1 -1
- 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-host.js +3 -1
- 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 +10 -9
- 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 +88 -22
- 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 +4 -0
- package/server/lib/small-model/call.js +9 -3
- package/server/lib/small-model/call.test.js +29 -6
- package/server/lib/small-model/index.js +8 -7
- 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/system-prompt/runtime.js +3 -22
- package/server/lib/system-prompt/runtime.test.js +3 -3
- package/server/lib/terminal/DOCUMENTATION.md +11 -7
- package/server/lib/terminal/runtime.js +180 -26
- package/server/lib/terminal/runtime.test.js +554 -27
- 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/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 +3 -3
- package/server/lib/walkthrough/model-settings.test.js +1 -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/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/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/MarkdownRendererImpl-Dt9mjTRN.js +0 -12
- 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/index-C57jT0Sa.css +0 -1
- package/dist/assets/index-DW_E3rnL.js +0 -7
- package/dist/assets/main-Bv5Pkngh.js +0 -928
- package/dist/assets/pl-Dioz29BI.js +0 -15
- package/dist/assets/renderMobileApp-D9yQJy0L.js +0 -25
- package/dist/assets/runtimeEndpointReset-CjhvxLH0.js +0 -16
- package/dist/assets/tr-DFnMlPxY.js +0 -6
- package/dist/assets/useInlineCommentController-CDEuSlPH.js +0 -2
- package/dist/assets/vendor-zod-BLfBSgBh.js +0 -39
- 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
package/README.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
# <picture><source media="(prefers-color-scheme: dark)" srcset="https://
|
|
1
|
+
# <picture><source media="(prefers-color-scheme: dark)" srcset="https://roweb.cn/roweb/openchamber/raw/HEAD/docs/references/badges/openchamber-logo-dark.svg"><img src="https://roweb.cn/roweb/openchamber/raw/HEAD/docs/references/badges/openchamber-logo-light.svg" width="32" height="32" align="absmiddle" /></picture> @aiworker/web
|
|
2
2
|
|
|
3
|
-
[](https://
|
|
4
|
-
[](https://
|
|
3
|
+
[](https://roweb.cn/roweb/openchamber/stargazers)
|
|
4
|
+
[](https://roweb.cn/roweb/openchamber/releases/latest)
|
|
5
5
|
[](https://discord.gg/ZYRSdnwwKA)
|
|
6
6
|
|
|
7
7
|
Run [OpenCode](https://opencode.ai) in your browser. Install the CLI, open `localhost:3000`, done. Works on desktop browsers, tablets, and phones as a PWA.
|
|
8
8
|
|
|
9
|
-
Full project overview, screenshots, and all features: [
|
|
9
|
+
Full project overview, screenshots, and all features: [roweb.cn/roweb/openchamber](https://roweb.cn/roweb/openchamber)
|
|
10
10
|
|
|
11
11
|
## Install
|
|
12
12
|
|
package/bin/cli.js
CHANGED
|
@@ -48,7 +48,7 @@ import { createTunnelCommand, isValidTunnelDoctorResponse, shouldDisplayTunnelQr
|
|
|
48
48
|
import {
|
|
49
49
|
resolveDoctorPortStatuses,
|
|
50
50
|
discoverRunningInstances,
|
|
51
|
-
|
|
51
|
+
discoverAiWorkerInstanceOnPort,
|
|
52
52
|
discoverLifecycleInstances,
|
|
53
53
|
discoverUnconfirmedRegistryInstanceOnPort,
|
|
54
54
|
resolveTunnelProviders,
|
|
@@ -117,7 +117,10 @@ function getBunBinary() {
|
|
|
117
117
|
return process.env.BUN_BINARY.trim();
|
|
118
118
|
}
|
|
119
119
|
if (typeof process.env.BUN_INSTALL === 'string' && process.env.BUN_INSTALL.trim().length > 0) {
|
|
120
|
-
|
|
120
|
+
// The Windows installer places bun.exe there; spawnSync does not append
|
|
121
|
+
// the extension to an explicit path, so without it the probe fails and
|
|
122
|
+
// the CLI silently falls back to Node.
|
|
123
|
+
return path.join(process.env.BUN_INSTALL.trim(), 'bin', process.platform === 'win32' ? 'bun.exe' : 'bun');
|
|
121
124
|
}
|
|
122
125
|
return 'bun';
|
|
123
126
|
}
|
|
@@ -345,7 +348,7 @@ async function main() {
|
|
|
345
348
|
await commands[command](options);
|
|
346
349
|
}
|
|
347
350
|
|
|
348
|
-
const isCliExecution = isModuleCliExecution(process.argv[1], import.meta.url, fs.realpathSync, '
|
|
351
|
+
const isCliExecution = isModuleCliExecution(process.argv[1], import.meta.url, fs.realpathSync, 'aiworker');
|
|
349
352
|
|
|
350
353
|
if (isCliExecution) {
|
|
351
354
|
let isHandlingSigint = false;
|
|
@@ -445,7 +448,7 @@ export {
|
|
|
445
448
|
fetchTunnelProvidersFromPort,
|
|
446
449
|
fetchSystemInfoFromPort,
|
|
447
450
|
discoverRunningInstances,
|
|
448
|
-
|
|
451
|
+
discoverAiWorkerInstanceOnPort,
|
|
449
452
|
discoverLifecycleInstances,
|
|
450
453
|
discoverUnconfirmedRegistryInstanceOnPort,
|
|
451
454
|
ensureTunnelProfilesMigrated,
|
package/bin/cli.test.js
CHANGED
|
@@ -11,6 +11,7 @@ import { isModuleCliExecution, normalizeCliEntryPath } from './cli-entry.js';
|
|
|
11
11
|
import { requestJson } from './lib/cli-http.js';
|
|
12
12
|
import { requestControlAction } from './lib/cli-control.js';
|
|
13
13
|
import { inspectTunnelAttachability } from './lib/cli-lifecycle.js';
|
|
14
|
+
import { startupCommand } from './lib/commands-startup.js';
|
|
14
15
|
import { formatGoal } from './lib/commands-schedule.js';
|
|
15
16
|
import {
|
|
16
17
|
buildSessionCreatePayload,
|
|
@@ -29,11 +30,12 @@ import {
|
|
|
29
30
|
import {
|
|
30
31
|
assertAuthenticatedNetworkExposure,
|
|
31
32
|
commands,
|
|
32
|
-
|
|
33
|
+
discoverAiWorkerInstanceOnPort,
|
|
33
34
|
discoverLifecycleInstances,
|
|
34
35
|
discoverRunningInstances,
|
|
35
36
|
discoverUnconfirmedRegistryInstanceOnPort,
|
|
36
37
|
ensureTunnelProfilesMigrated,
|
|
38
|
+
EXIT_CODE,
|
|
37
39
|
generateUiPassword,
|
|
38
40
|
getInstanceFilePath,
|
|
39
41
|
getPidFilePath,
|
|
@@ -45,17 +47,17 @@ import {
|
|
|
45
47
|
} from './cli.js';
|
|
46
48
|
import { buildWindowsStartupTaskCommand } from './lib/cli-startup.js';
|
|
47
49
|
|
|
48
|
-
async function
|
|
49
|
-
const previous = process.env.
|
|
50
|
+
async function withTempAiWorkerDataDir(fn) {
|
|
51
|
+
const previous = process.env.AIWORKER_DATA_DIR;
|
|
50
52
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'openchamber-cli-test-'));
|
|
51
|
-
process.env.
|
|
53
|
+
process.env.AIWORKER_DATA_DIR = dir;
|
|
52
54
|
try {
|
|
53
55
|
return await fn(dir);
|
|
54
56
|
} finally {
|
|
55
57
|
if (typeof previous === 'string') {
|
|
56
|
-
process.env.
|
|
58
|
+
process.env.AIWORKER_DATA_DIR = previous;
|
|
57
59
|
} else {
|
|
58
|
-
delete process.env.
|
|
60
|
+
delete process.env.AIWORKER_DATA_DIR;
|
|
59
61
|
}
|
|
60
62
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
61
63
|
}
|
|
@@ -85,7 +87,7 @@ async function captureStdout(fn) {
|
|
|
85
87
|
}
|
|
86
88
|
}
|
|
87
89
|
|
|
88
|
-
async function
|
|
90
|
+
async function startMockAiWorkerServer(options = {}) {
|
|
89
91
|
const runtime = options.runtime || 'web';
|
|
90
92
|
const pid = Number.isFinite(options.pid) ? options.pid : null;
|
|
91
93
|
let shutdownRequested = false;
|
|
@@ -173,11 +175,11 @@ async function waitForTcpPort(port, timeoutMs = 3000) {
|
|
|
173
175
|
return false;
|
|
174
176
|
}
|
|
175
177
|
|
|
176
|
-
function
|
|
178
|
+
function spawnAiWorkerLikeIdleProcess() {
|
|
177
179
|
return spawn(process.execPath, ['-e', 'setInterval(() => {}, 1000)', 'openchamber-idle'], { stdio: 'ignore' });
|
|
178
180
|
}
|
|
179
181
|
|
|
180
|
-
function
|
|
182
|
+
function spawnAiWorkerLikeHungServer(port) {
|
|
181
183
|
const script = `
|
|
182
184
|
const net = require('net');
|
|
183
185
|
const sockets = new Set();
|
|
@@ -315,7 +317,7 @@ describe('cli args', () => {
|
|
|
315
317
|
'--worktree',
|
|
316
318
|
'side-task',
|
|
317
319
|
'--branch',
|
|
318
|
-
'
|
|
320
|
+
'aiworker/side-task',
|
|
319
321
|
'--base',
|
|
320
322
|
'main',
|
|
321
323
|
'--no-upstream',
|
|
@@ -328,7 +330,7 @@ describe('cli args', () => {
|
|
|
328
330
|
expect(parsed.options.prompt).toBe('Investigate cache invalidation');
|
|
329
331
|
expect(parsed.options.model).toBe('openai/gpt-5.5');
|
|
330
332
|
expect(parsed.options.worktree).toBe('side-task');
|
|
331
|
-
expect(parsed.options.branch).toBe('
|
|
333
|
+
expect(parsed.options.branch).toBe('aiworker/side-task');
|
|
332
334
|
expect(parsed.options.startRef).toBe('main');
|
|
333
335
|
expect(parsed.options.setUpstream).toBe(false);
|
|
334
336
|
});
|
|
@@ -347,7 +349,7 @@ describe('cli args', () => {
|
|
|
347
349
|
model: 'openai/gpt-5.5',
|
|
348
350
|
agent: 'build',
|
|
349
351
|
worktree: 'side-task',
|
|
350
|
-
branch: '
|
|
352
|
+
branch: 'aiworker/side-task',
|
|
351
353
|
startRef: 'main',
|
|
352
354
|
setUpstream: true,
|
|
353
355
|
})).toEqual({
|
|
@@ -355,7 +357,7 @@ describe('cli args', () => {
|
|
|
355
357
|
title: 'Side task',
|
|
356
358
|
worktree: {
|
|
357
359
|
name: 'side-task',
|
|
358
|
-
branchName: '
|
|
360
|
+
branchName: 'aiworker/side-task',
|
|
359
361
|
startRef: 'main',
|
|
360
362
|
},
|
|
361
363
|
prompt: 'Investigate cache invalidation',
|
|
@@ -660,7 +662,7 @@ describe('cli API target resolution', () => {
|
|
|
660
662
|
discoverDesktopInstance: async () => null,
|
|
661
663
|
discoverLifecycleInstances: async () => [{ port: 3001 }, { port: 3002 }],
|
|
662
664
|
isServerHealthReady: async () => false,
|
|
663
|
-
})).rejects.toThrow('Multiple
|
|
665
|
+
})).rejects.toThrow('Multiple AiWorker instances are running');
|
|
664
666
|
});
|
|
665
667
|
});
|
|
666
668
|
|
|
@@ -681,15 +683,15 @@ describe('network-exposed auth validation', () => {
|
|
|
681
683
|
});
|
|
682
684
|
|
|
683
685
|
it('allows explicit unsafe LAN override from process env only', () => {
|
|
684
|
-
const previous = process.env.
|
|
685
|
-
process.env.
|
|
686
|
+
const previous = process.env.AIWORKER_ALLOW_UNAUTHENTICATED_LAN;
|
|
687
|
+
process.env.AIWORKER_ALLOW_UNAUTHENTICATED_LAN = 'true';
|
|
686
688
|
try {
|
|
687
689
|
expect(() => assertAuthenticatedNetworkExposure({ host: '0.0.0.0' })).not.toThrow();
|
|
688
690
|
} finally {
|
|
689
691
|
if (typeof previous === 'string') {
|
|
690
|
-
process.env.
|
|
692
|
+
process.env.AIWORKER_ALLOW_UNAUTHENTICATED_LAN = previous;
|
|
691
693
|
} else {
|
|
692
|
-
delete process.env.
|
|
694
|
+
delete process.env.AIWORKER_ALLOW_UNAUTHENTICATED_LAN;
|
|
693
695
|
}
|
|
694
696
|
}
|
|
695
697
|
});
|
|
@@ -733,30 +735,30 @@ describe('serve UI password resolution', () => {
|
|
|
733
735
|
});
|
|
734
736
|
|
|
735
737
|
describe('serve host resolution', () => {
|
|
736
|
-
it('uses
|
|
737
|
-
const previous = process.env.
|
|
738
|
-
process.env.
|
|
738
|
+
it('uses AIWORKER_HOST when --host is not provided', () => {
|
|
739
|
+
const previous = process.env.AIWORKER_HOST;
|
|
740
|
+
process.env.AIWORKER_HOST = '192.0.2.20';
|
|
739
741
|
try {
|
|
740
742
|
expect(resolveServeHost(undefined)).toBe('192.0.2.20');
|
|
741
743
|
} finally {
|
|
742
744
|
if (typeof previous === 'string') {
|
|
743
|
-
process.env.
|
|
745
|
+
process.env.AIWORKER_HOST = previous;
|
|
744
746
|
} else {
|
|
745
|
-
delete process.env.
|
|
747
|
+
delete process.env.AIWORKER_HOST;
|
|
746
748
|
}
|
|
747
749
|
}
|
|
748
750
|
});
|
|
749
751
|
|
|
750
|
-
it('prefers explicit --host over
|
|
751
|
-
const previous = process.env.
|
|
752
|
-
process.env.
|
|
752
|
+
it('prefers explicit --host over AIWORKER_HOST', () => {
|
|
753
|
+
const previous = process.env.AIWORKER_HOST;
|
|
754
|
+
process.env.AIWORKER_HOST = '192.0.2.20';
|
|
753
755
|
try {
|
|
754
756
|
expect(resolveServeHost('192.0.2.21')).toBe('192.0.2.21');
|
|
755
757
|
} finally {
|
|
756
758
|
if (typeof previous === 'string') {
|
|
757
|
-
process.env.
|
|
759
|
+
process.env.AIWORKER_HOST = previous;
|
|
758
760
|
} else {
|
|
759
|
-
delete process.env.
|
|
761
|
+
delete process.env.AIWORKER_HOST;
|
|
760
762
|
}
|
|
761
763
|
}
|
|
762
764
|
});
|
|
@@ -764,7 +766,7 @@ describe('serve host resolution', () => {
|
|
|
764
766
|
|
|
765
767
|
describe('compatibility exports', () => {
|
|
766
768
|
it('allows tunnel profile migration before command options are initialized', async () => {
|
|
767
|
-
await
|
|
769
|
+
await withTempAiWorkerDataDir(async () => {
|
|
768
770
|
const store = ensureTunnelProfilesMigrated();
|
|
769
771
|
|
|
770
772
|
expect(store).toEqual({ version: 1, profiles: [] });
|
|
@@ -772,7 +774,7 @@ describe('compatibility exports', () => {
|
|
|
772
774
|
});
|
|
773
775
|
|
|
774
776
|
it('includes ngrok in fallback tunnel providers when no server is reachable', async () => {
|
|
775
|
-
await
|
|
777
|
+
await withTempAiWorkerDataDir(async () => {
|
|
776
778
|
const port = await allocateLoopbackPort();
|
|
777
779
|
const output = await captureStdout(async () => {
|
|
778
780
|
await commands.tunnel({ json: true, explicitPort: true, port }, 'providers');
|
|
@@ -785,7 +787,7 @@ describe('compatibility exports', () => {
|
|
|
785
787
|
});
|
|
786
788
|
|
|
787
789
|
it('supports ngrok quick dry-run with an explicit port', async () => {
|
|
788
|
-
await
|
|
790
|
+
await withTempAiWorkerDataDir(async () => {
|
|
789
791
|
const output = await captureStdout(async () => {
|
|
790
792
|
await commands.tunnel({
|
|
791
793
|
json: true,
|
|
@@ -831,7 +833,7 @@ describe('CLI HTTP helpers', () => {
|
|
|
831
833
|
});
|
|
832
834
|
|
|
833
835
|
it('retries UI-authenticated API requests with the stored instance password', async () => {
|
|
834
|
-
await
|
|
836
|
+
await withTempAiWorkerDataDir(async () => {
|
|
835
837
|
const port = 45678;
|
|
836
838
|
fs.writeFileSync(await getInstanceFilePath(port), JSON.stringify({ port, uiPassword: 'secret' }, null, 2));
|
|
837
839
|
const originalFetch = globalThis.fetch;
|
|
@@ -876,7 +878,7 @@ describe('CLI HTTP helpers', () => {
|
|
|
876
878
|
});
|
|
877
879
|
|
|
878
880
|
it('prefers the stored instance password over a non-explicit env password', async () => {
|
|
879
|
-
await
|
|
881
|
+
await withTempAiWorkerDataDir(async () => {
|
|
880
882
|
const port = 45679;
|
|
881
883
|
fs.writeFileSync(await getInstanceFilePath(port), JSON.stringify({ port, uiPassword: 'stored-secret' }, null, 2));
|
|
882
884
|
const originalFetch = globalThis.fetch;
|
|
@@ -914,7 +916,7 @@ describe('CLI HTTP helpers', () => {
|
|
|
914
916
|
});
|
|
915
917
|
|
|
916
918
|
it('authenticates desktop-local API requests with the stored client token', async () => {
|
|
917
|
-
await
|
|
919
|
+
await withTempAiWorkerDataDir(async (dir) => {
|
|
918
920
|
const port = 57123;
|
|
919
921
|
fs.writeFileSync(path.join(dir, 'settings.json'), JSON.stringify({
|
|
920
922
|
desktopLocalPort: port,
|
|
@@ -982,7 +984,7 @@ describe('cli entry detection', () => {
|
|
|
982
984
|
|
|
983
985
|
it('accepts wrapper binary name fallback when requested', () => {
|
|
984
986
|
const wrapperPath = '/home/user/.local/bin/openchamber';
|
|
985
|
-
expect(isModuleCliExecution(wrapperPath, moduleUrl, undefined, '
|
|
987
|
+
expect(isModuleCliExecution(wrapperPath, moduleUrl, undefined, 'aiworker')).toBe(true);
|
|
986
988
|
});
|
|
987
989
|
|
|
988
990
|
it('normalizes direct paths when realpath fails', () => {
|
|
@@ -996,9 +998,9 @@ describe('cli entry detection', () => {
|
|
|
996
998
|
});
|
|
997
999
|
|
|
998
1000
|
describe('isOpenchamberCmdline', () => {
|
|
999
|
-
it('accepts
|
|
1000
|
-
expect(isOpenchamberCmdline('node /x/@
|
|
1001
|
-
expect(isOpenchamberCmdline('node /x/@
|
|
1001
|
+
it('accepts AiWorker CLI and daemon cmdlines', () => {
|
|
1002
|
+
expect(isOpenchamberCmdline('node /x/@aiworker/web/bin/cli.js serve')).toBe(true);
|
|
1003
|
+
expect(isOpenchamberCmdline('node /x/@aiworker/web/server/index.js --port 9090')).toBe(true);
|
|
1002
1004
|
expect(isOpenchamberCmdline('bun /home/u/projects/openchamber/packages/web/server/index.js --port 3001')).toBe(true);
|
|
1003
1005
|
});
|
|
1004
1006
|
|
|
@@ -1019,7 +1021,7 @@ describe('isOpenchamberProcessRunning', () => {
|
|
|
1019
1021
|
// platforms a live but unrelated process (a recycled stale PID) must read as
|
|
1020
1022
|
// not-running so it can't trip the "already running" guard (issue #1721).
|
|
1021
1023
|
it.skipIf(process.platform !== 'linux' && process.platform !== 'darwin')(
|
|
1022
|
-
'returns false for a live non-
|
|
1024
|
+
'returns false for a live non-AiWorker PID',
|
|
1023
1025
|
async () => {
|
|
1024
1026
|
const child = spawn('sleep', ['30'], { stdio: 'ignore' });
|
|
1025
1027
|
try {
|
|
@@ -1034,10 +1036,10 @@ describe('isOpenchamberProcessRunning', () => {
|
|
|
1034
1036
|
|
|
1035
1037
|
describe('lifecycle instance discovery', () => {
|
|
1036
1038
|
it('does not attribute a desktop runtime response to a different explicit port', async () => {
|
|
1037
|
-
await
|
|
1039
|
+
await withTempAiWorkerDataDir(async (dir) => {
|
|
1038
1040
|
fs.writeFileSync(path.join(dir, 'settings.json'), JSON.stringify({ desktopLocalPort: 57123 }, null, 2));
|
|
1039
1041
|
|
|
1040
|
-
const instance = await
|
|
1042
|
+
const instance = await discoverAiWorkerInstanceOnPort(3003, {
|
|
1041
1043
|
fetchImpl: async () => createMockJsonResponse({ runtime: 'desktop', pid: 934 }),
|
|
1042
1044
|
});
|
|
1043
1045
|
|
|
@@ -1046,10 +1048,10 @@ describe('lifecycle instance discovery', () => {
|
|
|
1046
1048
|
});
|
|
1047
1049
|
|
|
1048
1050
|
it('attributes a desktop runtime response to its configured desktop port', async () => {
|
|
1049
|
-
await
|
|
1051
|
+
await withTempAiWorkerDataDir(async (dir) => {
|
|
1050
1052
|
fs.writeFileSync(path.join(dir, 'settings.json'), JSON.stringify({ desktopLocalPort: 57123 }, null, 2));
|
|
1051
1053
|
|
|
1052
|
-
const instance = await
|
|
1054
|
+
const instance = await discoverAiWorkerInstanceOnPort(57123, {
|
|
1053
1055
|
fetchImpl: async () => createMockJsonResponse({ runtime: 'desktop', pid: 934 }),
|
|
1054
1056
|
});
|
|
1055
1057
|
|
|
@@ -1062,7 +1064,7 @@ describe('lifecycle instance discovery', () => {
|
|
|
1062
1064
|
});
|
|
1063
1065
|
|
|
1064
1066
|
it('does not mark tunnel attachability as desktop for a different explicit port', async () => {
|
|
1065
|
-
await
|
|
1067
|
+
await withTempAiWorkerDataDir(async (dir) => {
|
|
1066
1068
|
fs.writeFileSync(path.join(dir, 'settings.json'), JSON.stringify({ desktopLocalPort: 57123 }, null, 2));
|
|
1067
1069
|
const originalFetch = globalThis.fetch;
|
|
1068
1070
|
globalThis.fetch = async () => createMockJsonResponse({ runtime: 'desktop', pid: 934 });
|
|
@@ -1077,7 +1079,7 @@ describe('lifecycle instance discovery', () => {
|
|
|
1077
1079
|
});
|
|
1078
1080
|
|
|
1079
1081
|
it('keeps pid and instance files when live port probe confirms a cmdline mismatch', async () => {
|
|
1080
|
-
await
|
|
1082
|
+
await withTempAiWorkerDataDir(async () => {
|
|
1081
1083
|
const port = 45123;
|
|
1082
1084
|
const pid = 12345;
|
|
1083
1085
|
const pidFile = await getPidFilePath(port);
|
|
@@ -1099,7 +1101,7 @@ describe('lifecycle instance discovery', () => {
|
|
|
1099
1101
|
});
|
|
1100
1102
|
|
|
1101
1103
|
it('removes stale pid and instance files when a cmdline mismatch is not confirmed by live probe', async () => {
|
|
1102
|
-
await
|
|
1104
|
+
await withTempAiWorkerDataDir(async () => {
|
|
1103
1105
|
const port = 45124;
|
|
1104
1106
|
const pid = 12346;
|
|
1105
1107
|
const pidFile = await getPidFilePath(port);
|
|
@@ -1119,7 +1121,7 @@ describe('lifecycle instance discovery', () => {
|
|
|
1119
1121
|
});
|
|
1120
1122
|
|
|
1121
1123
|
it('preserves matched pid and instance files when the recorded port probe is inconclusive', async () => {
|
|
1122
|
-
await
|
|
1124
|
+
await withTempAiWorkerDataDir(async () => {
|
|
1123
1125
|
const port = 45126;
|
|
1124
1126
|
const pid = 12347;
|
|
1125
1127
|
const pidFile = await getPidFilePath(port);
|
|
@@ -1139,7 +1141,7 @@ describe('lifecycle instance discovery', () => {
|
|
|
1139
1141
|
});
|
|
1140
1142
|
|
|
1141
1143
|
it('preserves unknown-identity pid and instance files when the recorded port probe is inconclusive', async () => {
|
|
1142
|
-
await
|
|
1144
|
+
await withTempAiWorkerDataDir(async () => {
|
|
1143
1145
|
const port = 45129;
|
|
1144
1146
|
const pid = 12350;
|
|
1145
1147
|
const pidFile = await getPidFilePath(port);
|
|
@@ -1158,8 +1160,8 @@ describe('lifecycle instance discovery', () => {
|
|
|
1158
1160
|
});
|
|
1159
1161
|
});
|
|
1160
1162
|
|
|
1161
|
-
it('uses the live system-info pid instead of a stale
|
|
1162
|
-
await
|
|
1163
|
+
it('uses the live system-info pid instead of a stale AiWorker-looking pid-file pid', async () => {
|
|
1164
|
+
await withTempAiWorkerDataDir(async () => {
|
|
1163
1165
|
const port = 45127;
|
|
1164
1166
|
const stalePid = 12348;
|
|
1165
1167
|
const livePid = 54321;
|
|
@@ -1180,7 +1182,7 @@ describe('lifecycle instance discovery', () => {
|
|
|
1180
1182
|
});
|
|
1181
1183
|
|
|
1182
1184
|
it('uses the explicit host when probing a pid-file entry without a stored host', async () => {
|
|
1183
|
-
await
|
|
1185
|
+
await withTempAiWorkerDataDir(async () => {
|
|
1184
1186
|
const port = 45128;
|
|
1185
1187
|
const pid = 12349;
|
|
1186
1188
|
const host = '192.0.2.10';
|
|
@@ -1207,7 +1209,7 @@ describe('lifecycle instance discovery', () => {
|
|
|
1207
1209
|
});
|
|
1208
1210
|
|
|
1209
1211
|
it('tries loopback before treating an explicit-host pid-file probe as inconclusive', async () => {
|
|
1210
|
-
await
|
|
1212
|
+
await withTempAiWorkerDataDir(async () => {
|
|
1211
1213
|
const port = 45130;
|
|
1212
1214
|
const pid = 12351;
|
|
1213
1215
|
const host = '192.0.2.11';
|
|
@@ -1237,7 +1239,7 @@ describe('lifecycle instance discovery', () => {
|
|
|
1237
1239
|
});
|
|
1238
1240
|
|
|
1239
1241
|
it('does not accept a fallback loopback probe with a different pid for a concrete host registry', async () => {
|
|
1240
|
-
await
|
|
1242
|
+
await withTempAiWorkerDataDir(async () => {
|
|
1241
1243
|
const port = 45131;
|
|
1242
1244
|
const pid = 12352;
|
|
1243
1245
|
const otherPid = 54322;
|
|
@@ -1265,8 +1267,8 @@ describe('lifecycle instance discovery', () => {
|
|
|
1265
1267
|
});
|
|
1266
1268
|
});
|
|
1267
1269
|
|
|
1268
|
-
it('discovers an explicit live
|
|
1269
|
-
await
|
|
1270
|
+
it('discovers an explicit live AiWorker port without a pid-file registry entry', async () => {
|
|
1271
|
+
await withTempAiWorkerDataDir(async () => {
|
|
1270
1272
|
const port = 45125;
|
|
1271
1273
|
const instances = await discoverLifecycleInstances(
|
|
1272
1274
|
{ explicitPort: true, port },
|
|
@@ -1280,9 +1282,9 @@ describe('lifecycle instance discovery', () => {
|
|
|
1280
1282
|
});
|
|
1281
1283
|
|
|
1282
1284
|
it('cleans a matched pid-file entry without stopping it when the recorded port is free', async () => {
|
|
1283
|
-
await
|
|
1285
|
+
await withTempAiWorkerDataDir(async () => {
|
|
1284
1286
|
const port = await allocateLoopbackPort();
|
|
1285
|
-
const child =
|
|
1287
|
+
const child = spawnAiWorkerLikeIdleProcess();
|
|
1286
1288
|
const pidFile = await getPidFilePath(port);
|
|
1287
1289
|
const instanceFile = await getInstanceFilePath(port);
|
|
1288
1290
|
try {
|
|
@@ -1304,9 +1306,9 @@ describe('lifecycle instance discovery', () => {
|
|
|
1304
1306
|
});
|
|
1305
1307
|
|
|
1306
1308
|
describe('lifecycle commands with unmanaged explicit ports', () => {
|
|
1307
|
-
it('serve refuses to start on a live
|
|
1308
|
-
await
|
|
1309
|
-
const server = await
|
|
1309
|
+
it('serve refuses to start on a live AiWorker port without requiring pid files', async () => {
|
|
1310
|
+
await withTempAiWorkerDataDir(async () => {
|
|
1311
|
+
const server = await startMockAiWorkerServer();
|
|
1310
1312
|
try {
|
|
1311
1313
|
await expect(commands.serve({ explicitPort: true, port: server.port, quiet: true })).rejects.toThrow(
|
|
1312
1314
|
/already running on port/
|
|
@@ -1318,8 +1320,8 @@ describe('lifecycle commands with unmanaged explicit ports', () => {
|
|
|
1318
1320
|
});
|
|
1319
1321
|
|
|
1320
1322
|
it('status --port reports a live unmanaged server when the registry is empty', async () => {
|
|
1321
|
-
await
|
|
1322
|
-
const server = await
|
|
1323
|
+
await withTempAiWorkerDataDir(async () => {
|
|
1324
|
+
const server = await startMockAiWorkerServer();
|
|
1323
1325
|
try {
|
|
1324
1326
|
const output = await captureStdout(() => commands.status({ explicitPort: true, port: server.port, json: true }));
|
|
1325
1327
|
const payload = JSON.parse(output);
|
|
@@ -1335,8 +1337,8 @@ describe('lifecycle commands with unmanaged explicit ports', () => {
|
|
|
1335
1337
|
});
|
|
1336
1338
|
|
|
1337
1339
|
it('stop --port reaches unmanaged shutdown when the registry is empty', async () => {
|
|
1338
|
-
await
|
|
1339
|
-
const server = await
|
|
1340
|
+
await withTempAiWorkerDataDir(async () => {
|
|
1341
|
+
const server = await startMockAiWorkerServer();
|
|
1340
1342
|
try {
|
|
1341
1343
|
await commands.stop({ explicitPort: true, port: server.port, quiet: true, suppressQuietOutput: true });
|
|
1342
1344
|
expect(server.shutdownRequested).toBe(true);
|
|
@@ -1347,9 +1349,9 @@ describe('lifecycle commands with unmanaged explicit ports', () => {
|
|
|
1347
1349
|
});
|
|
1348
1350
|
|
|
1349
1351
|
it('stop --port can recover a matched pid-file instance whose HTTP endpoint is unresponsive', async () => {
|
|
1350
|
-
await
|
|
1352
|
+
await withTempAiWorkerDataDir(async () => {
|
|
1351
1353
|
const port = await allocateLoopbackPort();
|
|
1352
|
-
const child =
|
|
1354
|
+
const child = spawnAiWorkerLikeHungServer(port);
|
|
1353
1355
|
const pidFile = await getPidFilePath(port);
|
|
1354
1356
|
const instanceFile = await getInstanceFilePath(port);
|
|
1355
1357
|
try {
|
|
@@ -1369,8 +1371,8 @@ describe('lifecycle commands with unmanaged explicit ports', () => {
|
|
|
1369
1371
|
});
|
|
1370
1372
|
|
|
1371
1373
|
it('plain stop ignores a stale CLI registry entry that resolves to desktop runtime', async () => {
|
|
1372
|
-
await
|
|
1373
|
-
const server = await
|
|
1374
|
+
await withTempAiWorkerDataDir(async () => {
|
|
1375
|
+
const server = await startMockAiWorkerServer({ runtime: 'desktop' });
|
|
1374
1376
|
const child = spawn('sleep', ['30'], { stdio: 'ignore' });
|
|
1375
1377
|
const pidFile = await getPidFilePath(server.port);
|
|
1376
1378
|
const instanceFile = await getInstanceFilePath(server.port);
|
|
@@ -1392,8 +1394,8 @@ describe('lifecycle commands with unmanaged explicit ports', () => {
|
|
|
1392
1394
|
});
|
|
1393
1395
|
|
|
1394
1396
|
it('restart --port restarts a live unmanaged server through the shared explicit-port discovery path', async () => {
|
|
1395
|
-
await
|
|
1396
|
-
const server = await
|
|
1397
|
+
await withTempAiWorkerDataDir(async () => {
|
|
1398
|
+
const server = await startMockAiWorkerServer();
|
|
1397
1399
|
const calls = [];
|
|
1398
1400
|
const host = '127.0.0.1';
|
|
1399
1401
|
try {
|
|
@@ -1426,19 +1428,19 @@ describe('lifecycle commands with unmanaged explicit ports', () => {
|
|
|
1426
1428
|
describe('Windows startup task command builder', () => {
|
|
1427
1429
|
it('default-path length stays under 200 chars', () => {
|
|
1428
1430
|
const cmd = buildWindowsStartupTaskCommand(
|
|
1429
|
-
'C:\\Users\\test\\.config\\
|
|
1431
|
+
'C:\\Users\\test\\.config\\aiworker\\bin\\AiWorker.ps1'
|
|
1430
1432
|
);
|
|
1431
1433
|
expect(cmd).toMatch(/^powershell\.exe -NoProfile -ExecutionPolicy Bypass -File /);
|
|
1432
1434
|
expect(cmd.length).toBeLessThan(200);
|
|
1433
1435
|
});
|
|
1434
1436
|
|
|
1435
1437
|
it('worst-case long path stays under 261-char Task Scheduler ceiling', () => {
|
|
1436
|
-
// Build a wrapper path >= 180 chars (simulates long
|
|
1438
|
+
// Build a wrapper path >= 180 chars (simulates long AIWORKER_DATA_DIR)
|
|
1437
1439
|
// Overhead = 57 chars (prefix + closing quote), so max wrapper for <261 total is 203
|
|
1438
1440
|
const longPath =
|
|
1439
1441
|
'C:\\Users\\' +
|
|
1440
1442
|
'a'.repeat(139) +
|
|
1441
|
-
'\\.config\\
|
|
1443
|
+
'\\.config\\aiworker\\bin\\AiWorker.ps1';
|
|
1442
1444
|
expect(longPath.length).toBeGreaterThanOrEqual(180);
|
|
1443
1445
|
|
|
1444
1446
|
const cmd = buildWindowsStartupTaskCommand(longPath);
|
|
@@ -1456,3 +1458,93 @@ describe('Windows startup task command builder', () => {
|
|
|
1456
1458
|
expect(cmd).not.toContain('-Command ');
|
|
1457
1459
|
});
|
|
1458
1460
|
});
|
|
1461
|
+
|
|
1462
|
+
describe('startup command lingering output', () => {
|
|
1463
|
+
const linuxStatus = (lingerEnabled, lingerUser = 'alice') => ({
|
|
1464
|
+
supported: true,
|
|
1465
|
+
platform: 'linux',
|
|
1466
|
+
enabled: true,
|
|
1467
|
+
active: true,
|
|
1468
|
+
activeState: 'active',
|
|
1469
|
+
servicePath: '/home/alice/.config/systemd/user/openchamber.service',
|
|
1470
|
+
lingerEnabled,
|
|
1471
|
+
lingerUser,
|
|
1472
|
+
});
|
|
1473
|
+
|
|
1474
|
+
const dependenciesFor = (status) => ({
|
|
1475
|
+
getStartupStatus: () => status,
|
|
1476
|
+
enableStartupService: () => status,
|
|
1477
|
+
disableStartupService: () => status,
|
|
1478
|
+
});
|
|
1479
|
+
|
|
1480
|
+
const runCommand = (status, options, action) => startupCommand(options, action, dependenciesFor(status));
|
|
1481
|
+
|
|
1482
|
+
it.each([
|
|
1483
|
+
['enable', true, 'ok', undefined],
|
|
1484
|
+
['enable', false, 'warning', 'LINGER_DISABLED'],
|
|
1485
|
+
['enable', null, 'warning', 'LINGER_UNKNOWN'],
|
|
1486
|
+
['status', true, 'ok', undefined],
|
|
1487
|
+
['status', false, 'warning', 'LINGER_DISABLED'],
|
|
1488
|
+
['status', null, 'warning', 'LINGER_UNKNOWN'],
|
|
1489
|
+
])('reports %s with Linux linger=%s as JSON-only output', async (action, lingerEnabled, expectedStatus, warningCode) => {
|
|
1490
|
+
const output = await captureStdout(() => runCommand(linuxStatus(lingerEnabled), { json: true }, action));
|
|
1491
|
+
const payload = JSON.parse(output);
|
|
1492
|
+
|
|
1493
|
+
expect(payload.status).toBe(expectedStatus);
|
|
1494
|
+
expect(payload.action).toBe(action);
|
|
1495
|
+
expect(payload.lingerEnabled).toBe(lingerEnabled);
|
|
1496
|
+
expect(payload.messages?.[0]?.code).toBe(warningCode);
|
|
1497
|
+
});
|
|
1498
|
+
|
|
1499
|
+
it.each([
|
|
1500
|
+
['enable', true, 'yes'],
|
|
1501
|
+
['enable', false, 'no'],
|
|
1502
|
+
['enable', null, 'unknown'],
|
|
1503
|
+
['status', true, 'yes'],
|
|
1504
|
+
['status', false, 'no'],
|
|
1505
|
+
['status', null, 'unknown'],
|
|
1506
|
+
])('reports %s with Linux linger=%s in one quiet result line', async (action, lingerEnabled, label) => {
|
|
1507
|
+
const output = await captureStdout(() => runCommand(linuxStatus(lingerEnabled), { quiet: true }, action));
|
|
1508
|
+
|
|
1509
|
+
expect(output.split('\n')).toHaveLength(2);
|
|
1510
|
+
expect(output).toContain(` linger:${label}\n`);
|
|
1511
|
+
expect(output).not.toContain('loginctl');
|
|
1512
|
+
});
|
|
1513
|
+
|
|
1514
|
+
it.each([true, false])('warns with an actionable command in human TTY=%s output', async (isTTY) => {
|
|
1515
|
+
const descriptor = Object.getOwnPropertyDescriptor(process.stdout, 'isTTY');
|
|
1516
|
+
Object.defineProperty(process.stdout, 'isTTY', { configurable: true, value: isTTY });
|
|
1517
|
+
try {
|
|
1518
|
+
const output = await captureStdout(() => runCommand(linuxStatus(false), {}, 'enable'));
|
|
1519
|
+
|
|
1520
|
+
expect(output).toContain('[LINGER_DISABLED]');
|
|
1521
|
+
expect(output).toContain('sudo loginctl enable-linger alice');
|
|
1522
|
+
} finally {
|
|
1523
|
+
if (descriptor) Object.defineProperty(process.stdout, 'isTTY', descriptor);
|
|
1524
|
+
else delete process.stdout.isTTY;
|
|
1525
|
+
}
|
|
1526
|
+
});
|
|
1527
|
+
|
|
1528
|
+
it('reports unknown state without inventing a user when detection is unavailable', async () => {
|
|
1529
|
+
const output = await captureStdout(() => runCommand(linuxStatus(null, null), {}, 'status'));
|
|
1530
|
+
|
|
1531
|
+
expect(output).toContain('[LINGER_UNKNOWN]');
|
|
1532
|
+
expect(output).toContain('loginctl show-user "$USER" -p Linger');
|
|
1533
|
+
});
|
|
1534
|
+
|
|
1535
|
+
it('reports disabled-service linger state without warning or remediation', async () => {
|
|
1536
|
+
const output = await captureStdout(() => runCommand({ ...linuxStatus(false), enabled: false }, {}, 'status'));
|
|
1537
|
+
|
|
1538
|
+
expect(output).toContain('user lingering is disabled');
|
|
1539
|
+
expect(output).not.toContain('[LINGER_DISABLED]');
|
|
1540
|
+
expect(output).not.toContain('loginctl enable-linger');
|
|
1541
|
+
});
|
|
1542
|
+
|
|
1543
|
+
it('does not emit linger guidance for unsupported systems', async () => {
|
|
1544
|
+
await expect(runCommand(
|
|
1545
|
+
{ supported: false, platform: 'freebsd', enabled: false, servicePath: null },
|
|
1546
|
+
{ json: true },
|
|
1547
|
+
'status'
|
|
1548
|
+
)).rejects.toMatchObject({ exitCode: EXIT_CODE.USAGE_ERROR });
|
|
1549
|
+
});
|
|
1550
|
+
});
|
|
@@ -56,7 +56,7 @@ async function resolveTargetPort(options = {}, deps = {}) {
|
|
|
56
56
|
return DEFAULT_PORT;
|
|
57
57
|
}
|
|
58
58
|
throw new TunnelCliError(
|
|
59
|
-
`Multiple
|
|
59
|
+
`Multiple AiWorker instances are running (ports: ${ports.join(', ')}). Choose one with --port <port>.`,
|
|
60
60
|
EXIT_CODE.USAGE_ERROR,
|
|
61
61
|
);
|
|
62
62
|
}
|
|
@@ -66,7 +66,7 @@ async function resolveTargetPort(options = {}, deps = {}) {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
throw new TunnelCliError(
|
|
69
|
-
'No running
|
|
69
|
+
'No running AiWorker server found. Start one with `aiworker serve`, or pass --port <port>.',
|
|
70
70
|
EXIT_CODE.GENERAL_ERROR,
|
|
71
71
|
);
|
|
72
72
|
}
|