@toddzheng024/dscode-desktop 0.0.0-stage → 0.7.34
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/LICENSE +21 -0
- package/README.md +120 -2
- package/THIRD_PARTY_NOTICES.md +30 -0
- package/bin/apply_patch +9 -0
- package/cordis.patch.yml +433 -0
- package/docs/CHANGELOG.md +649 -0
- package/docs/account-login.md +44 -0
- package/docs/auto-review.md +128 -0
- package/docs/browser-use.md +784 -0
- package/docs/computer-use.md +81 -0
- package/docs/custom-providers.md +88 -0
- package/docs/demo.md +158 -0
- package/docs/distribution.md +58 -0
- package/docs/dscode-ultra.md +75 -0
- package/docs/email.md +296 -0
- package/docs/exec.md +40 -0
- package/docs/hub-distribution.md +212 -0
- package/docs/memory.md +121 -0
- package/docs/opencode-go.md +61 -0
- package/docs/plugin-hub.md +68 -0
- package/docs/releases/0.2.0.md +28 -0
- package/docs/releases/0.3.0.md +21 -0
- package/docs/releases/0.4.0.md +20 -0
- package/docs/releases/0.5.0.md +19 -0
- package/docs/releases/0.6.0.md +20 -0
- package/docs/releases/0.7.0.md +22 -0
- package/docs/releases/0.7.1.md +20 -0
- package/docs/releases/0.7.10.md +10 -0
- package/docs/releases/0.7.11.md +17 -0
- package/docs/releases/0.7.12.md +11 -0
- package/docs/releases/0.7.13.md +12 -0
- package/docs/releases/0.7.14.md +11 -0
- package/docs/releases/0.7.15.md +11 -0
- package/docs/releases/0.7.16.md +10 -0
- package/docs/releases/0.7.17.md +12 -0
- package/docs/releases/0.7.18.md +13 -0
- package/docs/releases/0.7.19.md +8 -0
- package/docs/releases/0.7.2.md +19 -0
- package/docs/releases/0.7.20.md +10 -0
- package/docs/releases/0.7.21.md +9 -0
- package/docs/releases/0.7.22.md +11 -0
- package/docs/releases/0.7.23.md +14 -0
- package/docs/releases/0.7.24.md +11 -0
- package/docs/releases/0.7.25.md +8 -0
- package/docs/releases/0.7.26.md +18 -0
- package/docs/releases/0.7.27.md +8 -0
- package/docs/releases/0.7.28.md +22 -0
- package/docs/releases/0.7.29.md +12 -0
- package/docs/releases/0.7.3.md +23 -0
- package/docs/releases/0.7.30.md +19 -0
- package/docs/releases/0.7.31.md +14 -0
- package/docs/releases/0.7.32.md +14 -0
- package/docs/releases/0.7.33.md +37 -0
- package/docs/releases/0.7.34.md +24 -0
- package/docs/releases/0.7.4.md +16 -0
- package/docs/releases/0.7.5.md +18 -0
- package/docs/releases/0.7.6.md +29 -0
- package/docs/releases/0.7.7.md +36 -0
- package/docs/releases/0.7.8.md +14 -0
- package/docs/releases/0.7.9.md +15 -0
- package/docs/session-bridge.md +80 -0
- package/docs/session-cards.md +53 -0
- package/docs/session-communication.md +186 -0
- package/docs/session-metrics.md +41 -0
- package/docs/skills.md +81 -0
- package/docs/triggers.md +338 -0
- package/docs/tui-commands.md +204 -0
- package/extensions/browser/manifest.json +22 -0
- package/extensions/browser/popup.css +6 -0
- package/extensions/browser/popup.html +11 -0
- package/extensions/browser/popup.mjs +47 -0
- package/extensions/browser/protocol.mjs +168 -0
- package/extensions/browser/worker.mjs +94 -0
- package/icon.svg +7 -0
- package/locale/en.json +6 -0
- package/locale/zh-cn.json +6 -0
- package/node_modules/@deepseek-ai/node-addon-system/LICENSE +21 -0
- package/node_modules/@deepseek-ai/node-addon-system/README.i18n.yaml +6 -0
- package/node_modules/@deepseek-ai/node-addon-system/README.md +15 -0
- package/node_modules/@deepseek-ai/node-addon-system/README.zh.md +15 -0
- package/node_modules/@deepseek-ai/node-addon-system/lib/flock.d.ts +13 -0
- package/node_modules/@deepseek-ai/node-addon-system/lib/flock.js +51 -0
- package/node_modules/@deepseek-ai/node-addon-system/lib/index.d.ts +73 -0
- package/node_modules/@deepseek-ai/node-addon-system/lib/index.js +98 -0
- package/node_modules/@deepseek-ai/node-addon-system/package.json +45 -0
- package/node_modules/@deepseek-ai/node-addon-system/src/flock.c +164 -0
- package/node_modules/@deepseek-ai/node-addon-system/src/main.c +298 -0
- package/node_modules/@deepseek-ai/node-addon-system-darwin-arm64/LICENSE +28 -0
- package/node_modules/@deepseek-ai/node-addon-system-darwin-arm64/README.i18n.yaml +6 -0
- package/node_modules/@deepseek-ai/node-addon-system-darwin-arm64/README.md +9 -0
- package/node_modules/@deepseek-ai/node-addon-system-darwin-arm64/README.zh.md +9 -0
- package/node_modules/@deepseek-ai/node-addon-system-darwin-arm64/bin/system.node +0 -0
- package/node_modules/@deepseek-ai/node-addon-system-darwin-arm64/package.json +31 -0
- package/node_modules/@deepseek-ai/node-addon-system-darwin-arm64/prebuilds.json +6 -0
- package/node_modules/@deepseek-ai/node-addon-system-darwin-x64/LICENSE +28 -0
- package/node_modules/@deepseek-ai/node-addon-system-darwin-x64/README.i18n.yaml +6 -0
- package/node_modules/@deepseek-ai/node-addon-system-darwin-x64/README.md +9 -0
- package/node_modules/@deepseek-ai/node-addon-system-darwin-x64/README.zh.md +9 -0
- package/node_modules/@deepseek-ai/node-addon-system-darwin-x64/bin/system.node +0 -0
- package/node_modules/@deepseek-ai/node-addon-system-darwin-x64/package.json +31 -0
- package/node_modules/@deepseek-ai/node-addon-system-darwin-x64/prebuilds.json +6 -0
- package/node_modules/@deepseek-ai/node-addon-system-linux-arm64/LICENSE +28 -0
- package/node_modules/@deepseek-ai/node-addon-system-linux-arm64/README.i18n.yaml +6 -0
- package/node_modules/@deepseek-ai/node-addon-system-linux-arm64/README.md +11 -0
- package/node_modules/@deepseek-ai/node-addon-system-linux-arm64/README.zh.md +11 -0
- package/node_modules/@deepseek-ai/node-addon-system-linux-arm64/bin/glibc/system.node +0 -0
- package/node_modules/@deepseek-ai/node-addon-system-linux-arm64/bin/landlock-run +0 -0
- package/node_modules/@deepseek-ai/node-addon-system-linux-arm64/bin/musl/system.node +0 -0
- package/node_modules/@deepseek-ai/node-addon-system-linux-arm64/package.json +31 -0
- package/node_modules/@deepseek-ai/node-addon-system-linux-arm64/prebuilds.json +24 -0
- package/node_modules/@deepseek-ai/node-addon-system-linux-x64/LICENSE +28 -0
- package/node_modules/@deepseek-ai/node-addon-system-linux-x64/README.i18n.yaml +6 -0
- package/node_modules/@deepseek-ai/node-addon-system-linux-x64/README.md +11 -0
- package/node_modules/@deepseek-ai/node-addon-system-linux-x64/README.zh.md +11 -0
- package/node_modules/@deepseek-ai/node-addon-system-linux-x64/bin/glibc/system.node +0 -0
- package/node_modules/@deepseek-ai/node-addon-system-linux-x64/bin/landlock-run +0 -0
- package/node_modules/@deepseek-ai/node-addon-system-linux-x64/bin/musl/system.node +0 -0
- package/node_modules/@deepseek-ai/node-addon-system-linux-x64/package.json +31 -0
- package/node_modules/@deepseek-ai/node-addon-system-linux-x64/prebuilds.json +24 -0
- package/package.json +156 -4
- package/plugins/account/index.mjs +178 -0
- package/plugins/account/state.mjs +123 -0
- package/plugins/auto-review/audit.mjs +24 -0
- package/plugins/auto-review/index.mjs +267 -0
- package/plugins/auto-review/policy.mjs +92 -0
- package/plugins/browser/SKILL.md +158 -0
- package/plugins/browser/access.mjs +143 -0
- package/plugins/browser/config.mjs +60 -0
- package/plugins/browser/connection.mjs +385 -0
- package/plugins/browser/desktop-client.mjs +282 -0
- package/plugins/browser/desktop-host.mjs +74 -0
- package/plugins/browser/document-identity.mjs +57 -0
- package/plugins/browser/extension-relay.mjs +66 -0
- package/plugins/browser/files.mjs +32 -0
- package/plugins/browser/index.mjs +235 -0
- package/plugins/browser/mcp-entry.mjs +18 -0
- package/plugins/browser/mcp-tools.mjs +21 -0
- package/plugins/browser/presentation.mjs +45 -0
- package/plugins/browser/preview.mjs +77 -0
- package/plugins/browser/review.mjs +20 -0
- package/plugins/browser/screenshots.mjs +35 -0
- package/plugins/clipboard-image/clipboard-image.swift +56 -0
- package/plugins/clipboard-image/index.mjs +70 -0
- package/plugins/code-review/baseline.mjs +143 -0
- package/plugins/code-review/git.mjs +163 -0
- package/plugins/code-review/index.mjs +236 -0
- package/plugins/compaction/engine.mjs +304 -0
- package/plugins/compaction/tetris.mjs +65 -0
- package/plugins/compaction/threshold.mjs +99 -0
- package/plugins/computer-use/DESKTOP-SKILL.md +64 -0
- package/plugins/computer-use/desktop-host.mjs +75 -0
- package/plugins/computer-use/desktop-skill.mjs +28 -0
- package/plugins/computer-use/desktop-upstream.mjs +3 -0
- package/plugins/computer-use/index.mjs +37 -0
- package/plugins/cordis-config/plain.mjs +31 -0
- package/plugins/credentials/index.mjs +73 -0
- package/plugins/custom/adapter.mjs +110 -0
- package/plugins/custom/config.mjs +85 -0
- package/plugins/custom/desktop-client.mjs +150 -0
- package/plugins/custom/desktop-host.mjs +78 -0
- package/plugins/custom/images.mjs +32 -0
- package/plugins/custom/index.mjs +145 -0
- package/plugins/custom/wire.mjs +179 -0
- package/plugins/desktop/client.mjs +1041 -0
- package/plugins/desktop/index.mjs +10 -0
- package/plugins/desktop/shell-environment.mjs +9 -0
- package/plugins/desktop-workspace/hooks.mjs +29 -0
- package/plugins/desktop-workspace/index.mjs +37 -0
- package/plugins/dscode/board.mjs +260 -0
- package/plugins/dscode/child-target.mjs +11 -0
- package/plugins/dscode/control.mjs +17 -0
- package/plugins/dscode/desktop-client.mjs +55 -0
- package/plugins/dscode/desktop-host.mjs +37 -0
- package/plugins/dscode/index.mjs +270 -0
- package/plugins/email/cli.mjs +32 -0
- package/plugins/email/contacts.mjs +36 -0
- package/plugins/email/desktop-client.mjs +117 -0
- package/plugins/email/desktop-host.mjs +54 -0
- package/plugins/email/desktop-service.mjs +106 -0
- package/plugins/email/gmail-oauth.mjs +69 -0
- package/plugins/email/gmail-store.mjs +2 -0
- package/plugins/email/gmail.mjs +194 -0
- package/plugins/email/imap.mjs +136 -0
- package/plugins/email/inbox.d.mts +25 -0
- package/plugins/email/inbox.mjs +76 -0
- package/plugins/email/smtp.mjs +70 -0
- package/plugins/email/store.mjs +32 -0
- package/plugins/email-tools/index.mjs +46 -0
- package/plugins/exec/cli.mjs +64 -0
- package/plugins/exec/index.mjs +112 -0
- package/plugins/grok/adapter.mjs +114 -0
- package/plugins/grok/auth.mjs +76 -0
- package/plugins/grok/billing.mjs +147 -0
- package/plugins/grok/index.mjs +123 -0
- package/plugins/grok/models.mjs +129 -0
- package/plugins/grok/status.mjs +29 -0
- package/plugins/grok/wire.mjs +268 -0
- package/plugins/hub/catalog.mjs +82 -0
- package/plugins/hub/desktop-client.mjs +114 -0
- package/plugins/hub/desktop-host.mjs +129 -0
- package/plugins/i18n/messages.d.mts +21 -0
- package/plugins/i18n/messages.mjs +537 -0
- package/plugins/jev/approval.mjs +131 -0
- package/plugins/jev/client.mjs +74 -0
- package/plugins/jev/index.mjs +100 -0
- package/plugins/memory/content.mjs +57 -0
- package/plugins/memory/desktop-host.mjs +3 -0
- package/plugins/memory/index.mjs +168 -0
- package/plugins/memory/pipeline.mjs +79 -0
- package/plugins/memory/store.mjs +93 -0
- package/plugins/message-source/kind.mjs +20 -0
- package/plugins/opencode-go/adapter.mjs +96 -0
- package/plugins/opencode-go/command.mjs +128 -0
- package/plugins/opencode-go/index.mjs +121 -0
- package/plugins/opencode-go/models.mjs +58 -0
- package/plugins/opencode-go/oauth.mjs +194 -0
- package/plugins/opencode-go/search.mjs +86 -0
- package/plugins/opencode-go/usage.mjs +120 -0
- package/plugins/opencode-go/version.mjs +10 -0
- package/plugins/opencode-go/wire.mjs +73 -0
- package/plugins/openrouter/adapter.mjs +232 -0
- package/plugins/openrouter/index.mjs +124 -0
- package/plugins/openrouter/models.mjs +157 -0
- package/plugins/openrouter/search.mjs +113 -0
- package/plugins/openrouter/wire.mjs +101 -0
- package/plugins/providers/catalog.mjs +160 -0
- package/plugins/providers/chat-messages.mjs +120 -0
- package/plugins/providers/chat-stream.mjs +174 -0
- package/plugins/providers/desktop-client.mjs +84 -0
- package/plugins/providers/desktop-host.mjs +63 -0
- package/plugins/providers/effort.mjs +35 -0
- package/plugins/providers/http-errors.mjs +50 -0
- package/plugins/providers/openrouter-account.mjs +171 -0
- package/plugins/session-bridge/client.mjs +106 -0
- package/plugins/session-bridge/communication.mjs +260 -0
- package/plugins/session-bridge/index.mjs +67 -0
- package/plugins/session-bridge/mailbox.mjs +267 -0
- package/plugins/session-bridge/paths.mjs +21 -0
- package/plugins/session-bridge/server.mjs +172 -0
- package/plugins/session-bridge/tasks.mjs +229 -0
- package/plugins/session-cards/content.mjs +64 -0
- package/plugins/session-cards/index.mjs +38 -0
- package/plugins/session-cards/manager.mjs +140 -0
- package/plugins/session-metrics/attribution.mjs +30 -0
- package/plugins/session-metrics/balance.mjs +81 -0
- package/plugins/session-metrics/desktop-client.mjs +62 -0
- package/plugins/session-metrics/desktop-host.mjs +46 -0
- package/plugins/session-metrics/index.mjs +97 -0
- package/plugins/session-metrics/pricing.mjs +84 -0
- package/plugins/session-metrics/rate.mjs +59 -0
- package/plugins/session-metrics/store.mjs +75 -0
- package/plugins/session-metrics/turns.mjs +134 -0
- package/plugins/session-metrics/view.mjs +272 -0
- package/plugins/time-marks/desktop-host.mjs +2 -0
- package/plugins/time-marks/index.mjs +116 -0
- package/plugins/time-marks/marks.mjs +152 -0
- package/plugins/triggers/cli.mjs +558 -0
- package/plugins/triggers/commands.mjs +143 -0
- package/plugins/triggers/config.mjs +264 -0
- package/plugins/triggers/desktop-client.mjs +155 -0
- package/plugins/triggers/desktop-host.mjs +88 -0
- package/plugins/triggers/desktop-management.mjs +97 -0
- package/plugins/triggers/desktop-scheduler.mjs +125 -0
- package/plugins/triggers/host.mjs +24 -0
- package/plugins/triggers/index.mjs +27 -0
- package/plugins/triggers/job-cli.mjs +110 -0
- package/plugins/triggers/jobs.mjs +156 -0
- package/plugins/triggers/launchd.mjs +101 -0
- package/plugins/triggers/lease.mjs +24 -0
- package/plugins/triggers/log.mjs +104 -0
- package/plugins/triggers/management.mjs +181 -0
- package/plugins/triggers/options.mjs +117 -0
- package/plugins/triggers/overlay.mjs +7 -0
- package/plugins/triggers/poll.mjs +65 -0
- package/plugins/triggers/run.mjs +157 -0
- package/plugins/triggers/schedule.mjs +40 -0
- package/plugins/triggers/scheduler-service.mjs +46 -0
- package/plugins/triggers/scheduler.mjs +92 -0
- package/plugins/triggers/session-run.mjs +125 -0
- package/plugins/triggers/session.mjs +48 -0
- package/plugins/triggers/source-emit.mjs +19 -0
- package/plugins/triggers/source-host.mjs +78 -0
- package/plugins/triggers/source-ingress.mjs +70 -0
- package/plugins/triggers/source-sandbox.mjs +35 -0
- package/plugins/triggers/sources.mjs +45 -0
- package/plugins/triggers/spool.mjs +145 -0
- package/plugins/triggers/tools.mjs +67 -0
- package/plugins/triggers/unattended.mjs +18 -0
- package/plugins/tui-tools/composition.mjs +28 -0
- package/plugins/tui-tools/desktop-host.mjs +2 -0
- package/plugins/tui-tools/doctor-cli.mjs +22 -0
- package/plugins/tui-tools/doctor.mjs +149 -0
- package/plugins/tui-tools/hook-sources.mjs +113 -0
- package/plugins/tui-tools/hooks.mjs +23 -0
- package/plugins/tui-tools/index.mjs +269 -0
- package/plugins/tui-tools/sandbox-runner.mjs +123 -0
- package/plugins/tui-tools/shell.mjs +27 -0
- package/plugins/tui-tools/update.mjs +111 -0
- package/plugins/tui-tools/workspace-discovery.mjs +129 -0
- package/plugins/ultra/policy.mjs +42 -0
- package/plugins/worktree-subagent/worktree.mjs +56 -0
- package/runtime-sources.json +94 -0
- package/vendor/bash/LICENSE +21 -0
- package/vendor/bash/index.js +717 -0
- package/vendor/bash/types/background.d.ts +50 -0
- package/vendor/bash/types/index.d.ts +38 -0
- package/vendor/bash/types/render.d.ts +57 -0
- package/vendor/command-goal/LICENSE +21 -0
- package/vendor/command-goal/index.js +210 -0
- package/vendor/command-goal/types/index.d.ts +10 -0
- package/vendor/computer-use/LICENSE +21 -0
- package/vendor/computer-use/lib/approval-policy.js +12 -0
- package/vendor/computer-use/lib/approval-policy.js.map +1 -0
- package/vendor/computer-use/lib/artifacts.js +79 -0
- package/vendor/computer-use/lib/artifacts.js.map +1 -0
- package/vendor/computer-use/lib/backend.js +3 -0
- package/vendor/computer-use/lib/backend.js.map +1 -0
- package/vendor/computer-use/lib/client.js +369 -0
- package/vendor/computer-use/lib/client.js.map +1 -0
- package/vendor/computer-use/lib/config.js +159 -0
- package/vendor/computer-use/lib/config.js.map +1 -0
- package/vendor/computer-use/lib/confirmations.js +99 -0
- package/vendor/computer-use/lib/confirmations.js.map +1 -0
- package/vendor/computer-use/lib/diff.js +58 -0
- package/vendor/computer-use/lib/diff.js.map +1 -0
- package/vendor/computer-use/lib/errors.js +22 -0
- package/vendor/computer-use/lib/errors.js.map +1 -0
- package/vendor/computer-use/lib/exposure.js +244 -0
- package/vendor/computer-use/lib/exposure.js.map +1 -0
- package/vendor/computer-use/lib/index.js +66 -0
- package/vendor/computer-use/lib/index.js.map +1 -0
- package/vendor/computer-use/lib/leases.js +262 -0
- package/vendor/computer-use/lib/leases.js.map +1 -0
- package/vendor/computer-use/lib/providers/macos.js +243 -0
- package/vendor/computer-use/lib/providers/macos.js.map +1 -0
- package/vendor/computer-use/lib/providers/native-helper.js +726 -0
- package/vendor/computer-use/lib/providers/native-helper.js.map +1 -0
- package/vendor/computer-use/lib/providers/unsupported.js +51 -0
- package/vendor/computer-use/lib/providers/unsupported.js.map +1 -0
- package/vendor/computer-use/lib/service.js +608 -0
- package/vendor/computer-use/lib/service.js.map +1 -0
- package/vendor/computer-use/lib/skill.js +136 -0
- package/vendor/computer-use/lib/skill.js.map +1 -0
- package/vendor/computer-use/lib/target-resolver.js +180 -0
- package/vendor/computer-use/lib/target-resolver.js.map +1 -0
- package/vendor/computer-use/lib/tools.js +511 -0
- package/vendor/computer-use/lib/tools.js.map +1 -0
- package/vendor/computer-use/lib/types/approval-policy.d.ts +13 -0
- package/vendor/computer-use/lib/types/approval-policy.d.ts.map +1 -0
- package/vendor/computer-use/lib/types/artifacts.d.ts +10 -0
- package/vendor/computer-use/lib/types/artifacts.d.ts.map +1 -0
- package/vendor/computer-use/lib/types/backend.d.ts +124 -0
- package/vendor/computer-use/lib/types/backend.d.ts.map +1 -0
- package/vendor/computer-use/lib/types/client/index.d.ts +83 -0
- package/vendor/computer-use/lib/types/client/index.d.ts.map +1 -0
- package/vendor/computer-use/lib/types/config.d.ts +94 -0
- package/vendor/computer-use/lib/types/config.d.ts.map +1 -0
- package/vendor/computer-use/lib/types/confirmations.d.ts +23 -0
- package/vendor/computer-use/lib/types/confirmations.d.ts.map +1 -0
- package/vendor/computer-use/lib/types/diff.d.ts +5 -0
- package/vendor/computer-use/lib/types/diff.d.ts.map +1 -0
- package/vendor/computer-use/lib/types/errors.d.ts +15 -0
- package/vendor/computer-use/lib/types/errors.d.ts.map +1 -0
- package/vendor/computer-use/lib/types/exposure.d.ts +30 -0
- package/vendor/computer-use/lib/types/exposure.d.ts.map +1 -0
- package/vendor/computer-use/lib/types/index.d.ts +21 -0
- package/vendor/computer-use/lib/types/index.d.ts.map +1 -0
- package/vendor/computer-use/lib/types/leases.d.ts +81 -0
- package/vendor/computer-use/lib/types/leases.d.ts.map +1 -0
- package/vendor/computer-use/lib/types/providers/macos.d.ts +39 -0
- package/vendor/computer-use/lib/types/providers/macos.d.ts.map +1 -0
- package/vendor/computer-use/lib/types/providers/native-helper.d.ts +48 -0
- package/vendor/computer-use/lib/types/providers/native-helper.d.ts.map +1 -0
- package/vendor/computer-use/lib/types/providers/unsupported.d.ts +23 -0
- package/vendor/computer-use/lib/types/providers/unsupported.d.ts.map +1 -0
- package/vendor/computer-use/lib/types/service.d.ts +58 -0
- package/vendor/computer-use/lib/types/service.d.ts.map +1 -0
- package/vendor/computer-use/lib/types/skill.d.ts +14 -0
- package/vendor/computer-use/lib/types/skill.d.ts.map +1 -0
- package/vendor/computer-use/lib/types/target-resolver.d.ts +38 -0
- package/vendor/computer-use/lib/types/target-resolver.d.ts.map +1 -0
- package/vendor/computer-use/lib/types/tools.d.ts +6 -0
- package/vendor/computer-use/lib/types/tools.d.ts.map +1 -0
- package/vendor/computer-use/lib/types/types.d.ts +270 -0
- package/vendor/computer-use/lib/types/types.d.ts.map +1 -0
- package/vendor/computer-use/lib/types/web.d.ts +33 -0
- package/vendor/computer-use/lib/types/web.d.ts.map +1 -0
- package/vendor/computer-use/lib/types.js +9 -0
- package/vendor/computer-use/lib/types.js.map +1 -0
- package/vendor/computer-use/lib/web.js +188 -0
- package/vendor/computer-use/lib/web.js.map +1 -0
- package/vendor/computer-use/native/macos/Sources/Fixture/main.swift +412 -0
- package/vendor/computer-use/native/macos/Sources/Helper/CursorImage.swift +11 -0
- package/vendor/computer-use/native/macos/Sources/Helper/CursorMotion.swift +67 -0
- package/vendor/computer-use/native/macos/Sources/Helper/CursorOverlay.swift +840 -0
- package/vendor/computer-use/native/macos/Sources/Helper/TargetedPointer.swift +327 -0
- package/vendor/computer-use/native/macos/Sources/Helper/WindowNumberMatcher.swift +29 -0
- package/vendor/computer-use/native/macos/Sources/Helper/main.swift +1244 -0
- package/vendor/computer-use/native/macos/Sources/Monitor/main.swift +153 -0
- package/vendor/computer-use/native/macos/bin/dsh-computer-use-helper +0 -0
- package/vendor/computer-use/native/macos/manifest.json +14 -0
- package/vendor/persistent/LICENSE +21 -0
- package/vendor/persistent/index.js +411 -0
- package/vendor/persistent/types/index.d.ts +24 -0
- package/vendor/subagent/LICENSE +21 -0
- package/vendor/subagent/index.js +694 -0
- package/vendor/subagent/invariant.js +52 -0
- package/vendor/subagent/model-selection-settings.js +68 -0
- package/vendor/subagent/types/index.d.ts +82 -0
- package/vendor/subagent/types/invariant.d.ts +16 -0
- package/vendor/subagent/types/list-models.d.ts +10 -0
- package/vendor/subagent/types/model-selection-settings.d.ts +45 -0
- package/vendor/subagent/types/model-selection-state.d.ts +48 -0
- package/vendor/subagent/types/model-selection.d.ts +81 -0
- package/vendor/subagent-core/LICENSE +21 -0
- package/vendor/subagent-core/index.js +3202 -0
- package/vendor/subagent-core/invariant.js +76 -0
- package/vendor/subagent-core/typert.host.d.ts +3 -0
- package/vendor/subagent-core/typert.host.js +978 -0
- package/vendor/subagent-core/typert.remote-client.d.ts +25 -0
- package/vendor/subagent-core/typert.remote-client.js +114 -0
- package/vendor/subagent-core/types/archive-admission.d.ts +16 -0
- package/vendor/subagent-core/types/archive-admission.js +151 -0
- package/vendor/subagent-core/types/assistant-output.d.ts +48 -0
- package/vendor/subagent-core/types/assistant-output.js +73 -0
- package/vendor/subagent-core/types/catalog.d.ts +84 -0
- package/vendor/subagent-core/types/catalog.js +109 -0
- package/vendor/subagent-core/types/child-agent.d.ts +156 -0
- package/vendor/subagent-core/types/child-agent.js +213 -0
- package/vendor/subagent-core/types/client.d.ts +8 -0
- package/vendor/subagent-core/types/client.js +7 -0
- package/vendor/subagent-core/types/continuation-activation.d.ts +267 -0
- package/vendor/subagent-core/types/continuation-activation.js +700 -0
- package/vendor/subagent-core/types/continuation-messages.d.ts +62 -0
- package/vendor/subagent-core/types/continuation-messages.js +106 -0
- package/vendor/subagent-core/types/continuation.d.ts +124 -0
- package/vendor/subagent-core/types/continuation.js +455 -0
- package/vendor/subagent-core/types/control-types.d.ts +135 -0
- package/vendor/subagent-core/types/control-types.js +8 -0
- package/vendor/subagent-core/types/control.d.ts +45 -0
- package/vendor/subagent-core/types/control.js +79 -0
- package/vendor/subagent-core/types/depth.d.ts +31 -0
- package/vendor/subagent-core/types/depth.js +39 -0
- package/vendor/subagent-core/types/descriptor.d.ts +144 -0
- package/vendor/subagent-core/types/descriptor.js +193 -0
- package/vendor/subagent-core/types/error.d.ts +11 -0
- package/vendor/subagent-core/types/error.js +14 -0
- package/vendor/subagent-core/types/inbox.d.ts +43 -0
- package/vendor/subagent-core/types/inbox.js +61 -0
- package/vendor/subagent-core/types/index.d.ts +320 -0
- package/vendor/subagent-core/types/index.js +475 -0
- package/vendor/subagent-core/types/internal.d.ts +58 -0
- package/vendor/subagent-core/types/internal.js +58 -0
- package/vendor/subagent-core/types/invariant.d.ts +13 -0
- package/vendor/subagent-core/types/invariant.js +91 -0
- package/vendor/subagent-core/types/lifecycle.d.ts +113 -0
- package/vendor/subagent-core/types/lifecycle.js +180 -0
- package/vendor/subagent-core/types/list-children.d.ts +36 -0
- package/vendor/subagent-core/types/list-children.js +166 -0
- package/vendor/subagent-core/types/out-of-process.d.ts +118 -0
- package/vendor/subagent-core/types/out-of-process.js +219 -0
- package/vendor/subagent-core/types/projection-types.d.ts +82 -0
- package/vendor/subagent-core/types/projection-types.js +7 -0
- package/vendor/subagent-core/types/projection.d.ts +100 -0
- package/vendor/subagent-core/types/projection.js +157 -0
- package/vendor/subagent-core/types/run-settlement.d.ts +17 -0
- package/vendor/subagent-core/types/run-settlement.js +70 -0
- package/vendor/subagent-core/types/types.d.ts +377 -0
- package/vendor/subagent-core/types/types.js +19 -0
- package/vendor/subagent-driver/LICENSE +21 -0
- package/vendor/subagent-driver/index.js +255 -0
- package/vendor/subagent-driver/types/index.d.ts +33 -0
- package/vendor/subagent-driver/types/structured.d.ts +42 -0
- package/vendor/subagent-fork/LICENSE +21 -0
- package/vendor/subagent-fork/index.js +61 -0
- package/vendor/subagent-fork/types/index.d.ts +20 -0
- package/vendor/subagent-spawn/LICENSE +21 -0
- package/vendor/subagent-spawn/index.js +45 -0
- package/vendor/subagent-spawn/types/index.d.ts +19 -0
- package/vendor/terminal/LICENSE +21 -0
- package/vendor/terminal/index.js +1093 -0
- package/vendor/terminal/types/config.d.ts +82 -0
- package/vendor/terminal/types/index.d.ts +36 -0
- package/vendor/terminal/types/sanitize.d.ts +47 -0
- package/vendor/terminal/types/session.d.ts +87 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { resolveDshHome } from '@deepseek-ai/dsh-home-paths';
|
|
2
|
+
import { clientRequestSchema } from '@deepseek-ai/dsh-client-connection';
|
|
3
|
+
import { DesktopTriggerControl, DesktopTriggerManagement } from './desktop-management.mjs';
|
|
4
|
+
import { registerTriggerTools } from './tools.mjs';
|
|
5
|
+
import { triggerCommand } from './commands.mjs';
|
|
6
|
+
import { mutationProblem } from './unattended.mjs';
|
|
7
|
+
|
|
8
|
+
export const inject = ['agents', 'commands', 'jobs', 'agentPresets', 'agentDefaultModel', 'sessions', 'goals', 'permissionPresets'];
|
|
9
|
+
const isDscode = (ctx, agent) => agent && ctx.agentPresets.composedPreset(agent.ctx) === 'dscode';
|
|
10
|
+
|
|
11
|
+
/** UI requests select a live session, never a client-supplied workspace path. */
|
|
12
|
+
export async function triggerDesktopRequest(ctx, { control, management }, payload, signal) {
|
|
13
|
+
signal?.throwIfAborted();
|
|
14
|
+
if (!payload || typeof payload !== 'object') throw Error('Invalid scheduling request');
|
|
15
|
+
if (['status', 'start', 'stop'].includes(payload.action)) {
|
|
16
|
+
if (payload.action !== 'status') await control.setEnabled(payload.action === 'start');
|
|
17
|
+
return { scheduler: await control.status(), sessions: ctx.agents.roots().filter(agent => isDscode(ctx, agent)).map(agent => ({
|
|
18
|
+
id: agent.id, workspace: management.workspace(agent), writable: !mutationProblem(ctx, agent),
|
|
19
|
+
})) };
|
|
20
|
+
}
|
|
21
|
+
if (typeof payload.sessionId !== 'string') throw Error('Choose an open DSCODE session');
|
|
22
|
+
const agent = ctx.agents.get(payload.sessionId);
|
|
23
|
+
if (!isDscode(ctx, agent)) throw Error('The DSCODE session is no longer open');
|
|
24
|
+
if (payload.action === 'workspace') return { ...(await management.manage({ action: 'list' }, agent)),
|
|
25
|
+
...(await management.jobs({ action: 'list', limit: 50 }, agent)), writable: !mutationProblem(ctx, agent) };
|
|
26
|
+
if (!['manage', 'jobs', 'source'].includes(payload.action)) throw Error('Unknown scheduling action');
|
|
27
|
+
const action = payload.args?.action;
|
|
28
|
+
const reading = payload.action === 'manage' ? ['list', 'get'].includes(action) : payload.action === 'jobs' ? action === 'list' : ['status', 'logs'].includes(action);
|
|
29
|
+
if (!reading) {
|
|
30
|
+
const problem = mutationProblem(ctx, agent);
|
|
31
|
+
if (problem) throw Error(problem);
|
|
32
|
+
}
|
|
33
|
+
return management[payload.action](payload.args ?? {}, agent);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function apply(ctx) {
|
|
37
|
+
const home = resolveDshHome();
|
|
38
|
+
const control = new DesktopTriggerControl(ctx, { home });
|
|
39
|
+
const management = new DesktopTriggerManagement({ home, control });
|
|
40
|
+
const service = { control, management };
|
|
41
|
+
const toolScopes = new Set();
|
|
42
|
+
ctx.provide('dscodeTriggers', service);
|
|
43
|
+
ctx.effect(() => async () => {
|
|
44
|
+
await control.dispose();
|
|
45
|
+
await Promise.all([...toolScopes].map(fiber => fiber.dispose()));
|
|
46
|
+
}, 'dscode desktop scheduling drain');
|
|
47
|
+
void control.initialize(ctx.get('loader').await());
|
|
48
|
+
|
|
49
|
+
ctx.on('agent/created', async ({ agent }) => {
|
|
50
|
+
if (!isDscode(ctx, agent) || control.closed) return;
|
|
51
|
+
const fiber = agent.ctx.inject(['tools', 'systemPrompt', 'permissionPresets'], scope => {
|
|
52
|
+
if (!control.closed) registerTriggerTools(scope, { home, management });
|
|
53
|
+
});
|
|
54
|
+
toolScopes.add(fiber);
|
|
55
|
+
agent.ctx.effect(() => () => { toolScopes.delete(fiber); });
|
|
56
|
+
await fiber;
|
|
57
|
+
if (control.closed) await fiber.dispose();
|
|
58
|
+
});
|
|
59
|
+
const handler = triggerCommand({ management, mutationProblem: agent => mutationProblem(ctx, agent) });
|
|
60
|
+
for (const name of ['trigger', 'triggers']) ctx.commands.register({ name, description: 'Manage DSCODE schedules; /trigger help',
|
|
61
|
+
input: { hint: '[list|create|update|schedule|jobs|source|scheduler|help]' },
|
|
62
|
+
handler: args => isDscode(ctx, args.agent) ? handler(args) : { kind: 'error', text: 'Choose a DSCODE session to manage its schedules.' },
|
|
63
|
+
});
|
|
64
|
+
// An unattended run must not use generic job_kill to stop every other
|
|
65
|
+
// project's scheduler after its own trigger_scheduler mutation was denied.
|
|
66
|
+
ctx.on('tools/pre-execute', async (exec, next) => {
|
|
67
|
+
const decision = await next();
|
|
68
|
+
if (decision.kind !== 'allow' || exec.name !== 'job_kill' || exec.arguments?.job_id !== control.owner.status().job_id) return decision;
|
|
69
|
+
const problem = mutationProblem(ctx, exec.agent);
|
|
70
|
+
return problem ? { kind: 'deny', reason: problem } : { kind: 'ask', reason: 'Stopping the DSCODE scheduler interrupts scheduled work for every registered project.' };
|
|
71
|
+
}, { prepend: true });
|
|
72
|
+
|
|
73
|
+
ctx.inject(['connection'], scope => {
|
|
74
|
+
scope.effect(() => scope.connection.fetch.register({ path: '/api/dscode-triggers', methods: ['POST'], requestBody: 'buffered',
|
|
75
|
+
fetch: async request => {
|
|
76
|
+
let body;
|
|
77
|
+
try { body = await request.json(); } catch { return new Response('Invalid JSON', { status: 400 }); }
|
|
78
|
+
const parsed = clientRequestSchema.safeParse(body);
|
|
79
|
+
if (!parsed.success || parsed.data.method !== 'dscode-triggers') return new Response('Invalid scheduling request', { status: 400 });
|
|
80
|
+
const { rpcId, payload } = parsed.data;
|
|
81
|
+
let result;
|
|
82
|
+
try { result = { ok: true, value: await triggerDesktopRequest(ctx, service, payload, request.signal) }; }
|
|
83
|
+
catch (error) { result = { ok: false, error: { code: 'dscode-triggers', message: error.message, details: {} } }; }
|
|
84
|
+
return Response.json({ type: 'server-response', rpcId, result }, { headers: { 'Cache-Control': 'no-store' } });
|
|
85
|
+
},
|
|
86
|
+
}), 'dscode scheduling rpc');
|
|
87
|
+
}).then(undefined, error => { ctx.logger.error(`Scheduling transport failed: ${error.message}`); });
|
|
88
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
4
|
+
import { DesktopTriggerScheduler } from './desktop-scheduler.mjs';
|
|
5
|
+
import { TriggerManagement } from './management.mjs';
|
|
6
|
+
import { acquireTriggerLease } from './lease.mjs';
|
|
7
|
+
|
|
8
|
+
export const desktopSchedulerDescription = 'Desktop scheduler start enables delivery for all registered projects and remembers that choice for future Desktop launches. stop disables that choice, interrupts active trigger runs and drains script sources; pending jobs remain queued. Desktop must stay open to deliver jobs. No OS service is installed.';
|
|
9
|
+
|
|
10
|
+
/** Persistent user choice and serialized controls around one process-owned scheduler. */
|
|
11
|
+
export class DesktopTriggerControl {
|
|
12
|
+
constructor(ctx, { home, owner = new DesktopTriggerScheduler(ctx, { home }) }) {
|
|
13
|
+
this.home = home; this.owner = owner; this.enabled = false; this.closed = false;
|
|
14
|
+
this.pending = Promise.resolve(); this.ready = Promise.resolve();
|
|
15
|
+
this.path = join(home, 'config', 'desktop-scheduler.json');
|
|
16
|
+
try {
|
|
17
|
+
const value = JSON.parse(readFileSync(this.path, 'utf8'));
|
|
18
|
+
if (value?.version !== 1 || typeof value.enabled !== 'boolean') throw Error('Invalid Desktop scheduler settings');
|
|
19
|
+
this.enabled = value.enabled;
|
|
20
|
+
} catch (error) { if (error.code !== 'ENOENT') this.error = error.message; }
|
|
21
|
+
}
|
|
22
|
+
enqueue(operation) {
|
|
23
|
+
const result = this.pending.then(() => {
|
|
24
|
+
if (this.closed) throw Error('Desktop scheduling is shutting down');
|
|
25
|
+
return operation();
|
|
26
|
+
});
|
|
27
|
+
this.pending = result.catch(() => {});
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
30
|
+
initialize(hostReady = Promise.resolve()) {
|
|
31
|
+
// Loading all providers/preset hooks precedes automatic delivery. Do not
|
|
32
|
+
// block the plugin apply callback on its own loader's readiness barrier.
|
|
33
|
+
this.ready = Promise.resolve(hostReady).then(() => this.enqueue(async () => {
|
|
34
|
+
if (this.enabled) await this.owner.start();
|
|
35
|
+
})).catch(error => { this.error = error.message; });
|
|
36
|
+
return this.ready;
|
|
37
|
+
}
|
|
38
|
+
save(enabled) {
|
|
39
|
+
mkdirSync(dirname(this.path), { recursive: true, mode: 0o700 });
|
|
40
|
+
const temp = `${this.path}.${randomUUID()}.tmp`;
|
|
41
|
+
try {
|
|
42
|
+
writeFileSync(temp, JSON.stringify({ version: 1, enabled }) + '\n', { flag: 'wx', mode: 0o600 });
|
|
43
|
+
renameSync(temp, this.path); this.enabled = enabled;
|
|
44
|
+
} finally { rmSync(temp, { force: true }); }
|
|
45
|
+
}
|
|
46
|
+
async setEnabled(enabled) {
|
|
47
|
+
if (typeof enabled !== 'boolean') throw Error('enabled must be a boolean');
|
|
48
|
+
await this.ready;
|
|
49
|
+
await this.enqueue(async () => {
|
|
50
|
+
const previous = this.enabled;
|
|
51
|
+
this.save(enabled); // A failed save cannot start autonomous work.
|
|
52
|
+
this.error = undefined;
|
|
53
|
+
try { if (enabled) await this.owner.start(); else await this.owner.stop(); }
|
|
54
|
+
catch (error) {
|
|
55
|
+
if (enabled && !previous) this.save(false);
|
|
56
|
+
this.error = error.message; throw error;
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
return this.status();
|
|
60
|
+
}
|
|
61
|
+
async status() {
|
|
62
|
+
await this.ready;
|
|
63
|
+
const local = this.owner.status();
|
|
64
|
+
let external = false;
|
|
65
|
+
if (!local.running && !local.stopping) {
|
|
66
|
+
const lease = await acquireTriggerLease(this.home, '_scheduler');
|
|
67
|
+
external = !lease; lease?.release();
|
|
68
|
+
}
|
|
69
|
+
return { ...local, enabled: this.enabled, running: local.running || external,
|
|
70
|
+
owner: external ? 'external' : 'desktop', requires_open_application: !external,
|
|
71
|
+
next_step: external ? 'Another process owns the queue; control it from its terminal or service manager.' : 'Use /trigger scheduler start or Settings > DSCODE schedules to enable delivery.',
|
|
72
|
+
...(this.error ? { error: this.error } : {}) };
|
|
73
|
+
}
|
|
74
|
+
async dispose() {
|
|
75
|
+
this.closed = true;
|
|
76
|
+
// Keep the user's saved choice; quit/unload only stops this process owner.
|
|
77
|
+
await this.owner.dispose();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export class DesktopTriggerManagement extends TriggerManagement {
|
|
82
|
+
constructor({ control, ...options }) {
|
|
83
|
+
super(options); this.control = control;
|
|
84
|
+
this.schedulerActions = ['status', 'start', 'stop'];
|
|
85
|
+
this.schedulerDescription = desktopSchedulerDescription;
|
|
86
|
+
}
|
|
87
|
+
workspace(agent) {
|
|
88
|
+
if (this.control.closed) throw Error('Desktop scheduling is shutting down');
|
|
89
|
+
return super.workspace(agent);
|
|
90
|
+
}
|
|
91
|
+
status() { return this.control.status(); }
|
|
92
|
+
async scheduler({ action }) {
|
|
93
|
+
if (action === 'status') return this.status();
|
|
94
|
+
if (!['start', 'stop'].includes(action)) throw Error('Desktop scheduler action must be status, start or stop');
|
|
95
|
+
return { scheduler: await this.control.setEnabled(action === 'start') };
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// Desktop queue owner. Management/UI chooses when to start it; merely loading
|
|
2
|
+
// this module creates no jobs, timers, service registration or OS launch agent.
|
|
3
|
+
import { appendFileSync, mkdirSync } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { installModelSelection } from '@deepseek-ai/dsh-agent';
|
|
6
|
+
import { runScheduler } from './scheduler.mjs';
|
|
7
|
+
import { runTriggerCli } from './cli.mjs';
|
|
8
|
+
import { loadTriggerDefinitions } from './config.mjs';
|
|
9
|
+
import { startTriggerSession } from './session-run.mjs';
|
|
10
|
+
|
|
11
|
+
export class DesktopTriggerScheduler {
|
|
12
|
+
constructor(ctx, { home, jobs = ctx.get?.('jobs'), report = message => ctx.logger.warn(message) }) {
|
|
13
|
+
this.ctx = ctx; this.home = home; this.report = report; this.jobs = jobs;
|
|
14
|
+
this.task = undefined; this.ready = undefined; this.controller = undefined;
|
|
15
|
+
this.stopping = false; this.error = undefined; this.disposed = false;
|
|
16
|
+
}
|
|
17
|
+
status() {
|
|
18
|
+
return { running: !!this.task && this.started === true && !this.stopping,
|
|
19
|
+
stopping: this.stopping, owner: 'desktop', requires_open_application: true,
|
|
20
|
+
...(this.jobId ? { job_id: this.jobId } : {}),
|
|
21
|
+
...(this.error ? { error: this.error } : {}) };
|
|
22
|
+
}
|
|
23
|
+
async start() {
|
|
24
|
+
if (this.disposed) throw Error('Desktop scheduler has been disposed.');
|
|
25
|
+
if (this.stopping) throw Error('Desktop scheduler is stopping; wait until it has drained before starting again.');
|
|
26
|
+
if (this.task) { await this.ready; return this.status(); }
|
|
27
|
+
const ready = Promise.withResolvers();
|
|
28
|
+
this.ready = ready.promise;
|
|
29
|
+
this.controller = new AbortController(); this.error = undefined; this.started = false;
|
|
30
|
+
const signal = this.controller.signal;
|
|
31
|
+
const launch = () => runScheduler({ home: this.home, signal,
|
|
32
|
+
onReady: () => { this.started = true; ready.resolve(); },
|
|
33
|
+
spawnWorker: request => this.execute(request.job, signal), report: this.report,
|
|
34
|
+
});
|
|
35
|
+
let task, detach;
|
|
36
|
+
try {
|
|
37
|
+
if (this.jobs) {
|
|
38
|
+
detach = this.jobs.attachController('DSCODE Desktop scheduler');
|
|
39
|
+
// Register before dispatching anything. Native quit/update inspection
|
|
40
|
+
// sees this global job even while the queue has no live Agent. Its
|
|
41
|
+
// cancellation hook drains both Agents and supervised script sources.
|
|
42
|
+
this.jobId = this.jobs.start({ kind: 'dscode-scheduler', label: 'DSCODE scheduler — requires the application to stay open',
|
|
43
|
+
run: handle => {
|
|
44
|
+
handle.updateProgress('Waiting for scheduled work');
|
|
45
|
+
task = launch();
|
|
46
|
+
return { cancel: reason => this.requestStop(reason), done: task.then(
|
|
47
|
+
() => ({ status: signal.aborted ? 'killed' : 'completed' }),
|
|
48
|
+
error => ({ status: 'failed', detail: error.message }),
|
|
49
|
+
) };
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
} else task = launch();
|
|
53
|
+
} catch (error) {
|
|
54
|
+
detach?.(); this.controller = undefined; this.ready = undefined;
|
|
55
|
+
this.error = error.message;
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
this.task = task;
|
|
59
|
+
// Completion is observed even when a UI no longer waits on its start request.
|
|
60
|
+
void task.catch(error => { this.error = error.message; ready.reject(error); this.report(error.message); }).finally(() => {
|
|
61
|
+
detach?.();
|
|
62
|
+
if (this.task === task) { this.task = undefined; this.controller = undefined; this.started = false; this.stopping = false; }
|
|
63
|
+
});
|
|
64
|
+
await this.ready;
|
|
65
|
+
return this.status();
|
|
66
|
+
}
|
|
67
|
+
async stop() {
|
|
68
|
+
const task = this.task;
|
|
69
|
+
if (!task) return this.status();
|
|
70
|
+
this.requestStop();
|
|
71
|
+
try { await task; } finally {
|
|
72
|
+
if (this.task === task) { this.task = undefined; this.controller = undefined; this.started = false; this.stopping = false; }
|
|
73
|
+
}
|
|
74
|
+
return this.status();
|
|
75
|
+
}
|
|
76
|
+
requestStop(reason = 'Desktop scheduler stopped') {
|
|
77
|
+
if (!this.controller) return;
|
|
78
|
+
this.stopping = true;
|
|
79
|
+
this.controller.abort(new Error(reason));
|
|
80
|
+
}
|
|
81
|
+
async dispose() {
|
|
82
|
+
this.disposed = true;
|
|
83
|
+
return this.stop();
|
|
84
|
+
}
|
|
85
|
+
async execute(job, signal) {
|
|
86
|
+
if (signal.aborted) return 0; // leave unclaimed work pending for the next owner
|
|
87
|
+
// Do not let manually edited files bypass the same bounds as agent-managed
|
|
88
|
+
// definitions. CLI-only presets and elevated unattended modes stay excluded.
|
|
89
|
+
const definition = loadTriggerDefinitions({ home: this.home, workspace: job.project }).definitions.find(row => row.id === job.triggerId);
|
|
90
|
+
if (definition && (definition.preset !== 'dscode' || !['read-only', 'workspace-write'].includes(definition.permission))) {
|
|
91
|
+
throw Error('Desktop scheduling requires the dscode preset and read-only or workspace-write permission');
|
|
92
|
+
}
|
|
93
|
+
const directory = join(this.home, 'triggers/jobs'); mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
94
|
+
const log = { write: text => appendFileSync(join(directory, `${job.id}.log`), text, { mode: 0o600 }) };
|
|
95
|
+
return runTriggerCli(['run-job', job.id], { home: this.home, project: job.project, stdout: log, stderr: log,
|
|
96
|
+
spawnRun: async ({ spec }) => {
|
|
97
|
+
if (spec.preset !== 'dscode' || !['read-only', 'workspace-write'].includes(spec.permission)) throw Error('Unsupported Desktop trigger preset or permission');
|
|
98
|
+
const deadline = AbortSignal.timeout(spec.limits.timeoutSeconds * 1000);
|
|
99
|
+
const combined = AbortSignal.any([signal, deadline]);
|
|
100
|
+
const stopped = () => ({ outcome: deadline.aborted && !signal.aborted ? 'timedout' : 'failed',
|
|
101
|
+
reason: deadline.aborted && !signal.aborted ? 'timeout' : 'interrupted', exitCode: deadline.aborted && !signal.aborted ? 124 : 130,
|
|
102
|
+
sessionId: null, cost: null, rounds: null });
|
|
103
|
+
if (combined.aborted) { const result = stopped(); return { code: result.exitCode, result }; }
|
|
104
|
+
try {
|
|
105
|
+
const run = await startTriggerSession(this.ctx, { spec, home: this.home, signal: combined,
|
|
106
|
+
prepare: (scope, agent) => installModelSelection(scope, { get current() { return agent.session.requestHeader()?.config ?? agent.options; }, assembled: undefined }),
|
|
107
|
+
});
|
|
108
|
+
let result = await run.done;
|
|
109
|
+
if (result.reason === 'interrupted' && deadline.aborted && !signal.aborted) result = { ...result, outcome: 'timedout', reason: 'timeout', exitCode: 124 };
|
|
110
|
+
return { code: result.exitCode, result };
|
|
111
|
+
} catch (error) {
|
|
112
|
+
if (!combined.aborted) throw error;
|
|
113
|
+
const result = stopped(); return { code: result.exitCode, result };
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Bind shutdown and plugin removal to the same drain as an explicit stop. */
|
|
121
|
+
export function createDesktopTriggerScheduler(ctx, options) {
|
|
122
|
+
const scheduler = new DesktopTriggerScheduler(ctx, options);
|
|
123
|
+
ctx.effect(() => () => scheduler.dispose(), 'dscode scheduler drain');
|
|
124
|
+
return scheduler;
|
|
125
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Process-owned adapter for the CLI. Desktop can use startTriggerSession without
|
|
2
|
+
// exiting its shared Host when a task completes or reaches its deadline.
|
|
3
|
+
import { startTriggerSession } from './session-run.mjs';
|
|
4
|
+
import { readRunSpec, writeRunResult } from './options.mjs';
|
|
5
|
+
import { sessionSpend } from '../session-metrics/view.mjs';
|
|
6
|
+
export { triggerOverlay } from './overlay.mjs';
|
|
7
|
+
|
|
8
|
+
export const name = 'dscode-trigger-host';
|
|
9
|
+
export const inject = ['agents', 'sessions', 'agentPresets', 'agentDefaultModel', 'permissionPresets', 'llm', 'goals', 'appExit'];
|
|
10
|
+
export function apply(ctx) {
|
|
11
|
+
void runTriggerHost(ctx).catch(error => { process.stderr.write(`dscode trigger run: ${error.message}\n`); ctx.get('appExit')(1); });
|
|
12
|
+
}
|
|
13
|
+
export async function runTriggerHost(ctx, { optionsPath = process.env.DSCODE_TRIGGER_OPTIONS, home = process.env.DSH_HOME, spend = sessionSpend } = {}) {
|
|
14
|
+
await ctx.get('loader').await();
|
|
15
|
+
if (!optionsPath) throw Error('DSCODE_TRIGGER_OPTIONS is missing');
|
|
16
|
+
const run = await startTriggerSession(ctx, { spec: readRunSpec(optionsPath), home, spend });
|
|
17
|
+
void run.done.then(result => {
|
|
18
|
+
writeRunResult(`${optionsPath}.result.json`, result);
|
|
19
|
+
ctx.get('appExit')(result.exitCode);
|
|
20
|
+
}).catch(error => {
|
|
21
|
+
process.stderr.write(`dscode trigger run: ${error.message}\n`);
|
|
22
|
+
ctx.get('appExit')(1);
|
|
23
|
+
});
|
|
24
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// TUI management and agent tools use one definitions/jobs/source service.
|
|
2
|
+
import { registerTriggerTools, mutationProblem } from './tools.mjs';
|
|
3
|
+
import { TriggerManagement } from './management.mjs';
|
|
4
|
+
import { triggerCommand } from './commands.mjs';
|
|
5
|
+
import { homedir } from 'node:os';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
|
|
8
|
+
export const name = 'dscode-triggers';
|
|
9
|
+
export const inject = ['commands'];
|
|
10
|
+
const stateHome = () => process.env.DSH_HOME ?? process.env.DSCODE_HOME ?? join(homedir(), '.local/share/dscode-hub');
|
|
11
|
+
|
|
12
|
+
export function apply(ctx) {
|
|
13
|
+
const home = stateHome();
|
|
14
|
+
let managementContext;
|
|
15
|
+
ctx.inject(['tools', 'systemPrompt', 'permissionPresets'], toolCtx => {
|
|
16
|
+
managementContext = toolCtx;
|
|
17
|
+
registerTriggerTools(toolCtx, { home });
|
|
18
|
+
});
|
|
19
|
+
const management = new TriggerManagement({ home, dscodePath: process.env.DSCODE_CLI_PATH });
|
|
20
|
+
const handler = triggerCommand({ management, mutationProblem: agent => managementContext
|
|
21
|
+
? mutationProblem(managementContext, agent) : 'Trigger management services are not available yet.' });
|
|
22
|
+
for (const name of ['trigger', 'triggers']) ctx.commands.register({
|
|
23
|
+
name, description: 'Manage triggers, jobs and script sources; /trigger help',
|
|
24
|
+
input: { hint: '[list|show|new|update|enable|disable|run|schedule|jobs|cancel|source|scheduler|help]' },
|
|
25
|
+
handler,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { JobStore, formatJob } from './jobs.mjs';
|
|
3
|
+
import { normalizeEvent } from './spool.mjs';
|
|
4
|
+
import { dueTime } from './schedule.mjs';
|
|
5
|
+
import { runScheduler } from './scheduler.mjs';
|
|
6
|
+
import { schedulerService } from './scheduler-service.mjs';
|
|
7
|
+
import { acquireTriggerLease } from './lease.mjs';
|
|
8
|
+
import { appendRun, readRuns } from './log.mjs';
|
|
9
|
+
|
|
10
|
+
export const JOB_COMMANDS = ['schedule', 'jobs', 'cancel', 'scheduler', 'run-job'];
|
|
11
|
+
|
|
12
|
+
export async function handleJobCommand(options, context) {
|
|
13
|
+
const { home, project, now, platform, dscodePath, launchctl, out, err, deps, findDefinition, readEventBody } = context;
|
|
14
|
+
if (options.command === 'scheduler') {
|
|
15
|
+
if (!['start', 'tick', 'install', 'uninstall', 'status'].includes(options.id)) throw new Error('scheduler expects start, tick, install, uninstall or status');
|
|
16
|
+
if (options.id === 'status') {
|
|
17
|
+
const lease = await acquireTriggerLease(home, '_scheduler');
|
|
18
|
+
out(lease ? 'scheduler stopped' : 'scheduler running');
|
|
19
|
+
lease?.release();
|
|
20
|
+
return 0;
|
|
21
|
+
}
|
|
22
|
+
if (!dscodePath || !existsSync(dscodePath)) throw new Error('the scheduler needs a valid dscode program path');
|
|
23
|
+
if (['install', 'uninstall'].includes(options.id)) {
|
|
24
|
+
await schedulerService(options.id, { home, dscodePath, platform, launchctl, agentsDirectory: deps.agentsDirectory, out });
|
|
25
|
+
return 0;
|
|
26
|
+
}
|
|
27
|
+
const controller = new AbortController();
|
|
28
|
+
const stop = () => controller.abort();
|
|
29
|
+
for (const signal of ['SIGINT', 'SIGTERM', 'SIGHUP']) process.once(signal, stop);
|
|
30
|
+
try {
|
|
31
|
+
out(`scheduler ${options.id}: ${home}`);
|
|
32
|
+
return await runScheduler({ home, dscodePath, once: options.id === 'tick', signal: controller.signal, spawnWorker: deps.spawnWorker, report: err });
|
|
33
|
+
} finally { for (const signal of ['SIGINT', 'SIGTERM', 'SIGHUP']) process.off(signal, stop); }
|
|
34
|
+
}
|
|
35
|
+
const store = new JobStore(home);
|
|
36
|
+
try {
|
|
37
|
+
if (options.command === 'jobs') {
|
|
38
|
+
const jobs = store.list(options.id || undefined);
|
|
39
|
+
out(jobs.length ? jobs.map(formatJob).join('\n') : 'No jobs scheduled.');
|
|
40
|
+
return 0;
|
|
41
|
+
}
|
|
42
|
+
if (options.command === 'cancel') {
|
|
43
|
+
store.cancel(options.id, now);
|
|
44
|
+
out(`cancelled ${options.id}`);
|
|
45
|
+
return 0;
|
|
46
|
+
}
|
|
47
|
+
if (options.command === 'schedule') {
|
|
48
|
+
const definition = findDefinition(home, project, options.id);
|
|
49
|
+
const dueAt = dueTime(options, now);
|
|
50
|
+
const payload = (await readEventBody(options, deps.stdin ?? process.stdin)) ?? {};
|
|
51
|
+
normalizeEvent(definition.id, payload, { eventId: 'validation', now });
|
|
52
|
+
if (options.eventId !== undefined) throw new Error('schedule assigns a jobId; --event-id is only for emit/fire/run');
|
|
53
|
+
const job = store.create({ triggerId: definition.id, project, workspace: definition.workspace, payload, dueAt, now });
|
|
54
|
+
out(formatJob(job));
|
|
55
|
+
out('The scheduler delivers this job: dscode trigger scheduler install (or scheduler start).');
|
|
56
|
+
return 0;
|
|
57
|
+
}
|
|
58
|
+
return await executeJob(store, options.id, context);
|
|
59
|
+
} finally { store.close(); }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export async function executeJob(store, id, context) {
|
|
63
|
+
const { home, now, deps, out, err, executeEvent, findDefinition } = context;
|
|
64
|
+
const job = store.get(id);
|
|
65
|
+
if (!job) throw new Error(`no job "${id}"`);
|
|
66
|
+
if (job.state !== 'pending' || job.availableAt > now) { out(formatJob(job)); return 0; }
|
|
67
|
+
const triggerLease = await acquireTriggerLease(home, job.triggerId);
|
|
68
|
+
if (!triggerLease) { store.defer(id, 'already_running', now + 1000); return 0; }
|
|
69
|
+
let handle;
|
|
70
|
+
try {
|
|
71
|
+
let definition;
|
|
72
|
+
try { definition = findDefinition(home, job.project, job.triggerId); }
|
|
73
|
+
catch (error) { store.defer(id, 'definition_missing', now + 30000); err(error.message); return 0; }
|
|
74
|
+
if (definition.workspace !== job.workspace) {
|
|
75
|
+
store.defer(id, 'workspace_changed', now + 30000);
|
|
76
|
+
err(`job ${id} belongs to ${job.workspace}; cancel it and schedule a new job for the changed workspace`);
|
|
77
|
+
return 0;
|
|
78
|
+
}
|
|
79
|
+
const eventId = store.eventIdentity(job.id) ?? `job:${job.id}`;
|
|
80
|
+
const chosen = normalizeEvent(job.triggerId, { source: job.kind, ...JSON.parse(job.payload) }, { eventId, now: job.createdAt });
|
|
81
|
+
const result = await executeEvent(definition, {
|
|
82
|
+
home, now, deps, out, err, triggerLease, chosen, eventId, jobId: id, quietSkips: true,
|
|
83
|
+
beforeRun: planned => { handle = planned; return store.claim(id, planned.runId, now); },
|
|
84
|
+
});
|
|
85
|
+
if (result.skip) {
|
|
86
|
+
let availableAt = now + 30000;
|
|
87
|
+
const runs = readRuns(home, { triggerId: job.triggerId, limit: 0 }).filter(r => r.outcome !== 'skipped');
|
|
88
|
+
if (result.skip === 'too_soon' && runs[0]) availableAt = runs[0].startedAt + definition.limits.minIntervalSeconds * 1000;
|
|
89
|
+
if (result.skip === 'over_daily_limit') {
|
|
90
|
+
const withinDay = runs.filter(r => r.startedAt > now - 86400000).sort((a, b) => a.startedAt - b.startedAt);
|
|
91
|
+
if (withinDay[0]) availableAt = withinDay[0].startedAt + 86400000;
|
|
92
|
+
}
|
|
93
|
+
if (result.skip === 'duplicate') {
|
|
94
|
+
// Reconcile a durable run record after a crash before job-state update.
|
|
95
|
+
const previous = runs.find(r => r.eventId === eventId);
|
|
96
|
+
if (store.claim(id, previous.runId, now)) store.finish(id, previous, now);
|
|
97
|
+
} else store.defer(id, result.skip, availableAt);
|
|
98
|
+
return 0;
|
|
99
|
+
}
|
|
100
|
+
store.finish(id, result.record ?? { exitCode: result.code, reason: 'no_match' });
|
|
101
|
+
return result.code;
|
|
102
|
+
} catch (error) {
|
|
103
|
+
if (handle && store.get(id).state === 'running') {
|
|
104
|
+
const record = { ...handle, jobId: id, outcome: 'failed', reason: 'model_error', exitCode: 1, endedAt: Date.now(), cwd: job.workspace };
|
|
105
|
+
appendRun(home, record);
|
|
106
|
+
store.finish(id, record);
|
|
107
|
+
}
|
|
108
|
+
throw error;
|
|
109
|
+
} finally { triggerLease.release(); }
|
|
110
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// The schedule cursor and generated job commit together. Claim/cancel are SQL
|
|
2
|
+
// compare-and-swap transitions; a running job is never automatically retried.
|
|
3
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
4
|
+
import { chmodSync, mkdirSync } from 'node:fs';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
7
|
+
import { normalizeEvent } from './spool.mjs';
|
|
8
|
+
import { nextFiring, dueFiring } from './schedule.mjs';
|
|
9
|
+
|
|
10
|
+
const key = value => createHash('sha256').update(JSON.stringify(value)).digest('hex');
|
|
11
|
+
export class JobStore {
|
|
12
|
+
constructor(home) {
|
|
13
|
+
const directory = join(home, 'triggers');
|
|
14
|
+
mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
15
|
+
const path = join(directory, 'jobs.sqlite');
|
|
16
|
+
this.db = new DatabaseSync(path);
|
|
17
|
+
chmodSync(path, 0o600);
|
|
18
|
+
this.db.exec(`PRAGMA busy_timeout=5000; PRAGMA journal_mode=WAL; PRAGMA synchronous=FULL;
|
|
19
|
+
CREATE TABLE IF NOT EXISTS schedules (id TEXT PRIMARY KEY, triggerId TEXT NOT NULL, project TEXT NOT NULL, workspace TEXT NOT NULL, source TEXT NOT NULL, nextAt INTEGER NOT NULL, enabled INTEGER NOT NULL DEFAULT 1);
|
|
20
|
+
CREATE TABLE IF NOT EXISTS sources (id TEXT PRIMARY KEY, triggerId TEXT NOT NULL, project TEXT NOT NULL, desired TEXT NOT NULL DEFAULT 'running', revision INTEGER NOT NULL DEFAULT 0, nextAt INTEGER NOT NULL DEFAULT 0, failures INTEGER NOT NULL DEFAULT 0, status TEXT NOT NULL DEFAULT 'waiting', pid INTEGER, error TEXT, log TEXT NOT NULL DEFAULT '');
|
|
21
|
+
CREATE TABLE IF NOT EXISTS event_receipts (id TEXT PRIMARY KEY, eventId TEXT NOT NULL, request TEXT NOT NULL);
|
|
22
|
+
CREATE TABLE IF NOT EXISTS job_requests (id TEXT PRIMARY KEY, request TEXT NOT NULL);
|
|
23
|
+
CREATE TABLE IF NOT EXISTS jobs (seq INTEGER PRIMARY KEY AUTOINCREMENT, id TEXT UNIQUE NOT NULL, triggerId TEXT NOT NULL, project TEXT NOT NULL, workspace TEXT NOT NULL, kind TEXT NOT NULL, scheduleId TEXT, dueAt INTEGER NOT NULL, availableAt INTEGER NOT NULL, createdAt INTEGER NOT NULL, payload TEXT NOT NULL, state TEXT NOT NULL DEFAULT 'pending', reason TEXT, runId TEXT, sessionId TEXT, pid INTEGER, startedAt INTEGER, endedAt INTEGER, exitCode INTEGER);
|
|
24
|
+
CREATE INDEX IF NOT EXISTS jobs_due ON jobs(state,availableAt,dueAt,seq);`);
|
|
25
|
+
}
|
|
26
|
+
close() { this.db.close(); }
|
|
27
|
+
all(sql, ...args) { return this.db.prepare(sql).all(...args); }
|
|
28
|
+
one(sql, ...args) { return this.db.prepare(sql).get(...args); }
|
|
29
|
+
run(sql, ...args) { return this.db.prepare(sql).run(...args); }
|
|
30
|
+
transaction(fn) {
|
|
31
|
+
this.db.exec('BEGIN IMMEDIATE');
|
|
32
|
+
try { const result = fn(); this.db.exec('COMMIT'); return result; }
|
|
33
|
+
catch (error) { this.db.exec('ROLLBACK'); throw error; }
|
|
34
|
+
}
|
|
35
|
+
get(id) { return this.one('SELECT * FROM jobs WHERE id=?', id); }
|
|
36
|
+
list(triggerId) { return this.all(`SELECT * FROM jobs ${triggerId ? 'WHERE triggerId=?' : ''} ORDER BY dueAt DESC,seq DESC`, ...(triggerId ? [triggerId] : [])); }
|
|
37
|
+
pending(now) { return this.all(`SELECT j.* FROM jobs j WHERE j.state='pending' AND j.availableAt<=?
|
|
38
|
+
AND NOT EXISTS (SELECT 1 FROM jobs older WHERE older.triggerId=j.triggerId AND older.state='pending' AND older.dueAt<=?
|
|
39
|
+
AND (older.dueAt<j.dueAt OR (older.dueAt=j.dueAt AND older.seq<j.seq))) ORDER BY j.dueAt,j.seq`, now, now); }
|
|
40
|
+
running() { return this.all("SELECT * FROM jobs WHERE state='running'"); }
|
|
41
|
+
create({ triggerId, project, workspace, payload = {}, dueAt, kind = 'delay', scheduleId = null, id = `job-${randomUUID()}`, now = Date.now() }) {
|
|
42
|
+
this.run('INSERT OR IGNORE INTO jobs(id,triggerId,project,workspace,kind,scheduleId,dueAt,availableAt,createdAt,payload) VALUES(?,?,?,?,?,?,?,?,?,?)', id, triggerId, project, workspace, kind, scheduleId, dueAt, dueAt, now, JSON.stringify(payload));
|
|
43
|
+
return this.get(id);
|
|
44
|
+
}
|
|
45
|
+
acceptEvent({ definition, project, payload, eventId, now = Date.now(), maxPending = 100 }) {
|
|
46
|
+
const event = normalizeEvent(definition.id, payload, { eventId, now });
|
|
47
|
+
const { triggerId, receivedAt: _received, eventId: identity, ...body } = event;
|
|
48
|
+
const request = JSON.stringify(body, (_k, v) => v && typeof v === 'object' && !Array.isArray(v) ? Object.fromEntries(Object.entries(v).sort(([a], [b]) => a.localeCompare(b))) : v);
|
|
49
|
+
if (Buffer.byteLength(request) > 131072) throw new Error('event body exceeds 128 KiB');
|
|
50
|
+
const id = `event-${key([project, triggerId, identity])}`;
|
|
51
|
+
return this.transaction(() => {
|
|
52
|
+
const receipt = this.one('SELECT * FROM event_receipts WHERE id=?', id);
|
|
53
|
+
if (receipt) {
|
|
54
|
+
if (receipt.request !== request) throw new Error('eventId was already used for a different payload');
|
|
55
|
+
return this.get(id);
|
|
56
|
+
}
|
|
57
|
+
const count = this.one("SELECT COUNT(*) AS n FROM jobs WHERE project=? AND triggerId=? AND state IN ('pending','running')", project, triggerId).n;
|
|
58
|
+
if (count >= maxPending) throw new Error('QUEUE_FULL: retry this event later with the same eventId');
|
|
59
|
+
this.run('INSERT INTO event_receipts(id,eventId,request) VALUES(?,?,?)', id, identity, request);
|
|
60
|
+
return this.create({ id, triggerId, project, workspace: definition.workspace, payload: body, kind: 'event', dueAt: now, now });
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
eventIdentity(id) { return this.one('SELECT eventId FROM event_receipts WHERE id=?', id)?.eventId; }
|
|
64
|
+
sources() { return this.all('SELECT * FROM sources'); }
|
|
65
|
+
source(triggerId, project) { return this.one('SELECT * FROM sources WHERE id=?', key([project, triggerId])); }
|
|
66
|
+
registerSource(definition, project) {
|
|
67
|
+
if (definition.source.kind !== 'script') throw new Error('source management requires a script source');
|
|
68
|
+
const id = key([project, definition.id]);
|
|
69
|
+
this.run('INSERT OR IGNORE INTO sources(id,triggerId,project) VALUES(?,?,?)', id, definition.id, project);
|
|
70
|
+
return this.source(definition.id, project);
|
|
71
|
+
}
|
|
72
|
+
controlSource(definition, project, action) {
|
|
73
|
+
if (!['start', 'stop', 'restart'].includes(action)) throw new Error('source expects status, start, stop, restart or logs');
|
|
74
|
+
const saved = this.registerSource(definition, project);
|
|
75
|
+
this.run("UPDATE sources SET desired=?,revision=revision+?,nextAt=0,failures=0 WHERE id=?", action === 'stop' ? 'stopped' : 'running', Number(action === 'restart'), saved.id);
|
|
76
|
+
return this.source(definition.id, project);
|
|
77
|
+
}
|
|
78
|
+
cancel(id, now = Date.now()) {
|
|
79
|
+
if (!this.get(id)) throw new Error(`no job "${id}"`);
|
|
80
|
+
if (!this.run("UPDATE jobs SET state='cancelled',reason='cancelled',endedAt=? WHERE id=? AND state='pending'", now, id).changes) {
|
|
81
|
+
throw new Error(`job ${id} is ${this.get(id).state}; only pending jobs can be cancelled`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/** Tool retries reuse the first due time; a changed request cannot reuse its key. */
|
|
85
|
+
createOnce({ requestKey, request, ...options }) {
|
|
86
|
+
const id = `job-${key([options.project, requestKey])}`;
|
|
87
|
+
return this.transaction(() => {
|
|
88
|
+
const saved = this.one('SELECT request FROM job_requests WHERE id=?', id);
|
|
89
|
+
if (saved) {
|
|
90
|
+
if (saved.request !== request) throw new Error('idempotency_key was already used for a different job request');
|
|
91
|
+
return this.get(id);
|
|
92
|
+
}
|
|
93
|
+
this.run('INSERT INTO job_requests(id,request) VALUES(?,?)', id, request);
|
|
94
|
+
return this.create({ ...options, dueAt: typeof options.dueAt === 'function' ? options.dueAt() : options.dueAt, id });
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
defer(id, reason, availableAt) { this.run("UPDATE jobs SET reason=?,availableAt=? WHERE id=? AND state='pending'", reason, availableAt, id); }
|
|
98
|
+
claim(id, runId, now) { return this.run("UPDATE jobs SET state='running',reason=NULL,runId=?,pid=?,startedAt=? WHERE id=? AND state='pending' AND availableAt<=?", runId, process.pid, now, id, now).changes === 1; }
|
|
99
|
+
finish(id, result, now = Date.now()) {
|
|
100
|
+
const state = result.exitCode === 0 ? 'completed' : 'failed';
|
|
101
|
+
this.run("UPDATE jobs SET state=?,reason=?,sessionId=?,endedAt=?,exitCode=? WHERE id=? AND state='running'", state, result.reason ?? null, result.sessionId ?? null, now, result.exitCode, id);
|
|
102
|
+
}
|
|
103
|
+
schedules() { return this.all('SELECT * FROM schedules'); }
|
|
104
|
+
register(definition, project, now = Date.now()) {
|
|
105
|
+
if (definition.source.kind === 'script') {
|
|
106
|
+
this.run('DELETE FROM schedules WHERE id=?', key([project, definition.id]));
|
|
107
|
+
return this.registerSource(definition, project);
|
|
108
|
+
}
|
|
109
|
+
this.run('DELETE FROM sources WHERE id=?', key([project, definition.id]));
|
|
110
|
+
if (!['calendar', 'interval', 'poll'].includes(definition.source.kind)) throw new Error('only calendar, interval and poll sources have recurring jobs');
|
|
111
|
+
const id = key([project, definition.id]);
|
|
112
|
+
const source = JSON.stringify(definition.source);
|
|
113
|
+
const old = this.one('SELECT * FROM schedules WHERE id=?', id);
|
|
114
|
+
if (old?.workspace === definition.workspace && old.source === source) return old;
|
|
115
|
+
const nextAt = nextFiring(definition.source, now);
|
|
116
|
+
this.run('INSERT INTO schedules(id,triggerId,project,workspace,source,nextAt,enabled) VALUES(?,?,?,?,?,?,?) ON CONFLICT(id) DO UPDATE SET workspace=excluded.workspace,source=excluded.source,nextAt=excluded.nextAt,enabled=excluded.enabled', id, definition.id, project, definition.workspace, source, nextAt, Number(definition.enabled));
|
|
117
|
+
return this.one('SELECT * FROM schedules WHERE id=?', id);
|
|
118
|
+
}
|
|
119
|
+
unregister(triggerId, project, now = Date.now()) {
|
|
120
|
+
const id = key([project, triggerId]);
|
|
121
|
+
this.transaction(() => {
|
|
122
|
+
this.run('DELETE FROM schedules WHERE id=?', id);
|
|
123
|
+
this.run('DELETE FROM sources WHERE id=?', id);
|
|
124
|
+
this.run("UPDATE jobs SET state='cancelled',reason='uninstalled',endedAt=? WHERE scheduleId=? AND state='pending'", now, id);
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
/** Reloaded definitions drive enable/disable and source edits; absent definitions stop producing jobs. */
|
|
128
|
+
materialize(resolveDefinition, now = Date.now()) {
|
|
129
|
+
return this.transaction(() => {
|
|
130
|
+
const created = [];
|
|
131
|
+
for (const saved of this.schedules()) {
|
|
132
|
+
const definition = resolveDefinition(saved);
|
|
133
|
+
if (!definition || !['calendar', 'interval', 'poll'].includes(definition.source.kind)) continue;
|
|
134
|
+
const source = JSON.stringify(definition.source);
|
|
135
|
+
if (saved.source !== source || saved.workspace !== definition.workspace || Boolean(saved.enabled) !== definition.enabled) {
|
|
136
|
+
this.run('UPDATE schedules SET source=?,workspace=?,enabled=?,nextAt=? WHERE id=?', source, definition.workspace, Number(definition.enabled), nextFiring(definition.source, now), saved.id);
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
if (!definition.enabled) continue;
|
|
140
|
+
const firing = dueFiring(definition.source, saved.nextAt, now);
|
|
141
|
+
if (!firing) continue;
|
|
142
|
+
if (!firing.skipped) created.push(this.create({
|
|
143
|
+
id: `cron-${key([saved.id, source, firing.scheduledAt])}`, kind: 'cron', scheduleId: saved.id,
|
|
144
|
+
triggerId: saved.triggerId, project: saved.project, workspace: saved.workspace,
|
|
145
|
+
dueAt: firing.scheduledAt, now, payload: { source: definition.source.kind, fields: { scheduledAt: new Date(firing.scheduledAt).toISOString() } },
|
|
146
|
+
}));
|
|
147
|
+
this.run('UPDATE schedules SET nextAt=? WHERE id=?', firing.nextAt, saved.id);
|
|
148
|
+
}
|
|
149
|
+
return created;
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function formatJob(job) {
|
|
155
|
+
return `${job.id} ${job.triggerId} ${job.state}${job.reason ? ` (${job.reason})` : ''} · due ${new Date(job.dueAt).toISOString()}${job.runId ? ` · run ${job.runId}` : ''}${job.sessionId ? ` · session ${job.sessionId}` : ''}`;
|
|
156
|
+
}
|