@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
|
@@ -18,7 +18,7 @@ const goal = {
|
|
|
18
18
|
const session = {
|
|
19
19
|
id: SESSION_ID,
|
|
20
20
|
directory: DIRECTORY,
|
|
21
|
-
metadata: {
|
|
21
|
+
metadata: { aiworker: { goal } },
|
|
22
22
|
};
|
|
23
23
|
|
|
24
24
|
const jsonResponse = (body, status = 200) => new Response(JSON.stringify(body), {
|
|
@@ -46,6 +46,78 @@ const startIdleTick = async (fetchImpl) => {
|
|
|
46
46
|
return { runtime, getSmallModelService };
|
|
47
47
|
};
|
|
48
48
|
|
|
49
|
+
const assistantMessage = (id, infoOverrides = {}) => ({
|
|
50
|
+
info: {
|
|
51
|
+
id,
|
|
52
|
+
sessionID: SESSION_ID,
|
|
53
|
+
role: 'assistant',
|
|
54
|
+
providerID: 'provider',
|
|
55
|
+
modelID: 'model',
|
|
56
|
+
time: { created: 2, completed: 2 },
|
|
57
|
+
tokens: { input: 1, output: 1, cache: { read: 0 } },
|
|
58
|
+
...infoOverrides,
|
|
59
|
+
},
|
|
60
|
+
parts: [{ type: 'text', text: 'The agent made progress.' }],
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const createRuntimeHarness = ({ messages, messageFactory, goalOverrides = {}, maxAutoTurns = 20 }) => {
|
|
64
|
+
const requests = [];
|
|
65
|
+
let messageFetchCount = 0;
|
|
66
|
+
const activeSession = {
|
|
67
|
+
...session,
|
|
68
|
+
metadata: { aiworker: { goal: { ...goal, ...goalOverrides } } },
|
|
69
|
+
};
|
|
70
|
+
const service = {
|
|
71
|
+
generateSmallModelText: vi.fn(async () => ({
|
|
72
|
+
text: '{"verdict":"continue","note":"More work remains"}',
|
|
73
|
+
providerID: 'provider',
|
|
74
|
+
modelID: 'model',
|
|
75
|
+
})),
|
|
76
|
+
};
|
|
77
|
+
const fetchImpl = vi.fn(async (input, init = {}) => {
|
|
78
|
+
const pathname = requestPath(input);
|
|
79
|
+
requests.push({ pathname, method: init.method ?? 'GET', body: init.body });
|
|
80
|
+
if (pathname === `/session/${SESSION_ID}` && init.method === 'PATCH') {
|
|
81
|
+
activeSession.metadata = JSON.parse(init.body).metadata;
|
|
82
|
+
return jsonResponse(activeSession);
|
|
83
|
+
}
|
|
84
|
+
if (pathname === `/session/${SESSION_ID}`) return jsonResponse(activeSession);
|
|
85
|
+
if (pathname === '/session/status') return jsonResponse({});
|
|
86
|
+
if (pathname === `/session/${SESSION_ID}/children`) return jsonResponse([]);
|
|
87
|
+
if (pathname === `/session/${SESSION_ID}/message`) {
|
|
88
|
+
const nextMessages = messageFactory ? messageFactory(messageFetchCount) : messages;
|
|
89
|
+
messageFetchCount += 1;
|
|
90
|
+
return jsonResponse(nextMessages);
|
|
91
|
+
}
|
|
92
|
+
if (pathname === `/session/${SESSION_ID}/prompt_async`) return jsonResponse({ ok: true });
|
|
93
|
+
throw new Error(`Unexpected request: ${pathname}`);
|
|
94
|
+
});
|
|
95
|
+
vi.stubGlobal('fetch', fetchImpl);
|
|
96
|
+
const runtime = createSessionGoalRuntime({
|
|
97
|
+
buildOpenCodeUrl: (pathname) => `http://opencode.test${pathname}`,
|
|
98
|
+
getOpenCodeAuthHeaders: () => ({}),
|
|
99
|
+
getSmallModelService: async () => service,
|
|
100
|
+
isEnabled: () => true,
|
|
101
|
+
idleQuietMs: 10,
|
|
102
|
+
maxAutoTurns,
|
|
103
|
+
});
|
|
104
|
+
return { runtime, requests, service, activeSession };
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const runIdleTick = async (runtime) => {
|
|
108
|
+
runtime.processPayload({
|
|
109
|
+
type: 'session.status',
|
|
110
|
+
properties: { sessionID: SESSION_ID, status: { type: 'idle' }, directory: DIRECTORY },
|
|
111
|
+
});
|
|
112
|
+
await vi.runOnlyPendingTimersAsync();
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const lastPatchedGoal = (requests) => {
|
|
116
|
+
const patches = requests.filter((request) => request.pathname === `/session/${SESSION_ID}` && request.method === 'PATCH');
|
|
117
|
+
expect(patches.length).toBeGreaterThan(0);
|
|
118
|
+
return JSON.parse(patches.at(-1).body).metadata.aiworker.goal;
|
|
119
|
+
};
|
|
120
|
+
|
|
49
121
|
describe('session goal live activity gate', () => {
|
|
50
122
|
beforeEach(() => {
|
|
51
123
|
vi.useFakeTimers();
|
|
@@ -170,7 +242,7 @@ describe('session goal live activity gate', () => {
|
|
|
170
242
|
expect(service.generateSmallModelText).toHaveBeenCalledOnce();
|
|
171
243
|
const patch = requests.find((request) => request.pathname === `/session/${SESSION_ID}` && request.method === 'PATCH');
|
|
172
244
|
expect(patch).toBeDefined();
|
|
173
|
-
const writtenGoal = JSON.parse(patch.body).metadata.
|
|
245
|
+
const writtenGoal = JSON.parse(patch.body).metadata.aiworker.goal;
|
|
174
246
|
expect(writtenGoal).toMatchObject({
|
|
175
247
|
status: 'complete',
|
|
176
248
|
evaluationProviderID: 'provider',
|
|
@@ -178,4 +250,358 @@ describe('session goal live activity gate', () => {
|
|
|
178
250
|
});
|
|
179
251
|
runtime.stop();
|
|
180
252
|
});
|
|
253
|
+
|
|
254
|
+
it('skips audit and sends continuation prompt when assistant message finishes with length stop', async () => {
|
|
255
|
+
const requests = [];
|
|
256
|
+
const fetchImpl = vi.fn(async (input, init = {}) => {
|
|
257
|
+
const pathname = requestPath(input);
|
|
258
|
+
requests.push({ pathname, method: init.method ?? 'GET', body: init.body });
|
|
259
|
+
if (pathname === `/session/${SESSION_ID}` && init.method === 'PATCH') return jsonResponse(session);
|
|
260
|
+
if (pathname === `/session/${SESSION_ID}`) return jsonResponse(session);
|
|
261
|
+
if (pathname === '/session/status') return jsonResponse({});
|
|
262
|
+
if (pathname === `/session/${SESSION_ID}/children`) return jsonResponse([]);
|
|
263
|
+
if (pathname === `/session/${SESSION_ID}/prompt_async`) return jsonResponse({ ok: true });
|
|
264
|
+
if (pathname === `/session/${SESSION_ID}/message`) {
|
|
265
|
+
return jsonResponse([{
|
|
266
|
+
info: {
|
|
267
|
+
id: 'msg_assistant_len',
|
|
268
|
+
sessionID: SESSION_ID,
|
|
269
|
+
role: 'assistant',
|
|
270
|
+
providerID: 'provider',
|
|
271
|
+
modelID: 'model',
|
|
272
|
+
finish: 'length',
|
|
273
|
+
time: { completed: 2 },
|
|
274
|
+
tokens: { input: 100, output: 4096, reasoning: 4096, cache: { read: 0 } },
|
|
275
|
+
},
|
|
276
|
+
parts: [{ type: 'reasoning', text: 'Drafting extensive implementation...' }],
|
|
277
|
+
}]);
|
|
278
|
+
}
|
|
279
|
+
throw new Error(`Unexpected request: ${pathname}`);
|
|
280
|
+
});
|
|
281
|
+
const service = {
|
|
282
|
+
generateSmallModelText: vi.fn(),
|
|
283
|
+
};
|
|
284
|
+
vi.stubGlobal('fetch', fetchImpl);
|
|
285
|
+
const runtime = createSessionGoalRuntime({
|
|
286
|
+
buildOpenCodeUrl: (pathname) => `http://opencode.test${pathname}`,
|
|
287
|
+
getOpenCodeAuthHeaders: () => ({}),
|
|
288
|
+
getSmallModelService: async () => service,
|
|
289
|
+
isEnabled: () => true,
|
|
290
|
+
idleQuietMs: 10,
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
runtime.processPayload({
|
|
294
|
+
type: 'session.status',
|
|
295
|
+
properties: { sessionID: SESSION_ID, status: { type: 'idle' }, directory: DIRECTORY },
|
|
296
|
+
});
|
|
297
|
+
await vi.runOnlyPendingTimersAsync();
|
|
298
|
+
|
|
299
|
+
// Audit must be skipped on length-truncated turn
|
|
300
|
+
expect(service.generateSmallModelText).not.toHaveBeenCalled();
|
|
301
|
+
|
|
302
|
+
// Goal accounting is persisted and turnsUsed incremented
|
|
303
|
+
const patch = requests.find((request) => request.pathname === `/session/${SESSION_ID}` && request.method === 'PATCH');
|
|
304
|
+
expect(patch).toBeDefined();
|
|
305
|
+
const writtenGoal = JSON.parse(patch.body).metadata.aiworker.goal;
|
|
306
|
+
expect(writtenGoal).toMatchObject({
|
|
307
|
+
status: 'active',
|
|
308
|
+
turnsUsed: 2,
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
// Continuation prompt is dispatched
|
|
312
|
+
const promptAsync = requests.find((request) => request.pathname === `/session/${SESSION_ID}/prompt_async` && request.method === 'POST');
|
|
313
|
+
expect(promptAsync).toBeDefined();
|
|
314
|
+
const promptBody = JSON.parse(promptAsync.body);
|
|
315
|
+
expect(promptBody.parts[0].text).toContain('Continue working toward the active session goal.');
|
|
316
|
+
runtime.stop();
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
it('skips audit and sends continuation prompt when assistant message carries MessageOutputLengthError', async () => {
|
|
320
|
+
const requests = [];
|
|
321
|
+
const fetchImpl = vi.fn(async (input, init = {}) => {
|
|
322
|
+
const pathname = requestPath(input);
|
|
323
|
+
requests.push({ pathname, method: init.method ?? 'GET', body: init.body });
|
|
324
|
+
if (pathname === `/session/${SESSION_ID}` && init.method === 'PATCH') return jsonResponse(session);
|
|
325
|
+
if (pathname === `/session/${SESSION_ID}`) return jsonResponse(session);
|
|
326
|
+
if (pathname === '/session/status') return jsonResponse({});
|
|
327
|
+
if (pathname === `/session/${SESSION_ID}/children`) return jsonResponse([]);
|
|
328
|
+
if (pathname === `/session/${SESSION_ID}/prompt_async`) return jsonResponse({ ok: true });
|
|
329
|
+
if (pathname === `/session/${SESSION_ID}/message`) {
|
|
330
|
+
return jsonResponse([{
|
|
331
|
+
info: {
|
|
332
|
+
id: 'msg_assistant_len_err',
|
|
333
|
+
sessionID: SESSION_ID,
|
|
334
|
+
role: 'assistant',
|
|
335
|
+
providerID: 'provider',
|
|
336
|
+
modelID: 'model',
|
|
337
|
+
error: { name: 'MessageOutputLengthError', message: 'Maximum token limit reached' },
|
|
338
|
+
time: { completed: 2 },
|
|
339
|
+
tokens: { input: 100, output: 4096, reasoning: 4096, cache: { read: 0 } },
|
|
340
|
+
},
|
|
341
|
+
parts: [{ type: 'reasoning', text: 'Drafting extensive implementation...' }],
|
|
342
|
+
}]);
|
|
343
|
+
}
|
|
344
|
+
throw new Error(`Unexpected request: ${pathname}`);
|
|
345
|
+
});
|
|
346
|
+
const service = {
|
|
347
|
+
generateSmallModelText: vi.fn(),
|
|
348
|
+
};
|
|
349
|
+
vi.stubGlobal('fetch', fetchImpl);
|
|
350
|
+
const runtime = createSessionGoalRuntime({
|
|
351
|
+
buildOpenCodeUrl: (pathname) => `http://opencode.test${pathname}`,
|
|
352
|
+
getOpenCodeAuthHeaders: () => ({}),
|
|
353
|
+
getSmallModelService: async () => service,
|
|
354
|
+
isEnabled: () => true,
|
|
355
|
+
idleQuietMs: 10,
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
runtime.processPayload({
|
|
359
|
+
type: 'session.status',
|
|
360
|
+
properties: { sessionID: SESSION_ID, status: { type: 'idle' }, directory: DIRECTORY },
|
|
361
|
+
});
|
|
362
|
+
await vi.runOnlyPendingTimersAsync();
|
|
363
|
+
|
|
364
|
+
// Audit must be skipped on MessageOutputLengthError
|
|
365
|
+
expect(service.generateSmallModelText).not.toHaveBeenCalled();
|
|
366
|
+
|
|
367
|
+
// Goal remains active and turnsUsed incremented
|
|
368
|
+
const patch = requests.find((request) => request.pathname === `/session/${SESSION_ID}` && request.method === 'PATCH');
|
|
369
|
+
expect(patch).toBeDefined();
|
|
370
|
+
const writtenGoal = JSON.parse(patch.body).metadata.aiworker.goal;
|
|
371
|
+
expect(writtenGoal).toMatchObject({
|
|
372
|
+
status: 'active',
|
|
373
|
+
turnsUsed: 2,
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
// Continuation prompt is dispatched
|
|
377
|
+
const promptAsync = requests.find((request) => request.pathname === `/session/${SESSION_ID}/prompt_async` && request.method === 'POST');
|
|
378
|
+
expect(promptAsync).toBeDefined();
|
|
379
|
+
runtime.stop();
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
it.each([
|
|
383
|
+
{ name: 'APIError', error: { name: 'APIError', message: 'provider failed' } },
|
|
384
|
+
{ name: 'StructuredOutputError', error: { name: 'StructuredOutputError', message: 'invalid output' } },
|
|
385
|
+
{ name: 'unnamed errors', error: {} },
|
|
386
|
+
])('blocks a length finish when the assistant also has a non-length $name', async ({ error }) => {
|
|
387
|
+
const { runtime, requests, service } = createRuntimeHarness({
|
|
388
|
+
messages: [assistantMessage('msg_assistant_error', { finish: 'length', error })],
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
await runIdleTick(runtime);
|
|
392
|
+
|
|
393
|
+
expect(service.generateSmallModelText).not.toHaveBeenCalled();
|
|
394
|
+
expect(requests.some((request) => request.pathname === `/session/${SESSION_ID}/prompt_async`)).toBe(false);
|
|
395
|
+
expect(lastPatchedGoal(requests)).toMatchObject({
|
|
396
|
+
status: 'blocked',
|
|
397
|
+
statusReason: error.name || 'assistant turn failed',
|
|
398
|
+
});
|
|
399
|
+
runtime.stop();
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
it('blocks after two consecutive length-truncated agent turns without persisting a counter, regardless of message IDs', async () => {
|
|
403
|
+
const firstLength = assistantMessage('z', { finish: 'length', time: { created: 10, completed: 11 } });
|
|
404
|
+
const summary = assistantMessage('summary', { summary: true, time: { created: 15, completed: 16 } });
|
|
405
|
+
const secondLength = assistantMessage('a', { finish: 'length', time: { created: 20, completed: 21 } });
|
|
406
|
+
const { runtime, requests, service } = createRuntimeHarness({
|
|
407
|
+
messageFactory: (fetchCount) => fetchCount < 2 ? [firstLength] : [firstLength, summary, secondLength],
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
await runIdleTick(runtime);
|
|
411
|
+
runtime.processPayload({
|
|
412
|
+
type: 'session.status',
|
|
413
|
+
properties: { sessionID: SESSION_ID, status: { type: 'idle' }, directory: DIRECTORY },
|
|
414
|
+
});
|
|
415
|
+
await vi.runOnlyPendingTimersAsync();
|
|
416
|
+
|
|
417
|
+
expect(service.generateSmallModelText).not.toHaveBeenCalled();
|
|
418
|
+
expect(requests.filter((request) => request.pathname === `/session/${SESSION_ID}/prompt_async`)).toHaveLength(1);
|
|
419
|
+
expect(lastPatchedGoal(requests)).toMatchObject({
|
|
420
|
+
status: 'blocked',
|
|
421
|
+
statusReason: 'repeated output truncation',
|
|
422
|
+
});
|
|
423
|
+
runtime.stop();
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
it('allows one explicit Resume after repeated truncation, then blocks another cutoff', async () => {
|
|
427
|
+
const first = assistantMessage('first', { finish: 'length', time: { created: 10, completed: 11 } });
|
|
428
|
+
const second = assistantMessage('second', { finish: 'length', time: { created: 20, completed: 21 } });
|
|
429
|
+
const messages = [first, second];
|
|
430
|
+
const { runtime, requests, activeSession } = createRuntimeHarness({ messages });
|
|
431
|
+
try {
|
|
432
|
+
await runIdleTick(runtime);
|
|
433
|
+
expect(lastPatchedGoal(requests).status).toBe('blocked');
|
|
434
|
+
expect(requests.filter((request) => request.pathname.endsWith('/prompt_async'))).toHaveLength(0);
|
|
435
|
+
Object.assign(activeSession.metadata.aiworker.goal, { status: 'active', statusReason: 'resumed', turnsUsed: 0 });
|
|
436
|
+
await runIdleTick(runtime);
|
|
437
|
+
expect(lastPatchedGoal(requests)).toMatchObject({ status: 'active', statusReason: '', turnsUsed: 1 });
|
|
438
|
+
expect(requests.filter((request) => request.pathname.endsWith('/prompt_async'))).toHaveLength(1);
|
|
439
|
+
messages.push(assistantMessage('third', { finish: 'length', time: { created: 30, completed: 31 } }));
|
|
440
|
+
await runIdleTick(runtime);
|
|
441
|
+
expect(lastPatchedGoal(requests)).toMatchObject({ status: 'blocked', statusReason: 'repeated output truncation' });
|
|
442
|
+
expect(requests.filter((request) => request.pathname.endsWith('/prompt_async'))).toHaveLength(1);
|
|
443
|
+
} finally {
|
|
444
|
+
runtime.stop();
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
it.each([
|
|
449
|
+
{ tokenBudget: 1, error: undefined, expectedStatus: 'budgetLimited' },
|
|
450
|
+
{ tokenBudget: null, error: { name: 'APIError' }, expectedStatus: 'blocked' },
|
|
451
|
+
])('keeps $expectedStatus protection on explicit Resume', async ({ tokenBudget, error, expectedStatus }) => {
|
|
452
|
+
const { runtime, requests } = createRuntimeHarness({
|
|
453
|
+
goalOverrides: { statusReason: 'resumed', tokenBudget },
|
|
454
|
+
messages: [assistantMessage('resumed', { finish: 'length', error })],
|
|
455
|
+
});
|
|
456
|
+
try {
|
|
457
|
+
await runIdleTick(runtime);
|
|
458
|
+
expect(lastPatchedGoal(requests).status).toBe(expectedStatus);
|
|
459
|
+
expect(requests.filter((request) => request.pathname.endsWith('/prompt_async'))).toHaveLength(0);
|
|
460
|
+
} finally {
|
|
461
|
+
runtime.stop();
|
|
462
|
+
}
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
it('continues after a truncated agent turn followed by a length-finished summary', async () => {
|
|
466
|
+
const firstLength = assistantMessage('agent-length', { finish: 'length', time: { created: 10, completed: 11 } });
|
|
467
|
+
const summary = assistantMessage('summary', {
|
|
468
|
+
summary: true,
|
|
469
|
+
finish: 'length',
|
|
470
|
+
time: { created: 15, completed: 16 },
|
|
471
|
+
});
|
|
472
|
+
const { runtime, requests, service } = createRuntimeHarness({
|
|
473
|
+
messageFactory: (fetchCount) => fetchCount < 2 ? [firstLength] : [firstLength, summary],
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
await runIdleTick(runtime);
|
|
477
|
+
await runIdleTick(runtime);
|
|
478
|
+
|
|
479
|
+
expect(service.generateSmallModelText).not.toHaveBeenCalled();
|
|
480
|
+
expect(requests.filter((request) => request.pathname === `/session/${SESSION_ID}/prompt_async`)).toHaveLength(2);
|
|
481
|
+
expect(lastPatchedGoal(requests)).toMatchObject({ status: 'active' });
|
|
482
|
+
runtime.stop();
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
it('does not infer a repeated streak when a completed assistant timestamp is missing', async () => {
|
|
486
|
+
const firstLength = assistantMessage('z', { finish: 'length', time: { completed: 11 } });
|
|
487
|
+
const secondLength = assistantMessage('a', { finish: 'length', time: { created: 20, completed: 21 } });
|
|
488
|
+
const { runtime, requests, service } = createRuntimeHarness({
|
|
489
|
+
messageFactory: (fetchCount) => fetchCount < 2 ? [firstLength] : [firstLength, secondLength],
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
await runIdleTick(runtime);
|
|
493
|
+
await runIdleTick(runtime);
|
|
494
|
+
|
|
495
|
+
expect(service.generateSmallModelText).not.toHaveBeenCalled();
|
|
496
|
+
expect(requests.filter((request) => request.pathname === `/session/${SESSION_ID}/prompt_async`)).toHaveLength(2);
|
|
497
|
+
expect(lastPatchedGoal(requests)).toMatchObject({ status: 'active' });
|
|
498
|
+
runtime.stop();
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
it('ignores a missing timestamp on an older assistant when known post-goal turns establish a streak', async () => {
|
|
502
|
+
const olderLength = assistantMessage('legacy', { finish: 'length', time: { completed: 5 } });
|
|
503
|
+
const firstLength = assistantMessage('z', { finish: 'length', time: { created: 10, completed: 11 } });
|
|
504
|
+
const secondLength = assistantMessage('a', { finish: 'length', time: { created: 20, completed: 21 } });
|
|
505
|
+
const { runtime, requests, service } = createRuntimeHarness({
|
|
506
|
+
goalOverrides: { createdAt: 6 },
|
|
507
|
+
messageFactory: (fetchCount) => fetchCount < 2 ? [firstLength] : [olderLength, firstLength, secondLength],
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
await runIdleTick(runtime);
|
|
511
|
+
await runIdleTick(runtime);
|
|
512
|
+
|
|
513
|
+
expect(service.generateSmallModelText).not.toHaveBeenCalled();
|
|
514
|
+
expect(requests.filter((request) => request.pathname === `/session/${SESSION_ID}/prompt_async`)).toHaveLength(1);
|
|
515
|
+
expect(lastPatchedGoal(requests)).toMatchObject({
|
|
516
|
+
status: 'blocked',
|
|
517
|
+
statusReason: 'repeated output truncation',
|
|
518
|
+
});
|
|
519
|
+
runtime.stop();
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
it('does not count a previous truncated turn created at the goal boundary', async () => {
|
|
523
|
+
const firstLength = assistantMessage('before-boundary', { finish: 'length', time: { created: 10, completed: 11 } });
|
|
524
|
+
const secondLength = assistantMessage('after-boundary', { finish: 'length', time: { created: 20, completed: 21 } });
|
|
525
|
+
const { runtime, requests, service } = createRuntimeHarness({
|
|
526
|
+
goalOverrides: { createdAt: 10 },
|
|
527
|
+
messageFactory: (fetchCount) => fetchCount < 2 ? [firstLength] : [firstLength, secondLength],
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
await runIdleTick(runtime);
|
|
531
|
+
await runIdleTick(runtime);
|
|
532
|
+
|
|
533
|
+
expect(service.generateSmallModelText).not.toHaveBeenCalled();
|
|
534
|
+
expect(requests.filter((request) => request.pathname === `/session/${SESSION_ID}/prompt_async`)).toHaveLength(2);
|
|
535
|
+
expect(lastPatchedGoal(requests)).toMatchObject({ status: 'active' });
|
|
536
|
+
runtime.stop();
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
it('allows length recovery after an ordinary assistant turn breaks the streak', async () => {
|
|
540
|
+
const firstLength = assistantMessage('msg_length_before_normal', { finish: 'length' });
|
|
541
|
+
const normal = assistantMessage('msg_normal');
|
|
542
|
+
const secondLength = assistantMessage('msg_length_after_normal', { finish: 'length' });
|
|
543
|
+
const { runtime, requests, service } = createRuntimeHarness({
|
|
544
|
+
messageFactory: (fetchCount) => fetchCount < 2
|
|
545
|
+
? [firstLength]
|
|
546
|
+
: [firstLength, normal, secondLength],
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
await runIdleTick(runtime);
|
|
550
|
+
await runIdleTick(runtime);
|
|
551
|
+
|
|
552
|
+
expect(service.generateSmallModelText).not.toHaveBeenCalled();
|
|
553
|
+
expect(requests.filter((request) => request.pathname === `/session/${SESSION_ID}/prompt_async`)).toHaveLength(2);
|
|
554
|
+
expect(lastPatchedGoal(requests)).toMatchObject({ status: 'active' });
|
|
555
|
+
runtime.stop();
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
it('keeps MessageAbortedError pause behavior instead of continuing', async () => {
|
|
559
|
+
const { runtime, requests, service } = createRuntimeHarness({
|
|
560
|
+
messages: [assistantMessage('msg_aborted', { error: { name: 'MessageAbortedError' } })],
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
await runIdleTick(runtime);
|
|
564
|
+
|
|
565
|
+
expect(service.generateSmallModelText).not.toHaveBeenCalled();
|
|
566
|
+
expect(requests.some((request) => request.pathname === `/session/${SESSION_ID}/prompt_async`)).toBe(false);
|
|
567
|
+
expect(lastPatchedGoal(requests)).toMatchObject({
|
|
568
|
+
status: 'paused',
|
|
569
|
+
statusReason: 'paused after abort',
|
|
570
|
+
});
|
|
571
|
+
runtime.stop();
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
it('checks the token budget before allowing length recovery', async () => {
|
|
575
|
+
const { runtime, requests, service } = createRuntimeHarness({
|
|
576
|
+
goalOverrides: { tokenBudget: 5 },
|
|
577
|
+
messages: [assistantMessage('msg_budget_length', {
|
|
578
|
+
finish: 'length',
|
|
579
|
+
tokens: { input: 3, output: 3, cache: { read: 0 } },
|
|
580
|
+
})],
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
await runIdleTick(runtime);
|
|
584
|
+
|
|
585
|
+
expect(service.generateSmallModelText).not.toHaveBeenCalled();
|
|
586
|
+
expect(requests.some((request) => request.pathname === `/session/${SESSION_ID}/prompt_async`)).toBe(false);
|
|
587
|
+
expect(lastPatchedGoal(requests)).toMatchObject({ status: 'budgetLimited' });
|
|
588
|
+
runtime.stop();
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
it('checks the auto-continuation cap before allowing length recovery', async () => {
|
|
592
|
+
const { runtime, requests, service } = createRuntimeHarness({
|
|
593
|
+
maxAutoTurns: 1,
|
|
594
|
+
messages: [assistantMessage('msg_cap_length', { finish: 'length' })],
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
await runIdleTick(runtime);
|
|
598
|
+
|
|
599
|
+
expect(service.generateSmallModelText).not.toHaveBeenCalled();
|
|
600
|
+
expect(requests.some((request) => request.pathname === `/session/${SESSION_ID}/prompt_async`)).toBe(false);
|
|
601
|
+
expect(lastPatchedGoal(requests)).toMatchObject({
|
|
602
|
+
status: 'blocked',
|
|
603
|
+
statusReason: 'auto-continuation limit reached',
|
|
604
|
+
});
|
|
605
|
+
runtime.stop();
|
|
606
|
+
});
|
|
181
607
|
});
|
|
@@ -60,7 +60,7 @@ const buildMemoryBlock = ({ global, project }) => {
|
|
|
60
60
|
return [
|
|
61
61
|
'You have stored memory from earlier sessions. Only the titles are listed below.',
|
|
62
62
|
'A title is an abbreviation, not the memory. Read the entry with the'
|
|
63
|
-
+ '
|
|
63
|
+
+ ' aiworker_memory tool before you act on it: titles routinely leave out'
|
|
64
64
|
+ ' the conditions, exceptions and reasons that decide how the memory'
|
|
65
65
|
+ ' applies, and a title that looks self-explanatory is the most likely to'
|
|
66
66
|
+ ' be hiding them. Read every title that could bear on the task at hand;'
|
|
@@ -122,8 +122,8 @@ export const createSessionKnowledgeRuntime = (dependencies) => {
|
|
|
122
122
|
*/
|
|
123
123
|
const readPins = (session) => {
|
|
124
124
|
const metadata = isRecord(session?.metadata) ? session.metadata : {};
|
|
125
|
-
const
|
|
126
|
-
const pins = isRecord(
|
|
125
|
+
const aiworker = isRecord(metadata.aiworker) ? metadata.aiworker : {};
|
|
126
|
+
const pins = isRecord(aiworker[PINS_METADATA_KEY]) ? aiworker[PINS_METADATA_KEY] : {};
|
|
127
127
|
const strings = (value) => Array.isArray(value)
|
|
128
128
|
? [...new Set(value.filter((entry) => typeof entry === 'string' && entry.trim()).map((entry) => entry.trim()))]
|
|
129
129
|
: [];
|
|
@@ -229,8 +229,8 @@ export const createSessionKnowledgeRuntime = (dependencies) => {
|
|
|
229
229
|
|
|
230
230
|
const readDeliveredSignature = (session) => {
|
|
231
231
|
const metadata = isRecord(session?.metadata) ? session.metadata : {};
|
|
232
|
-
const
|
|
233
|
-
const delivered =
|
|
232
|
+
const aiworker = isRecord(metadata.aiworker) ? metadata.aiworker : {};
|
|
233
|
+
const delivered = aiworker[KNOWLEDGE_METADATA_KEY];
|
|
234
234
|
return typeof delivered === 'string' ? delivered : '';
|
|
235
235
|
};
|
|
236
236
|
|
|
@@ -267,7 +267,7 @@ export const createSessionKnowledgeRuntime = (dependencies) => {
|
|
|
267
267
|
const setPin = async (sessionId, directory, kind, id, pinned) => {
|
|
268
268
|
const fresh = await readSession(sessionId, directory);
|
|
269
269
|
const metadata = isRecord(fresh?.metadata) ? fresh.metadata : {};
|
|
270
|
-
const
|
|
270
|
+
const aiworker = isRecord(metadata.aiworker) ? metadata.aiworker : {};
|
|
271
271
|
const pins = readPins(fresh);
|
|
272
272
|
const key = kind === 'note' ? 'notes' : 'plans';
|
|
273
273
|
const next = new Set(pins[key]);
|
|
@@ -279,8 +279,8 @@ export const createSessionKnowledgeRuntime = (dependencies) => {
|
|
|
279
279
|
body: {
|
|
280
280
|
metadata: {
|
|
281
281
|
...metadata,
|
|
282
|
-
|
|
283
|
-
...
|
|
282
|
+
aiworker: {
|
|
283
|
+
...aiworker,
|
|
284
284
|
[PINS_METADATA_KEY]: { ...pins, [key]: [...next] },
|
|
285
285
|
[KNOWLEDGE_METADATA_KEY]: '',
|
|
286
286
|
},
|
|
@@ -296,20 +296,20 @@ export const createSessionKnowledgeRuntime = (dependencies) => {
|
|
|
296
296
|
* agent had context it never received.
|
|
297
297
|
*
|
|
298
298
|
* Merged onto a fresh read, because the session's metadata holds other
|
|
299
|
-
*
|
|
299
|
+
* AiWorker state — pinned messages among it — and a blind write would
|
|
300
300
|
* drop whatever changed in between.
|
|
301
301
|
*/
|
|
302
302
|
const recordDelivered = async (sessionId, directory, signature) => {
|
|
303
303
|
const fresh = await readSession(sessionId, directory);
|
|
304
304
|
const metadata = isRecord(fresh?.metadata) ? fresh.metadata : {};
|
|
305
|
-
const
|
|
305
|
+
const aiworker = isRecord(metadata.aiworker) ? metadata.aiworker : {};
|
|
306
306
|
await openCodeFetch(`/session/${encodeURIComponent(sessionId)}`, {
|
|
307
307
|
directory,
|
|
308
308
|
method: 'PATCH',
|
|
309
309
|
body: {
|
|
310
310
|
metadata: {
|
|
311
311
|
...metadata,
|
|
312
|
-
|
|
312
|
+
aiworker: { ...aiworker, [KNOWLEDGE_METADATA_KEY]: signature },
|
|
313
313
|
},
|
|
314
314
|
},
|
|
315
315
|
});
|
|
@@ -193,10 +193,10 @@ describe('reading what a session was told', () => {
|
|
|
193
193
|
const runtime = createRuntime();
|
|
194
194
|
|
|
195
195
|
expect(runtime.readPins({
|
|
196
|
-
metadata: {
|
|
196
|
+
metadata: { aiworker: { project_context_pins: { notes: ['n1'], plans: [] } } },
|
|
197
197
|
})).toEqual({ notes: ['n1'], plans: [] });
|
|
198
198
|
expect(runtime.readPins({
|
|
199
|
-
metadata: {
|
|
199
|
+
metadata: { aiworker: { project_context_pins: { notes: [], plans: ['p1'] } } },
|
|
200
200
|
})).toEqual({ notes: [], plans: ['p1'] });
|
|
201
201
|
expect(runtime.readPins({})).toEqual({ notes: [], plans: [] });
|
|
202
202
|
});
|
|
@@ -208,7 +208,7 @@ describe('reading what a session was told', () => {
|
|
|
208
208
|
requests.push({ path, options });
|
|
209
209
|
if (options.method === 'PATCH') return {};
|
|
210
210
|
return {
|
|
211
|
-
metadata: {
|
|
211
|
+
metadata: { aiworker: { project_context_pins: { notes: [], plans: [] }, knowledge_context_delivered: 'old' } },
|
|
212
212
|
};
|
|
213
213
|
},
|
|
214
214
|
});
|
|
@@ -216,7 +216,7 @@ describe('reading what a session was told', () => {
|
|
|
216
216
|
await runtime.setPin('ses_a', DIRECTORY, 'note', 'n1', true);
|
|
217
217
|
|
|
218
218
|
expect(requests.map((request) => request.path)).toEqual(['/session/ses_a', '/session/ses_a']);
|
|
219
|
-
expect(requests[1].options.body.metadata.
|
|
219
|
+
expect(requests[1].options.body.metadata.aiworker).toEqual({
|
|
220
220
|
project_context_pins: { notes: ['n1'], plans: [] },
|
|
221
221
|
knowledge_context_delivered: '',
|
|
222
222
|
});
|
|
@@ -226,7 +226,7 @@ describe('reading what a session was told', () => {
|
|
|
226
226
|
const runtime = createRuntime();
|
|
227
227
|
|
|
228
228
|
expect(runtime.readDeliveredSignature({
|
|
229
|
-
metadata: {
|
|
229
|
+
metadata: { aiworker: { knowledge_context_delivered: 'sig' } },
|
|
230
230
|
})).toBe('sig');
|
|
231
231
|
});
|
|
232
232
|
|
|
@@ -58,7 +58,7 @@ The following functions are internal helpers used by exported functions:
|
|
|
58
58
|
- `safeRm(dir)`: Safely remove directory recursively (ignores errors).
|
|
59
59
|
- `ensureDir(dirPath)`: Ensure directory exists with recursive creation.
|
|
60
60
|
- `copyDirectoryNoSymlinks(srcDir, dstDir)`: Copy directory contents without symlinks, with path traversal protection.
|
|
61
|
-
- `normalizeUserSkillDir(userSkillDir)`: Normalize user skill directory path (handles legacy `~/.config/opencode/skill`
|
|
61
|
+
- `normalizeUserSkillDir(userSkillDir)`: Normalize the user skill directory path (handles the legacy `skill` directory in the XDG config location, or `~/.config/opencode/skill` when XDG is unset, by selecting the plural `skills` directory when appropriate).
|
|
62
62
|
|
|
63
63
|
### Git Clone Helpers (`install.js`, `scan.js`)
|
|
64
64
|
- `cloneRepo({ cloneUrl, identity, tempDir })`: Clone git repository with preferred partial clone (`--filter=blob:none`) and fallback. Uses non-interactive mode.
|
|
@@ -8,11 +8,11 @@ let tempDataDir;
|
|
|
8
8
|
|
|
9
9
|
beforeEach(() => {
|
|
10
10
|
tempDataDir = fs.mkdtempSync(path.join(os.tmpdir(), 'skills-cache-test-'));
|
|
11
|
-
process.env.
|
|
11
|
+
process.env.AIWORKER_DATA_DIR = tempDataDir;
|
|
12
12
|
});
|
|
13
13
|
|
|
14
14
|
afterEach(() => {
|
|
15
|
-
delete process.env.
|
|
15
|
+
delete process.env.AIWORKER_DATA_DIR;
|
|
16
16
|
clearCache();
|
|
17
17
|
vi.restoreAllMocks();
|
|
18
18
|
fs.rmSync(tempDataDir, { recursive: true, force: true });
|
|
@@ -2,9 +2,9 @@ import fs from 'fs';
|
|
|
2
2
|
import os from 'os';
|
|
3
3
|
import path from 'path';
|
|
4
4
|
|
|
5
|
-
const resolveDataDir = () => (process.env.
|
|
6
|
-
? path.resolve(process.env.
|
|
7
|
-
: path.join(os.homedir(), '.config', '
|
|
5
|
+
const resolveDataDir = () => (process.env.AIWORKER_DATA_DIR
|
|
6
|
+
? path.resolve(process.env.AIWORKER_DATA_DIR)
|
|
7
|
+
: path.join(os.homedir(), '.config', 'aiworker'));
|
|
8
8
|
|
|
9
9
|
const readJsonFile = (filePath) => {
|
|
10
10
|
try {
|
|
@@ -17,7 +17,7 @@ const readJsonFile = (filePath) => {
|
|
|
17
17
|
};
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
|
-
* Read a persisted cache object from the
|
|
20
|
+
* Read a persisted cache object from the AiWorker data directory.
|
|
21
21
|
* Returns null when the file is missing, unreadable, or malformed.
|
|
22
22
|
*/
|
|
23
23
|
export const readDiskCache = (fileName) => {
|
|
@@ -29,7 +29,7 @@ export const readDiskCache = (fileName) => {
|
|
|
29
29
|
};
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
|
-
* Persist a cache object to the
|
|
32
|
+
* Persist a cache object to the AiWorker data directory with an atomic
|
|
33
33
|
* temp-file rename. Failures are ignored: the in-memory cache stays
|
|
34
34
|
* authoritative and the next successful write retries persistence.
|
|
35
35
|
*/
|
|
@@ -10,11 +10,11 @@ let tempDataDir;
|
|
|
10
10
|
|
|
11
11
|
beforeEach(() => {
|
|
12
12
|
tempDataDir = fs.mkdtempSync(path.join(os.tmpdir(), 'github-meta-test-'));
|
|
13
|
-
process.env.
|
|
13
|
+
process.env.AIWORKER_DATA_DIR = tempDataDir;
|
|
14
14
|
});
|
|
15
15
|
|
|
16
16
|
afterEach(() => {
|
|
17
|
-
delete process.env.
|
|
17
|
+
delete process.env.AIWORKER_DATA_DIR;
|
|
18
18
|
globalThis.fetch = originalFetch;
|
|
19
19
|
clearGitHubMetaCache();
|
|
20
20
|
vi.restoreAllMocks();
|
|
@@ -4,13 +4,14 @@ import path from 'path';
|
|
|
4
4
|
|
|
5
5
|
import { assertGitAvailable, looksLikeAuthError, runGit } from './git.js';
|
|
6
6
|
import { parseSkillRepoSource } from './source.js';
|
|
7
|
+
import { OPENCODE_CONFIG_DIR } from '../opencode/shared.js';
|
|
7
8
|
|
|
8
9
|
const SKILL_NAME_PATTERN = /^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$/;
|
|
9
10
|
|
|
10
11
|
function normalizeUserSkillDir(userSkillDir) {
|
|
11
12
|
if (!userSkillDir) return null;
|
|
12
|
-
const legacySkillDir = path.join(
|
|
13
|
-
const pluralSkillDir = path.join(
|
|
13
|
+
const legacySkillDir = path.join(OPENCODE_CONFIG_DIR, 'skill');
|
|
14
|
+
const pluralSkillDir = path.join(OPENCODE_CONFIG_DIR, 'skills');
|
|
14
15
|
if (userSkillDir === legacySkillDir) {
|
|
15
16
|
if (fs.existsSync(legacySkillDir) && !fs.existsSync(pluralSkillDir)) return legacySkillDir;
|
|
16
17
|
return pluralSkillDir;
|