@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,157 @@
|
|
|
1
|
+
// The run decision: everything that must be true before an event may start a
|
|
2
|
+
// session, and the record left afterwards. This is the safety half of the
|
|
3
|
+
// mechanism, so it is deliberately small and entirely testable: an unattended
|
|
4
|
+
// run must never overlap its own previous run, must never fire twice for one
|
|
5
|
+
// event, and must never exceed the limits its definition declares.
|
|
6
|
+
//
|
|
7
|
+
// It does NOT run an agent: executing the session (Host plugin, `dscode trigger
|
|
8
|
+
// run`, the goal service) is separate work. `planTriggerRun` answers "may this
|
|
9
|
+
// run start, and with what identity", `finishTriggerRun` records how it ended.
|
|
10
|
+
|
|
11
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
12
|
+
import { randomUUID } from 'node:crypto';
|
|
13
|
+
import { dirname, join } from 'node:path';
|
|
14
|
+
import { appendRun, readRuns } from './log.mjs';
|
|
15
|
+
|
|
16
|
+
/** Grace added to a run's timeout before its lock is treated as abandoned. */
|
|
17
|
+
export const LOCK_STALE_GRACE_MS = 60000;
|
|
18
|
+
|
|
19
|
+
/** One trigger's single-flight lock. */
|
|
20
|
+
export const lockPath = (home, triggerId) => join(home, 'triggers', 'locks', `${triggerId}.json`);
|
|
21
|
+
|
|
22
|
+
/** True when a process id exists; EPERM means it exists but belongs to someone else. */
|
|
23
|
+
export function alivePid(pid) {
|
|
24
|
+
if (!Number.isSafeInteger(pid) || pid <= 0) return false;
|
|
25
|
+
try {
|
|
26
|
+
process.kill(pid, 0);
|
|
27
|
+
return true;
|
|
28
|
+
} catch (error) {
|
|
29
|
+
return error?.code === 'EPERM';
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function readLock(home, triggerId) {
|
|
34
|
+
try {
|
|
35
|
+
const lock = JSON.parse(readFileSync(lockPath(home, triggerId), 'utf8'));
|
|
36
|
+
return typeof lock?.runId === 'string' ? lock : undefined;
|
|
37
|
+
} catch {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function writeLock(home, handle) {
|
|
43
|
+
const path = lockPath(home, handle.triggerId);
|
|
44
|
+
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
45
|
+
writeFileSync(path, JSON.stringify(handle) + '\n', { mode: 0o600 });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Decide whether one event may start a run, and take the lock when it may.
|
|
50
|
+
*
|
|
51
|
+
* Order matters: a disabled trigger never runs; a repeated event is recognised
|
|
52
|
+
* before any lock is touched; the rolling day and the minimum interval are
|
|
53
|
+
* checked against recorded runs; and only then does a live lock stop the run.
|
|
54
|
+
* An identity is REQUIRED: without one the duplicate check is inert, so a
|
|
55
|
+
* re-drained event would start a second session. A scheduled source passes the
|
|
56
|
+
* planned instant, an external producer passes its own event id.
|
|
57
|
+
* @param definition - a normalized definition (plugins/triggers/config.mjs).
|
|
58
|
+
* @param options - `{ home, now, eventId, isAlive, runId }`; `eventId` is required.
|
|
59
|
+
* @returns `{ action: 'run', handle }` or `{ action: 'skip', reason }`.
|
|
60
|
+
* @throws {Error} when `eventId` is missing or empty.
|
|
61
|
+
*/
|
|
62
|
+
export function planTriggerRun(definition, { home, now = Date.now(), eventId, isAlive = alivePid, runId = randomUUID() } = {}) {
|
|
63
|
+
if (typeof eventId !== 'string' || eventId.trim() === '') throw new Error('planTriggerRun needs an eventId: it is how a repeated firing is recognised');
|
|
64
|
+
if (!definition.enabled) return { action: 'skip', reason: 'disabled' };
|
|
65
|
+
if (!existsSync(definition.workspace)) return { action: 'skip', reason: 'workspace_missing' };
|
|
66
|
+
const recorded = readRuns(home, { triggerId: definition.id, limit: 0 }).filter(entry => entry.outcome !== 'skipped');
|
|
67
|
+
if (recorded.some(entry => entry.eventId === eventId)) return { action: 'skip', reason: 'duplicate' };
|
|
68
|
+
const lastDay = recorded.filter(entry => now - entry.startedAt < 24 * 60 * 60 * 1000);
|
|
69
|
+
if (lastDay.length >= definition.limits.maxRunsPerDay) return { action: 'skip', reason: 'over_daily_limit' };
|
|
70
|
+
const newest = recorded[0];
|
|
71
|
+
if (newest !== undefined && now - newest.startedAt < definition.limits.minIntervalSeconds * 1000) return { action: 'skip', reason: 'too_soon' };
|
|
72
|
+
|
|
73
|
+
const lock = readLock(home, definition.id);
|
|
74
|
+
if (lock !== undefined) {
|
|
75
|
+
// A dead holder is reclaimed at once. A live pid is normally the run itself,
|
|
76
|
+
// but a recycled pid would look alive forever, so a lock older than the run's
|
|
77
|
+
// own timeout (plus a grace) is abandoned regardless.
|
|
78
|
+
const abandoned = now - lock.startedAt > definition.limits.timeoutSeconds * 1000 + LOCK_STALE_GRACE_MS;
|
|
79
|
+
if (isAlive(lock.pid) && !abandoned) return { action: 'skip', reason: 'already_running' };
|
|
80
|
+
}
|
|
81
|
+
const handle = { triggerId: definition.id, runId, startedAt: now, pid: process.pid, eventId };
|
|
82
|
+
writeLock(home, handle);
|
|
83
|
+
return { action: 'run', handle };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Drop a lock this run owns, without recording anything.
|
|
88
|
+
* A lock whose contents cannot be read is left in place rather than deleted: an
|
|
89
|
+
* unreadable lock may belong to another run, and deleting it would let two
|
|
90
|
+
* sessions run for one trigger. It ages out on its own.
|
|
91
|
+
*/
|
|
92
|
+
export function releaseTriggerRun(home, handle) {
|
|
93
|
+
const lock = readLock(home, handle.triggerId);
|
|
94
|
+
if (lock === undefined || lock.runId !== handle.runId) return false;
|
|
95
|
+
try {
|
|
96
|
+
rmSync(lockPath(home, handle.triggerId));
|
|
97
|
+
return true;
|
|
98
|
+
} catch {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Decide whether a run in flight should stop, and how it ended. Called after
|
|
105
|
+
* every turn and by the timeout, so it only reads durable goal state plus the
|
|
106
|
+
* spend the metrics ledger reports.
|
|
107
|
+
*
|
|
108
|
+
* Completion wins over a rejected approval: the goal was reached, and the
|
|
109
|
+
* rejection is recorded in the run tail. When a cap or the timeout stops a run
|
|
110
|
+
* that needed a human, `approval_required` is the reason — that is the failure an
|
|
111
|
+
* operator has to fix.
|
|
112
|
+
* @param state - `{ goal, costUsd, limits, approvalsRejected }`.
|
|
113
|
+
* @returns `{ stop: false }` or `{ stop: true, outcome, reason, exitCode }`.
|
|
114
|
+
*/
|
|
115
|
+
export function decideStop({ goal, costUsd, limits, approvalsRejected = false } = {}) {
|
|
116
|
+
const capped = approvalsRejected ? 'approval_required' : undefined;
|
|
117
|
+
// A goal that is gone was cleared: nothing left to continue for.
|
|
118
|
+
if (goal === undefined || goal === null) return { stop: true, outcome: 'completed', reason: null, exitCode: 0 };
|
|
119
|
+
if (goal.phase === 'complete') return { stop: true, outcome: 'completed', reason: null, exitCode: 0 };
|
|
120
|
+
if (goal.phase === 'blocked') return { stop: true, outcome: 'blocked', reason: 'goal_blocked', exitCode: 3 };
|
|
121
|
+
if (goal.phase === 'paused') return { stop: true, outcome: 'failed', reason: 'goal_paused', exitCode: 3 };
|
|
122
|
+
if (Number.isFinite(limits?.maxCostUsd) && Number.isFinite(costUsd) && costUsd >= limits.maxCostUsd) {
|
|
123
|
+
return { stop: true, outcome: 'overrun', reason: capped ?? 'cost_cap', exitCode: 2 };
|
|
124
|
+
}
|
|
125
|
+
if (Number.isFinite(goal.maxGoalRounds) && goal.roundsStarted >= goal.maxGoalRounds) {
|
|
126
|
+
return { stop: true, outcome: 'overrun', reason: capped ?? 'round_cap', exitCode: 2 };
|
|
127
|
+
}
|
|
128
|
+
return { stop: false };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Record how a run ended and release its lock.
|
|
133
|
+
* @param home - the state directory.
|
|
134
|
+
* @param handle - the handle `planTriggerRun` returned.
|
|
135
|
+
* @param result - `{ outcome, reason?, exitCode, sessionId?, cost?, rounds?, cwd?, endedAt?, eventId? }`.
|
|
136
|
+
* @returns the appended record.
|
|
137
|
+
*/
|
|
138
|
+
export function finishTriggerRun(home, handle, result) {
|
|
139
|
+
const record = appendRun(home, {
|
|
140
|
+
triggerId: handle.triggerId,
|
|
141
|
+
runId: handle.runId,
|
|
142
|
+
startedAt: handle.startedAt,
|
|
143
|
+
endedAt: result.endedAt ?? Date.now(),
|
|
144
|
+
eventId: result.eventId ?? handle.eventId,
|
|
145
|
+
...(result.jobId ? { jobId: result.jobId } : {}),
|
|
146
|
+
source: result.source ?? null,
|
|
147
|
+
outcome: result.outcome,
|
|
148
|
+
reason: result.reason ?? null,
|
|
149
|
+
exitCode: result.exitCode,
|
|
150
|
+
sessionId: result.sessionId ?? null,
|
|
151
|
+
cost: result.cost ?? null,
|
|
152
|
+
rounds: result.rounds ?? null,
|
|
153
|
+
cwd: result.cwd ?? handle.cwd ?? null,
|
|
154
|
+
});
|
|
155
|
+
releaseTriggerRun(home, handle);
|
|
156
|
+
return record;
|
|
157
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { CronExpressionParser } from 'cron-parser';
|
|
2
|
+
|
|
3
|
+
export function validateCalendar(cron, timezone) {
|
|
4
|
+
if (cron.trim().split(/\s+/u).length !== 5) throw new Error('source.cron must have five fields (minute hour day month weekday)');
|
|
5
|
+
if (!/^[\d*/,\-\s]+$/u.test(cron)) throw new Error('source.cron supports numeric fields, *, ranges, lists and steps');
|
|
6
|
+
new Intl.DateTimeFormat('en', { timeZone: timezone }).format(0);
|
|
7
|
+
CronExpressionParser.parse(cron, { tz: timezone }).next();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function nextFiring(source, after) {
|
|
11
|
+
if (source.kind === 'calendar') return CronExpressionParser.parse(source.cron, { tz: source.timezone, currentDate: after }).next().getTime();
|
|
12
|
+
return after + (source.seconds ?? source.everySeconds) * 1000;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Coalesce all overdue occurrences to the latest one, without iterating years. */
|
|
16
|
+
export function dueFiring(source, nextAt, now) {
|
|
17
|
+
if (now < nextAt) return undefined;
|
|
18
|
+
const scheduledAt = source.kind === 'calendar'
|
|
19
|
+
? CronExpressionParser.parse(source.cron, { tz: source.timezone, currentDate: now + 1 }).prev().getTime()
|
|
20
|
+
: nextAt + Math.floor((now - nextAt) / ((source.seconds ?? source.everySeconds) * 1000)) * (source.seconds ?? source.everySeconds) * 1000;
|
|
21
|
+
return { scheduledAt, nextAt: nextFiring(source, now), skipped: source.misfire === 'skip' && now - scheduledAt >= 60000 };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function dueTime({ after, at }, now = Date.now()) {
|
|
25
|
+
if ((after === undefined) === (at === undefined)) throw new Error('schedule needs exactly one of --after or --at');
|
|
26
|
+
let due;
|
|
27
|
+
if (after !== undefined) {
|
|
28
|
+
const match = /^(\d+(?:\.\d+)?)(s|m|h|d)$/u.exec(after);
|
|
29
|
+
if (!match) throw new Error('--after expects a duration such as 30s, 10m, 2h or 1d');
|
|
30
|
+
due = now + Number(match[1]) * { s: 1000, m: 60000, h: 3600000, d: 86400000 }[match[2]];
|
|
31
|
+
} else {
|
|
32
|
+
const match = /^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})(?:\.\d{1,3})?(Z|[+-]\d{2}:\d{2})$/u.exec(at);
|
|
33
|
+
if (!match) throw new Error('--at needs an ISO timestamp with Z or an explicit UTC offset');
|
|
34
|
+
due = Date.parse(at);
|
|
35
|
+
const offset = match[2] === 'Z' ? 0 : (match[2][0] === '-' ? -1 : 1) * (Number(match[2].slice(1, 3)) * 60 + Number(match[2].slice(4))) * 60000;
|
|
36
|
+
if (!Number.isFinite(due) || new Date(due + offset).toISOString().slice(0, 19) !== match[1]) throw new Error('--at is not a valid calendar timestamp');
|
|
37
|
+
}
|
|
38
|
+
if (!Number.isSafeInteger(due) || due <= now || due > 8640000000000000) throw new Error('the scheduled time must be in the future');
|
|
39
|
+
return due;
|
|
40
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
5
|
+
|
|
6
|
+
const xml = value => String(value).replace(/[<>&"']/gu, char => ({ '<': '<', '>': '>', '&': '&', '"': '"', "'": ''' }[char]));
|
|
7
|
+
export const schedulerLabel = home => `ai.dscode.scheduler.${createHash('sha256').update(home).digest('hex').slice(0, 12)}`;
|
|
8
|
+
export const schedulerPath = (home, directory = join(homedir(), 'Library', 'LaunchAgents')) => join(directory, `${schedulerLabel(home)}.plist`);
|
|
9
|
+
|
|
10
|
+
export function schedulerPlist(home, dscodePath) {
|
|
11
|
+
const args = [process.execPath, dscodePath, 'trigger', 'scheduler', 'start'];
|
|
12
|
+
const log = join(home, 'triggers', 'scheduler.log');
|
|
13
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
14
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
15
|
+
<plist version="1.0"><dict>
|
|
16
|
+
<key>Label</key><string>${xml(schedulerLabel(home))}</string>
|
|
17
|
+
<key>ProgramArguments</key><array>${args.map(arg => `<string>${xml(arg)}</string>`).join('')}</array>
|
|
18
|
+
<key>WorkingDirectory</key><string>${xml(home)}</string>
|
|
19
|
+
<key>EnvironmentVariables</key><dict><key>DSH_HOME</key><string>${xml(home)}</string><key>DSCODE_HOME</key><string>${xml(home)}</string><key>PATH</key><string>${xml(`${dirname(process.execPath)}:/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin`)}</string></dict>
|
|
20
|
+
<key>RunAtLoad</key><true/><key>KeepAlive</key><true/>
|
|
21
|
+
<key>ThrottleInterval</key><integer>5</integer>
|
|
22
|
+
<key>StandardOutPath</key><string>${xml(log)}</string>
|
|
23
|
+
<key>StandardErrorPath</key><string>${xml(log)}</string>
|
|
24
|
+
</dict></plist>
|
|
25
|
+
`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function schedulerService(action, { home, dscodePath, platform, launchctl, agentsDirectory, out }) {
|
|
29
|
+
if (platform !== 'darwin' || !launchctl) {
|
|
30
|
+
out(`Manage the scheduler with your service manager: ${JSON.stringify(process.execPath)} ${JSON.stringify(dscodePath)} trigger scheduler start (DSH_HOME=${JSON.stringify(home)}, DSCODE_HOME=${JSON.stringify(home)})`);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const path = schedulerPath(home, agentsDirectory);
|
|
34
|
+
const label = `gui/${process.getuid()}/${schedulerLabel(home)}`;
|
|
35
|
+
if (action === 'uninstall') {
|
|
36
|
+
await launchctl(['bootout', label], { ignoreFailure: true });
|
|
37
|
+
rmSync(path, { force: true });
|
|
38
|
+
out(`removed scheduler service ${path}; pending jobs remain on disk`);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
42
|
+
mkdirSync(join(home, 'triggers'), { recursive: true, mode: 0o700 });
|
|
43
|
+
writeFileSync(path, schedulerPlist(home, dscodePath), { mode: 0o600 });
|
|
44
|
+
if (await launchctl(['print', label], { ignoreFailure: true }) !== 0) await launchctl(['bootstrap', `gui/${process.getuid()}`, path]);
|
|
45
|
+
out(`scheduler installed: ${path}`);
|
|
46
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { SourceSupervisor } from './sources.mjs';
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import { closeSync, mkdirSync, openSync } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { setTimeout as sleep } from 'node:timers/promises';
|
|
6
|
+
import { JobStore } from './jobs.mjs';
|
|
7
|
+
import { loadTriggerDefinitions } from './config.mjs';
|
|
8
|
+
import { acquireTriggerLease } from './lease.mjs';
|
|
9
|
+
import { alivePid } from './run.mjs';
|
|
10
|
+
import { readRuns } from './log.mjs';
|
|
11
|
+
|
|
12
|
+
export function launchJobWorker({ home, dscodePath, job }) {
|
|
13
|
+
const directory = join(home, 'triggers', 'jobs');
|
|
14
|
+
mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
15
|
+
const fd = openSync(join(directory, `${job.id}.log`), 'a', 0o600);
|
|
16
|
+
let child;
|
|
17
|
+
try {
|
|
18
|
+
child = spawn(process.execPath, [dscodePath, 'trigger', 'run-job', job.id], {
|
|
19
|
+
cwd: home, env: { ...process.env, DSH_HOME: home, DSCODE_HOME: home },
|
|
20
|
+
stdio: ['ignore', fd, fd],
|
|
21
|
+
});
|
|
22
|
+
} finally { closeSync(fd); }
|
|
23
|
+
// Workers may finish after the scheduler exits. Their trigger leases and
|
|
24
|
+
// durable claims still exclude a replacement scheduler from double-running.
|
|
25
|
+
return new Promise((resolve, reject) => {
|
|
26
|
+
child.once('error', reject);
|
|
27
|
+
child.once('exit', code => resolve(code ?? 130));
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** One scan; active promises are tracked separately so model latency cannot stop the clock. */
|
|
32
|
+
export async function schedulerTick({ home, store, active, dscodePath, now = Date.now(), spawnWorker = launchJobWorker, report = console.error }) {
|
|
33
|
+
const definitions = new Map();
|
|
34
|
+
const resolve = saved => {
|
|
35
|
+
if (!definitions.has(saved.project)) {
|
|
36
|
+
const found = loadTriggerDefinitions({ home, workspace: saved.project });
|
|
37
|
+
for (const problem of found.problems) report(`${problem.path}: ${problem.message}`);
|
|
38
|
+
definitions.set(saved.project, found.definitions);
|
|
39
|
+
}
|
|
40
|
+
return definitions.get(saved.project).find(d => d.id === saved.triggerId);
|
|
41
|
+
};
|
|
42
|
+
store.materialize(resolve, now);
|
|
43
|
+
for (const job of store.running()) {
|
|
44
|
+
if (alivePid(job.pid)) continue;
|
|
45
|
+
const lease = await acquireTriggerLease(home, job.triggerId);
|
|
46
|
+
if (!lease) continue; // its orphaned Host still owns the descriptor
|
|
47
|
+
try {
|
|
48
|
+
const result = readRuns(home, { triggerId: job.triggerId, limit: 0 }).find(run => run.runId === job.runId);
|
|
49
|
+
store.finish(job.id, result ?? { exitCode: 130, reason: 'interrupted' }, now);
|
|
50
|
+
} finally { lease.release(); }
|
|
51
|
+
}
|
|
52
|
+
const busy = new Set([...active.values()].map(entry => entry.triggerId));
|
|
53
|
+
for (const job of store.pending(now)) {
|
|
54
|
+
if (active.size >= 4) break;
|
|
55
|
+
if (busy.has(job.triggerId)) continue;
|
|
56
|
+
busy.add(job.triggerId);
|
|
57
|
+
const promise = Promise.resolve().then(() => spawnWorker({ home, dscodePath, job })).then(code => {
|
|
58
|
+
if (code && store.get(job.id)?.state === 'pending') store.defer(job.id, 'worker_start_failed', Date.now() + 30000);
|
|
59
|
+
}).catch(error => {
|
|
60
|
+
report(`scheduler ${job.id}: ${error.message}`);
|
|
61
|
+
store.defer(job.id, 'worker_start_failed', Date.now() + 30000);
|
|
62
|
+
}).finally(() => { active.delete(job.id); });
|
|
63
|
+
active.set(job.id, { triggerId: job.triggerId, promise });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export async function runScheduler({ home, dscodePath, once = false, signal, spawnWorker, onReady, report = console.error }) {
|
|
68
|
+
const lease = await acquireTriggerLease(home, '_scheduler');
|
|
69
|
+
if (!lease) throw new Error('the scheduler is already running for this state directory');
|
|
70
|
+
let store, sources;
|
|
71
|
+
const active = new Map();
|
|
72
|
+
try {
|
|
73
|
+
store = new JobStore(home);
|
|
74
|
+
sources = new SourceSupervisor({ home, store, report });
|
|
75
|
+
await onReady?.();
|
|
76
|
+
do {
|
|
77
|
+
if (signal?.aborted) break;
|
|
78
|
+
if (!once) sources.tick();
|
|
79
|
+
await schedulerTick({ home, store, active, dscodePath, spawnWorker, report });
|
|
80
|
+
if (once) break;
|
|
81
|
+
await sleep(1000, undefined, { signal }).catch(error => { if (error.name !== 'AbortError') throw error; });
|
|
82
|
+
} while (!signal?.aborted);
|
|
83
|
+
await sources.close();
|
|
84
|
+
await Promise.allSettled([...active.values()].map(entry => entry.promise));
|
|
85
|
+
return 0;
|
|
86
|
+
} finally {
|
|
87
|
+
await sources?.close();
|
|
88
|
+
await Promise.allSettled([...active.values()].map(entry => entry.promise));
|
|
89
|
+
store?.close();
|
|
90
|
+
lease.release();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// One owned unattended run, independent of the process that hosts it. The caller
|
|
2
|
+
// owns admission/leases and persists the final run record; this module owns only
|
|
3
|
+
// its Agent, goal, listeners and deadline.
|
|
4
|
+
import { createUserMessage } from '@deepseek-ai/dsh-llm';
|
|
5
|
+
import { openTriggerSession } from './session.mjs';
|
|
6
|
+
import { decideStop } from './run.mjs';
|
|
7
|
+
import { writeRunTail } from './log.mjs';
|
|
8
|
+
import { sessionSpend } from '../session-metrics/view.mjs';
|
|
9
|
+
import { markUnattended } from './unattended.mjs';
|
|
10
|
+
|
|
11
|
+
function splitRoute(value) {
|
|
12
|
+
const at = value.indexOf('/');
|
|
13
|
+
if (at <= 0 || at === value.length - 1) throw Error(`model expects provider/model, got ${value}`);
|
|
14
|
+
return [value.slice(0, at), value.slice(at + 1)];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Starts input after setup; done settles only after the owned Agent is drained. */
|
|
18
|
+
export async function startTriggerSession(ctx, { spec, home, spend = sessionSpend, signal, prepare }) {
|
|
19
|
+
signal?.throwIfAborted();
|
|
20
|
+
const selection = ctx.agentDefaultModel.currentSelection();
|
|
21
|
+
const [provider, model] = spec.model ? splitRoute(spec.model) : [selection.provider, selection.model];
|
|
22
|
+
const effort = spec.effort ?? selection.reasoningEffort;
|
|
23
|
+
const agentOptions = { provider, model, ...(effort ? { reasoningEffort: effort } : {}) };
|
|
24
|
+
const setup = async (scope, agent) => {
|
|
25
|
+
await ctx.agentPresets.mount(scope, spec.preset ?? 'dscode');
|
|
26
|
+
await prepare?.(scope, agent);
|
|
27
|
+
};
|
|
28
|
+
const handle = await openTriggerSession(ctx, spec, { home, agentOptions, setup, signal });
|
|
29
|
+
const agent = handle.agent, session = agent.session;
|
|
30
|
+
const unmark = markUnattended(agent);
|
|
31
|
+
const disposers = [];
|
|
32
|
+
let timer, finished = false, approvalsRejected = false, lastText = '';
|
|
33
|
+
const cleanup = () => { clearTimeout(timer); for (const dispose of disposers.splice(0)) dispose(); };
|
|
34
|
+
try {
|
|
35
|
+
signal?.throwIfAborted();
|
|
36
|
+
if (spec.permission) {
|
|
37
|
+
ctx.permissionPresets.resolve(spec.permission);
|
|
38
|
+
ctx.permissionPresets.set(session, spec.permission);
|
|
39
|
+
}
|
|
40
|
+
const previous = ctx.goals.get(agent);
|
|
41
|
+
if (previous) ctx.goals.clear(agent, { id: previous.id, revision: previous.revision });
|
|
42
|
+
const initialCost = spend(session.id).cost;
|
|
43
|
+
const runCost = () => {
|
|
44
|
+
try {
|
|
45
|
+
const total = spend(session.id).cost;
|
|
46
|
+
return Number.isFinite(initialCost) && Number.isFinite(total) ? Math.max(0, total - initialCost) : undefined;
|
|
47
|
+
} catch { return undefined; }
|
|
48
|
+
};
|
|
49
|
+
ctx.goals.create(agent, { objective: spec.goal.objective, maxGoalRounds: spec.goal.maxRounds });
|
|
50
|
+
const completion = Promise.withResolvers();
|
|
51
|
+
// Callers receive the promise after setup; avoid a premature rejection warning.
|
|
52
|
+
void completion.promise.catch(() => {});
|
|
53
|
+
const finish = result => {
|
|
54
|
+
if (finished) return completion.promise;
|
|
55
|
+
finished = true;
|
|
56
|
+
cleanup();
|
|
57
|
+
void (async () => {
|
|
58
|
+
try {
|
|
59
|
+
// Cancelling only this Agent stops queued goal continuations too.
|
|
60
|
+
agent.cancel({ kind: 'user' });
|
|
61
|
+
await agent.whenIdle();
|
|
62
|
+
if (lastText.trim()) {
|
|
63
|
+
try { writeRunTail(home, spec.triggerId, spec.runId, lastText); } catch { /* the caller still receives the result */ }
|
|
64
|
+
}
|
|
65
|
+
await ctx.sessions.flush(session);
|
|
66
|
+
await handle.dispose();
|
|
67
|
+
completion.resolve({ ...result, sessionId: session.id });
|
|
68
|
+
} catch (error) {
|
|
69
|
+
await handle.dispose().catch(() => {});
|
|
70
|
+
completion.reject(error);
|
|
71
|
+
} finally { unmark(); }
|
|
72
|
+
})();
|
|
73
|
+
return completion.promise;
|
|
74
|
+
};
|
|
75
|
+
const currentGoal = () => { try { return ctx.goals.get(agent); } catch { return undefined; } };
|
|
76
|
+
const numbers = () => ({ cost: runCost() ?? null, rounds: currentGoal()?.roundsStarted ?? null });
|
|
77
|
+
const observe = (event, callback) => disposers.push(ctx.on(event, callback));
|
|
78
|
+
observe('session/event', (subject, event) => {
|
|
79
|
+
if (subject.id !== session.id || finished) return;
|
|
80
|
+
if (event.type === 'assistant/message') {
|
|
81
|
+
const text = (event.data.message?.content ?? []).filter(block => block.type === 'text').map(block => block.text).join('');
|
|
82
|
+
if (text) lastText = text;
|
|
83
|
+
} else if (event.type === 'turn/end') {
|
|
84
|
+
const decision = decideStop({ goal: currentGoal(), costUsd: runCost(), limits: spec.limits, approvalsRejected });
|
|
85
|
+
if (decision.stop) void finish({ outcome: decision.outcome, reason: decision.reason ?? null, exitCode: decision.exitCode, ...numbers() });
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
observe('session/disposed', subject => {
|
|
89
|
+
if (subject.id === session.id) void finish({ outcome: 'failed', reason: 'model_error', exitCode: 1, ...numbers() });
|
|
90
|
+
});
|
|
91
|
+
const owns = candidate => {
|
|
92
|
+
const seen = new Set();
|
|
93
|
+
while (candidate && !seen.has(candidate.id)) {
|
|
94
|
+
if (candidate.id === agent.id) return true;
|
|
95
|
+
seen.add(candidate.id);
|
|
96
|
+
const parent = candidate.session?.header.parentSession;
|
|
97
|
+
candidate = parent ? ctx.agents.get(parent) : undefined;
|
|
98
|
+
}
|
|
99
|
+
return false;
|
|
100
|
+
};
|
|
101
|
+
observe('approval/request', (request, next) => {
|
|
102
|
+
if (!owns(request.agent)) return next();
|
|
103
|
+
approvalsRejected = true;
|
|
104
|
+
return 'rejected';
|
|
105
|
+
});
|
|
106
|
+
const cancel = () => finish({ outcome: 'failed', reason: 'interrupted', exitCode: 130, ...numbers() });
|
|
107
|
+
if (signal) {
|
|
108
|
+
const abort = () => { void cancel(); };
|
|
109
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
110
|
+
disposers.push(() => signal.removeEventListener('abort', abort));
|
|
111
|
+
if (signal.aborted) abort();
|
|
112
|
+
}
|
|
113
|
+
const seconds = spec.limits?.timeoutSeconds;
|
|
114
|
+
if (!finished && Number.isFinite(seconds) && seconds > 0) {
|
|
115
|
+
timer = setTimeout(() => { void finish({ outcome: 'timedout', reason: approvalsRejected ? 'approval_required' : 'timeout', exitCode: 124, ...numbers() }); }, seconds * 1000);
|
|
116
|
+
timer.unref();
|
|
117
|
+
}
|
|
118
|
+
if (!finished) agent.followup(createUserMessage({ content: [{ type: 'text', text: spec.prompt }], source: { kind: 'user' } }));
|
|
119
|
+
return { agent, done: completion.promise, cancel };
|
|
120
|
+
} catch (error) {
|
|
121
|
+
cleanup();
|
|
122
|
+
try { await handle.dispose(); } finally { unmark(); }
|
|
123
|
+
throw error;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// A trigger owns this binding, independently of any one run's outcome. Publish
|
|
2
|
+
// it only after the new session has been flushed, before delivering its input.
|
|
3
|
+
import { mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { dirname, join } from 'node:path';
|
|
5
|
+
import { randomUUID } from 'node:crypto';
|
|
6
|
+
|
|
7
|
+
export const bindingPath = (home, id) => join(home, 'triggers', 'sessions', `${id}.json`);
|
|
8
|
+
|
|
9
|
+
export function readSessionBinding(home, spec) {
|
|
10
|
+
let binding;
|
|
11
|
+
try { binding = JSON.parse(readFileSync(bindingPath(home, spec.triggerId), 'utf8')); }
|
|
12
|
+
catch (error) { if (error.code === 'ENOENT') return undefined; throw error; }
|
|
13
|
+
if (!binding || typeof binding.sessionId !== 'string' || !binding.sessionId || binding.triggerId !== spec.triggerId) {
|
|
14
|
+
throw new Error(`invalid session binding for ${spec.triggerId}`);
|
|
15
|
+
}
|
|
16
|
+
if (binding.workspace !== spec.workspace || binding.preset !== (spec.preset ?? 'dscode')) {
|
|
17
|
+
throw new Error(`persistent trigger ${spec.triggerId} is bound to another workspace or preset; use a new trigger id`);
|
|
18
|
+
}
|
|
19
|
+
return binding;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function writeSessionBinding(home, spec, sessionId) {
|
|
23
|
+
const path = bindingPath(home, spec.triggerId);
|
|
24
|
+
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
25
|
+
const scratch = `${path}.${randomUUID()}.tmp`;
|
|
26
|
+
writeFileSync(scratch, JSON.stringify({ triggerId: spec.triggerId, sessionId, workspace: spec.workspace, preset: spec.preset ?? 'dscode' }) + '\n', { mode: 0o600 });
|
|
27
|
+
renameSync(scratch, path);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Called under the CLI's trigger lease. A failed resume never erases history. */
|
|
31
|
+
export async function openTriggerSession(ctx, spec, { home, agentOptions, setup, signal }) {
|
|
32
|
+
const persistent = spec.session?.mode === 'persistent';
|
|
33
|
+
const binding = persistent ? readSessionBinding(home, spec) : undefined;
|
|
34
|
+
const handle = binding
|
|
35
|
+
? await ctx.agents.resume({ resumeSessionId: binding.sessionId, agentOptions, setup, signal })
|
|
36
|
+
: await ctx.agents.create({ sessionId: randomUUID(), meta: { cwd: spec.workspace, agentPreset: spec.preset ?? 'dscode' }, agentOptions, setup, signal });
|
|
37
|
+
try {
|
|
38
|
+
if (handle.agent.session.header.cwd !== spec.workspace) throw new Error('trigger session workspace does not match its definition');
|
|
39
|
+
if (persistent && !binding) {
|
|
40
|
+
await ctx.sessions.flush(handle.agent.session);
|
|
41
|
+
writeSessionBinding(home, spec, handle.agent.session.id);
|
|
42
|
+
}
|
|
43
|
+
return handle;
|
|
44
|
+
} catch (error) {
|
|
45
|
+
await handle.dispose();
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Private command exposed to sandboxed producers; it grants only emit access.
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { emitToSource } from './source-ingress.mjs';
|
|
4
|
+
|
|
5
|
+
try {
|
|
6
|
+
const [group, command, triggerId, ...args] = process.argv.slice(2);
|
|
7
|
+
if (group !== 'trigger' || command !== 'emit' || !triggerId) throw new Error('source scripts may use: dscode trigger emit ID --event-id ID --text TEXT (or --event FILE)');
|
|
8
|
+
let eventId, payload = {}, bodySet = false;
|
|
9
|
+
for (let i = 0; i < args.length; i += 2) {
|
|
10
|
+
const flag = args[i], value = args[i + 1];
|
|
11
|
+
if (value === undefined) throw new Error(`${flag} requires a value`);
|
|
12
|
+
if (flag === '--event-id') eventId = value;
|
|
13
|
+
else if (['--text', '--event'].includes(flag) && !bodySet) {
|
|
14
|
+
payload = flag === '--text' ? { source: 'cli', text: value } : JSON.parse(readFileSync(value === '-' ? 0 : value, 'utf8'));
|
|
15
|
+
bodySet = true;
|
|
16
|
+
} else throw new Error(`unsupported or repeated option ${flag}`);
|
|
17
|
+
}
|
|
18
|
+
console.log(JSON.stringify(await emitToSource({ triggerId, eventId, payload })));
|
|
19
|
+
} catch (error) { console.error(error.message); process.exitCode = 1; }
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// One guarded script attempt. The scheduler owns this guardian over IPC; losing
|
|
2
|
+
// that channel terminates the script process group, including ordinary children.
|
|
3
|
+
import { spawn } from 'node:child_process';
|
|
4
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync, realpathSync, lstatSync } from 'node:fs';
|
|
5
|
+
import { join, delimiter } from 'node:path';
|
|
6
|
+
import { setTimeout as sleep } from 'node:timers/promises';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
import { JobStore } from './jobs.mjs';
|
|
9
|
+
import { acquireTriggerLease } from './lease.mjs';
|
|
10
|
+
import { sandboxCommand, signalGroup } from './source-sandbox.mjs';
|
|
11
|
+
import { openSourceIngress } from './source-ingress.mjs';
|
|
12
|
+
|
|
13
|
+
export async function runSourceAttempt({ home, project, definition, sourceId }) {
|
|
14
|
+
const store = new JobStore(home);
|
|
15
|
+
const lease = await acquireTriggerLease(home, `source-${sourceId}`);
|
|
16
|
+
if (!lease) { store.close(); throw new Error('source lease is held by another process; inspect the previous source before restarting'); }
|
|
17
|
+
let child, ingress, scratch, timer, flush, stopped = false, timedOut = false, log = '', failure;
|
|
18
|
+
const stop = () => { stopped = true; if (child) signalGroup(child, 'SIGTERM'); };
|
|
19
|
+
process.once('disconnect', stop);
|
|
20
|
+
process.on('message', stop);
|
|
21
|
+
for (const signal of ['SIGTERM', 'SIGINT', 'SIGHUP']) process.once(signal, stop);
|
|
22
|
+
const persistLog = () => store.run('UPDATE sources SET log=? WHERE id=?', log, sourceId);
|
|
23
|
+
const collect = chunk => { log = (log + String(chunk)).slice(-32768); };
|
|
24
|
+
try {
|
|
25
|
+
const data = join(home, 'triggers', 'source-data', sourceId);
|
|
26
|
+
mkdirSync(data, { recursive: true, mode: 0o700 });
|
|
27
|
+
if (lstatSync(data).isSymbolicLink() || realpathSync(data) !== join(realpathSync(home), 'triggers', 'source-data', sourceId)) throw new Error('source state directory must not be redirected through symlinks');
|
|
28
|
+
scratch = mkdtempSync('/tmp/dscode-script-');
|
|
29
|
+
ingress = await openSourceIngress({ home, project, definition, store });
|
|
30
|
+
const bin = join(scratch, 'bin');
|
|
31
|
+
mkdirSync(bin);
|
|
32
|
+
// A tiny private CLI avoids launcher provisioning and writes to global state.
|
|
33
|
+
const client = fileURLToPath(new URL('./source-emit.mjs', import.meta.url));
|
|
34
|
+
const quote = value => "'" + value.replaceAll("'", "'\\''") + "'";
|
|
35
|
+
writeFileSync(join(bin, 'dscode'), `#!/bin/sh\nexec ${quote(process.execPath)} ${quote(client)} "$@"\n`, { mode: 0o700 });
|
|
36
|
+
const launch = sandboxCommand(definition.source.command, { workspace: definition.workspace, home, writable: [data, scratch], permission: definition.source.permission });
|
|
37
|
+
if (stopped || (process.send && !process.connected)) return;
|
|
38
|
+
const env = { ...process.env };
|
|
39
|
+
delete env.NODE_CHANNEL_FD; delete env.NODE_CHANNEL_SERIALIZATION_MODE;
|
|
40
|
+
child = spawn(launch.command, launch.args, {
|
|
41
|
+
cwd: definition.workspace, detached: true,
|
|
42
|
+
env: { ...env, PATH: bin + delimiter + (process.env.PATH ?? ''), TMPDIR: scratch, TMP: scratch, TEMP: scratch,
|
|
43
|
+
DSCODE_SOURCE_SOCKET: ingress.path, DSCODE_SOURCE_TOKEN: ingress.token, DSCODE_SOURCE_STATE: data, DSCODE_TRIGGER_ID: definition.id },
|
|
44
|
+
stdio: ['ignore', 'pipe', 'pipe', lease.fd],
|
|
45
|
+
});
|
|
46
|
+
child.stdout.on('data', collect); child.stderr.on('data', collect);
|
|
47
|
+
store.run("UPDATE sources SET status='running',pid=?,error=NULL WHERE id=?", child.pid ?? null, sourceId);
|
|
48
|
+
flush = setInterval(persistLog, 1000);
|
|
49
|
+
if (definition.source.mode === 'poll') timer = setTimeout(() => { timedOut = true; stop(); }, definition.source.timeoutSeconds * 1000);
|
|
50
|
+
// A bounded grace period also handles children ignoring SIGTERM.
|
|
51
|
+
const reap = setInterval(() => { if (stopped) signalGroup(child, 'SIGKILL'); }, 1000);
|
|
52
|
+
try {
|
|
53
|
+
const code = await new Promise((resolve, reject) => { child.once('error', reject); child.once('exit', (code, signal) => resolve(code ?? signal)); });
|
|
54
|
+
if (timedOut) throw new Error('script poll timed out');
|
|
55
|
+
if (!stopped && (code !== 0 || definition.source.mode === 'daemon')) throw new Error(`script exited (${code})`);
|
|
56
|
+
} finally { clearInterval(reap); }
|
|
57
|
+
} catch (error) { failure = error.message; collect(`\n${failure}\n`); }
|
|
58
|
+
finally {
|
|
59
|
+
clearTimeout(timer); clearInterval(flush);
|
|
60
|
+
if (child) { signalGroup(child, 'SIGTERM'); await sleep(100); signalGroup(child, 'SIGKILL'); }
|
|
61
|
+
await ingress?.close();
|
|
62
|
+
if (scratch) rmSync(scratch, { recursive: true, force: true });
|
|
63
|
+
const saved = store.one('SELECT * FROM sources WHERE id=?', sourceId);
|
|
64
|
+
const failures = failure ? (saved?.failures ?? 0) + 1 : 0;
|
|
65
|
+
const delay = failure ? Math.min(300000, 1000 * 2 ** Math.min(failures, 8)) : (definition.source.everySeconds ?? 1) * 1000;
|
|
66
|
+
persistLog();
|
|
67
|
+
store.run('UPDATE sources SET status=?,pid=NULL,error=?,failures=?,nextAt=? WHERE id=?', failure ? 'backoff' : 'stopped', failure ?? null, failures, Date.now() + delay, sourceId);
|
|
68
|
+
store.close(); lease.release();
|
|
69
|
+
process.off('disconnect', stop); process.off('message', stop);
|
|
70
|
+
for (const signal of ['SIGTERM', 'SIGINT', 'SIGHUP']) process.off(signal, stop);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
75
|
+
try { await runSourceAttempt(JSON.parse(process.argv[2])); }
|
|
76
|
+
catch (error) { console.error(error.message); process.exitCode = 1; }
|
|
77
|
+
if (process.connected) process.disconnect();
|
|
78
|
+
}
|