@toddzheng024/dscode-desktop 0.0.0-stage → 0.7.33
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 +641 -0
- package/docs/account-login.md +44 -0
- package/docs/auto-review.md +128 -0
- package/docs/browser-use.md +783 -0
- package/docs/computer-use.md +81 -0
- package/docs/custom-providers.md +88 -0
- package/docs/demo.md +76 -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 +190 -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 +30 -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 +98 -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 +146 -0
- package/plugins/grok/index.mjs +122 -0
- package/plugins/grok/models.mjs +129 -0
- package/plugins/grok/status.mjs +28 -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 +99 -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 +120 -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 +119 -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 +123 -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 +68 -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 +96 -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 +271 -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 +148 -0
- package/plugins/tui-tools/hook-sources.mjs +113 -0
- package/plugins/tui-tools/hooks.mjs +23 -0
- package/plugins/tui-tools/index.mjs +268 -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,143 @@
|
|
|
1
|
+
// Human slash commands share the same durable management service as agent tools.
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { formatTrigger } from './config.mjs';
|
|
4
|
+
import { formatJob } from './jobs.mjs';
|
|
5
|
+
import { formatRun, readRuns } from './log.mjs';
|
|
6
|
+
import { formatEvent, listEvents } from './spool.mjs';
|
|
7
|
+
|
|
8
|
+
export const TRIGGER_HELP = `Usage: /trigger (or /triggers)
|
|
9
|
+
list Definitions, registrations and scheduler status
|
|
10
|
+
show <id> Definition and last run
|
|
11
|
+
new <id> Create a disabled, project-local starter
|
|
12
|
+
create|update <id> <JSON> Create or edit definition fields
|
|
13
|
+
enable|disable <id> Enable or pause a trigger
|
|
14
|
+
register|unregister <id> Register or remove its recurring source
|
|
15
|
+
run <id> [text] Queue one event for the scheduler
|
|
16
|
+
schedule <id> <delay|ISO> [text] Queue a delayed event, e.g. 30m
|
|
17
|
+
jobs [id] Jobs and waiting reasons in this workspace
|
|
18
|
+
cancel <jobId> Cancel a pending job
|
|
19
|
+
events <id> Queued events and legacy spool entries
|
|
20
|
+
runs <id> Recent run outcomes
|
|
21
|
+
source <id> [action] status | start | stop | restart | logs
|
|
22
|
+
scheduler [action] status | install
|
|
23
|
+
|
|
24
|
+
A queued event needs a running scheduler. Disabling retains pending jobs;
|
|
25
|
+
stopping a source retains accepted events. Scheduler install starts the shared
|
|
26
|
+
service for all registered projects. Changes require a writable, non-plan session.`;
|
|
27
|
+
|
|
28
|
+
const ok = text => ({ kind: 'success', text });
|
|
29
|
+
const word = input => {
|
|
30
|
+
const match = input.trim().match(/^(\S+)(?:\s+([\s\S]*))?$/u);
|
|
31
|
+
return match ? [match[1], match[2] ?? ''] : ['', ''];
|
|
32
|
+
};
|
|
33
|
+
const usage = text => { throw new Error(`Usage: /trigger ${text}`); };
|
|
34
|
+
const exactId = (input, syntax) => { const [id, rest] = word(input); if (!id || rest) usage(syntax); return id; };
|
|
35
|
+
const schedulerText = state => `Scheduler: ${state.running ? 'running' : `stopped — pending work waits; ${state.next_step ?? 'use /trigger scheduler install (macOS), or run dscode trigger scheduler start under a service manager'}`}${state.requires_open_application ? '; requires Desktop to stay open' : ''}${state.error ? `; ${state.error}` : ''}`;
|
|
36
|
+
|
|
37
|
+
export function triggerCommand({ management, mutationProblem }) {
|
|
38
|
+
const schedulerActions = management.schedulerActions ?? ['status', 'install'];
|
|
39
|
+
const help = management.schedulerDescription ? TRIGGER_HELP.replace('status | install', schedulerActions.join(' | '))
|
|
40
|
+
.replace('Scheduler install starts the shared\nservice for all registered projects.', management.schedulerDescription) : TRIGGER_HELP;
|
|
41
|
+
return async ({ agent, rawInput = '', commandId, signal }) => {
|
|
42
|
+
try {
|
|
43
|
+
signal?.throwIfAborted();
|
|
44
|
+
const [verb, input] = word(rawInput), action = verb || 'list';
|
|
45
|
+
if (['help', '--help', '-h'].includes(action)) return ok(help);
|
|
46
|
+
const project = management.workspace(agent);
|
|
47
|
+
const mutate = () => {
|
|
48
|
+
signal?.throwIfAborted();
|
|
49
|
+
const problem = mutationProblem(agent);
|
|
50
|
+
if (problem) throw new Error(problem);
|
|
51
|
+
};
|
|
52
|
+
if (action === 'list') {
|
|
53
|
+
if (input) usage('list');
|
|
54
|
+
const result = await management.manage({ action: 'list' }, agent);
|
|
55
|
+
const rows = result.definitions.map(definition => {
|
|
56
|
+
const registration = result.sources.find(s => s.triggerId === definition.id) ?? result.schedules.find(s => s.triggerId === definition.id);
|
|
57
|
+
const source = registration?.desired ? `source ${registration.desired} (${registration.status})` : registration ? 'registered' : definition.source.kind === 'external' ? 'external event ingress' : 'not registered';
|
|
58
|
+
return `${formatTrigger(definition, { lastRun: readRuns(management.home, { triggerId: definition.id, limit: 1 })[0] })}\n scheduler: ${source}`;
|
|
59
|
+
});
|
|
60
|
+
return ok([schedulerText(result.scheduler), ...rows, ...(result.problems.length ? ['Unreadable definitions:'] : []), ...result.problems.map(p => `${p.path}: ${p.message}`),
|
|
61
|
+
...(!rows.length ? ['No triggers defined. Use /trigger new <id> to create a disabled starter in .dsh/triggers/.'] : []),
|
|
62
|
+
'Use /trigger help for management commands.'].join('\n'));
|
|
63
|
+
}
|
|
64
|
+
if (['show', 'runs', 'events'].includes(action)) {
|
|
65
|
+
const id = exactId(input, `${action} <id>`);
|
|
66
|
+
const definition = management.definition(project, id);
|
|
67
|
+
if (action === 'show') return ok(formatTrigger(definition, { lastRun: readRuns(management.home, { triggerId: id, limit: 1 })[0] }));
|
|
68
|
+
if (action === 'runs') {
|
|
69
|
+
const runs = readRuns(management.home, { triggerId: id, limit: 0 }).filter(r => !r.cwd || r.cwd === project).slice(0, 20);
|
|
70
|
+
return ok(runs.length ? runs.map(formatRun).join('\n') : `No runs recorded for ${id}.`);
|
|
71
|
+
}
|
|
72
|
+
const pending = listEvents(management.home, id);
|
|
73
|
+
management.withStore(store => {
|
|
74
|
+
for (const job of store.list(id).filter(j => j.project === project && j.workspace === project && j.kind === 'event' && ['pending', 'running'].includes(j.state))) {
|
|
75
|
+
pending.push({ ...JSON.parse(job.payload), eventId: store.eventIdentity(job.id), receivedAt: job.createdAt });
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
pending.sort((a, b) => a.receivedAt - b.receivedAt);
|
|
79
|
+
return ok(pending.length ? pending.map(formatEvent).join('\n') : `No pending events for ${id}.`);
|
|
80
|
+
}
|
|
81
|
+
if (action === 'jobs') {
|
|
82
|
+
const id = input ? exactId(input, 'jobs [id]') : undefined;
|
|
83
|
+
const result = await management.jobs({ action: 'list', trigger_id: id, limit: 100 }, agent);
|
|
84
|
+
return ok([result.jobs.length ? result.jobs.map(formatJob).join('\n') : 'No jobs scheduled in this workspace.', schedulerText(result.scheduler)].join('\n'));
|
|
85
|
+
}
|
|
86
|
+
if (action === 'source') {
|
|
87
|
+
const [id, rest] = word(input), operation = rest || 'status';
|
|
88
|
+
if (!id || !['status', 'start', 'stop', 'restart', 'logs'].includes(operation)) usage('source <id> [status|start|stop|restart|logs]');
|
|
89
|
+
if (!['status', 'logs'].includes(operation)) mutate();
|
|
90
|
+
const result = await management.source({ trigger_id: id, action: operation }, agent);
|
|
91
|
+
if (operation === 'logs') return ok(result.log || 'No source output recorded.');
|
|
92
|
+
const s = result.source;
|
|
93
|
+
return ok([`${id}: desired ${s.desired ?? 'unregistered'}; observed ${s.status}${s.pid ? `; pid ${s.pid}` : ''}`,
|
|
94
|
+
...(s.error ? [`Last error: ${s.error}`] : []), ...(s.nextAt ? [`Next attempt: ${new Date(s.nextAt).toISOString()}`] : []),
|
|
95
|
+
schedulerText(result.scheduler), ...(!['status'].includes(operation) ? ['Saved desired state; the scheduler applies it asynchronously. Accepted jobs remain queued.'] : [])].join('\n'));
|
|
96
|
+
}
|
|
97
|
+
if (action === 'scheduler') {
|
|
98
|
+
const operation = input || 'status';
|
|
99
|
+
if (!schedulerActions.includes(operation)) usage(`scheduler [${schedulerActions.join('|')}]`);
|
|
100
|
+
if (operation !== 'status') mutate();
|
|
101
|
+
const result = await management.scheduler({ action: operation });
|
|
102
|
+
return ok([...(result.messages ?? []), schedulerText(result.scheduler ?? result)].join('\n'));
|
|
103
|
+
}
|
|
104
|
+
if (['new', 'create', 'update', 'enable', 'disable', 'register', 'unregister'].includes(action)) {
|
|
105
|
+
let id, definition;
|
|
106
|
+
if (['create', 'update'].includes(action)) {
|
|
107
|
+
let json; [id, json] = word(input);
|
|
108
|
+
if (!id || !json) usage(`${action} <id> <JSON definition fields>`);
|
|
109
|
+
try { definition = JSON.parse(json); } catch { throw new Error('Definition fields must be a JSON object; see /trigger help.'); }
|
|
110
|
+
} else id = exactId(input, `${action} <id>`);
|
|
111
|
+
mutate();
|
|
112
|
+
if (action === 'new') definition = { enabled: false, source: { kind: 'external' }, prompt: 'Describe the task for this trigger.', goal: { objective: 'Describe the outcome to complete.' } };
|
|
113
|
+
const result = await management.manage({ action: action === 'new' ? 'create' : action, trigger_id: id, definition }, agent);
|
|
114
|
+
return ok([`${action}: ${id}`, formatTrigger(result.definition), schedulerText(result.scheduler),
|
|
115
|
+
...(action === 'new' ? ['Starter is disabled. Edit its prompt, goal and source with /trigger update <id> <JSON>, or ask the agent to configure it, then enable it.'] : []),
|
|
116
|
+
...(action === 'disable' ? ['Pending jobs are retained; cancel them separately if needed.'] : []),
|
|
117
|
+
...(action === 'unregister' ? ['Pending recurring jobs were cancelled. Delay and emitted jobs are retained.'] : [])].join('\n'));
|
|
118
|
+
}
|
|
119
|
+
if (action === 'cancel') {
|
|
120
|
+
const id = exactId(input, 'cancel <jobId>'); mutate();
|
|
121
|
+
const result = await management.jobs({ action: 'cancel', job_id: id }, agent);
|
|
122
|
+
return ok(formatJob(result.job));
|
|
123
|
+
}
|
|
124
|
+
if (action === 'schedule' || action === 'run') {
|
|
125
|
+
const [id, rest] = word(input);
|
|
126
|
+
if (!id) usage(action === 'run' ? 'run <id> [text]' : 'schedule <id> <delay|ISO> [text]');
|
|
127
|
+
const identity = `slash-${commandId ?? randomUUID()}`;
|
|
128
|
+
let result;
|
|
129
|
+
if (action === 'schedule') {
|
|
130
|
+
const [when, text] = word(rest);
|
|
131
|
+
if (!when) usage('schedule <id> <delay|ISO> [text]');
|
|
132
|
+
mutate();
|
|
133
|
+
result = await management.jobs({ action: 'schedule', trigger_id: id, ...(/^\d+(?:\.\d+)?[smhd]$/u.test(when) ? { after: when } : { at: when }), event: { source: 'tui', text }, idempotency_key: identity }, agent);
|
|
134
|
+
} else {
|
|
135
|
+
mutate();
|
|
136
|
+
result = await management.emit({ trigger_id: id, eventId: identity, event: { source: 'tui', text: rest } }, agent);
|
|
137
|
+
}
|
|
138
|
+
return ok([formatJob(result.job), schedulerText(result.scheduler), 'Queued durably; this command does not wait for the agent run.'].join('\n'));
|
|
139
|
+
}
|
|
140
|
+
return { kind: 'error', text: `Unknown action "${action}".\n${help}` };
|
|
141
|
+
} catch (error) { return { kind: 'error', text: error.message }; }
|
|
142
|
+
};
|
|
143
|
+
}
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
// Trigger definitions: the configuration half of the trigger mechanism
|
|
2
|
+
// (docs/triggers-design.md). A definition says what produces an event, which
|
|
3
|
+
// folder the session binds to, what it is asked to do, and the limits that
|
|
4
|
+
// keep an unattended run bounded. Nothing here starts a run: the ingress, the
|
|
5
|
+
// runner and the source installers are separate work, and `/triggers` reads this
|
|
6
|
+
// layer only.
|
|
7
|
+
//
|
|
8
|
+
// One bad file never hides the rest: discovery returns the definitions it could
|
|
9
|
+
// read plus a per-file problem list, so a listing always shows what is usable.
|
|
10
|
+
|
|
11
|
+
import { readdirSync, readFileSync } from 'node:fs';
|
|
12
|
+
import { isAbsolute, join, resolve } from 'node:path';
|
|
13
|
+
import { parse as parseYaml } from 'yaml';
|
|
14
|
+
import { validateCalendar } from './schedule.mjs';
|
|
15
|
+
|
|
16
|
+
/** Source kinds this version accepts; each has its own required fields. */
|
|
17
|
+
export const SOURCE_KINDS = Object.freeze(['interval', 'calendar', 'watch', 'poll', 'external', 'script']);
|
|
18
|
+
|
|
19
|
+
/** The default fresh-session overlap policy; persistent sessions queue instead. */
|
|
20
|
+
export const OVERLAP = 'skip';
|
|
21
|
+
|
|
22
|
+
/** The only notification policy for now: append to the run log. */
|
|
23
|
+
export const NOTIFY = 'log';
|
|
24
|
+
|
|
25
|
+
/** Defaults a definition may omit. */
|
|
26
|
+
export const DEFAULTS = Object.freeze({
|
|
27
|
+
enabled: true,
|
|
28
|
+
preset: 'dscode',
|
|
29
|
+
permission: 'workspace-write',
|
|
30
|
+
maxGoalRounds: 20,
|
|
31
|
+
timeoutSeconds: 1800,
|
|
32
|
+
maxRunsPerDay: 24,
|
|
33
|
+
minIntervalSeconds: 60,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
/** Permission presets an unattended run may use; `ask` can never complete. */
|
|
37
|
+
const UNATTENDED_PERMISSIONS = Object.freeze(['auto-review', 'auto', 'workspace-write', 'read-only', 'danger-full-access']);
|
|
38
|
+
|
|
39
|
+
const ID_PATTERN = /^[a-z0-9][a-z0-9._-]*$/u;
|
|
40
|
+
|
|
41
|
+
class TriggerConfigError extends Error {}
|
|
42
|
+
|
|
43
|
+
const fail = message => { throw new TriggerConfigError(message); };
|
|
44
|
+
|
|
45
|
+
const isPlainObject = value => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
46
|
+
|
|
47
|
+
function positiveNumber(value, label) {
|
|
48
|
+
if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0) fail(`${label} must be a positive number`);
|
|
49
|
+
return value;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function positiveInteger(value, label) {
|
|
53
|
+
if (!Number.isSafeInteger(value) || value <= 0) fail(`${label} must be a positive whole number`);
|
|
54
|
+
return value;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function nonEmptyString(value, label) {
|
|
58
|
+
if (typeof value !== 'string' || value.trim() === '') fail(`${label} must be a non-empty string`);
|
|
59
|
+
return value.trim();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Reject a key this version does not read, so a typo cannot fall back silently. */
|
|
63
|
+
function knownKeys(raw, allowed, label) {
|
|
64
|
+
const unknown = Object.keys(raw).filter(key => !allowed.includes(key));
|
|
65
|
+
if (unknown.length > 0) fail(`${label} has no such field: ${unknown.join(', ')} (accepted: ${allowed.join(', ')})`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Validate and normalize one source block. */
|
|
69
|
+
function normalizeSource(raw) {
|
|
70
|
+
if (!isPlainObject(raw)) fail('source must be an object');
|
|
71
|
+
const kind = nonEmptyString(raw.kind, 'source.kind');
|
|
72
|
+
if (!SOURCE_KINDS.includes(kind)) fail(`source.kind must be one of: ${SOURCE_KINDS.join(', ')}`);
|
|
73
|
+
const fields = kind === 'interval' ? ['kind', 'seconds']
|
|
74
|
+
: kind === 'calendar' ? ['kind', 'cron', 'timezone', 'misfire']
|
|
75
|
+
: kind === 'watch' ? ['kind', 'paths']
|
|
76
|
+
: kind === 'poll' ? ['kind', 'everySeconds', 'check']
|
|
77
|
+
: kind === 'script' ? ['kind', 'mode', 'command', 'everySeconds', 'timeoutSeconds', 'permission'] : ['kind'];
|
|
78
|
+
knownKeys(raw, fields, `source (${kind})`);
|
|
79
|
+
if (kind === 'script') {
|
|
80
|
+
if (!['poll', 'daemon'].includes(raw.mode)) fail('source.mode must be poll or daemon');
|
|
81
|
+
if (!Array.isArray(raw.command) || !raw.command.length || raw.command.some(arg => typeof arg !== 'string' || arg.includes('\0')) || !raw.command[0].trim()) fail('source.command must be a non-empty argv array');
|
|
82
|
+
const permission = raw.permission ?? 'read-only';
|
|
83
|
+
if (!['read-only', 'workspace-write'].includes(permission)) fail('source.permission must be read-only or workspace-write');
|
|
84
|
+
if (raw.mode === 'daemon' && (raw.everySeconds !== undefined || raw.timeoutSeconds !== undefined)) fail('daemon sources do not accept everySeconds or timeoutSeconds');
|
|
85
|
+
return { kind, mode: raw.mode, command: raw.command, permission, ...(raw.mode === 'poll' ? { everySeconds: positiveInteger(raw.everySeconds, 'source.everySeconds'), timeoutSeconds: positiveInteger(raw.timeoutSeconds ?? 60, 'source.timeoutSeconds') } : {}) };
|
|
86
|
+
}
|
|
87
|
+
if (kind === 'interval') return { kind, seconds: positiveInteger(raw.seconds, 'source.seconds') };
|
|
88
|
+
if (kind === 'calendar') {
|
|
89
|
+
const cron = nonEmptyString(raw.cron, 'source.cron');
|
|
90
|
+
const timezone = raw.timezone === undefined ? Intl.DateTimeFormat().resolvedOptions().timeZone : nonEmptyString(raw.timezone, 'source.timezone');
|
|
91
|
+
const misfire = raw.misfire ?? 'run-once';
|
|
92
|
+
if (!['run-once', 'skip'].includes(misfire)) fail('source.misfire must be run-once or skip');
|
|
93
|
+
validateCalendar(cron, timezone);
|
|
94
|
+
return { kind, cron, timezone, misfire };
|
|
95
|
+
}
|
|
96
|
+
if (kind === 'watch') {
|
|
97
|
+
if (!Array.isArray(raw.paths) || raw.paths.length === 0) fail('source.paths must be a non-empty list of paths');
|
|
98
|
+
return { kind, paths: raw.paths.map((path, index) => nonEmptyString(path, `source.paths[${index}]`)) };
|
|
99
|
+
}
|
|
100
|
+
if (kind === 'poll') {
|
|
101
|
+
return { kind, everySeconds: positiveInteger(raw.everySeconds, 'source.everySeconds'), check: nonEmptyString(raw.check, 'source.check') };
|
|
102
|
+
}
|
|
103
|
+
return { kind };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Validate one raw definition into the normalized shape.
|
|
108
|
+
* @param raw - the parsed YAML/JSON body.
|
|
109
|
+
* @param meta - where it came from: `{ origin, path }`.
|
|
110
|
+
* @returns the normalized definition.
|
|
111
|
+
* @throws {TriggerConfigError} with a user-facing message.
|
|
112
|
+
*/
|
|
113
|
+
export function normalizeTrigger(raw, { origin, path } = {}) {
|
|
114
|
+
if (!isPlainObject(raw)) fail('a trigger definition must be a mapping');
|
|
115
|
+
knownKeys(raw, ['id', 'enabled', 'source', 'workspace', 'prompt', 'preset', 'permission', 'model', 'effort', 'session', 'goal', 'limits', 'overlap', 'notify'], 'the definition');
|
|
116
|
+
const id = nonEmptyString(raw.id, 'id');
|
|
117
|
+
if (!ID_PATTERN.test(id)) fail('id must start with a letter or digit and use only a-z, 0-9, dot, underscore or dash');
|
|
118
|
+
const workspace = nonEmptyString(raw.workspace, 'workspace');
|
|
119
|
+
if (!isAbsolute(workspace)) fail('workspace must be an absolute path: a triggered session binds to it and cannot change folders');
|
|
120
|
+
const prompt = nonEmptyString(raw.prompt, 'prompt');
|
|
121
|
+
const enabled = raw.enabled === undefined ? DEFAULTS.enabled : raw.enabled;
|
|
122
|
+
if (typeof enabled !== 'boolean') fail('enabled must be true or false');
|
|
123
|
+
|
|
124
|
+
const permission = raw.permission === undefined ? DEFAULTS.permission : nonEmptyString(raw.permission, 'permission');
|
|
125
|
+
if (!UNATTENDED_PERMISSIONS.includes(permission)) {
|
|
126
|
+
fail(`permission must be one of: ${UNATTENDED_PERMISSIONS.join(', ')} — an unattended run cannot ask for approval`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const session = raw.session === undefined ? { mode: 'new' } : raw.session;
|
|
130
|
+
if (!isPlainObject(session)) fail('session must be an object with mode: new or persistent');
|
|
131
|
+
knownKeys(session, ['mode'], 'session');
|
|
132
|
+
if (!['new', 'persistent'].includes(session.mode)) fail('session.mode must be new or persistent');
|
|
133
|
+
|
|
134
|
+
const goal = raw.goal;
|
|
135
|
+
if (!isPlainObject(goal)) fail('goal must be an object with the objective to finish');
|
|
136
|
+
knownKeys(goal, ['objective', 'maxRounds'], 'goal');
|
|
137
|
+
const objective = nonEmptyString(goal.objective, 'goal.objective');
|
|
138
|
+
const maxRounds = goal.maxRounds === undefined ? DEFAULTS.maxGoalRounds : positiveInteger(goal.maxRounds, 'goal.maxRounds');
|
|
139
|
+
|
|
140
|
+
const limits = raw.limits === undefined ? {} : raw.limits;
|
|
141
|
+
if (!isPlainObject(limits)) fail('limits must be an object');
|
|
142
|
+
knownKeys(limits, ['timeoutSeconds', 'maxRunsPerDay', 'minIntervalSeconds', 'maxCostUsd'], 'limits');
|
|
143
|
+
const timeoutSeconds = limits.timeoutSeconds === undefined ? DEFAULTS.timeoutSeconds : positiveInteger(limits.timeoutSeconds, 'limits.timeoutSeconds');
|
|
144
|
+
const maxRunsPerDay = limits.maxRunsPerDay === undefined ? DEFAULTS.maxRunsPerDay : positiveInteger(limits.maxRunsPerDay, 'limits.maxRunsPerDay');
|
|
145
|
+
const minIntervalSeconds = limits.minIntervalSeconds === undefined ? DEFAULTS.minIntervalSeconds : positiveInteger(limits.minIntervalSeconds, 'limits.minIntervalSeconds');
|
|
146
|
+
const maxCostUsd = limits.maxCostUsd === undefined ? undefined : positiveNumber(limits.maxCostUsd, 'limits.maxCostUsd');
|
|
147
|
+
|
|
148
|
+
const overlap = session.mode === 'persistent' ? 'queue' : OVERLAP;
|
|
149
|
+
if (raw.overlap !== undefined && raw.overlap !== overlap) fail(`overlap must be "${overlap}" for session.mode ${session.mode}`);
|
|
150
|
+
if (raw.notify !== undefined && raw.notify !== NOTIFY) fail(`notify must be "${NOTIFY}"`);
|
|
151
|
+
|
|
152
|
+
const source = normalizeSource(raw.source);
|
|
153
|
+
return {
|
|
154
|
+
id, enabled, source, workspace, prompt,
|
|
155
|
+
preset: raw.preset === undefined ? DEFAULTS.preset : nonEmptyString(raw.preset, 'preset'),
|
|
156
|
+
permission,
|
|
157
|
+
...(raw.model === undefined ? {} : { model: nonEmptyString(raw.model, 'model') }),
|
|
158
|
+
...(raw.effort === undefined ? {} : { effort: nonEmptyString(raw.effort, 'effort') }),
|
|
159
|
+
goal: { objective, maxRounds },
|
|
160
|
+
limits: { timeoutSeconds, maxRunsPerDay, minIntervalSeconds, ...(maxCostUsd === undefined ? {} : { maxCostUsd }) },
|
|
161
|
+
session: { mode: session.mode },
|
|
162
|
+
overlap,
|
|
163
|
+
notify: NOTIFY,
|
|
164
|
+
origin: origin ?? 'user',
|
|
165
|
+
path: path ?? '',
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** Parse one file body: JSON or YAML by extension, YAML as the general form. */
|
|
170
|
+
function parseBody(text, path) {
|
|
171
|
+
if (/\.json$/iu.test(path)) {
|
|
172
|
+
try { return JSON.parse(text); } catch (error) { fail(`not valid JSON: ${error.message}`); }
|
|
173
|
+
}
|
|
174
|
+
try { return parseYaml(text); } catch (error) { fail(`not valid YAML: ${error.message}`); }
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** Read one directory of definitions, returning definitions and problems. */
|
|
178
|
+
function readDirectory(directory, origin) {
|
|
179
|
+
const definitions = [];
|
|
180
|
+
const problems = [];
|
|
181
|
+
let entries;
|
|
182
|
+
try {
|
|
183
|
+
entries = readdirSync(directory);
|
|
184
|
+
} catch (error) {
|
|
185
|
+
// Absent is normal (most machines and projects have no triggers); anything
|
|
186
|
+
// else must be reported, or a permission problem reads as "none defined".
|
|
187
|
+
if (error?.code !== 'ENOENT' && error?.code !== 'ENOTDIR') {
|
|
188
|
+
problems.push({ path: directory, message: `cannot read the trigger directory: ${error?.code ?? error?.message ?? error}` });
|
|
189
|
+
}
|
|
190
|
+
return { definitions, problems };
|
|
191
|
+
}
|
|
192
|
+
for (const entry of entries.sort()) {
|
|
193
|
+
if (!/\.(ya?ml|json)$/iu.test(entry)) continue;
|
|
194
|
+
const path = join(directory, entry);
|
|
195
|
+
try {
|
|
196
|
+
const normalized = normalizeTrigger(parseBody(readFileSync(path, 'utf8'), path), { origin, path });
|
|
197
|
+
if (normalized.id !== entry.replace(/\.(ya?ml|json)$/iu, '')) {
|
|
198
|
+
fail(`id "${normalized.id}" must match the file name ("${entry}")`);
|
|
199
|
+
}
|
|
200
|
+
definitions.push(normalized);
|
|
201
|
+
} catch (error) {
|
|
202
|
+
problems.push({ path, message: error instanceof TriggerConfigError ? error.message : String(error?.message ?? error) });
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return { definitions, problems };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Discover trigger definitions. User-level files live under `<state>/triggers/`;
|
|
210
|
+
* project files under `<workspace>/.dsh/triggers/` and win on an id collision, so
|
|
211
|
+
* a repository can pin its own definition and have it reviewed in a pull request.
|
|
212
|
+
* @param options - `{ home, workspace }`; either may be absent.
|
|
213
|
+
* @returns `{ definitions, problems }`, definitions sorted by id.
|
|
214
|
+
*/
|
|
215
|
+
export function loadTriggerDefinitions({ home, workspace } = {}) {
|
|
216
|
+
const user = home === undefined ? { definitions: [], problems: [] } : readDirectory(join(home, 'triggers'), 'user');
|
|
217
|
+
const project = workspace === undefined ? { definitions: [], problems: [] } : readDirectory(join(resolve(workspace), '.dsh', 'triggers'), 'project');
|
|
218
|
+
const byId = new Map();
|
|
219
|
+
for (const definition of user.definitions) byId.set(definition.id, definition);
|
|
220
|
+
for (const definition of project.definitions) byId.set(definition.id, { ...definition, overrides: byId.has(definition.id) });
|
|
221
|
+
return {
|
|
222
|
+
definitions: [...byId.values()].sort((left, right) => left.id.localeCompare(right.id)),
|
|
223
|
+
problems: [...user.problems, ...project.problems],
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** The newest run in one line; `formatRun` in log.mjs owns the vocabulary. */
|
|
228
|
+
function formatLastRun(record) {
|
|
229
|
+
const when = new Date(record.startedAt).toISOString().replace('T', ' ').slice(0, 19);
|
|
230
|
+
const reason = record.reason === null || record.reason === undefined ? '' : ` (${record.reason})`;
|
|
231
|
+
return `${when} ${record.outcome}${reason} · exit ${record.exitCode}`;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** One line, bounded: a listing row must never be forged out of a value. */
|
|
235
|
+
const inline = (text, limit = 96) => {
|
|
236
|
+
const folded = String(text).replace(/\s+/gu, ' ').trim();
|
|
237
|
+
return folded.length > limit ? `${folded.slice(0, limit - 1)}…` : folded;
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* One definition as the text `/triggers` prints.
|
|
242
|
+
* @param definition - a normalized definition.
|
|
243
|
+
* @param options - `{ lastRun }`: the newest run record, when the log has one.
|
|
244
|
+
* @returns the aligned block.
|
|
245
|
+
*/
|
|
246
|
+
export function formatTrigger(definition, { lastRun } = {}) {
|
|
247
|
+
const source = definition.source.kind === 'interval' ? `every ${definition.source.seconds}s`
|
|
248
|
+
: definition.source.kind === 'calendar' ? `cron ${definition.source.cron} (${definition.source.timezone}, ${definition.source.misfire})`
|
|
249
|
+
: definition.source.kind === 'watch' ? `watch ${definition.source.paths.join(', ')}`
|
|
250
|
+
: definition.source.kind === 'poll' ? `poll ${definition.source.everySeconds}s: ${definition.source.check}`
|
|
251
|
+
: definition.source.kind === 'script' ? `script ${definition.source.mode}: ${definition.source.command.join(' ')}` : 'external (emit only)';
|
|
252
|
+
return [
|
|
253
|
+
`${definition.enabled ? 'on ' : 'off'} ${definition.id}${definition.overrides ? ' (project overrides user)' : ''}`,
|
|
254
|
+
` source: ${source}`,
|
|
255
|
+
` workspace: ${definition.workspace}`,
|
|
256
|
+
` session: ${definition.session?.mode ?? 'new'}`,
|
|
257
|
+
` goal: ${inline(definition.goal.objective)} (max ${definition.goal.maxRounds} rounds)`,
|
|
258
|
+
` prompt: ${inline(definition.prompt)}`,
|
|
259
|
+
` limits: ${definition.limits.timeoutSeconds}s, ${definition.limits.maxRunsPerDay}/day${definition.limits.maxCostUsd === undefined ? '' : `, $${definition.limits.maxCostUsd}`}`,
|
|
260
|
+
` run as: ${definition.preset}/${definition.permission}${definition.model === undefined ? '' : ` · ${definition.model}`}`,
|
|
261
|
+
` file: ${definition.path === '' ? '(unknown)' : definition.path}`,
|
|
262
|
+
...(lastRun === undefined ? [] : [` last: ${formatLastRun(lastRun)}`]),
|
|
263
|
+
].join('\n');
|
|
264
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
globalThis.__ModuleLoader__.load({
|
|
2
|
+
id: '@toddzheng024/dscode-desktop',
|
|
3
|
+
factory: require => {
|
|
4
|
+
const { createElement: h, useState, useEffect, useRef } = require('react');
|
|
5
|
+
const control = { padding: '8px 10px', border: '1px solid #8886', borderRadius: 6, background: 'transparent', color: 'inherit', font: 'inherit', boxSizing: 'border-box', minWidth: 0, maxWidth: '100%' };
|
|
6
|
+
const row = { display: 'flex', flexWrap: 'wrap', gap: 8, alignItems: 'center' };
|
|
7
|
+
const initial = () => ({ id: '', prompt: '', objective: '', kind: 'interval', seconds: '3600', cron: '0 9 * * 1-5', timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
8
|
+
command: '[]', scriptMode: 'poll', persistent: true, permission: 'read-only', enabled: true });
|
|
9
|
+
function Schedules({ execute }) {
|
|
10
|
+
const [snapshot, setSnapshot] = useState(null), [session, setSession] = useState(''), [workspace, setWorkspace] = useState(null);
|
|
11
|
+
const [draft, setDraft] = useState(initial), [editing, setEditing] = useState(null), [busy, setBusy] = useState(false), [error, setError] = useState(''), [log, setLog] = useState('');
|
|
12
|
+
const [refreshError, setRefreshError] = useState('');
|
|
13
|
+
const pending = useRef(null), foreground = useRef(false), alive = useRef(true), selected = useRef(''), eventKeys = useRef(new Map()), actionError = useRef(null);
|
|
14
|
+
// Foreground failures must be visible even after submitting a long form.
|
|
15
|
+
// Polling errors use separate state and must not steal the user's focus.
|
|
16
|
+
useEffect(() => { if (error) actionError.current?.focus(); }, [error]);
|
|
17
|
+
const refresh = async () => {
|
|
18
|
+
const value = await execute('status');
|
|
19
|
+
if (!alive.current) return;
|
|
20
|
+
setSnapshot(value);
|
|
21
|
+
const id = value.sessions.some(item => item.id === selected.current) ? selected.current : value.sessions[0]?.id ?? '';
|
|
22
|
+
if (id !== selected.current) { setWorkspace(null); setEditing(null); setDraft(initial()); setLog(''); }
|
|
23
|
+
selected.current = id; setSession(id);
|
|
24
|
+
if (id) {
|
|
25
|
+
const result = await execute('workspace', { sessionId: id });
|
|
26
|
+
if (alive.current && selected.current === id) setWorkspace(result);
|
|
27
|
+
} else setWorkspace(null);
|
|
28
|
+
if (alive.current) setRefreshError('');
|
|
29
|
+
};
|
|
30
|
+
const run = async (task, background = false) => {
|
|
31
|
+
if (background ? pending.current : foreground.current) return;
|
|
32
|
+
if (!background) { foreground.current = true; setBusy(true); setError(''); }
|
|
33
|
+
const previous = pending.current;
|
|
34
|
+
const operation = (async () => {
|
|
35
|
+
if (previous) await previous;
|
|
36
|
+
if (!alive.current) return;
|
|
37
|
+
try { await task(); } catch (error) {
|
|
38
|
+
if (alive.current) {
|
|
39
|
+
if (background) setRefreshError(`Unable to refresh schedules: ${error.message}`);
|
|
40
|
+
else setError(error.message);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
})();
|
|
44
|
+
pending.current = operation;
|
|
45
|
+
try { await operation; } finally {
|
|
46
|
+
if (pending.current === operation) pending.current = null;
|
|
47
|
+
if (!background) { foreground.current = false; if (alive.current) setBusy(false); }
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
alive.current = true; void run(refresh);
|
|
52
|
+
const timer = setInterval(() => { void run(refresh, true); }, 5000);
|
|
53
|
+
return () => { alive.current = false; clearInterval(timer); };
|
|
54
|
+
}, []);
|
|
55
|
+
const mutate = (action, args) => run(async () => {
|
|
56
|
+
await execute(action, { sessionId: selected.current, args }); await refresh();
|
|
57
|
+
});
|
|
58
|
+
const button = (label, action, disabled = false) => h('button', { type: 'button', style: control, disabled: busy || disabled, onClick: action }, label);
|
|
59
|
+
const field = (label, key, type = 'text') => h('label', { style: { display: 'grid', gap: 4 } }, label,
|
|
60
|
+
h('input', { 'aria-label': label, style: control, type, value: draft[key], disabled: key === 'id' && !!editing,
|
|
61
|
+
onChange: event => setDraft(previous => ({ ...previous, [key]: event.target.value })) }));
|
|
62
|
+
const select = (label, key, values) => h('label', null, label, h('select', { 'aria-label': label, style: control, value: draft[key],
|
|
63
|
+
onChange: event => setDraft(previous => ({ ...previous, [key]: event.target.value })) }, ...values.map(([value, label]) => h('option', { key: value, value }, label))));
|
|
64
|
+
const edit = definition => {
|
|
65
|
+
setEditing(definition);
|
|
66
|
+
setDraft({ ...initial(), id: definition.id, prompt: definition.prompt, objective: definition.goal.objective, kind: definition.source.kind,
|
|
67
|
+
seconds: String(definition.source.seconds ?? definition.source.everySeconds ?? 3600), cron: definition.source.cron ?? '0 9 * * 1-5',
|
|
68
|
+
timezone: definition.source.timezone ?? initial().timezone, command: JSON.stringify(definition.source.command ?? []),
|
|
69
|
+
scriptMode: definition.source.mode ?? 'poll', persistent: definition.session.mode === 'persistent', permission: definition.permission, enabled: definition.enabled });
|
|
70
|
+
};
|
|
71
|
+
const save = () => run(async () => {
|
|
72
|
+
const source = draft.kind === 'calendar' ? { kind: 'calendar', cron: draft.cron, timezone: draft.timezone }
|
|
73
|
+
: draft.kind === 'interval' ? { kind: 'interval', seconds: Number(draft.seconds) }
|
|
74
|
+
: draft.kind === 'script' ? { kind: 'script', mode: draft.scriptMode, command: JSON.parse(draft.command),
|
|
75
|
+
...(draft.scriptMode === 'poll' ? { everySeconds: Number(draft.seconds) } : {}) } : { kind: 'external' };
|
|
76
|
+
const previousSource = editing?.source.kind === draft.kind ? { ...editing.source } : {};
|
|
77
|
+
if (draft.kind === 'script' && draft.scriptMode === 'daemon') { delete previousSource.everySeconds; delete previousSource.timeoutSeconds; }
|
|
78
|
+
await execute('manage', { sessionId: selected.current, args: { action: editing ? 'update' : 'create', trigger_id: draft.id,
|
|
79
|
+
definition: { prompt: draft.prompt, source: { ...previousSource, ...source },
|
|
80
|
+
goal: { ...(editing?.goal ?? {}), objective: draft.objective.trim() || draft.prompt }, session: { mode: draft.persistent ? 'persistent' : 'new' },
|
|
81
|
+
permission: draft.permission, enabled: draft.enabled } } });
|
|
82
|
+
if (alive.current) { setEditing(null); setDraft(initial()); }
|
|
83
|
+
await refresh();
|
|
84
|
+
});
|
|
85
|
+
const runIdentity = definition => JSON.stringify([definition.workspace, definition.id]);
|
|
86
|
+
const runOnce = definition => run(async () => {
|
|
87
|
+
const identity = runIdentity(definition);
|
|
88
|
+
if (!eventKeys.current.has(identity)) eventKeys.current.set(identity, crypto.randomUUID());
|
|
89
|
+
await execute('jobs', { sessionId: selected.current, args: { action: 'schedule', trigger_id: definition.id, after: '1s', idempotency_key: eventKeys.current.get(identity) } });
|
|
90
|
+
// Keep this task's key through a lost reply or failed refresh, even if
|
|
91
|
+
// the user runs another task or changes workspace before retrying.
|
|
92
|
+
await refresh(); eventKeys.current.delete(identity);
|
|
93
|
+
});
|
|
94
|
+
return h('section', { 'aria-label': 'DSCODE scheduling settings', style: { display: 'grid', gridTemplateColumns: 'minmax(0, 1fr)', gap: 16, padding: 20, maxWidth: 960, minWidth: 0, boxSizing: 'border-box', overflowWrap: 'anywhere' } },
|
|
95
|
+
h('h2', { style: { margin: 0 } }, 'DSCODE schedules'),
|
|
96
|
+
h('p', null, 'Enabling delivery runs due tasks for every registered project and remembers this choice when Desktop opens again. Keep the application open. Stopping delivery interrupts active tasks and script sources; pending jobs stay queued.'),
|
|
97
|
+
error && h('div', { role: 'alert', tabIndex: -1, ref: actionError }, error),
|
|
98
|
+
refreshError && h('div', { role: 'alert' }, refreshError),
|
|
99
|
+
snapshot && h('div', { role: 'status' }, `Delivery: ${snapshot.scheduler.running ? 'running' : snapshot.scheduler.stopping ? 'stopping' : 'stopped'} · Resume on launch: ${snapshot.scheduler.enabled ? 'on' : 'off'}${snapshot.scheduler.owner === 'external' ? ' · controlled by another process' : ''}`),
|
|
100
|
+
snapshot?.scheduler.error && h('div', { role: 'alert' }, snapshot.scheduler.error),
|
|
101
|
+
h('div', { style: row },
|
|
102
|
+
button('Enable delivery and resume on launch', () => run(async () => { await execute('start'); await refresh(); }), !snapshot || snapshot.scheduler.owner === 'external' || snapshot.scheduler.running || snapshot.scheduler.stopping),
|
|
103
|
+
button('Stop delivery and disable resume', () => run(async () => { await execute('stop'); await refresh(); }), !snapshot || (!snapshot.scheduler.enabled && !snapshot.scheduler.running)),
|
|
104
|
+
button('Refresh schedules', () => run(refresh))),
|
|
105
|
+
snapshot?.scheduler.owner === 'external' && h('p', null, snapshot.scheduler.next_step),
|
|
106
|
+
h('label', null, 'Workspace session', h('select', { 'aria-label': 'Workspace session', style: { ...control, display: 'block', width: '100%' }, value: session, disabled: busy,
|
|
107
|
+
onChange: event => { selected.current = event.target.value; setSession(event.target.value); setWorkspace(null); setEditing(null); setDraft(initial()); setLog(''); void run(refresh); } },
|
|
108
|
+
h('option', { value: '' }, 'Choose an open DSCODE session'), ...(snapshot?.sessions ?? []).map(item => h('option', { key: item.id, value: item.id }, `${item.workspace} · ${item.id.slice(0, 8)}`)))),
|
|
109
|
+
!session && h('p', null, 'Open a DSCODE session in the project you want to schedule.'),
|
|
110
|
+
workspace && h('div', null,
|
|
111
|
+
!workspace.writable && h('p', null, 'This session can inspect schedules. Use a writable, non-plan session to change them.'),
|
|
112
|
+
...workspace.problems.map(problem => h('div', { role: 'alert', key: problem.path }, `${problem.path}: ${problem.message}`)),
|
|
113
|
+
h('h3', null, 'Tasks'),
|
|
114
|
+
...workspace.definitions.map(item => h('article', { key: item.id, style: { padding: '12px 0', borderBottom: '1px solid #8884' } },
|
|
115
|
+
h('strong', null, item.id), h('p', null, `${item.enabled ? 'Enabled' : 'Paused'} · ${item.source.kind} · ${item.permission}`), h('p', { style: { whiteSpace: 'pre-wrap' } }, item.prompt),
|
|
116
|
+
h('div', { style: row }, button(`Edit ${item.id}`, () => edit(item), !workspace.writable || item.origin !== 'project'),
|
|
117
|
+
button(`${item.enabled ? 'Pause' : 'Enable'} ${item.id}`, () => mutate('manage', { action: item.enabled ? 'disable' : 'enable', trigger_id: item.id }), !workspace.writable || item.origin !== 'project'),
|
|
118
|
+
button(`${eventKeys.current.has(runIdentity(item)) ? 'Retry' : 'Run'} ${item.id} once`, () => runOnce(item), !workspace.writable || !item.enabled)),
|
|
119
|
+
item.source.kind === 'script' && h('div', { style: row },
|
|
120
|
+
h('span', null, `Source: ${workspace.sources.find(source => source.triggerId === item.id)?.status ?? 'unregistered'}`),
|
|
121
|
+
...['start', 'stop', 'restart'].map(action => button(`${action} ${item.id} source`, () => mutate('source', { action, trigger_id: item.id }), !workspace.writable)),
|
|
122
|
+
button(`Read ${item.id} source log`, () => run(async () => { const result = await execute('source', { sessionId: selected.current, args: { action: 'logs', trigger_id: item.id } }); if (alive.current) setLog(result.log || 'No source output recorded.'); }))))),
|
|
123
|
+
log && h('pre', { 'aria-label': 'Source log', style: { whiteSpace: 'pre-wrap', overflowWrap: 'anywhere' } }, log),
|
|
124
|
+
!workspace.definitions.length && h('p', null, 'No tasks in this workspace.'),
|
|
125
|
+
h('fieldset', { disabled: busy || !workspace.writable, style: { display: 'grid', gridTemplateColumns: 'minmax(0, 1fr)', minWidth: 0, gap: 12, border: '1px solid #8886', borderRadius: 8, padding: 16 } },
|
|
126
|
+
h('legend', null, editing ? `Edit ${editing.id}` : 'Create task'), field('Task ID', 'id'),
|
|
127
|
+
h('label', null, 'Task instructions', h('textarea', { 'aria-label': 'Task instructions', style: { ...control, display: 'block', width: '100%', minHeight: 90 }, value: draft.prompt,
|
|
128
|
+
onChange: event => setDraft(previous => ({ ...previous, prompt: event.target.value })) })),
|
|
129
|
+
field('Goal (defaults to task instructions)', 'objective'),
|
|
130
|
+
select('Schedule', 'kind', [['interval', 'Interval'], ['calendar', 'Calendar'], ['external', 'Manual or external event'], ['script', 'Script events']]),
|
|
131
|
+
draft.kind === 'calendar' && h('div', { style: { display: 'grid', gap: 8 } }, field('Cron expression', 'cron'), field('Timezone', 'timezone')),
|
|
132
|
+
draft.kind === 'script' && h('div', { style: { display: 'grid', gap: 8 } }, field('Script command (JSON argument array)', 'command'), select('Script mode', 'scriptMode', [['poll', 'Poll'], ['daemon', 'Daemon']])),
|
|
133
|
+
(draft.kind === 'interval' || (draft.kind === 'script' && draft.scriptMode === 'poll')) && field('Interval seconds', 'seconds', 'number'),
|
|
134
|
+
select('Run permission', 'permission', [['read-only', 'Read only'], ['workspace-write', 'Write in this workspace']]),
|
|
135
|
+
...[['persistent', 'Continue the task’s own session'], ['enabled', 'Enable this task']].map(([key, label]) => h('label', { key }, h('input', { type: 'checkbox', checked: draft[key],
|
|
136
|
+
onChange: event => setDraft(previous => ({ ...previous, [key]: event.target.checked })) }), label)),
|
|
137
|
+
h('div', { style: row }, button(editing ? 'Save task changes' : 'Create task', save, !draft.id.trim() || !draft.prompt.trim()),
|
|
138
|
+
editing && button('Cancel editing', () => { setEditing(null); setDraft(initial()); }))),
|
|
139
|
+
h('h3', null, 'Recent jobs'),
|
|
140
|
+
...workspace.jobs.map(job => h('div', { key: job.id, style: { ...row, padding: '8px 0' } },
|
|
141
|
+
h('span', { style: { flex: 1 } }, `${job.triggerId}: ${job.state}${job.reason ? ` (${job.reason})` : ''} · ${new Date(job.dueAt).toLocaleString()}`),
|
|
142
|
+
job.state === 'pending' && button(`Cancel ${job.triggerId} job`, () => mutate('jobs', { action: 'cancel', job_id: job.id }), !workspace.writable))),
|
|
143
|
+
!workspace.jobs.length && h('p', null, 'No jobs in this workspace.')));
|
|
144
|
+
}
|
|
145
|
+
return { inject: ['slots', 'connection'], apply(ctx) {
|
|
146
|
+
ctx.slots.inject('settings.section', () => ctx.slots.register({ name: 'settings.section', id: 'dscode-schedules', order: 13, label: () => 'DSCODE schedules',
|
|
147
|
+
inject: () => ({ execute: async (action, args = {}) => {
|
|
148
|
+
const response = await ctx.connection.rpc.call('/api', 'dscode-triggers', { action, ...args });
|
|
149
|
+
if (!response.ok) throw Error(response.error.message);
|
|
150
|
+
return response.value;
|
|
151
|
+
} }),
|
|
152
|
+
}, Schedules));
|
|
153
|
+
} };
|
|
154
|
+
},
|
|
155
|
+
});
|