@vercel/factory 0.0.14 → 0.0.16
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/CHANGELOG.md +356 -0
- package/README.md +96 -2
- package/dist/agent-routes.d.mts +113 -0
- package/dist/agent-routes.mjs +91 -0
- package/dist/agent-routes.mjs.map +1 -0
- package/dist/api-contracts.d.mts +1768 -0
- package/dist/api-contracts.mjs +119 -0
- package/dist/api-contracts.mjs.map +1 -0
- package/dist/api-images.d.mts +30 -0
- package/dist/api-images.mjs +28 -0
- package/dist/api-images.mjs.map +1 -0
- package/dist/api-task-graph.mjs +57 -0
- package/dist/api-task-graph.mjs.map +1 -0
- package/dist/api.d.mts +111 -216
- package/dist/api.mjs +1063 -99
- package/dist/api.mjs.map +1 -1
- package/dist/approval-access.d.mts +39 -0
- package/dist/approval-access.mjs +62 -0
- package/dist/approval-access.mjs.map +1 -0
- package/dist/approval-contracts.d.mts +698 -0
- package/dist/approval-contracts.mjs +27 -0
- package/dist/approval-contracts.mjs.map +1 -0
- package/dist/approval.d.mts +49 -0
- package/dist/approval.mjs +39 -0
- package/dist/approval.mjs.map +1 -0
- package/dist/blob/artifacts.d.mts +37 -0
- package/dist/blob/artifacts.mjs +45 -0
- package/dist/blob/artifacts.mjs.map +1 -0
- package/dist/blob/index.d.mts +84 -12
- package/dist/blob/index.mjs +179 -45
- package/dist/blob/index.mjs.map +1 -1
- package/dist/budget.d.mts +115 -0
- package/dist/budget.mjs +147 -0
- package/dist/budget.mjs.map +1 -0
- package/dist/budgets.d.mts +4 -0
- package/dist/budgets.mjs +4 -0
- package/dist/build-factory.d.mts +7 -4
- package/dist/build-factory.mjs +2 -1
- package/dist/build-factory.mjs.map +1 -1
- package/dist/change-verification/brief.d.mts +9 -0
- package/dist/change-verification/brief.mjs +13 -0
- package/dist/change-verification/brief.mjs.map +1 -0
- package/dist/change-verification/dispatch.d.mts +37 -0
- package/dist/change-verification/dispatch.mjs +83 -0
- package/dist/change-verification/dispatch.mjs.map +1 -0
- package/dist/change-verification/eve-tool.d.mts +61 -0
- package/dist/change-verification/eve-tool.mjs +89 -0
- package/dist/change-verification/eve-tool.mjs.map +1 -0
- package/dist/change-verification/result.mjs +130 -0
- package/dist/change-verification/result.mjs.map +1 -0
- package/dist/changes/eve-record-change.d.mts +56 -0
- package/dist/changes/eve-record-change.mjs +131 -0
- package/dist/changes/eve-record-change.mjs.map +1 -0
- package/dist/changes/presentation.d.mts +14 -0
- package/dist/changes.d.mts +24 -0
- package/dist/changes.mjs +29 -0
- package/dist/changes.mjs.map +1 -0
- package/dist/client-events.d.mts +25 -0
- package/dist/client-events.mjs +45 -0
- package/dist/client-events.mjs.map +1 -0
- package/dist/client-stream.d.mts +28 -0
- package/dist/client-stream.mjs +210 -0
- package/dist/client-stream.mjs.map +1 -0
- package/dist/client-transcript.d.mts +41 -0
- package/dist/client-transcript.mjs +233 -0
- package/dist/client-transcript.mjs.map +1 -0
- package/dist/client.d.mts +343 -0
- package/dist/client.mjs +366 -0
- package/dist/client.mjs.map +1 -0
- package/dist/code-review/contracts.d.mts +27 -0
- package/dist/code-review/eve-post-review.d.mts +70 -0
- package/dist/code-review/eve-post-review.mjs +195 -0
- package/dist/code-review/eve-post-review.mjs.map +1 -0
- package/dist/code-review/eve-review-comments.d.mts +75 -0
- package/dist/code-review/eve-review-comments.mjs +193 -0
- package/dist/code-review/eve-review-comments.mjs.map +1 -0
- package/dist/code-review/github-contracts.d.mts +10 -0
- package/dist/code-review/github-reporter.d.mts +46 -0
- package/dist/code-review/github-reporter.mjs +262 -0
- package/dist/code-review/github-reporter.mjs.map +1 -0
- package/dist/code-review/preparation.d.mts +34 -0
- package/dist/code-review/preparation.mjs +23 -0
- package/dist/code-review/preparation.mjs.map +1 -0
- package/dist/code-review/stall.d.mts +17 -0
- package/dist/code-review/stall.mjs +30 -0
- package/dist/code-review/stall.mjs.map +1 -0
- package/dist/code-review.d.mts +7 -0
- package/dist/code-review.mjs +5 -0
- package/dist/communication.d.mts +9 -0
- package/dist/communication.mjs +9 -0
- package/dist/connectors/github.d.mts +26 -0
- package/dist/connectors/github.mjs +55 -0
- package/dist/connectors/github.mjs.map +1 -0
- package/dist/connectors/index.d.mts +35 -3
- package/dist/connectors/index.mjs +3 -52
- package/dist/connectors/index.mjs.map +1 -1
- package/dist/connectors/outbox.d.mts +34 -0
- package/dist/connectors/outbox.mjs +50 -0
- package/dist/connectors/outbox.mjs.map +1 -0
- package/dist/connectors/slack-approval-blocks.mjs +136 -0
- package/dist/connectors/slack-approval-blocks.mjs.map +1 -0
- package/dist/connectors/slack-approvals.d.mts +29 -0
- package/dist/connectors/slack-approvals.mjs +346 -0
- package/dist/connectors/slack-approvals.mjs.map +1 -0
- package/dist/connectors/slack.d.mts +35 -52
- package/dist/connectors/slack.mjs +13 -85
- package/dist/connectors/slack.mjs.map +1 -1
- package/dist/deepsec/eve-tool.d.mts +110 -0
- package/dist/deepsec/eve-tool.mjs +166 -0
- package/dist/deepsec/eve-tool.mjs.map +1 -0
- package/dist/deepsec/finding.d.mts +84 -0
- package/dist/deepsec/finding.mjs +56 -0
- package/dist/deepsec/finding.mjs.map +1 -0
- package/dist/deepsec.d.mts +3 -0
- package/dist/delivery-metrics/metrics.d.mts +93 -0
- package/dist/delivery-metrics/metrics.mjs +200 -0
- package/dist/delivery-metrics/metrics.mjs.map +1 -0
- package/dist/delivery-metrics.d.mts +2 -0
- package/dist/delivery-metrics.mjs +2 -0
- package/dist/dispatch.d.mts +152 -0
- package/dist/dispatch.mjs +199 -0
- package/dist/dispatch.mjs.map +1 -0
- package/dist/errors.d.mts +15 -0
- package/dist/errors.mjs +38 -0
- package/dist/errors.mjs.map +1 -0
- package/dist/eve/index.d.mts +209 -1277
- package/dist/eve/index.mjs +312 -1073
- package/dist/eve/index.mjs.map +1 -1
- package/dist/eve/invoke.d.mts +144 -0
- package/dist/eve/invoke.mjs +254 -0
- package/dist/eve/invoke.mjs.map +1 -0
- package/dist/eve/session-client.d.mts +186 -0
- package/dist/eve/session-client.mjs +245 -0
- package/dist/eve/session-client.mjs.map +1 -0
- package/dist/eve/task-execution.d.mts +380 -0
- package/dist/eve/task-execution.mjs +73 -0
- package/dist/eve/task-execution.mjs.map +1 -0
- package/dist/eve/task-session.d.mts +129 -0
- package/dist/eve/task-session.mjs +245 -0
- package/dist/eve/task-session.mjs.map +1 -0
- package/dist/eve/transcript.d.mts +37 -0
- package/dist/eve/transcript.mjs +69 -0
- package/dist/eve/transcript.mjs.map +1 -0
- package/dist/execution-public.d.mts +8 -0
- package/dist/execution-public.mjs +7 -0
- package/dist/execution.d.mts +183 -0
- package/dist/execution.mjs +223 -0
- package/dist/execution.mjs.map +1 -0
- package/dist/finding-remediation/admission.d.mts +53 -0
- package/dist/finding-remediation/admission.mjs +90 -0
- package/dist/finding-remediation/admission.mjs.map +1 -0
- package/dist/finding-remediation/brief.d.mts +14 -0
- package/dist/finding-remediation/brief.mjs +25 -0
- package/dist/finding-remediation/brief.mjs.map +1 -0
- package/dist/finding-remediation/eve-tool.d.mts +29 -0
- package/dist/finding-remediation/eve-tool.mjs +34 -0
- package/dist/finding-remediation/eve-tool.mjs.map +1 -0
- package/dist/findings/fingerprint.d.mts +14 -0
- package/dist/findings/fingerprint.mjs +28 -0
- package/dist/findings/fingerprint.mjs.map +1 -0
- package/dist/findings.d.mts +7 -0
- package/dist/findings.mjs +6 -0
- package/dist/github-app.d.mts +54 -0
- package/dist/github-app.mjs +121 -0
- package/dist/github-app.mjs.map +1 -0
- package/dist/github-publication.d.mts +199 -0
- package/dist/github-publication.mjs +442 -0
- package/dist/github-publication.mjs.map +1 -0
- package/dist/github-transfer.d.mts +81 -0
- package/dist/github-transfer.mjs +273 -0
- package/dist/github-transfer.mjs.map +1 -0
- package/dist/github.d.mts +446 -2
- package/dist/github.mjs +357 -56
- package/dist/github.mjs.map +1 -1
- package/dist/image-input.d.mts +51 -0
- package/dist/image-input.mjs +63 -0
- package/dist/image-input.mjs.map +1 -0
- package/dist/inbox-activity.d.mts +53 -0
- package/dist/inbox-activity.mjs +41 -0
- package/dist/inbox-activity.mjs.map +1 -0
- package/dist/index.d.mts +16 -484
- package/dist/index.mjs +15 -522
- package/dist/intake-contracts.d.mts +55 -0
- package/dist/intake-contracts.mjs +33 -0
- package/dist/intake-contracts.mjs.map +1 -0
- package/dist/integrations/deepsec.d.mts +5 -0
- package/dist/integrations/deepsec.mjs +3 -0
- package/dist/integrations/github.d.mts +11 -0
- package/dist/integrations/github.mjs +9 -0
- package/dist/integrations/slack.d.mts +5 -0
- package/dist/integrations/slack.mjs +5 -0
- package/dist/integrations/vercel.d.mts +5 -0
- package/dist/integrations/vercel.mjs +4 -0
- package/dist/merge-resolution/contracts.d.mts +17 -0
- package/dist/merge-resolution/contracts.mjs +19 -0
- package/dist/merge-resolution/contracts.mjs.map +1 -0
- package/dist/merge-resolution/eve-tools.d.mts +47 -0
- package/dist/merge-resolution/eve-tools.mjs +122 -0
- package/dist/merge-resolution/eve-tools.mjs.map +1 -0
- package/dist/model-settings.d.mts +41 -0
- package/dist/model-settings.mjs +35 -0
- package/dist/model-settings.mjs.map +1 -0
- package/dist/multi-repository-changes/coordination.d.mts +49 -0
- package/dist/multi-repository-changes/coordination.mjs +115 -0
- package/dist/multi-repository-changes/coordination.mjs.map +1 -0
- package/dist/planning/cancellation.mjs +82 -0
- package/dist/planning/cancellation.mjs.map +1 -0
- package/dist/planning/configuration.d.mts +23 -0
- package/dist/planning/configuration.mjs +56 -0
- package/dist/planning/configuration.mjs.map +1 -0
- package/dist/planning/contracts.d.mts +136 -0
- package/dist/planning/contracts.mjs +42 -0
- package/dist/planning/contracts.mjs.map +1 -0
- package/dist/planning/dependencies.mjs +44 -0
- package/dist/planning/dependencies.mjs.map +1 -0
- package/dist/planning/effects.mjs +122 -0
- package/dist/planning/effects.mjs.map +1 -0
- package/dist/planning/reconcile.mjs +279 -0
- package/dist/planning/reconcile.mjs.map +1 -0
- package/dist/planning/runtime.d.mts +27 -0
- package/dist/planning/runtime.mjs +25 -0
- package/dist/planning/runtime.mjs.map +1 -0
- package/dist/planning/state.mjs +92 -0
- package/dist/planning/state.mjs.map +1 -0
- package/dist/planning/turns.mjs +262 -0
- package/dist/planning/turns.mjs.map +1 -0
- package/dist/planning/validation.mjs +106 -0
- package/dist/planning/validation.mjs.map +1 -0
- package/dist/postgres/index.d.mts +48 -1
- package/dist/postgres/index.mjs +45 -3
- package/dist/postgres/index.mjs.map +1 -1
- package/dist/presets/software-development/approval.d.mts +14 -0
- package/dist/presets/software-development/approval.mjs +21 -0
- package/dist/presets/software-development/approval.mjs.map +1 -0
- package/dist/presets/software-development/deepsec-brief.d.mts +8 -0
- package/dist/presets/software-development/deepsec-brief.mjs +14 -0
- package/dist/presets/software-development/deepsec-brief.mjs.map +1 -0
- package/dist/presets/software-development/dispatch.d.mts +56 -0
- package/dist/presets/software-development/dispatch.mjs +67 -0
- package/dist/presets/software-development/dispatch.mjs.map +1 -0
- package/dist/presets/software-development/eve-delegation-policy.d.mts +7 -0
- package/dist/presets/software-development/eve-delegation-policy.mjs +34 -0
- package/dist/presets/software-development/eve-delegation-policy.mjs.map +1 -0
- package/dist/presets/software-development/eve.d.mts +18 -0
- package/dist/presets/software-development/eve.mjs +19 -0
- package/dist/presets/software-development/eve.mjs.map +1 -0
- package/dist/presets/software-development/finish-task-policy.d.mts +7 -0
- package/dist/presets/software-development/finish-task-policy.mjs +9 -0
- package/dist/presets/software-development/finish-task-policy.mjs.map +1 -0
- package/dist/presets/software-development/recovery.d.mts +32 -0
- package/dist/presets/software-development/recovery.mjs +120 -0
- package/dist/presets/software-development/recovery.mjs.map +1 -0
- package/dist/presets/software-development/task-communication.d.mts +24 -0
- package/dist/presets/software-development/task-communication.mjs +157 -0
- package/dist/presets/software-development/task-communication.mjs.map +1 -0
- package/dist/presets/software-development.d.mts +10 -0
- package/dist/presets/software-development.mjs +9 -0
- package/dist/pull-requests/contracts.d.mts +72 -0
- package/dist/pull-requests/eve-fetch.d.mts +57 -0
- package/dist/pull-requests/eve-fetch.mjs +151 -0
- package/dist/pull-requests/eve-fetch.mjs.map +1 -0
- package/dist/pull-requests/github-label-manager.d.mts +27 -0
- package/dist/pull-requests/github-label-manager.mjs +40 -0
- package/dist/pull-requests/github-label-manager.mjs.map +1 -0
- package/dist/pull-requests/github-publisher.d.mts +67 -0
- package/dist/pull-requests/github-publisher.mjs +405 -0
- package/dist/pull-requests/github-publisher.mjs.map +1 -0
- package/dist/pull-requests.d.mts +6 -0
- package/dist/pull-requests.mjs +4 -0
- package/dist/receipts.d.mts +4 -0
- package/dist/receipts.mjs +3 -0
- package/dist/repositories.d.mts +18 -0
- package/dist/repositories.mjs +33 -0
- package/dist/repositories.mjs.map +1 -0
- package/dist/sandbox/command.d.mts +18 -0
- package/dist/sandbox/execution-abort.d.mts +16 -0
- package/dist/sandbox/execution-abort.mjs +49 -0
- package/dist/sandbox/execution-abort.mjs.map +1 -0
- package/dist/sandbox/execution-environment.d.mts +25 -0
- package/dist/sandbox/execution-run-ledger.d.mts +115 -0
- package/dist/sandbox/execution-run-ledger.mjs +209 -0
- package/dist/sandbox/execution-run-ledger.mjs.map +1 -0
- package/dist/sandbox/execution.d.mts +54 -0
- package/dist/sandbox/execution.mjs +228 -0
- package/dist/sandbox/execution.mjs.map +1 -0
- package/dist/sandbox/harness-runner.d.mts +79 -0
- package/dist/sandbox/harness-runner.mjs +162 -0
- package/dist/sandbox/harness-runner.mjs.map +1 -0
- package/dist/sandbox/index.d.mts +10 -0
- package/dist/sandbox/index.mjs +7 -0
- package/dist/sandbox/task-environment.d.mts +50 -0
- package/dist/sandbox/task-environment.mjs +140 -0
- package/dist/sandbox/task-environment.mjs.map +1 -0
- package/dist/sandbox/workspace.d.mts +29 -0
- package/dist/sandbox/workspace.mjs +28 -0
- package/dist/sandbox/workspace.mjs.map +1 -0
- package/dist/schema/agent-route.d.mts +40 -0
- package/dist/schema/agent-route.mjs +33 -0
- package/dist/schema/agent-route.mjs.map +1 -0
- package/dist/schema/answer.d.mts +23 -0
- package/dist/schema/answer.mjs +21 -0
- package/dist/schema/answer.mjs.map +1 -0
- package/dist/schema/budget.d.mts +38 -0
- package/dist/schema/budget.mjs +30 -0
- package/dist/schema/budget.mjs.map +1 -0
- package/dist/schema/catalog.mjs +72 -0
- package/dist/schema/catalog.mjs.map +1 -0
- package/dist/schema/change.d.mts +117 -0
- package/dist/schema/change.mjs +68 -0
- package/dist/schema/change.mjs.map +1 -0
- package/dist/schema/common.d.mts +33 -0
- package/dist/{id-BLIkCVue.mjs → schema/common.mjs} +7 -16
- package/dist/schema/common.mjs.map +1 -0
- package/dist/schema/communication.d.mts +52 -0
- package/dist/{communication-Kd9nU5RP.mjs → schema/communication.mjs} +8 -3
- package/dist/schema/communication.mjs.map +1 -0
- package/dist/schema/execution.d.mts +101 -0
- package/dist/schema/execution.mjs +86 -0
- package/dist/schema/execution.mjs.map +1 -0
- package/dist/schema/factory-config.d.mts +122 -0
- package/dist/schema/factory-config.mjs +90 -0
- package/dist/schema/factory-config.mjs.map +1 -0
- package/dist/schema/finding.d.mts +125 -0
- package/dist/schema/finding.mjs +67 -0
- package/dist/schema/finding.mjs.map +1 -0
- package/dist/schema/id.d.mts +43 -0
- package/dist/schema/id.mjs +32 -0
- package/dist/schema/id.mjs.map +1 -0
- package/dist/schema/outbox.d.mts +240 -0
- package/dist/schema/outbox.mjs +53 -0
- package/dist/schema/outbox.mjs.map +1 -0
- package/dist/schema/question-route.mjs +16 -0
- package/dist/schema/question-route.mjs.map +1 -0
- package/dist/schema/receipt.d.mts +81 -0
- package/dist/schema/receipt.mjs +94 -0
- package/dist/schema/receipt.mjs.map +1 -0
- package/dist/schema/repository.d.mts +30 -0
- package/dist/schema/repository.mjs +29 -0
- package/dist/schema/repository.mjs.map +1 -0
- package/dist/schema/session.d.mts +136 -0
- package/dist/schema/session.mjs +54 -0
- package/dist/schema/session.mjs.map +1 -0
- package/dist/schema/signal.d.mts +105 -0
- package/dist/schema/signal.mjs +81 -0
- package/dist/schema/signal.mjs.map +1 -0
- package/dist/schema/slack-pr-notifications.d.mts +12 -0
- package/dist/schema/slack-pr-notifications.mjs +11 -0
- package/dist/schema/slack-pr-notifications.mjs.map +1 -0
- package/dist/schema/stored-session.mjs +10 -0
- package/dist/schema/stored-session.mjs.map +1 -0
- package/dist/schema/stored-task.mjs +20 -0
- package/dist/schema/stored-task.mjs.map +1 -0
- package/dist/schema/task-effect.d.mts +39 -0
- package/dist/schema/task-effect.mjs +29 -0
- package/dist/schema/task-effect.mjs.map +1 -0
- package/dist/schema/task-graph.d.mts +3173 -0
- package/dist/schema/task-graph.mjs +97 -0
- package/dist/schema/task-graph.mjs.map +1 -0
- package/dist/schema/task-message.d.mts +254 -0
- package/dist/schema/task-message.mjs +189 -0
- package/dist/schema/task-message.mjs.map +1 -0
- package/dist/schema/task.d.mts +273 -0
- package/dist/schema/task.mjs +200 -0
- package/dist/schema/task.mjs.map +1 -0
- package/dist/schema/transcript.d.mts +176 -0
- package/dist/schema/transcript.mjs +87 -0
- package/dist/schema/transcript.mjs.map +1 -0
- package/dist/schema/transitions.d.mts +50 -0
- package/dist/schema/transitions.mjs +303 -0
- package/dist/schema/transitions.mjs.map +1 -0
- package/dist/schema/work.d.mts +150 -0
- package/dist/schema/work.mjs +102 -0
- package/dist/schema/work.mjs.map +1 -0
- package/dist/session-previews.d.mts +76 -0
- package/dist/session-previews.mjs +55 -0
- package/dist/session-previews.mjs.map +1 -0
- package/dist/session-review.d.mts +120 -0
- package/dist/session-review.mjs +79 -0
- package/dist/session-review.mjs.map +1 -0
- package/dist/signal-triage.d.mts +20 -0
- package/dist/signal-triage.mjs +85 -0
- package/dist/signal-triage.mjs.map +1 -0
- package/dist/signals.d.mts +20 -0
- package/dist/signals.mjs +19 -0
- package/dist/signals.mjs.map +1 -0
- package/dist/stall.d.mts +41 -0
- package/dist/stall.mjs +83 -0
- package/dist/stall.mjs.map +1 -0
- package/dist/storage.d.mts +4 -0
- package/dist/storage.mjs +4 -0
- package/dist/store/driver.d.mts +65 -0
- package/dist/store/driver.mjs +42 -0
- package/dist/store/driver.mjs.map +1 -0
- package/dist/store/engine.d.mts +683 -0
- package/dist/store/engine.mjs +2309 -0
- package/dist/store/engine.mjs.map +1 -0
- package/dist/store/memory.d.mts +24 -0
- package/dist/store/memory.mjs +69 -0
- package/dist/store/memory.mjs.map +1 -0
- package/dist/store/slack-pr-notifications.d.mts +44 -0
- package/dist/store/slack-pr-notifications.mjs +121 -0
- package/dist/store/slack-pr-notifications.mjs.map +1 -0
- package/dist/store/task-effects.d.mts +38 -0
- package/dist/store/task-effects.mjs +118 -0
- package/dist/store/task-effects.mjs.map +1 -0
- package/dist/store/task-graphs.d.mts +60 -0
- package/dist/store/task-graphs.mjs +212 -0
- package/dist/store/task-graphs.mjs.map +1 -0
- package/dist/store/task-messages.d.mts +136 -0
- package/dist/store/task-messages.mjs +475 -0
- package/dist/store/task-messages.mjs.map +1 -0
- package/dist/store/task-work.d.mts +180 -0
- package/dist/store/task-work.mjs +137 -0
- package/dist/store/task-work.mjs.map +1 -0
- package/dist/store/work-notifications.d.mts +19 -0
- package/dist/store/work-notifications.mjs +48 -0
- package/dist/store/work-notifications.mjs.map +1 -0
- package/dist/sweep.d.mts +79 -0
- package/dist/sweep.mjs +171 -0
- package/dist/sweep.mjs.map +1 -0
- package/dist/task-communication.d.mts +35 -0
- package/dist/task-communication.mjs +47 -0
- package/dist/task-communication.mjs.map +1 -0
- package/dist/task-graph-view.d.mts +267 -0
- package/dist/task-graph-view.mjs +17 -0
- package/dist/task-graph-view.mjs.map +1 -0
- package/dist/task-message-processing.d.mts +60 -0
- package/dist/task-message-processing.mjs +200 -0
- package/dist/task-message-processing.mjs.map +1 -0
- package/dist/task-protocols.d.mts +80 -0
- package/dist/task-protocols.mjs +47 -0
- package/dist/task-protocols.mjs.map +1 -0
- package/dist/tasks.d.mts +17 -0
- package/dist/tasks.mjs +17 -0
- package/dist/vercel-git.d.mts +53 -0
- package/dist/vercel-git.mjs +344 -0
- package/dist/vercel-git.mjs.map +1 -0
- package/dist/vercel-github-api.d.mts +103 -0
- package/dist/vercel-github-api.mjs +363 -0
- package/dist/vercel-github-api.mjs.map +1 -0
- package/dist/vercel.d.mts +34 -2
- package/dist/vercel.mjs +49 -3
- package/dist/vercel.mjs.map +1 -1
- package/dist/work-triage/eve-tool.d.mts +29 -0
- package/dist/work-triage/eve-tool.mjs +32 -0
- package/dist/work-triage/eve-tool.mjs.map +1 -0
- package/dist/work-triage/find-existing-work.d.mts +32 -0
- package/dist/work-triage/find-existing-work.mjs +31 -0
- package/dist/work-triage/find-existing-work.mjs.map +1 -0
- package/dist/work-triage/github.d.mts +15 -0
- package/dist/work-triage/github.mjs +14 -0
- package/dist/work-triage/github.mjs.map +1 -0
- package/dist/work-triage.d.mts +4 -0
- package/dist/work-triage.mjs +3 -0
- package/dist/workflows.d.mts +146 -0
- package/dist/workflows.mjs +139 -0
- package/dist/workflows.mjs.map +1 -0
- package/dist/workspace-diff-assembly.d.mts +33 -0
- package/dist/workspace-diff-assembly.mjs +95 -0
- package/dist/workspace-diff-assembly.mjs.map +1 -0
- package/dist/workspace-diff.d.mts +90 -0
- package/dist/workspace-diff.mjs +61 -0
- package/dist/workspace-diff.mjs.map +1 -0
- package/dist/workspace-files-git.d.mts +15 -0
- package/dist/workspace-files-git.mjs +61 -0
- package/dist/workspace-files-git.mjs.map +1 -0
- package/dist/workspace-files.d.mts +107 -0
- package/dist/workspace-files.mjs +74 -0
- package/dist/workspace-files.mjs.map +1 -0
- package/dist/workspace-snapshot.d.mts +26 -0
- package/dist/workspace-snapshot.mjs +80 -0
- package/dist/workspace-snapshot.mjs.map +1 -0
- package/docs/getting-started.md +104 -0
- package/docs/index.md +100 -0
- package/docs/recipes/cancellation.md +215 -0
- package/docs/recipes/custom-workflow.md +153 -0
- package/docs/recipes/dependent-tasks.md +207 -0
- package/docs/recipes/eve-agent.md +277 -0
- package/docs/recipes/human-input.md +204 -0
- package/docs/recipes/persistence-recovery.md +268 -0
- package/docs/recipes/retry-recovery.md +241 -0
- package/docs/recipes/task-messaging.md +215 -0
- package/docs/recipes/typed-eve-result.md +161 -0
- package/docs/runtime-integration.md +137 -0
- package/package.json +142 -46
- package/dist/approval-JX2Px9IC.mjs +0 -132
- package/dist/approval-JX2Px9IC.mjs.map +0 -1
- package/dist/budget-DzSiIA4t.mjs +0 -42
- package/dist/budget-DzSiIA4t.mjs.map +0 -1
- package/dist/change-D2U6v7Ek.mjs +0 -554
- package/dist/change-D2U6v7Ek.mjs.map +0 -1
- package/dist/communication-Kd9nU5RP.mjs.map +0 -1
- package/dist/coordination-Cq08hb1l.mjs +0 -76
- package/dist/coordination-Cq08hb1l.mjs.map +0 -1
- package/dist/dispatch-BaZS3PT8.d.mts +0 -76
- package/dist/driver-BC0Mt5KG.d.mts +0 -34
- package/dist/driver-Bbd25rNM.mjs +0 -23
- package/dist/driver-Bbd25rNM.mjs.map +0 -1
- package/dist/engine-DFHU2Pe-.d.mts +0 -781
- package/dist/engine-WaUM2cGA.mjs +0 -1068
- package/dist/engine-WaUM2cGA.mjs.map +0 -1
- package/dist/factory-config-BoRXOw43.mjs +0 -50
- package/dist/factory-config-BoRXOw43.mjs.map +0 -1
- package/dist/factory-config-D2yU3G0c.d.mts +0 -58
- package/dist/github-C5HNBN6p.d.mts +0 -279
- package/dist/id-BLIkCVue.mjs.map +0 -1
- package/dist/index-Bw6hggoH.d.mts +0 -40
- package/dist/index.mjs.map +0 -1
- package/dist/task-CcOyOJ3k.mjs +0 -154
- package/dist/task-CcOyOJ3k.mjs.map +0 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import "
|
|
1
|
+
import { repositorySlugSchema } from "../schema/repository.mjs";
|
|
2
|
+
import { communicationEventSchema } from "../schema/communication.mjs";
|
|
3
|
+
import { createSlackApprovals } from "./slack-approvals.mjs";
|
|
3
4
|
import { createHash } from "node:crypto";
|
|
4
5
|
//#region src/connectors/slack.ts
|
|
5
6
|
const slackOutcomeLabels = {
|
|
@@ -7,6 +8,7 @@ const slackOutcomeLabels = {
|
|
|
7
8
|
started: "Work started",
|
|
8
9
|
succeeded: "Work completed",
|
|
9
10
|
failed: "Work failed",
|
|
11
|
+
cancelled: "Work cancelled",
|
|
10
12
|
needs_human: "Human action required",
|
|
11
13
|
question: "Question requires an answer",
|
|
12
14
|
digest: "Factory digest",
|
|
@@ -21,6 +23,8 @@ const slackDetailFields = [
|
|
|
21
23
|
"summary",
|
|
22
24
|
"question",
|
|
23
25
|
"requestedAction",
|
|
26
|
+
"reference",
|
|
27
|
+
"answerLocation",
|
|
24
28
|
"reason",
|
|
25
29
|
"url"
|
|
26
30
|
];
|
|
@@ -29,7 +33,8 @@ const slackTextLimit = 12e3;
|
|
|
29
33
|
const slackOwnerOutcomes = /* @__PURE__ */ new Set([
|
|
30
34
|
"digest",
|
|
31
35
|
"budget_warning",
|
|
32
|
-
"budget_exhausted"
|
|
36
|
+
"budget_exhausted",
|
|
37
|
+
"needs_human"
|
|
33
38
|
]);
|
|
34
39
|
function escapeSlackText(value) {
|
|
35
40
|
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
@@ -43,21 +48,6 @@ function slackLink(url, label) {
|
|
|
43
48
|
return null;
|
|
44
49
|
}
|
|
45
50
|
}
|
|
46
|
-
/** Resolve an explicit pull-request reference after the repository has been selected. */
|
|
47
|
-
function selectSlackPullRequest(text, repositorySlug) {
|
|
48
|
-
const escapedSlug = repositorySlug.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
49
|
-
const references = [...text.matchAll(new RegExp(`https://github\\.com/${escapedSlug}/pull/(\\d+)`, "giu")), ...text.matchAll(/\b(?:pr|pull\s+request)\s*#?\s*(\d+)\b/giu)].flatMap((match) => {
|
|
50
|
-
const value = Number(match[1]);
|
|
51
|
-
return Number.isSafeInteger(value) && value > 0 ? [value] : [];
|
|
52
|
-
});
|
|
53
|
-
const distinct = [...new Set(references)];
|
|
54
|
-
if (distinct.length === 0) return { status: "none" };
|
|
55
|
-
if (distinct.length > 1) return { status: "ambiguous" };
|
|
56
|
-
return {
|
|
57
|
-
status: "selected",
|
|
58
|
-
pullRequestNumber: distinct[0]
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
51
|
/** Parse Eve's Slack continuation token into its channel and thread target. */
|
|
62
52
|
function parseSlackReplyAddress(address) {
|
|
63
53
|
const separator = address.indexOf(":");
|
|
@@ -93,8 +83,9 @@ function renderSlackCommunicationEvent(input) {
|
|
|
93
83
|
if (!Array.isArray(value)) return [];
|
|
94
84
|
return value.flatMap((item) => typeof item === "string" && item.trim() !== "" ? [`• ${escapeSlackText(item.trim())}`] : []);
|
|
95
85
|
});
|
|
86
|
+
const hasContent = detailLines.length > 0 || lists.length > 0;
|
|
96
87
|
const text = [
|
|
97
|
-
headline,
|
|
88
|
+
...event.outcome === "succeeded" && hasContent ? [] : [headline],
|
|
98
89
|
...detailLines,
|
|
99
90
|
...lists.length === 0 ? [] : [lists.join("\n")]
|
|
100
91
|
].join("\n\n");
|
|
@@ -129,6 +120,7 @@ function createSlackCommunicationAdapter(options) {
|
|
|
129
120
|
text: render(event),
|
|
130
121
|
target
|
|
131
122
|
});
|
|
123
|
+
await options.onDelivered?.(event);
|
|
132
124
|
}
|
|
133
125
|
});
|
|
134
126
|
}
|
|
@@ -137,7 +129,7 @@ function normalizeSlackMessage(event) {
|
|
|
137
129
|
return {
|
|
138
130
|
source: "slack",
|
|
139
131
|
deliveryId: `${event.teamId}:${event.channelId}:${event.messageTs}`,
|
|
140
|
-
repositorySlugs: [event.repositorySlugs[0].toLowerCase(), ...event.repositorySlugs.slice(1).map((slug) => slug.toLowerCase())],
|
|
132
|
+
repositorySlugs: [repositorySlugSchema.parse(event.repositorySlugs[0].toLowerCase()), ...event.repositorySlugs.slice(1).map((slug) => repositorySlugSchema.parse(slug.toLowerCase()))],
|
|
141
133
|
replyTo: {
|
|
142
134
|
channel: "slack",
|
|
143
135
|
address: event.replyAddress
|
|
@@ -153,71 +145,7 @@ function normalizeSlackMessage(event) {
|
|
|
153
145
|
payload: event.payload
|
|
154
146
|
};
|
|
155
147
|
}
|
|
156
|
-
function escapedPattern(value) {
|
|
157
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
158
|
-
}
|
|
159
|
-
function isDelimitedTextMentioned(text, value) {
|
|
160
|
-
return new RegExp(`(^|[^a-z0-9._/-])${escapedPattern(value)}($|[^a-z0-9._/-])`, "i").test(text);
|
|
161
|
-
}
|
|
162
|
-
function isRepositoryReferenceMentioned(text, slug) {
|
|
163
|
-
if (isDelimitedTextMentioned(text, slug)) return true;
|
|
164
|
-
return new RegExp(`https?://github\\.com/${escapedPattern(slug)}(?:\\.git)?/?(?=$|[\\s?#|>)\\],.!])`, "i").test(text);
|
|
165
|
-
}
|
|
166
|
-
function repositoryName(slug) {
|
|
167
|
-
return slug.slice(slug.lastIndexOf("/") + 1);
|
|
168
|
-
}
|
|
169
|
-
function resolveSlackRepositoryMentions(text, repositories) {
|
|
170
|
-
const normalized = [...new Set(repositories.map((slug) => slug.toLowerCase()))];
|
|
171
|
-
const entries = normalized.map((slug) => ({
|
|
172
|
-
slug,
|
|
173
|
-
name: repositoryName(slug)
|
|
174
|
-
}));
|
|
175
|
-
const counts = /* @__PURE__ */ new Map();
|
|
176
|
-
for (const { name } of entries) counts.set(name, (counts.get(name) ?? 0) + 1);
|
|
177
|
-
const mentionedNames = new Set([...counts.keys()].filter((name) => isDelimitedTextMentioned(text, name)));
|
|
178
|
-
const matches = new Set(entries.flatMap(({ slug, name }) => isRepositoryReferenceMentioned(text, slug) || counts.get(name) === 1 && mentionedNames.has(name) ? [slug] : []));
|
|
179
|
-
const ambiguous = new Set(entries.flatMap(({ slug, name }) => counts.get(name) !== 1 && mentionedNames.has(name) ? [slug] : []));
|
|
180
|
-
return {
|
|
181
|
-
repositories: normalized,
|
|
182
|
-
matches: normalized.filter((slug) => matches.has(slug)),
|
|
183
|
-
ambiguous: normalized.filter((slug) => ambiguous.has(slug)),
|
|
184
|
-
candidates: normalized.filter((slug) => matches.has(slug) || ambiguous.has(slug))
|
|
185
|
-
};
|
|
186
|
-
}
|
|
187
|
-
/** Select every named configured repository, preserving configuration order. */
|
|
188
|
-
function selectSlackRepositories(text, repositories) {
|
|
189
|
-
const mentions = resolveSlackRepositoryMentions(text, repositories);
|
|
190
|
-
if (mentions.repositories.length === 1) return {
|
|
191
|
-
status: "selected",
|
|
192
|
-
repositorySlugs: [mentions.repositories[0]]
|
|
193
|
-
};
|
|
194
|
-
if (mentions.ambiguous.length > 0) return { status: "required" };
|
|
195
|
-
return mentions.matches.length === 0 ? { status: "required" } : {
|
|
196
|
-
status: "selected",
|
|
197
|
-
repositorySlugs: [mentions.matches[0], ...mentions.matches.slice(1)]
|
|
198
|
-
};
|
|
199
|
-
}
|
|
200
|
-
/**
|
|
201
|
-
* Select the only configured repository, or match a full slug, unique short
|
|
202
|
-
* name, or GitHub repository URL when a Slack surface fronts multiple repositories.
|
|
203
|
-
*/
|
|
204
|
-
function selectSlackRepository(text, repositories) {
|
|
205
|
-
const mentions = resolveSlackRepositoryMentions(text, repositories);
|
|
206
|
-
if (mentions.repositories.length === 1) return {
|
|
207
|
-
status: "selected",
|
|
208
|
-
repositorySlug: mentions.repositories[0]
|
|
209
|
-
};
|
|
210
|
-
if (mentions.candidates.length === 1) return {
|
|
211
|
-
status: "selected",
|
|
212
|
-
repositorySlug: mentions.candidates[0]
|
|
213
|
-
};
|
|
214
|
-
if (mentions.candidates.length > 1) return {
|
|
215
|
-
status: "ambiguous",
|
|
216
|
-
repositorySlugs: mentions.candidates
|
|
217
|
-
};
|
|
218
|
-
return { status: "required" };
|
|
219
|
-
}
|
|
220
148
|
//#endregion
|
|
221
|
-
export { createSlackCommunicationAdapter, normalizeSlackMessage, parseSlackReplyAddress, renderSlackCommunicationEvent
|
|
149
|
+
export { createSlackApprovals, createSlackCommunicationAdapter, normalizeSlackMessage, parseSlackReplyAddress, renderSlackCommunicationEvent };
|
|
222
150
|
|
|
223
151
|
//# sourceMappingURL=slack.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slack.mjs","names":[],"sources":["../../src/connectors/slack.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport {\n communicationEventSchema,\n type CommunicationAdapter,\n type CommunicationEvent,\n type CommunicationOutcome,\n type NormalizedSignal,\n} from \"./index\";\n\nconst slackOutcomeLabels: Record<CommunicationOutcome, string> = {\n accepted: \"Request accepted\",\n started: \"Work started\",\n succeeded: \"Work completed\",\n failed: \"Work failed\",\n needs_human: \"Human action required\",\n question: \"Question requires an answer\",\n digest: \"Factory digest\",\n rolled_back: \"Change rolled back\",\n budget_warning: \"Factory budget warning\",\n budget_exhausted: \"Factory budget exhausted\",\n message: \"Factory update\",\n};\n\nconst slackDetailFields = [\"title\", \"message\", \"summary\", \"question\", \"requestedAction\", \"reason\", \"url\"] as const;\nconst slackListFields = [\"items\", \"options\"] as const;\nconst slackTextLimit = 12_000;\nconst slackOwnerOutcomes = new Set<CommunicationOutcome>([\n \"digest\",\n \"budget_warning\",\n \"budget_exhausted\",\n]);\n\nfunction escapeSlackText(value: string): string {\n return value.replaceAll(\"&\", \"&\").replaceAll(\"<\", \"<\").replaceAll(\">\", \">\");\n}\n\nfunction slackLink(url: string, label: string): string | null {\n try {\n const parsed = new URL(url);\n if (parsed.protocol !== \"https:\" && parsed.protocol !== \"http:\") return null;\n return `<${escapeSlackText(parsed.toString())}|${escapeSlackText(label)}>`;\n } catch {\n return null;\n }\n}\n\nexport interface SlackCommunicationDelivery {\n eventId: string;\n clientMessageId: string;\n text: string;\n target:\n | { type: \"owner\" }\n | { type: \"thread\"; channelId: string; threadTs: string };\n}\n\nexport interface SlackCommunicationAdapter extends CommunicationAdapter {\n readonly channel: \"slack\";\n render(event: CommunicationEvent): string;\n}\n\nexport interface SlackCommunicationAdapterOptions {\n send(delivery: SlackCommunicationDelivery): Promise<void>;\n render?: (event: CommunicationEvent) => string;\n}\n\nexport interface SlackReplyTarget {\n channelId: string;\n threadTs: string;\n}\n\nexport type SlackPullRequestSelection =\n | { status: \"none\" }\n | { status: \"ambiguous\" }\n | { status: \"selected\"; pullRequestNumber: number };\n\n/** Resolve an explicit pull-request reference after the repository has been selected. */\nexport function selectSlackPullRequest(\n text: string,\n repositorySlug: string,\n): SlackPullRequestSelection {\n const escapedSlug = repositorySlug.replace(/[.*+?^${}()|[\\]\\\\]/gu, \"\\\\$&\");\n const references = [\n ...text.matchAll(new RegExp(`https://github\\\\.com/${escapedSlug}/pull/(\\\\d+)`, \"giu\")),\n ...text.matchAll(/\\b(?:pr|pull\\s+request)\\s*#?\\s*(\\d+)\\b/giu),\n ].flatMap((match) => {\n const value = Number(match[1]);\n return Number.isSafeInteger(value) && value > 0 ? [value] : [];\n });\n const distinct = [...new Set(references)];\n if (distinct.length === 0) return { status: \"none\" };\n if (distinct.length > 1) return { status: \"ambiguous\" };\n return { status: \"selected\", pullRequestNumber: distinct[0]! };\n}\n\n/** Parse Eve's Slack continuation token into its channel and thread target. */\nexport function parseSlackReplyAddress(address: string): SlackReplyTarget {\n const separator = address.indexOf(\":\");\n const channelId = address.slice(0, separator);\n const threadTs = address.slice(separator + 1);\n if (separator <= 0 || !/^[A-Z][A-Z0-9]+$/.test(channelId) || !/^\\d+\\.\\d+$/.test(threadTs)) {\n throw new Error(`Invalid Slack reply address: ${address}.`);\n }\n return { channelId, threadTs };\n}\n\n/** Render a provider-neutral communication event as Slack mrkdwn. */\nexport function renderSlackCommunicationEvent(input: CommunicationEvent): string {\n const event = communicationEventSchema.parse(input);\n const headline = `*${slackOutcomeLabels[event.outcome]}*`;\n const details = new Map(slackDetailFields.flatMap((field) => {\n const value = event.data[field];\n return typeof value === \"string\" && value.trim() !== \"\" ? [[field, value.trim()] as const] : [];\n }));\n const primaryField = ([\"title\", \"question\", \"requestedAction\"] as const)\n .find((field) => details.has(field));\n const primary = primaryField === undefined ? undefined : details.get(primaryField);\n const url = details.get(\"url\");\n const linkedPrimary = primary !== undefined && url !== undefined ? slackLink(url, primary) : null;\n if (primaryField !== undefined) details.delete(primaryField);\n if (linkedPrimary !== null) details.delete(\"url\");\n\n const detailLines = [...new Set([\n ...(linkedPrimary !== null ? [linkedPrimary] : primary === undefined ? [] : [escapeSlackText(primary)]),\n ...[...details.values()].map(escapeSlackText),\n ])];\n const lists = slackListFields.flatMap((field) => {\n const value = event.data[field];\n if (!Array.isArray(value)) return [];\n return value.flatMap((item) =>\n typeof item === \"string\" && item.trim() !== \"\" ? [`• ${escapeSlackText(item.trim())}`] : []\n );\n });\n const text = [headline, ...detailLines, ...(lists.length === 0 ? [] : [lists.join(\"\\n\")])]\n .join(\"\\n\\n\");\n return text.length <= slackTextLimit ? text : `${text.slice(0, slackTextLimit - 1)}…`;\n}\n\nfunction slackClientMessageId(eventId: string): string {\n const digest = createHash(\"sha256\").update(eventId).digest(\"hex\").slice(0, 32);\n return [\n digest.slice(0, 8),\n digest.slice(8, 12),\n digest.slice(12, 16),\n digest.slice(16, 20),\n digest.slice(20),\n ].join(\"-\");\n}\n\n/** Bind Slack rendering and delivery to the channel-neutral communication contract. */\nexport function createSlackCommunicationAdapter(\n options: SlackCommunicationAdapterOptions,\n): SlackCommunicationAdapter {\n const render = options.render ?? renderSlackCommunicationEvent;\n return Object.freeze({\n channel: \"slack\" as const,\n render,\n async deliver(input: CommunicationEvent): Promise<void> {\n const event = communicationEventSchema.parse(input);\n if (event.replyTo.channel !== \"slack\") {\n throw new Error(`Slack communication adapter cannot deliver to ${event.replyTo.channel}.`);\n }\n const target = slackOwnerOutcomes.has(event.outcome)\n ? { type: \"owner\" as const }\n : { type: \"thread\" as const, ...parseSlackReplyAddress(event.replyTo.address) };\n await options.send({\n eventId: event.eventId,\n clientMessageId: slackClientMessageId(event.eventId),\n text: render(event),\n target,\n });\n },\n });\n}\n\nexport interface SlackMessageEvent {\n teamId: string;\n channelId: string;\n messageTs: string;\n threadTs: string;\n actorId: string;\n text: string;\n replyAddress: string;\n repositorySlugs: readonly [string, ...string[]];\n payload: unknown;\n}\n\nexport interface SlackMessageSignal extends NormalizedSignal<\"slack\"> {\n repositorySlugs: readonly [string, ...string[]];\n message: {\n teamId: string;\n channelId: string;\n messageTs: string;\n threadTs: string;\n actorId: string;\n text: string;\n };\n}\n\n/** Normalize an Eve-verified Slack message after the caller resolves its repositories. */\nexport function normalizeSlackMessage(event: SlackMessageEvent): SlackMessageSignal {\n return {\n source: \"slack\",\n deliveryId: `${event.teamId}:${event.channelId}:${event.messageTs}`,\n repositorySlugs: [\n event.repositorySlugs[0].toLowerCase(),\n ...event.repositorySlugs.slice(1).map((slug) => slug.toLowerCase()),\n ],\n replyTo: { channel: \"slack\", address: event.replyAddress },\n message: {\n teamId: event.teamId,\n channelId: event.channelId,\n messageTs: event.messageTs,\n threadTs: event.threadTs,\n actorId: event.actorId,\n text: event.text,\n },\n payload: event.payload,\n };\n}\n\nexport type SlackRepositorySelection =\n | { status: \"selected\"; repositorySlug: string }\n | { status: \"required\" }\n | { status: \"ambiguous\"; repositorySlugs: readonly string[] };\n\nexport type SlackRepositoriesSelection =\n | { status: \"selected\"; repositorySlugs: readonly [string, ...string[]] }\n | { status: \"required\" };\n\nfunction escapedPattern(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\nfunction isDelimitedTextMentioned(text: string, value: string): boolean {\n return new RegExp(\n `(^|[^a-z0-9._/-])${escapedPattern(value)}($|[^a-z0-9._/-])`,\n \"i\",\n ).test(text);\n}\n\nfunction isRepositoryReferenceMentioned(text: string, slug: string): boolean {\n if (isDelimitedTextMentioned(text, slug)) return true;\n return new RegExp(\n `https?://github\\\\.com/${escapedPattern(slug)}(?:\\\\.git)?/?(?=$|[\\\\s?#|>)\\\\],.!])`,\n \"i\",\n ).test(text);\n}\n\nfunction repositoryName(slug: string): string {\n return slug.slice(slug.lastIndexOf(\"/\") + 1);\n}\n\ninterface SlackRepositoryMentions {\n repositories: readonly string[];\n matches: readonly string[];\n ambiguous: readonly string[];\n candidates: readonly string[];\n}\n\nfunction resolveSlackRepositoryMentions(\n text: string,\n repositories: readonly string[],\n): SlackRepositoryMentions {\n const normalized = [...new Set(repositories.map((slug) => slug.toLowerCase()))];\n const entries = normalized.map((slug) => ({ slug, name: repositoryName(slug) }));\n const counts = new Map<string, number>();\n for (const { name } of entries) {\n counts.set(name, (counts.get(name) ?? 0) + 1);\n }\n const mentionedNames = new Set(\n [...counts.keys()].filter((name) => isDelimitedTextMentioned(text, name)),\n );\n const matches = new Set(entries.flatMap(({ slug, name }) =>\n isRepositoryReferenceMentioned(text, slug) || (\n counts.get(name) === 1 && mentionedNames.has(name)\n )\n ? [slug]\n : []\n ));\n const ambiguous = new Set(entries.flatMap(({ slug, name }) =>\n counts.get(name) !== 1 && mentionedNames.has(name) ? [slug] : []\n ));\n\n return {\n repositories: normalized,\n matches: normalized.filter((slug) => matches.has(slug)),\n ambiguous: normalized.filter((slug) => ambiguous.has(slug)),\n candidates: normalized.filter((slug) => matches.has(slug) || ambiguous.has(slug)),\n };\n}\n\n/** Select every named configured repository, preserving configuration order. */\nexport function selectSlackRepositories(\n text: string,\n repositories: readonly string[],\n): SlackRepositoriesSelection {\n const mentions = resolveSlackRepositoryMentions(text, repositories);\n if (mentions.repositories.length === 1) {\n return { status: \"selected\", repositorySlugs: [mentions.repositories[0]!] };\n }\n if (mentions.ambiguous.length > 0) return { status: \"required\" };\n return mentions.matches.length === 0\n ? { status: \"required\" }\n : {\n status: \"selected\",\n repositorySlugs: [mentions.matches[0]!, ...mentions.matches.slice(1)],\n };\n}\n\n/**\n * Select the only configured repository, or match a full slug, unique short\n * name, or GitHub repository URL when a Slack surface fronts multiple repositories.\n */\nexport function selectSlackRepository(\n text: string,\n repositories: readonly string[],\n): SlackRepositorySelection {\n const mentions = resolveSlackRepositoryMentions(text, repositories);\n if (mentions.repositories.length === 1) {\n return { status: \"selected\", repositorySlug: mentions.repositories[0]! };\n }\n if (mentions.candidates.length === 1) {\n return { status: \"selected\", repositorySlug: mentions.candidates[0]! };\n }\n if (mentions.candidates.length > 1) {\n return { status: \"ambiguous\", repositorySlugs: mentions.candidates };\n }\n return { status: \"required\" };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"slack.mjs","names":[],"sources":["../../src/connectors/slack.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport type { MaybePromise } from \"../schema/common\";\nimport { repositorySlugSchema, type RepositorySlug } from \"../schema/repository\";\nexport {\n createSlackApprovals,\n type SlackApprovals,\n type SlackApprovalsOptions,\n type SlackApprovalsStores,\n} from \"./slack-approvals\";\nimport {\n communicationEventSchema,\n type CommunicationAdapter,\n type CommunicationEvent,\n type CommunicationOutcome,\n type NormalizedSignal,\n} from \"./index\";\n\nconst slackOutcomeLabels: Record<CommunicationOutcome, string> = {\n accepted: \"Request accepted\",\n started: \"Work started\",\n succeeded: \"Work completed\",\n failed: \"Work failed\",\n cancelled: \"Work cancelled\",\n needs_human: \"Human action required\",\n question: \"Question requires an answer\",\n digest: \"Factory digest\",\n rolled_back: \"Change rolled back\",\n budget_warning: \"Factory budget warning\",\n budget_exhausted: \"Factory budget exhausted\",\n message: \"Factory update\",\n};\n\nconst slackDetailFields = [\n \"title\",\n \"message\",\n \"summary\",\n \"question\",\n \"requestedAction\",\n \"reference\",\n \"answerLocation\",\n \"reason\",\n \"url\",\n] as const;\nconst slackListFields = [\"items\", \"options\"] as const;\nconst slackTextLimit = 12_000;\nconst slackOwnerOutcomes = new Set<CommunicationOutcome>([\n \"digest\",\n \"budget_warning\",\n \"budget_exhausted\",\n \"needs_human\",\n]);\n\nfunction escapeSlackText(value: string): string {\n return value.replaceAll(\"&\", \"&\").replaceAll(\"<\", \"<\").replaceAll(\">\", \">\");\n}\n\nfunction slackLink(url: string, label: string): string | null {\n try {\n const parsed = new URL(url);\n if (parsed.protocol !== \"https:\" && parsed.protocol !== \"http:\") return null;\n return `<${escapeSlackText(parsed.toString())}|${escapeSlackText(label)}>`;\n } catch {\n return null;\n }\n}\n\n/** Fully rendered Slack delivery with a stable idempotency key and resolved destination. */\nexport interface SlackCommunicationDelivery {\n eventId: string;\n clientMessageId: string;\n text: string;\n target: { type: \"owner\" } | { type: \"thread\"; channelId: string; threadTs: string };\n}\n\n/** Slack-specific communication adapter with an inspectable mrkdwn renderer. */\nexport interface SlackCommunicationAdapter extends CommunicationAdapter {\n readonly channel: \"slack\";\n render(event: CommunicationEvent): string;\n}\n\n/** Configures Slack rendering, provider delivery, and durable delivery projection. */\nexport interface SlackCommunicationAdapterOptions {\n send(delivery: SlackCommunicationDelivery): Promise<void>;\n render?: (event: CommunicationEvent) => string;\n /**\n * Runs once send() resolves without throwing, i.e. once Slack has\n * confirmed delivery. Errors propagate so the outbox retries with the same\n * client message ID until both the send and its durable projection succeed.\n */\n onDelivered?: (event: CommunicationEvent) => MaybePromise<void>;\n}\n\n/** The channel and thread extracted from a Slack reply address. */\nexport interface SlackReplyTarget {\n channelId: string;\n threadTs: string;\n}\n\n/** Parse Eve's Slack continuation token into its channel and thread target. */\nexport function parseSlackReplyAddress(address: string): SlackReplyTarget {\n const separator = address.indexOf(\":\");\n const channelId = address.slice(0, separator);\n const threadTs = address.slice(separator + 1);\n if (separator <= 0 || !/^[A-Z][A-Z0-9]+$/.test(channelId) || !/^\\d+\\.\\d+$/.test(threadTs)) {\n throw new Error(`Invalid Slack reply address: ${address}.`);\n }\n return { channelId, threadTs };\n}\n\n/** Render a provider-neutral communication event as Slack mrkdwn. */\nexport function renderSlackCommunicationEvent(input: CommunicationEvent): string {\n const event = communicationEventSchema.parse(input);\n const headline = `*${slackOutcomeLabels[event.outcome]}*`;\n const details = new Map(\n slackDetailFields.flatMap((field) => {\n const value = event.data[field];\n return typeof value === \"string\" && value.trim() !== \"\"\n ? [[field, value.trim()] as const]\n : [];\n }),\n );\n const primaryField = ([\"title\", \"question\", \"requestedAction\"] as const).find((field) =>\n details.has(field),\n );\n const primary = primaryField === undefined ? undefined : details.get(primaryField);\n const url = details.get(\"url\");\n const linkedPrimary = primary !== undefined && url !== undefined ? slackLink(url, primary) : null;\n if (primaryField !== undefined) details.delete(primaryField);\n if (linkedPrimary !== null) details.delete(\"url\");\n\n const detailLines = [\n ...new Set([\n ...(linkedPrimary !== null\n ? [linkedPrimary]\n : primary === undefined\n ? []\n : [escapeSlackText(primary)]),\n ...[...details.values()].map(escapeSlackText),\n ]),\n ];\n const lists = slackListFields.flatMap((field) => {\n const value = event.data[field];\n if (!Array.isArray(value)) return [];\n return value.flatMap((item) =>\n typeof item === \"string\" && item.trim() !== \"\" ? [`• ${escapeSlackText(item.trim())}`] : [],\n );\n });\n // Success messages lead directly with the result; the generic outcome\n // headline is redundant once the work is done and the summary says it all.\n // When a succeeded event carries no other content, fall back to the\n // shared status-label mapping so the message is never blank and stays\n // consistent with how every other outcome renders.\n const hasContent = detailLines.length > 0 || lists.length > 0;\n const headlineLines = event.outcome === \"succeeded\" && hasContent ? [] : [headline];\n const text = [\n ...headlineLines,\n ...detailLines,\n ...(lists.length === 0 ? [] : [lists.join(\"\\n\")]),\n ].join(\"\\n\\n\");\n return text.length <= slackTextLimit ? text : `${text.slice(0, slackTextLimit - 1)}…`;\n}\n\nfunction slackClientMessageId(eventId: string): string {\n const digest = createHash(\"sha256\").update(eventId).digest(\"hex\").slice(0, 32);\n return [\n digest.slice(0, 8),\n digest.slice(8, 12),\n digest.slice(12, 16),\n digest.slice(16, 20),\n digest.slice(20),\n ].join(\"-\");\n}\n\n/** Bind Slack rendering and delivery to the channel-neutral communication contract. */\nexport function createSlackCommunicationAdapter(\n options: SlackCommunicationAdapterOptions,\n): SlackCommunicationAdapter {\n const render = options.render ?? renderSlackCommunicationEvent;\n return Object.freeze({\n channel: \"slack\" as const,\n render,\n async deliver(input: CommunicationEvent): Promise<void> {\n const event = communicationEventSchema.parse(input);\n if (event.replyTo.channel !== \"slack\") {\n throw new Error(`Slack communication adapter cannot deliver to ${event.replyTo.channel}.`);\n }\n const target = slackOwnerOutcomes.has(event.outcome)\n ? { type: \"owner\" as const }\n : { type: \"thread\" as const, ...parseSlackReplyAddress(event.replyTo.address) };\n await options.send({\n eventId: event.eventId,\n clientMessageId: slackClientMessageId(event.eventId),\n text: render(event),\n target,\n });\n await options.onDelivered?.(event);\n },\n });\n}\n\n/** Authenticated Slack message metadata after repository and reply context resolution. */\nexport interface SlackMessageEvent {\n readonly teamId: string;\n readonly channelId: string;\n readonly messageTs: string;\n readonly threadTs: string;\n readonly actorId: string;\n readonly text: string;\n readonly replyAddress: string;\n readonly repositorySlugs: readonly [RepositorySlug, ...RepositorySlug[]];\n readonly payload: unknown;\n}\n\n/** A normalized Slack signal with the authenticated message and repository scope retained. */\nexport interface SlackMessageSignal extends NormalizedSignal<\"slack\"> {\n readonly repositorySlugs: readonly [RepositorySlug, ...RepositorySlug[]];\n readonly message: {\n readonly teamId: string;\n readonly channelId: string;\n readonly messageTs: string;\n readonly threadTs: string;\n readonly actorId: string;\n readonly text: string;\n };\n}\n\n/** Normalize an Eve-verified Slack message after the caller resolves its repositories. */\nexport function normalizeSlackMessage(event: SlackMessageEvent): SlackMessageSignal {\n return {\n source: \"slack\",\n deliveryId: `${event.teamId}:${event.channelId}:${event.messageTs}`,\n repositorySlugs: [\n repositorySlugSchema.parse(event.repositorySlugs[0].toLowerCase()),\n ...event.repositorySlugs\n .slice(1)\n .map((slug) => repositorySlugSchema.parse(slug.toLowerCase())),\n ],\n replyTo: { channel: \"slack\", address: event.replyAddress },\n message: {\n teamId: event.teamId,\n channelId: event.channelId,\n messageTs: event.messageTs,\n threadTs: event.threadTs,\n actorId: event.actorId,\n text: event.text,\n },\n payload: event.payload,\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { TaskId } from "../schema/id.mjs";
|
|
2
|
+
import { MillisecondClock } from "../schema/common.mjs";
|
|
3
|
+
import { FactoryStores } from "../store/engine.mjs";
|
|
4
|
+
import { CommandSandbox, CommandSandboxRunResult } from "../sandbox/command.mjs";
|
|
5
|
+
import { Finding } from "../schema/finding.mjs";
|
|
6
|
+
import { AdmitFindingOptions } from "../finding-remediation/admission.mjs";
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
import { ToolContext, ToolDefinition } from "eve/tools";
|
|
9
|
+
//#region src/deepsec/eve-tool.d.ts
|
|
10
|
+
/** Repository, receipt, Signal, and Task capabilities required by the Deepsec scan tool. */
|
|
11
|
+
type DeepsecScanToolStores = Pick<FactoryStores, "receipts" | "repositories" | "signals" | "tasks">;
|
|
12
|
+
/** Deepsec investigation backend selected for one scan. */
|
|
13
|
+
type DeepsecAgent = "claude" | "codex" | "pi";
|
|
14
|
+
/** Reasoning effort accepted by Deepsec's investigation agent. */
|
|
15
|
+
type DeepsecThinkingLevel = "minimal" | "low" | "medium" | "high" | "xhigh";
|
|
16
|
+
/** Validates the repository and optional candidate-file cap supplied to the Deepsec tool. */
|
|
17
|
+
declare const deepsecScanInputSchema: z.ZodObject<{
|
|
18
|
+
repositoryId: z.ZodType<`repo_${string}`, string, z.core.$ZodTypeInternals<`repo_${string}`, string>>;
|
|
19
|
+
limit: z.ZodOptional<z.ZodNumber>;
|
|
20
|
+
}, z.core.$strip>;
|
|
21
|
+
/** Repository and optional candidate-file cap supplied to one Deepsec scan. */
|
|
22
|
+
type DeepsecScanInput = z.infer<typeof deepsecScanInputSchema>;
|
|
23
|
+
/** Configures credential isolation, execution limits, and finding admission for a deepsec scan. */
|
|
24
|
+
interface DeepsecScanToolOptions<S extends CommandSandbox = CommandSandbox> extends AdmitFindingOptions {
|
|
25
|
+
/** Factory capabilities used to report scan phases and admit findings. */
|
|
26
|
+
stores: DeepsecScanToolStores;
|
|
27
|
+
/**
|
|
28
|
+
* Runs one shell command with the model credential deepsec's gateway route
|
|
29
|
+
* expects, and removes it again before resolving. Supplied by the app, which
|
|
30
|
+
* owns where the credential comes from: on Vercel it is the deployment's own
|
|
31
|
+
* OIDC identity (VERCEL_OIDC_TOKEN), so nothing has to be configured.
|
|
32
|
+
*/
|
|
33
|
+
withModelCredential: (input: DeepsecCredentialCommandInput<S>) => Promise<DeepsecCommandResult>;
|
|
34
|
+
/** deepsec agent backend: claude, codex, or pi. */
|
|
35
|
+
agent?: DeepsecAgent;
|
|
36
|
+
/** Model deepsec's investigation agent uses, through the AI Gateway (e.g. claude-sonnet-4-6). */
|
|
37
|
+
model?: string;
|
|
38
|
+
/** Reasoning effort for the investigation: minimal, low, medium, high, or xhigh. */
|
|
39
|
+
thinkingLevel?: DeepsecThinkingLevel;
|
|
40
|
+
/** Parallel investigation batches. */
|
|
41
|
+
concurrency?: number;
|
|
42
|
+
/** deepsec version to install. */
|
|
43
|
+
version?: string;
|
|
44
|
+
/** Millisecond clock used for receipt elapsed-time reporting. */
|
|
45
|
+
now?: MillisecondClock;
|
|
46
|
+
/** Repository checkout inside the sandbox. Defaults to `repo`. */
|
|
47
|
+
repoPath?: string;
|
|
48
|
+
/** deepsec workspace inside the sandbox. Defaults to `deepsec`. */
|
|
49
|
+
workspacePath?: string;
|
|
50
|
+
/** Shell run before every deepsec command, e.g. to put a Node 24 on PATH. */
|
|
51
|
+
commandPrefix?: string;
|
|
52
|
+
/**
|
|
53
|
+
* Candidate files investigated per run. deepsec keeps state, so successive
|
|
54
|
+
* scans continue where the last one stopped; the cap keeps one run inside
|
|
55
|
+
* the deployment's time limit on large repositories. Defaults to 40.
|
|
56
|
+
*/
|
|
57
|
+
filesPerRun?: number;
|
|
58
|
+
}
|
|
59
|
+
/** Sandbox and trusted command that may temporarily receive a model credential. */
|
|
60
|
+
interface DeepsecCredentialCommandInput<S extends CommandSandbox = CommandSandbox> {
|
|
61
|
+
sandbox: S;
|
|
62
|
+
command: string;
|
|
63
|
+
}
|
|
64
|
+
/** Captured output from one Deepsec sandbox command. */
|
|
65
|
+
type DeepsecCommandResult = CommandSandboxRunResult;
|
|
66
|
+
/** Machine-readable reason a Deepsec scan did not produce findings. */
|
|
67
|
+
type DeepsecScanFailureCode = "repository_not_found" | "scan_failed" | "process_failed" | "invalid_export_json" | "invalid_export_shape";
|
|
68
|
+
/** Bounded Deepsec refusal with a human-readable reason code payload. */
|
|
69
|
+
interface DeepsecScanFailure {
|
|
70
|
+
scanned: false;
|
|
71
|
+
code: DeepsecScanFailureCode;
|
|
72
|
+
reason: string;
|
|
73
|
+
}
|
|
74
|
+
/** One normalized Deepsec finding and its admission outcome. */
|
|
75
|
+
type DeepsecScanFinding = Pick<Finding, "title" | "priority" | "path" | "line"> & ({
|
|
76
|
+
duplicate: true;
|
|
77
|
+
taskId?: never;
|
|
78
|
+
approval?: never;
|
|
79
|
+
} | {
|
|
80
|
+
duplicate?: never;
|
|
81
|
+
taskId: TaskId;
|
|
82
|
+
approval: "required" | "not_required";
|
|
83
|
+
});
|
|
84
|
+
/** Counts and bounded finding summaries returned after a completed Deepsec scan. */
|
|
85
|
+
interface DeepsecScanSuccess {
|
|
86
|
+
scanned: true;
|
|
87
|
+
tool: "deepsec";
|
|
88
|
+
found: number;
|
|
89
|
+
recorded: number;
|
|
90
|
+
duplicates: number;
|
|
91
|
+
awaitingApproval: number;
|
|
92
|
+
findings: readonly DeepsecScanFinding[];
|
|
93
|
+
}
|
|
94
|
+
/** Discriminated result returned by the optional Deepsec scanner tool. */
|
|
95
|
+
type DeepsecScanResult = DeepsecScanFailure | DeepsecScanSuccess;
|
|
96
|
+
/** Eve tool that runs one bounded Deepsec scan and admits its findings. */
|
|
97
|
+
interface DeepsecScanTool extends ToolDefinition<DeepsecScanInput, DeepsecScanResult> {
|
|
98
|
+
execute(input: DeepsecScanInput, ctx: ToolContext): Promise<DeepsecScanResult>;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Runs deepsec (vercel-labs/deepsec) against the sandbox checkout and records
|
|
102
|
+
* what it finds through the same path as record_finding, so every deepsec
|
|
103
|
+
* finding becomes a fix task subject to the approval policy. Trusted code
|
|
104
|
+
* owns the command sequence and the credential; the agent chooses when to
|
|
105
|
+
* scan and reports the outcome.
|
|
106
|
+
*/
|
|
107
|
+
declare function createDeepsecScanTool<S extends CommandSandbox>(input: DeepsecScanToolOptions<S>): DeepsecScanTool;
|
|
108
|
+
//#endregion
|
|
109
|
+
export { DeepsecAgent, DeepsecCommandResult, DeepsecCredentialCommandInput, DeepsecScanFailure, DeepsecScanFailureCode, DeepsecScanFinding, DeepsecScanInput, DeepsecScanResult, DeepsecScanSuccess, DeepsecScanTool, DeepsecScanToolOptions, DeepsecScanToolStores, DeepsecThinkingLevel, createDeepsecScanTool, deepsecScanInputSchema };
|
|
110
|
+
//# sourceMappingURL=eve-tool.d.mts.map
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { repositoryIdSchema } from "../schema/id.mjs";
|
|
2
|
+
import { optionalTaskIdForSession } from "../eve/task-session.mjs";
|
|
3
|
+
import { deepsecExportSchema, findingFromDeepsec } from "./finding.mjs";
|
|
4
|
+
import { admitFinding } from "../finding-remediation/admission.mjs";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { defineTool } from "eve/tools";
|
|
7
|
+
//#region src/deepsec/eve-tool.ts
|
|
8
|
+
/** Validates the repository and optional candidate-file cap supplied to the Deepsec tool. */
|
|
9
|
+
const deepsecScanInputSchema = z.object({
|
|
10
|
+
repositoryId: repositoryIdSchema,
|
|
11
|
+
limit: z.number().int().min(1).max(500).optional().describe("Candidate files to investigate this run (default 40; deepsec resumes where it left off next time).")
|
|
12
|
+
});
|
|
13
|
+
const findingsPath = "/tmp/deepsec-findings.json";
|
|
14
|
+
/** Parse "N file(s) with candidates" out of deepsec scan output; null when absent. */
|
|
15
|
+
function candidateCount(output) {
|
|
16
|
+
const match = /(\d+)\s+file\(s\) with candidates/u.exec(output);
|
|
17
|
+
return match?.[1] === void 0 ? null : Number(match[1]);
|
|
18
|
+
}
|
|
19
|
+
/** The last few meaningful lines of a command's output, ANSI stripped, for a receipt. */
|
|
20
|
+
function tail(output, lines = 4) {
|
|
21
|
+
return output.replaceAll(/\u001b\[[0-9;]*m/gu, "").split("\n").map((line) => line.trim()).filter((line) => line !== "").slice(-lines).join(" | ").slice(0, 600);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Runs deepsec (vercel-labs/deepsec) against the sandbox checkout and records
|
|
25
|
+
* what it finds through the same path as record_finding, so every deepsec
|
|
26
|
+
* finding becomes a fix task subject to the approval policy. Trusted code
|
|
27
|
+
* owns the command sequence and the credential; the agent chooses when to
|
|
28
|
+
* scan and reports the outcome.
|
|
29
|
+
*/
|
|
30
|
+
function createDeepsecScanTool(input) {
|
|
31
|
+
const { stores, ...options } = input;
|
|
32
|
+
const repoPath = options.repoPath ?? "repo";
|
|
33
|
+
const workspace = options.workspacePath ?? "deepsec";
|
|
34
|
+
const version = options.version ?? "2.3.8";
|
|
35
|
+
const agent = options.agent ?? "claude";
|
|
36
|
+
const model = options.model ?? "claude-sonnet-4-6";
|
|
37
|
+
const thinkingLevel = options.thinkingLevel ?? "medium";
|
|
38
|
+
const concurrency = options.concurrency ?? 4;
|
|
39
|
+
const prefix = options.commandPrefix === void 0 ? [] : [options.commandPrefix];
|
|
40
|
+
const filesPerRun = options.filesPerRun ?? 40;
|
|
41
|
+
const now = options.now ?? Date.now;
|
|
42
|
+
return defineTool({
|
|
43
|
+
description: "Run deepsec, the AI-powered vulnerability scanner, over the repository checkout and record every finding it reports as a fix task. Takes minutes. Call once per scan; then summarize the result.",
|
|
44
|
+
inputSchema: deepsecScanInputSchema,
|
|
45
|
+
async execute({ repositoryId, limit }, ctx) {
|
|
46
|
+
const repository = await stores.repositories.get(repositoryId);
|
|
47
|
+
if (repository === null) return {
|
|
48
|
+
scanned: false,
|
|
49
|
+
code: "repository_not_found",
|
|
50
|
+
reason: `repository ${repositoryId} does not exist`
|
|
51
|
+
};
|
|
52
|
+
const sandbox = await ctx.getSandbox();
|
|
53
|
+
const projectId = repository.slug.replace(/[^a-z0-9]+/gu, "-");
|
|
54
|
+
const startedAt = now();
|
|
55
|
+
const phase = async (state, reason) => {
|
|
56
|
+
await stores.receipts.append({
|
|
57
|
+
repositoryIds: [repositoryId],
|
|
58
|
+
kind: "deepsec",
|
|
59
|
+
state,
|
|
60
|
+
phase: "security_scan",
|
|
61
|
+
reason: `${reason} (${Math.round((now() - startedAt) / 1e3)}s in)`
|
|
62
|
+
});
|
|
63
|
+
};
|
|
64
|
+
await phase("started", `deepsec ${version} on ${repository.slug}, ${agent}/${model}`);
|
|
65
|
+
const prepare = await sandbox.run({ command: [
|
|
66
|
+
...prefix,
|
|
67
|
+
"set -e",
|
|
68
|
+
`if [ ! -f ${workspace}/deepsec.config.ts ]; then`,
|
|
69
|
+
` npx -y deepsec@${version} init ./${workspace} ./${repoPath} --id ${projectId} --scaffold-only --headless --yes --skip-install`,
|
|
70
|
+
` (cd ${workspace} && npm install --no-audit --no-fund --loglevel=error)`,
|
|
71
|
+
"fi",
|
|
72
|
+
`cd ${workspace}`,
|
|
73
|
+
`./node_modules/.bin/deepsec scan --project-id ${projectId}`
|
|
74
|
+
].join("\n") });
|
|
75
|
+
if (typeof prepare.exitCode === "number" && prepare.exitCode !== 0) {
|
|
76
|
+
const detail = (prepare.stderr ?? prepare.stdout).trim().slice(-1500);
|
|
77
|
+
await phase("failed", `scan phase failed: ${detail.slice(-300)}`);
|
|
78
|
+
return {
|
|
79
|
+
scanned: false,
|
|
80
|
+
code: "scan_failed",
|
|
81
|
+
reason: `deepsec scan failed: ${detail}`
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
const candidates = candidateCount(prepare.stdout);
|
|
85
|
+
await phase("scanned", `${candidates === null ? "scan output" : `${candidates} files with candidates`}: ${tail(`${prepare.stdout}\n${prepare.stderr ?? ""}`)}`);
|
|
86
|
+
const investigate = await options.withModelCredential({
|
|
87
|
+
sandbox,
|
|
88
|
+
command: [
|
|
89
|
+
...prefix,
|
|
90
|
+
"set -e",
|
|
91
|
+
`cd ${workspace}`,
|
|
92
|
+
`./node_modules/.bin/deepsec process --project-id ${projectId} --agent ${agent} --model ${model} --thinking-level ${thinkingLevel} --concurrency ${concurrency} --limit ${limit ?? filesPerRun}`,
|
|
93
|
+
`./node_modules/.bin/deepsec export --project-id ${projectId} --format json --out ${findingsPath}`
|
|
94
|
+
].join("\n")
|
|
95
|
+
});
|
|
96
|
+
if (typeof investigate.exitCode === "number" && investigate.exitCode !== 0) {
|
|
97
|
+
const detail = (investigate.stderr ?? investigate.stdout).trim().slice(-1500);
|
|
98
|
+
await phase("failed", `process phase failed: ${detail.slice(-300)}`);
|
|
99
|
+
return {
|
|
100
|
+
scanned: false,
|
|
101
|
+
code: "process_failed",
|
|
102
|
+
reason: `deepsec process failed: ${detail}`
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
const exported = await sandbox.run({ command: `cat ${findingsPath}` });
|
|
106
|
+
let parsed;
|
|
107
|
+
try {
|
|
108
|
+
parsed = JSON.parse(exported.stdout);
|
|
109
|
+
} catch {
|
|
110
|
+
return {
|
|
111
|
+
scanned: false,
|
|
112
|
+
code: "invalid_export_json",
|
|
113
|
+
reason: "deepsec export was not valid JSON"
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
const raw = deepsecExportSchema.safeParse(parsed);
|
|
117
|
+
if (!raw.success) return {
|
|
118
|
+
scanned: false,
|
|
119
|
+
code: "invalid_export_shape",
|
|
120
|
+
reason: `deepsec export had an unexpected shape: ${raw.error.issues[0]?.message ?? "unknown"}`
|
|
121
|
+
};
|
|
122
|
+
const results = [];
|
|
123
|
+
const taskId = optionalTaskIdForSession(ctx.session.auth.initiator);
|
|
124
|
+
const scan = taskId === null ? null : await stores.tasks.get(taskId);
|
|
125
|
+
const root = scan && scan.rootTaskId !== scan.id ? await stores.tasks.get(scan.rootTaskId) : scan;
|
|
126
|
+
const replyTo = scan?.replyTo.channel === "slack" && scan.repositoryIds.includes(repositoryId) ? scan.replyTo : options.replyTo;
|
|
127
|
+
for (const entry of raw.data) {
|
|
128
|
+
const finding = findingFromDeepsec(entry);
|
|
129
|
+
results.push({
|
|
130
|
+
finding,
|
|
131
|
+
result: await admitFinding(stores, {
|
|
132
|
+
repositoryId,
|
|
133
|
+
finding,
|
|
134
|
+
...options,
|
|
135
|
+
replyTo,
|
|
136
|
+
operator: scan?.repositoryIds.includes(repositoryId) === true ? scan.origin.operator ?? root?.origin.operator : options.operator
|
|
137
|
+
})
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
const recorded = results.filter(({ result }) => result.recorded);
|
|
141
|
+
await phase("completed", `${results.length} findings, ${recorded.length} new. process output: ${tail(`${investigate.stdout}\n${investigate.stderr ?? ""}`, 6)}`);
|
|
142
|
+
return {
|
|
143
|
+
scanned: true,
|
|
144
|
+
tool: "deepsec",
|
|
145
|
+
found: results.length,
|
|
146
|
+
recorded: recorded.length,
|
|
147
|
+
duplicates: results.length - recorded.length,
|
|
148
|
+
awaitingApproval: recorded.filter(({ result }) => result.recorded && result.approval === "required").length,
|
|
149
|
+
findings: results.map(({ finding, result }) => ({
|
|
150
|
+
title: finding.title,
|
|
151
|
+
priority: finding.priority,
|
|
152
|
+
path: finding.path,
|
|
153
|
+
line: finding.line,
|
|
154
|
+
...result.recorded ? {
|
|
155
|
+
taskId: result.taskId,
|
|
156
|
+
approval: result.approval
|
|
157
|
+
} : { duplicate: true }
|
|
158
|
+
}))
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
//#endregion
|
|
164
|
+
export { createDeepsecScanTool, deepsecScanInputSchema };
|
|
165
|
+
|
|
166
|
+
//# sourceMappingURL=eve-tool.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eve-tool.mjs","names":[],"sources":["../../src/deepsec/eve-tool.ts"],"sourcesContent":["import { defineTool, type ToolContext, type ToolDefinition } from \"eve/tools\";\nimport { z } from \"zod\";\nimport { deepsecExportSchema, findingFromDeepsec } from \"./finding\";\nimport {\n admitFinding,\n type AdmitFindingOptions,\n type AdmitFindingResult,\n} from \"../finding-remediation/admission\";\nimport type { Finding } from \"../schema/finding\";\nimport type { MillisecondClock } from \"../schema/common\";\nimport { repositoryIdSchema, type TaskId } from \"../schema/id\";\nimport type { FactoryStores } from \"../store/engine\";\nimport { optionalTaskIdForSession } from \"../eve/task-session\";\nimport type { CommandSandbox, CommandSandboxRunResult } from \"../sandbox/command\";\n\n/** Repository, receipt, Signal, and Task capabilities required by the Deepsec scan tool. */\nexport type DeepsecScanToolStores = Pick<\n FactoryStores,\n \"receipts\" | \"repositories\" | \"signals\" | \"tasks\"\n>;\n\n/** Deepsec investigation backend selected for one scan. */\nexport type DeepsecAgent = \"claude\" | \"codex\" | \"pi\";\n\n/** Reasoning effort accepted by Deepsec's investigation agent. */\nexport type DeepsecThinkingLevel = \"minimal\" | \"low\" | \"medium\" | \"high\" | \"xhigh\";\n\n/** Validates the repository and optional candidate-file cap supplied to the Deepsec tool. */\nexport const deepsecScanInputSchema = z.object({\n repositoryId: repositoryIdSchema,\n limit: z\n .number()\n .int()\n .min(1)\n .max(500)\n .optional()\n .describe(\n \"Candidate files to investigate this run (default 40; deepsec resumes where it left off next time).\",\n ),\n});\n\n/** Repository and optional candidate-file cap supplied to one Deepsec scan. */\nexport type DeepsecScanInput = z.infer<typeof deepsecScanInputSchema>;\n\n/** Configures credential isolation, execution limits, and finding admission for a deepsec scan. */\nexport interface DeepsecScanToolOptions<\n S extends CommandSandbox = CommandSandbox,\n> extends AdmitFindingOptions {\n /** Factory capabilities used to report scan phases and admit findings. */\n stores: DeepsecScanToolStores;\n /**\n * Runs one shell command with the model credential deepsec's gateway route\n * expects, and removes it again before resolving. Supplied by the app, which\n * owns where the credential comes from: on Vercel it is the deployment's own\n * OIDC identity (VERCEL_OIDC_TOKEN), so nothing has to be configured.\n */\n withModelCredential: (input: DeepsecCredentialCommandInput<S>) => Promise<DeepsecCommandResult>;\n /** deepsec agent backend: claude, codex, or pi. */\n agent?: DeepsecAgent;\n /** Model deepsec's investigation agent uses, through the AI Gateway (e.g. claude-sonnet-4-6). */\n model?: string;\n /** Reasoning effort for the investigation: minimal, low, medium, high, or xhigh. */\n thinkingLevel?: DeepsecThinkingLevel;\n /** Parallel investigation batches. */\n concurrency?: number;\n /** deepsec version to install. */\n version?: string;\n /** Millisecond clock used for receipt elapsed-time reporting. */\n now?: MillisecondClock;\n /** Repository checkout inside the sandbox. Defaults to `repo`. */\n repoPath?: string;\n /** deepsec workspace inside the sandbox. Defaults to `deepsec`. */\n workspacePath?: string;\n /** Shell run before every deepsec command, e.g. to put a Node 24 on PATH. */\n commandPrefix?: string;\n /**\n * Candidate files investigated per run. deepsec keeps state, so successive\n * scans continue where the last one stopped; the cap keeps one run inside\n * the deployment's time limit on large repositories. Defaults to 40.\n */\n filesPerRun?: number;\n}\n\n/** Sandbox and trusted command that may temporarily receive a model credential. */\nexport interface DeepsecCredentialCommandInput<S extends CommandSandbox = CommandSandbox> {\n sandbox: S;\n command: string;\n}\n\n/** Captured output from one Deepsec sandbox command. */\nexport type DeepsecCommandResult = CommandSandboxRunResult;\n\n/** Machine-readable reason a Deepsec scan did not produce findings. */\nexport type DeepsecScanFailureCode =\n | \"repository_not_found\"\n | \"scan_failed\"\n | \"process_failed\"\n | \"invalid_export_json\"\n | \"invalid_export_shape\";\n\n/** Bounded Deepsec refusal with a human-readable reason code payload. */\nexport interface DeepsecScanFailure {\n scanned: false;\n code: DeepsecScanFailureCode;\n reason: string;\n}\n\n/** One normalized Deepsec finding and its admission outcome. */\nexport type DeepsecScanFinding = Pick<Finding, \"title\" | \"priority\" | \"path\" | \"line\"> &\n (\n | { duplicate: true; taskId?: never; approval?: never }\n | { duplicate?: never; taskId: TaskId; approval: \"required\" | \"not_required\" }\n );\n\n/** Counts and bounded finding summaries returned after a completed Deepsec scan. */\nexport interface DeepsecScanSuccess {\n scanned: true;\n tool: \"deepsec\";\n found: number;\n recorded: number;\n duplicates: number;\n awaitingApproval: number;\n findings: readonly DeepsecScanFinding[];\n}\n\n/** Discriminated result returned by the optional Deepsec scanner tool. */\nexport type DeepsecScanResult = DeepsecScanFailure | DeepsecScanSuccess;\n\n/** Eve tool that runs one bounded Deepsec scan and admits its findings. */\nexport interface DeepsecScanTool extends ToolDefinition<DeepsecScanInput, DeepsecScanResult> {\n execute(input: DeepsecScanInput, ctx: ToolContext): Promise<DeepsecScanResult>;\n}\n\nconst findingsPath = \"/tmp/deepsec-findings.json\";\n\n/** Parse \"N file(s) with candidates\" out of deepsec scan output; null when absent. */\nfunction candidateCount(output: string): number | null {\n const match = /(\\d+)\\s+file\\(s\\) with candidates/u.exec(output);\n return match?.[1] === undefined ? null : Number(match[1]);\n}\n\n/** The last few meaningful lines of a command's output, ANSI stripped, for a receipt. */\nfunction tail(output: string, lines = 4): string {\n // oxlint-disable-next-line no-control-regex -- stripping terminal colour codes\n const ansi = /\\u001b\\[[0-9;]*m/gu;\n return output\n .replaceAll(ansi, \"\")\n .split(\"\\n\")\n .map((line) => line.trim())\n .filter((line) => line !== \"\")\n .slice(-lines)\n .join(\" | \")\n .slice(0, 600);\n}\n\n/**\n * Runs deepsec (vercel-labs/deepsec) against the sandbox checkout and records\n * what it finds through the same path as record_finding, so every deepsec\n * finding becomes a fix task subject to the approval policy. Trusted code\n * owns the command sequence and the credential; the agent chooses when to\n * scan and reports the outcome.\n */\nexport function createDeepsecScanTool<S extends CommandSandbox>(\n input: DeepsecScanToolOptions<S>,\n): DeepsecScanTool {\n const { stores, ...options } = input;\n const repoPath = options.repoPath ?? \"repo\";\n const workspace = options.workspacePath ?? \"deepsec\";\n const version = options.version ?? \"2.3.8\";\n const agent = options.agent ?? \"claude\";\n const model = options.model ?? \"claude-sonnet-4-6\";\n const thinkingLevel = options.thinkingLevel ?? \"medium\";\n const concurrency = options.concurrency ?? 4;\n const prefix = options.commandPrefix === undefined ? [] : [options.commandPrefix];\n const filesPerRun = options.filesPerRun ?? 40;\n const now = options.now ?? Date.now;\n\n return defineTool({\n description:\n \"Run deepsec, the AI-powered vulnerability scanner, over the repository checkout and record every finding it reports as a fix task. Takes minutes. Call once per scan; then summarize the result.\",\n inputSchema: deepsecScanInputSchema,\n async execute({ repositoryId, limit }, ctx): Promise<DeepsecScanResult> {\n const repository = await stores.repositories.get(repositoryId);\n if (repository === null) {\n return {\n scanned: false as const,\n code: \"repository_not_found\",\n reason: `repository ${repositoryId} does not exist`,\n };\n }\n const sandbox = (await ctx.getSandbox()) as unknown as S;\n const projectId = repository.slug.replace(/[^a-z0-9]+/gu, \"-\");\n const startedAt = now();\n\n // One receipt per phase, so the ledger answers \"where is the scan\" while\n // deepsec runs and shows what each phase cost afterwards.\n const phase = async (state: string, reason: string): Promise<void> => {\n await stores.receipts.append({\n repositoryIds: [repositoryId],\n kind: \"deepsec\",\n state,\n phase: \"security_scan\",\n reason: `${reason} (${Math.round((now() - startedAt) / 1000)}s in)`,\n });\n };\n await phase(\"started\", `deepsec ${version} on ${repository.slug}, ${agent}/${model}`);\n\n // Workspace: scaffolded once per sandbox, installed from npm. deepsec\n // needs Node 24; the sandbox bootstrap is expected to provide it.\n const prepare = await sandbox.run({\n command: [\n ...prefix,\n \"set -e\",\n `if [ ! -f ${workspace}/deepsec.config.ts ]; then`,\n ` npx -y deepsec@${version} init ./${workspace} ./${repoPath} --id ${projectId} --scaffold-only --headless --yes --skip-install`,\n ` (cd ${workspace} && npm install --no-audit --no-fund --loglevel=error)`,\n \"fi\",\n // deepsec resolves deepsec.config.ts from the current directory, so\n // every deepsec command runs inside the workspace. No --root: it is a\n // one-off override that skips the project's file records, leaving\n // `process` with nothing to investigate.\n `cd ${workspace}`,\n `./node_modules/.bin/deepsec scan --project-id ${projectId}`,\n ].join(\"\\n\"),\n });\n if (typeof prepare.exitCode === \"number\" && prepare.exitCode !== 0) {\n const detail = (prepare.stderr ?? prepare.stdout).trim().slice(-1500);\n await phase(\"failed\", `scan phase failed: ${detail.slice(-300)}`);\n return {\n scanned: false as const,\n code: \"scan_failed\",\n reason: `deepsec scan failed: ${detail}`,\n };\n }\n const candidates = candidateCount(prepare.stdout);\n await phase(\n \"scanned\",\n `${candidates === null ? \"scan output\" : `${candidates} files with candidates`}: ${tail(`${prepare.stdout}\\n${prepare.stderr ?? \"\"}`)}`,\n );\n\n // Investigation talks to the model through the AI Gateway (deepsec's\n // default route); this is the only step that needs a credential.\n const investigate = await options.withModelCredential({\n sandbox,\n command: [\n ...prefix,\n \"set -e\",\n `cd ${workspace}`,\n `./node_modules/.bin/deepsec process --project-id ${projectId} --agent ${agent} --model ${model} --thinking-level ${thinkingLevel} --concurrency ${concurrency} --limit ${limit ?? filesPerRun}`,\n `./node_modules/.bin/deepsec export --project-id ${projectId} --format json --out ${findingsPath}`,\n ].join(\"\\n\"),\n });\n if (typeof investigate.exitCode === \"number\" && investigate.exitCode !== 0) {\n const detail = (investigate.stderr ?? investigate.stdout).trim().slice(-1500);\n await phase(\"failed\", `process phase failed: ${detail.slice(-300)}`);\n return {\n scanned: false as const,\n code: \"process_failed\",\n reason: `deepsec process failed: ${detail}`,\n };\n }\n\n const exported = await sandbox.run({ command: `cat ${findingsPath}` });\n let parsed: unknown;\n try {\n parsed = JSON.parse(exported.stdout);\n } catch {\n return {\n scanned: false as const,\n code: \"invalid_export_json\",\n reason: \"deepsec export was not valid JSON\",\n };\n }\n const raw = deepsecExportSchema.safeParse(parsed);\n if (!raw.success) {\n return {\n scanned: false as const,\n code: \"invalid_export_shape\",\n reason: `deepsec export had an unexpected shape: ${raw.error.issues[0]?.message ?? \"unknown\"}`,\n };\n }\n\n const results: { finding: Finding; result: AdmitFindingResult }[] = [];\n const taskId = optionalTaskIdForSession(ctx.session.auth.initiator);\n const scan = taskId === null ? null : await stores.tasks.get(taskId);\n const root =\n scan && scan.rootTaskId !== scan.id ? await stores.tasks.get(scan.rootTaskId) : scan;\n const replyTo =\n scan?.replyTo.channel === \"slack\" && scan.repositoryIds.includes(repositoryId)\n ? scan.replyTo\n : options.replyTo;\n for (const entry of raw.data) {\n const finding = findingFromDeepsec(entry);\n results.push({\n finding,\n result: await admitFinding(stores, {\n repositoryId,\n finding,\n ...options,\n replyTo,\n operator:\n scan?.repositoryIds.includes(repositoryId) === true\n ? (scan.origin.operator ?? root?.origin.operator)\n : options.operator,\n }),\n });\n }\n const recorded = results.filter(({ result }) => result.recorded);\n await phase(\n \"completed\",\n `${results.length} findings, ${recorded.length} new. process output: ${tail(`${investigate.stdout}\\n${investigate.stderr ?? \"\"}`, 6)}`,\n );\n return {\n scanned: true as const,\n tool: \"deepsec\",\n found: results.length,\n recorded: recorded.length,\n duplicates: results.length - recorded.length,\n awaitingApproval: recorded.filter(\n ({ result }) => result.recorded && result.approval === \"required\",\n ).length,\n findings: results.map(({ finding, result }) => ({\n title: finding.title,\n priority: finding.priority,\n path: finding.path,\n line: finding.line,\n ...(result.recorded\n ? { taskId: result.taskId, approval: result.approval }\n : { duplicate: true }),\n })),\n };\n },\n });\n}\n"],"mappings":";;;;;;;;AA4BA,MAAa,yBAAyB,EAAE,OAAO;CAC7C,cAAc;CACd,OAAO,EACJ,OAAO,CAAC,CACR,IAAI,CAAC,CACL,IAAI,CAAC,CAAC,CACN,IAAI,GAAG,CAAC,CACR,SAAS,CAAC,CACV,SACC,oGACF;AACJ,CAAC;AA8FD,MAAM,eAAe;;AAGrB,SAAS,eAAe,QAA+B;CACrD,MAAM,QAAQ,qCAAqC,KAAK,MAAM;CAC9D,OAAO,QAAQ,OAAO,KAAA,IAAY,OAAO,OAAO,MAAM,EAAE;AAC1D;;AAGA,SAAS,KAAK,QAAgB,QAAQ,GAAW;CAG/C,OAAO,OACJ,WAAW,sBAAM,EAAE,CAAC,CACpB,MAAM,IAAI,CAAC,CACX,KAAK,SAAS,KAAK,KAAK,CAAC,CAAC,CAC1B,QAAQ,SAAS,SAAS,EAAE,CAAC,CAC7B,MAAM,CAAC,KAAK,CAAC,CACb,KAAK,KAAK,CAAC,CACX,MAAM,GAAG,GAAG;AACjB;;;;;;;;AASA,SAAgB,sBACd,OACiB;CACjB,MAAM,EAAE,QAAQ,GAAG,YAAY;CAC/B,MAAM,WAAW,QAAQ,YAAY;CACrC,MAAM,YAAY,QAAQ,iBAAiB;CAC3C,MAAM,UAAU,QAAQ,WAAW;CACnC,MAAM,QAAQ,QAAQ,SAAS;CAC/B,MAAM,QAAQ,QAAQ,SAAS;CAC/B,MAAM,gBAAgB,QAAQ,iBAAiB;CAC/C,MAAM,cAAc,QAAQ,eAAe;CAC3C,MAAM,SAAS,QAAQ,kBAAkB,KAAA,IAAY,CAAC,IAAI,CAAC,QAAQ,aAAa;CAChF,MAAM,cAAc,QAAQ,eAAe;CAC3C,MAAM,MAAM,QAAQ,OAAO,KAAK;CAEhC,OAAO,WAAW;EAChB,aACE;EACF,aAAa;EACb,MAAM,QAAQ,EAAE,cAAc,SAAS,KAAiC;GACtE,MAAM,aAAa,MAAM,OAAO,aAAa,IAAI,YAAY;GAC7D,IAAI,eAAe,MACjB,OAAO;IACL,SAAS;IACT,MAAM;IACN,QAAQ,cAAc,aAAa;GACrC;GAEF,MAAM,UAAW,MAAM,IAAI,WAAW;GACtC,MAAM,YAAY,WAAW,KAAK,QAAQ,gBAAgB,GAAG;GAC7D,MAAM,YAAY,IAAI;GAItB,MAAM,QAAQ,OAAO,OAAe,WAAkC;IACpE,MAAM,OAAO,SAAS,OAAO;KAC3B,eAAe,CAAC,YAAY;KAC5B,MAAM;KACN;KACA,OAAO;KACP,QAAQ,GAAG,OAAO,IAAI,KAAK,OAAO,IAAI,IAAI,aAAa,GAAI,EAAE;IAC/D,CAAC;GACH;GACA,MAAM,MAAM,WAAW,WAAW,QAAQ,MAAM,WAAW,KAAK,IAAI,MAAM,GAAG,OAAO;GAIpF,MAAM,UAAU,MAAM,QAAQ,IAAI,EAChC,SAAS;IACP,GAAG;IACH;IACA,aAAa,UAAU;IACvB,oBAAoB,QAAQ,UAAU,UAAU,KAAK,SAAS,QAAQ,UAAU;IAChF,SAAS,UAAU;IACnB;IAKA,MAAM;IACN,iDAAiD;GACnD,CAAC,CAAC,KAAK,IAAI,EACb,CAAC;GACD,IAAI,OAAO,QAAQ,aAAa,YAAY,QAAQ,aAAa,GAAG;IAClE,MAAM,UAAU,QAAQ,UAAU,QAAQ,OAAA,CAAQ,KAAK,CAAC,CAAC,MAAM,KAAK;IACpE,MAAM,MAAM,UAAU,sBAAsB,OAAO,MAAM,IAAI,GAAG;IAChE,OAAO;KACL,SAAS;KACT,MAAM;KACN,QAAQ,wBAAwB;IAClC;GACF;GACA,MAAM,aAAa,eAAe,QAAQ,MAAM;GAChD,MAAM,MACJ,WACA,GAAG,eAAe,OAAO,gBAAgB,GAAG,WAAW,wBAAwB,IAAI,KAAK,GAAG,QAAQ,OAAO,IAAI,QAAQ,UAAU,IAAI,GACtI;GAIA,MAAM,cAAc,MAAM,QAAQ,oBAAoB;IACpD;IACA,SAAS;KACP,GAAG;KACH;KACA,MAAM;KACN,oDAAoD,UAAU,WAAW,MAAM,WAAW,MAAM,oBAAoB,cAAc,iBAAiB,YAAY,WAAW,SAAS;KACnL,mDAAmD,UAAU,uBAAuB;IACtF,CAAC,CAAC,KAAK,IAAI;GACb,CAAC;GACD,IAAI,OAAO,YAAY,aAAa,YAAY,YAAY,aAAa,GAAG;IAC1E,MAAM,UAAU,YAAY,UAAU,YAAY,OAAA,CAAQ,KAAK,CAAC,CAAC,MAAM,KAAK;IAC5E,MAAM,MAAM,UAAU,yBAAyB,OAAO,MAAM,IAAI,GAAG;IACnE,OAAO;KACL,SAAS;KACT,MAAM;KACN,QAAQ,2BAA2B;IACrC;GACF;GAEA,MAAM,WAAW,MAAM,QAAQ,IAAI,EAAE,SAAS,OAAO,eAAe,CAAC;GACrE,IAAI;GACJ,IAAI;IACF,SAAS,KAAK,MAAM,SAAS,MAAM;GACrC,QAAQ;IACN,OAAO;KACL,SAAS;KACT,MAAM;KACN,QAAQ;IACV;GACF;GACA,MAAM,MAAM,oBAAoB,UAAU,MAAM;GAChD,IAAI,CAAC,IAAI,SACP,OAAO;IACL,SAAS;IACT,MAAM;IACN,QAAQ,2CAA2C,IAAI,MAAM,OAAO,EAAE,EAAE,WAAW;GACrF;GAGF,MAAM,UAA8D,CAAC;GACrE,MAAM,SAAS,yBAAyB,IAAI,QAAQ,KAAK,SAAS;GAClE,MAAM,OAAO,WAAW,OAAO,OAAO,MAAM,OAAO,MAAM,IAAI,MAAM;GACnE,MAAM,OACJ,QAAQ,KAAK,eAAe,KAAK,KAAK,MAAM,OAAO,MAAM,IAAI,KAAK,UAAU,IAAI;GAClF,MAAM,UACJ,MAAM,QAAQ,YAAY,WAAW,KAAK,cAAc,SAAS,YAAY,IACzE,KAAK,UACL,QAAQ;GACd,KAAK,MAAM,SAAS,IAAI,MAAM;IAC5B,MAAM,UAAU,mBAAmB,KAAK;IACxC,QAAQ,KAAK;KACX;KACA,QAAQ,MAAM,aAAa,QAAQ;MACjC;MACA;MACA,GAAG;MACH;MACA,UACE,MAAM,cAAc,SAAS,YAAY,MAAM,OAC1C,KAAK,OAAO,YAAY,MAAM,OAAO,WACtC,QAAQ;KAChB,CAAC;IACH,CAAC;GACH;GACA,MAAM,WAAW,QAAQ,QAAQ,EAAE,aAAa,OAAO,QAAQ;GAC/D,MAAM,MACJ,aACA,GAAG,QAAQ,OAAO,aAAa,SAAS,OAAO,wBAAwB,KAAK,GAAG,YAAY,OAAO,IAAI,YAAY,UAAU,MAAM,CAAC,GACrI;GACA,OAAO;IACL,SAAS;IACT,MAAM;IACN,OAAO,QAAQ;IACf,UAAU,SAAS;IACnB,YAAY,QAAQ,SAAS,SAAS;IACtC,kBAAkB,SAAS,QACxB,EAAE,aAAa,OAAO,YAAY,OAAO,aAAa,UACzD,CAAC,CAAC;IACF,UAAU,QAAQ,KAAK,EAAE,SAAS,cAAc;KAC9C,OAAO,QAAQ;KACf,UAAU,QAAQ;KAClB,MAAM,QAAQ;KACd,MAAM,QAAQ;KACd,GAAI,OAAO,WACP;MAAE,QAAQ,OAAO;MAAQ,UAAU,OAAO;KAAS,IACnD,EAAE,WAAW,KAAK;IACxB,EAAE;GACJ;EACF;CACF,CAAC;AACH"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { Finding } from "../schema/finding.mjs";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
//#region src/deepsec/finding.d.ts
|
|
4
|
+
/** Deepsec's exported finding shape, as far as Factory depends on it. */
|
|
5
|
+
declare const deepsecFindingSchema: z.ZodReadonly<z.ZodObject<{
|
|
6
|
+
title: z.ZodString;
|
|
7
|
+
description: z.ZodString;
|
|
8
|
+
severity: z.ZodString;
|
|
9
|
+
metadata: z.ZodOptional<z.ZodObject<{
|
|
10
|
+
filePath: z.ZodOptional<z.ZodString>;
|
|
11
|
+
lineNumbers: z.ZodOptional<z.ZodReadonly<z.ZodArray<z.ZodNumber>>>;
|
|
12
|
+
vulnSlug: z.ZodOptional<z.ZodString>;
|
|
13
|
+
confidence: z.ZodOptional<z.ZodString>;
|
|
14
|
+
runId: z.ZodOptional<z.ZodString>;
|
|
15
|
+
}, z.core.$loose>>;
|
|
16
|
+
}, z.core.$loose>>;
|
|
17
|
+
/** Deepsec export document accepted by the optional scanner adapter. */
|
|
18
|
+
declare const deepsecExportSchema: z.ZodUnion<readonly [z.ZodReadonly<z.ZodArray<z.ZodReadonly<z.ZodObject<{
|
|
19
|
+
title: z.ZodString;
|
|
20
|
+
description: z.ZodString;
|
|
21
|
+
severity: z.ZodString;
|
|
22
|
+
metadata: z.ZodOptional<z.ZodObject<{
|
|
23
|
+
filePath: z.ZodOptional<z.ZodString>;
|
|
24
|
+
lineNumbers: z.ZodOptional<z.ZodReadonly<z.ZodArray<z.ZodNumber>>>;
|
|
25
|
+
vulnSlug: z.ZodOptional<z.ZodString>;
|
|
26
|
+
confidence: z.ZodOptional<z.ZodString>;
|
|
27
|
+
runId: z.ZodOptional<z.ZodString>;
|
|
28
|
+
}, z.core.$loose>>;
|
|
29
|
+
}, z.core.$loose>>>>, z.ZodPipe<z.ZodObject<{
|
|
30
|
+
findings: z.ZodReadonly<z.ZodArray<z.ZodReadonly<z.ZodObject<{
|
|
31
|
+
title: z.ZodString;
|
|
32
|
+
description: z.ZodString;
|
|
33
|
+
severity: z.ZodString;
|
|
34
|
+
metadata: z.ZodOptional<z.ZodObject<{
|
|
35
|
+
filePath: z.ZodOptional<z.ZodString>;
|
|
36
|
+
lineNumbers: z.ZodOptional<z.ZodReadonly<z.ZodArray<z.ZodNumber>>>;
|
|
37
|
+
vulnSlug: z.ZodOptional<z.ZodString>;
|
|
38
|
+
confidence: z.ZodOptional<z.ZodString>;
|
|
39
|
+
runId: z.ZodOptional<z.ZodString>;
|
|
40
|
+
}, z.core.$loose>>;
|
|
41
|
+
}, z.core.$loose>>>>;
|
|
42
|
+
}, z.core.$loose>, z.ZodTransform<readonly Readonly<{
|
|
43
|
+
[x: string]: unknown;
|
|
44
|
+
title: string;
|
|
45
|
+
description: string;
|
|
46
|
+
severity: string;
|
|
47
|
+
metadata?: {
|
|
48
|
+
[x: string]: unknown;
|
|
49
|
+
filePath?: string | undefined;
|
|
50
|
+
lineNumbers?: readonly number[] | undefined;
|
|
51
|
+
vulnSlug?: string | undefined;
|
|
52
|
+
confidence?: string | undefined;
|
|
53
|
+
runId?: string | undefined;
|
|
54
|
+
} | undefined;
|
|
55
|
+
}>[], {
|
|
56
|
+
[x: string]: unknown;
|
|
57
|
+
findings: readonly Readonly<{
|
|
58
|
+
[x: string]: unknown;
|
|
59
|
+
title: string;
|
|
60
|
+
description: string;
|
|
61
|
+
severity: string;
|
|
62
|
+
metadata?: {
|
|
63
|
+
[x: string]: unknown;
|
|
64
|
+
filePath?: string | undefined;
|
|
65
|
+
lineNumbers?: readonly number[] | undefined;
|
|
66
|
+
vulnSlug?: string | undefined;
|
|
67
|
+
confidence?: string | undefined;
|
|
68
|
+
runId?: string | undefined;
|
|
69
|
+
} | undefined;
|
|
70
|
+
}>[];
|
|
71
|
+
}>>]>;
|
|
72
|
+
/** Provider payload accepted for one Deepsec finding before normalization. */
|
|
73
|
+
type DeepsecFindingInput = z.input<typeof deepsecFindingSchema>;
|
|
74
|
+
/** Parsed Deepsec finding payload used by the normalization adapter. */
|
|
75
|
+
type DeepsecFinding = z.output<typeof deepsecFindingSchema>;
|
|
76
|
+
/** Provider export document accepted before its wrapper is normalized. */
|
|
77
|
+
type DeepsecExportInput = z.input<typeof deepsecExportSchema>;
|
|
78
|
+
/** Parsed list of Deepsec findings produced by the export schema. */
|
|
79
|
+
type DeepsecExport = z.output<typeof deepsecExportSchema>;
|
|
80
|
+
/** Validate and convert one Deepsec finding into Factory's normalized Finding contract. */
|
|
81
|
+
declare function findingFromDeepsec(input: DeepsecFindingInput): Finding;
|
|
82
|
+
//#endregion
|
|
83
|
+
export { DeepsecExport, DeepsecExportInput, DeepsecFinding, DeepsecFindingInput, deepsecExportSchema, deepsecFindingSchema, findingFromDeepsec };
|
|
84
|
+
//# sourceMappingURL=finding.d.mts.map
|