@elie-laloum/outpost 3.0.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/README.md +3 -1
- package/SECURITY.md +3 -1
- package/dist/adapters/agents/gemini-adapter.d.ts +3 -0
- package/dist/adapters/agents/gemini-adapter.js +15 -0
- package/dist/adapters/agents/gemini-adapter.js.map +1 -0
- package/dist/adapters/agents/gemini-diagnostics.d.ts +2 -0
- package/dist/adapters/agents/gemini-diagnostics.js +15 -0
- package/dist/adapters/agents/gemini-diagnostics.js.map +1 -0
- package/dist/adapters/agents/gemini-events.d.ts +2 -0
- package/dist/adapters/agents/gemini-events.js +50 -0
- package/dist/adapters/agents/gemini-events.js.map +1 -0
- package/dist/adapters/agents/gemini-request.d.ts +4 -0
- package/dist/adapters/agents/gemini-request.js +21 -0
- package/dist/adapters/agents/gemini-request.js.map +1 -0
- package/dist/adapters/agents/gemini.types.d.ts +6 -0
- package/dist/adapters/agents/gemini.types.js +2 -0
- package/dist/adapters/agents/gemini.types.js.map +1 -0
- package/dist/adapters/agents/protocol-fixtures.constants.d.ts +1 -1
- package/dist/adapters/agents/protocol-fixtures.constants.js +37 -0
- package/dist/adapters/agents/protocol-fixtures.constants.js.map +1 -1
- package/dist/application/agent-bootstrap.constants.d.ts +1 -0
- package/dist/application/agent-bootstrap.constants.js +1 -0
- package/dist/application/agent-bootstrap.constants.js.map +1 -1
- package/dist/application/agent-bootstrap.js +6 -2
- package/dist/application/agent-bootstrap.js.map +1 -1
- package/dist/application/agent-output.js +9 -2
- package/dist/application/agent-output.js.map +1 -1
- package/dist/application/artifact-tasks.d.ts +5 -0
- package/dist/application/artifact-tasks.js +31 -0
- package/dist/application/artifact-tasks.js.map +1 -0
- package/dist/application/artifact-tasks.types.d.ts +8 -0
- package/dist/application/artifact-tasks.types.js +2 -0
- package/dist/application/artifact-tasks.types.js.map +1 -0
- package/dist/application/dispatch-validation.js +2 -0
- package/dist/application/dispatch-validation.js.map +1 -1
- package/dist/application/doctor-agent.constants.d.ts +2 -0
- package/dist/application/doctor-agent.constants.js +2 -0
- package/dist/application/doctor-agent.constants.js.map +1 -1
- package/dist/application/doctor-agent.js +1 -1
- package/dist/application/doctor-agent.js.map +1 -1
- package/dist/application/doctor-protocol.js +2 -1
- package/dist/application/doctor-protocol.js.map +1 -1
- package/dist/application/doctor.constants.js +1 -1
- package/dist/application/doctor.constants.js.map +1 -1
- package/dist/application/doctor.types.d.ts +1 -1
- package/dist/application/doctor.types.js.map +1 -1
- package/dist/application/outpost.types.d.ts +2 -0
- package/dist/application/outpost.types.js.map +1 -1
- package/dist/application/queue-worker.d.ts +2 -0
- package/dist/application/queue-worker.js +75 -0
- package/dist/application/queue-worker.js.map +1 -0
- package/dist/application/queue-worker.types.d.ts +15 -0
- package/dist/application/queue-worker.types.js +2 -0
- package/dist/application/queue-worker.types.js.map +1 -0
- package/dist/application/queued-task.d.ts +3 -0
- package/dist/application/queued-task.js +56 -0
- package/dist/application/queued-task.js.map +1 -0
- package/dist/application/queued-task.types.d.ts +11 -0
- package/dist/application/queued-task.types.js +2 -0
- package/dist/application/queued-task.types.js.map +1 -0
- package/dist/application/recovery-inspection.js +5 -0
- package/dist/application/recovery-inspection.js.map +1 -1
- package/dist/application/recovery-inspection.types.d.ts +3 -0
- package/dist/application/recovery-inspection.types.js.map +1 -1
- package/dist/application/recovery-restore-snapshot.d.ts +2 -0
- package/dist/application/recovery-restore-snapshot.js +57 -0
- package/dist/application/recovery-restore-snapshot.js.map +1 -0
- package/dist/application/recovery-restore.d.ts +3 -0
- package/dist/application/recovery-restore.js +161 -0
- package/dist/application/recovery-restore.js.map +1 -0
- package/dist/application/recovery-restore.types.d.ts +28 -0
- package/dist/application/recovery-restore.types.js +2 -0
- package/dist/application/recovery-restore.types.js.map +1 -0
- package/dist/application/recovery-retention.js +5 -0
- package/dist/application/recovery-retention.js.map +1 -1
- package/dist/application/recovery-verification.d.ts +2 -1
- package/dist/application/recovery-verification.js +1 -1
- package/dist/application/recovery-verification.js.map +1 -1
- package/dist/application/remote-apply.js +6 -0
- package/dist/application/remote-apply.js.map +1 -1
- package/dist/application/remote-download.js +2 -2
- package/dist/application/remote-download.js.map +1 -1
- package/dist/application/remote-history.d.ts +6 -0
- package/dist/application/remote-history.js +114 -0
- package/dist/application/remote-history.js.map +1 -0
- package/dist/application/remote-upload.d.ts +2 -0
- package/dist/application/remote-upload.js +17 -0
- package/dist/application/remote-upload.js.map +1 -0
- package/dist/application/remote-validation.js +20 -28
- package/dist/application/remote-validation.js.map +1 -1
- package/dist/application/remote-workspace.js +4 -10
- package/dist/application/remote-workspace.js.map +1 -1
- package/dist/application/sandbox-activity.d.ts +3 -0
- package/dist/application/sandbox-activity.js +25 -0
- package/dist/application/sandbox-activity.js.map +1 -0
- package/dist/application/sandbox-provision.js +55 -10
- package/dist/application/sandbox-provision.js.map +1 -1
- package/dist/application/sandbox-session.types.d.ts +2 -0
- package/dist/application/sandbox-session.types.js.map +1 -1
- package/dist/application/sandbox.js +23 -11
- package/dist/application/sandbox.js.map +1 -1
- package/dist/application/speculation-host.d.ts +2 -0
- package/dist/application/speculation-host.js +30 -0
- package/dist/application/speculation-host.js.map +1 -0
- package/dist/application/speculation.constants.d.ts +4 -0
- package/dist/application/speculation.constants.js +2 -0
- package/dist/application/speculation.constants.js.map +1 -0
- package/dist/application/speculation.d.ts +2 -0
- package/dist/application/speculation.js +166 -0
- package/dist/application/speculation.js.map +1 -0
- package/dist/application/speculation.types.d.ts +56 -0
- package/dist/application/speculation.types.js +2 -0
- package/dist/application/speculation.types.js.map +1 -0
- package/dist/application/storage-reservation.d.ts +3 -0
- package/dist/application/storage-reservation.js +10 -0
- package/dist/application/storage-reservation.js.map +1 -0
- package/dist/application/storage-reservation.types.d.ts +4 -0
- package/dist/application/storage-reservation.types.js +2 -0
- package/dist/application/storage-reservation.types.js.map +1 -0
- package/dist/application/task-usage.d.ts +1 -1
- package/dist/application/task-usage.js.map +1 -1
- package/dist/application/tasks.js +1 -1
- package/dist/application/tasks.js.map +1 -1
- package/dist/application/tasks.types.d.ts +4 -3
- package/dist/application/tasks.types.js.map +1 -1
- package/dist/application/workspace-allocation.d.ts +3 -0
- package/dist/application/workspace-allocation.js +34 -0
- package/dist/application/workspace-allocation.js.map +1 -0
- package/dist/application/workspace.js +2 -2
- package/dist/application/workspace.js.map +1 -1
- package/dist/cli/doctor-command.js +1 -1
- package/dist/cli/doctor-command.js.map +1 -1
- package/dist/cli/main.constants.d.ts +11 -2
- package/dist/cli/main.constants.js +8 -4
- package/dist/cli/main.constants.js.map +1 -1
- package/dist/cli/recovery-command.js +28 -4
- package/dist/cli/recovery-command.js.map +1 -1
- package/dist/cli/recovery-restore-command.d.ts +2 -0
- package/dist/cli/recovery-restore-command.js +34 -0
- package/dist/cli/recovery-restore-command.js.map +1 -0
- package/dist/cli/scaffold-files.js +2 -2
- package/dist/cli/scaffold-files.js.map +1 -1
- package/dist/cli/scaffold-validation.js +1 -1
- package/dist/cli/scaffold-validation.js.map +1 -1
- package/dist/cli/scaffold.constants.d.ts +5 -0
- package/dist/cli/scaffold.constants.js +7 -2
- package/dist/cli/scaffold.constants.js.map +1 -1
- package/dist/cli/scaffold.types.d.ts +1 -1
- package/dist/cli/scaffold.types.js.map +1 -1
- package/dist/domain/agent.types.d.ts +2 -0
- package/dist/domain/agent.types.js.map +1 -1
- package/dist/domain/artifact-reference.d.ts +4 -0
- package/dist/domain/artifact-reference.js +90 -0
- package/dist/domain/artifact-reference.js.map +1 -0
- package/dist/domain/artifact.constants.d.ts +2 -0
- package/dist/domain/artifact.constants.js +3 -0
- package/dist/domain/artifact.constants.js.map +1 -0
- package/dist/domain/artifact.d.ts +7 -0
- package/dist/domain/artifact.js +104 -0
- package/dist/domain/artifact.js.map +1 -0
- package/dist/domain/artifact.types.d.ts +44 -0
- package/dist/domain/artifact.types.js +2 -0
- package/dist/domain/artifact.types.js.map +1 -0
- package/dist/domain/egress.constants.d.ts +1 -0
- package/dist/domain/egress.constants.js +7 -0
- package/dist/domain/egress.constants.js.map +1 -0
- package/dist/domain/egress.d.ts +2 -0
- package/dist/domain/egress.js +48 -0
- package/dist/domain/egress.js.map +1 -0
- package/dist/domain/egress.types.d.ts +8 -0
- package/dist/domain/egress.types.js +2 -0
- package/dist/domain/egress.types.js.map +1 -0
- package/dist/domain/sandbox.types.d.ts +1 -0
- package/dist/domain/sandbox.types.js.map +1 -1
- package/dist/domain/task-queue.constants.d.ts +8 -0
- package/dist/domain/task-queue.constants.js +9 -0
- package/dist/domain/task-queue.constants.js.map +1 -0
- package/dist/domain/task-queue.d.ts +11 -0
- package/dist/domain/task-queue.js +105 -0
- package/dist/domain/task-queue.js.map +1 -0
- package/dist/domain/task-queue.types.d.ts +41 -0
- package/dist/domain/task-queue.types.js +2 -0
- package/dist/domain/task-queue.types.js.map +1 -0
- package/dist/domain/workflow/budget.d.ts +2 -2
- package/dist/domain/workflow/budget.js +5 -3
- package/dist/domain/workflow/budget.js.map +1 -1
- package/dist/domain/workflow/checkpoint-validation.d.ts +3 -0
- package/dist/domain/workflow/checkpoint-validation.js +81 -0
- package/dist/domain/workflow/checkpoint-validation.js.map +1 -0
- package/dist/domain/workflow/checkpoint-value.d.ts +2 -0
- package/dist/domain/workflow/checkpoint-value.js +49 -0
- package/dist/domain/workflow/checkpoint-value.js.map +1 -0
- package/dist/domain/workflow/checkpoint.d.ts +3 -0
- package/dist/domain/workflow/checkpoint.js +70 -0
- package/dist/domain/workflow/checkpoint.js.map +1 -0
- package/dist/domain/workflow/checkpoint.types.d.ts +40 -0
- package/dist/domain/workflow/checkpoint.types.js +2 -0
- package/dist/domain/workflow/checkpoint.types.js.map +1 -0
- package/dist/domain/workflow/gate-validation.d.ts +4 -0
- package/dist/domain/workflow/gate-validation.js +71 -0
- package/dist/domain/workflow/gate-validation.js.map +1 -0
- package/dist/domain/workflow/gates.constants.d.ts +4 -0
- package/dist/domain/workflow/gates.constants.js +5 -0
- package/dist/domain/workflow/gates.constants.js.map +1 -0
- package/dist/domain/workflow/gates.d.ts +7 -0
- package/dist/domain/workflow/gates.js +85 -0
- package/dist/domain/workflow/gates.js.map +1 -0
- package/dist/domain/workflow/gates.types.d.ts +27 -0
- package/dist/domain/workflow/gates.types.js +2 -0
- package/dist/domain/workflow/gates.types.js.map +1 -0
- package/dist/domain/workflow/task-runner.js +14 -2
- package/dist/domain/workflow/task-runner.js.map +1 -1
- package/dist/domain/workflow/usage-receipt.constants.d.ts +2 -0
- package/dist/domain/workflow/usage-receipt.constants.js +3 -0
- package/dist/domain/workflow/usage-receipt.constants.js.map +1 -0
- package/dist/domain/workflow/usage-receipt.d.ts +2 -0
- package/dist/domain/workflow/usage-receipt.js +18 -0
- package/dist/domain/workflow/usage-receipt.js.map +1 -0
- package/dist/domain/workflow/validation.js +6 -0
- package/dist/domain/workflow/validation.js.map +1 -1
- package/dist/domain/workflow/workflow-scheduler.js +65 -32
- package/dist/domain/workflow/workflow-scheduler.js.map +1 -1
- package/dist/domain/workflow/workflow-state.d.ts +2 -1
- package/dist/domain/workflow/workflow-state.js +91 -16
- package/dist/domain/workflow/workflow-state.js.map +1 -1
- package/dist/domain/workflow.types.d.ts +13 -2
- package/dist/domain/workflow.types.js.map +1 -1
- package/dist/index.d.ts +32 -2
- package/dist/index.js +14 -1
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/artifact-store.constants.d.ts +1 -0
- package/dist/infrastructure/artifact-store.constants.js +2 -0
- package/dist/infrastructure/artifact-store.constants.js.map +1 -0
- package/dist/infrastructure/artifact-store.d.ts +3 -0
- package/dist/infrastructure/artifact-store.js +100 -0
- package/dist/infrastructure/artifact-store.js.map +1 -0
- package/dist/infrastructure/artifact-store.types.d.ts +4 -0
- package/dist/infrastructure/artifact-store.types.js +2 -0
- package/dist/infrastructure/artifact-store.types.js.map +1 -0
- package/dist/infrastructure/file-manifest.d.ts +1 -1
- package/dist/infrastructure/file-manifest.js +3 -2
- package/dist/infrastructure/file-manifest.js.map +1 -1
- package/dist/infrastructure/git/verification-command.d.ts +1 -1
- package/dist/infrastructure/git/verification-command.js +3 -2
- package/dist/infrastructure/git/verification-command.js.map +1 -1
- package/dist/infrastructure/opentelemetry.js +1 -1
- package/dist/infrastructure/opentelemetry.js.map +1 -1
- package/dist/infrastructure/resource-activity-inspection.d.ts +2 -0
- package/dist/infrastructure/resource-activity-inspection.js +152 -0
- package/dist/infrastructure/resource-activity-inspection.js.map +1 -0
- package/dist/infrastructure/resource-activity.constants.d.ts +8 -0
- package/dist/infrastructure/resource-activity.constants.js +26 -0
- package/dist/infrastructure/resource-activity.constants.js.map +1 -0
- package/dist/infrastructure/resource-activity.d.ts +2 -0
- package/dist/infrastructure/resource-activity.js +166 -0
- package/dist/infrastructure/resource-activity.js.map +1 -0
- package/dist/infrastructure/resource-activity.types.d.ts +53 -0
- package/dist/infrastructure/resource-activity.types.js +2 -0
- package/dist/infrastructure/resource-activity.types.js.map +1 -0
- package/dist/infrastructure/storage-mutation-lock.constants.d.ts +5 -0
- package/dist/infrastructure/storage-mutation-lock.constants.js +6 -0
- package/dist/infrastructure/storage-mutation-lock.constants.js.map +1 -0
- package/dist/infrastructure/storage-mutation-lock.d.ts +1 -0
- package/dist/infrastructure/storage-mutation-lock.js +21 -0
- package/dist/infrastructure/storage-mutation-lock.js.map +1 -0
- package/dist/infrastructure/storage-reservations.constants.d.ts +4 -0
- package/dist/infrastructure/storage-reservations.constants.js +5 -0
- package/dist/infrastructure/storage-reservations.constants.js.map +1 -0
- package/dist/infrastructure/storage-reservations.d.ts +2 -0
- package/dist/infrastructure/storage-reservations.js +150 -0
- package/dist/infrastructure/storage-reservations.js.map +1 -0
- package/dist/infrastructure/storage-reservations.types.d.ts +21 -0
- package/dist/infrastructure/storage-reservations.types.js +2 -0
- package/dist/infrastructure/storage-reservations.types.js.map +1 -0
- package/dist/infrastructure/task-queue-http.constants.d.ts +2 -0
- package/dist/infrastructure/task-queue-http.constants.js +3 -0
- package/dist/infrastructure/task-queue-http.constants.js.map +1 -0
- package/dist/infrastructure/task-queue-http.d.ts +4 -0
- package/dist/infrastructure/task-queue-http.js +158 -0
- package/dist/infrastructure/task-queue-http.js.map +1 -0
- package/dist/infrastructure/task-queue-http.types.d.ts +16 -0
- package/dist/infrastructure/task-queue-http.types.js +2 -0
- package/dist/infrastructure/task-queue-http.types.js.map +1 -0
- package/dist/infrastructure/task-queue.d.ts +2 -0
- package/dist/infrastructure/task-queue.js +134 -0
- package/dist/infrastructure/task-queue.js.map +1 -0
- package/dist/infrastructure/transfer.js +5 -0
- package/dist/infrastructure/transfer.js.map +1 -1
- package/dist/infrastructure/workflow-checkpoint.constants.d.ts +1 -0
- package/dist/infrastructure/workflow-checkpoint.constants.js +2 -0
- package/dist/infrastructure/workflow-checkpoint.constants.js.map +1 -0
- package/dist/infrastructure/workflow-checkpoint.d.ts +3 -0
- package/dist/infrastructure/workflow-checkpoint.js +77 -0
- package/dist/infrastructure/workflow-checkpoint.js.map +1 -0
- package/dist/infrastructure/workflow-checkpoint.types.d.ts +4 -0
- package/dist/infrastructure/workflow-checkpoint.types.js +2 -0
- package/dist/infrastructure/workflow-checkpoint.types.js.map +1 -0
- package/dist/providers/agents.d.ts +2 -0
- package/dist/providers/agents.js +1 -0
- package/dist/providers/agents.js.map +1 -1
- package/dist/providers/container-isolation.d.ts +2 -0
- package/dist/providers/container-isolation.js +29 -0
- package/dist/providers/container-isolation.js.map +1 -0
- package/dist/providers/container-mounts.js +7 -2
- package/dist/providers/container-mounts.js.map +1 -1
- package/dist/providers/container-network.d.ts +2 -0
- package/dist/providers/container-network.js +14 -0
- package/dist/providers/container-network.js.map +1 -0
- package/dist/providers/container-plan.js +4 -2
- package/dist/providers/container-plan.js.map +1 -1
- package/dist/providers/container.constants.d.ts +1 -0
- package/dist/providers/container.constants.js +1 -0
- package/dist/providers/container.constants.js.map +1 -1
- package/dist/providers/container.js +19 -2
- package/dist/providers/container.js.map +1 -1
- package/dist/providers/container.types.d.ts +3 -0
- package/dist/providers/container.types.js.map +1 -1
- package/dist/providers/daytona-command.js +3 -2
- package/dist/providers/daytona-command.js.map +1 -1
- package/dist/providers/daytona-terminal.d.ts +3 -0
- package/dist/providers/daytona-terminal.js +128 -0
- package/dist/providers/daytona-terminal.js.map +1 -0
- package/dist/providers/daytona.js +2 -0
- package/dist/providers/daytona.js.map +1 -1
- package/dist/providers/docker.d.ts +1 -0
- package/dist/providers/docker.js.map +1 -1
- package/dist/providers/file-batches.d.ts +1 -1
- package/dist/providers/file-batches.js +9 -0
- package/dist/providers/file-batches.js.map +1 -1
- package/dist/providers/file-upload.constants.d.ts +1 -0
- package/dist/providers/file-upload.constants.js +68 -0
- package/dist/providers/file-upload.constants.js.map +1 -0
- package/dist/providers/file-upload.d.ts +2 -0
- package/dist/providers/file-upload.js +146 -0
- package/dist/providers/file-upload.js.map +1 -0
- package/dist/providers/firecracker-command.d.ts +5 -0
- package/dist/providers/firecracker-command.js +114 -0
- package/dist/providers/firecracker-command.js.map +1 -0
- package/dist/providers/firecracker-files.constants.d.ts +1 -0
- package/dist/providers/firecracker-files.constants.js +21 -0
- package/dist/providers/firecracker-files.constants.js.map +1 -0
- package/dist/providers/firecracker-files.d.ts +3 -0
- package/dist/providers/firecracker-files.js +110 -0
- package/dist/providers/firecracker-files.js.map +1 -0
- package/dist/providers/firecracker-machine.d.ts +3 -0
- package/dist/providers/firecracker-machine.js +155 -0
- package/dist/providers/firecracker-machine.js.map +1 -0
- package/dist/providers/firecracker.constants.d.ts +10 -0
- package/dist/providers/firecracker.constants.js +11 -0
- package/dist/providers/firecracker.constants.js.map +1 -0
- package/dist/providers/firecracker.d.ts +4 -0
- package/dist/providers/firecracker.js +103 -0
- package/dist/providers/firecracker.js.map +1 -0
- package/dist/providers/firecracker.types.d.ts +36 -0
- package/dist/providers/firecracker.types.js +2 -0
- package/dist/providers/firecracker.types.js.map +1 -0
- package/dist/providers/local.js +2 -1
- package/dist/providers/local.js.map +1 -1
- package/dist/providers/podman.d.ts +1 -0
- package/dist/providers/podman.js.map +1 -1
- package/dist/providers/terminal-bridge.d.ts +14 -0
- package/dist/providers/terminal-bridge.js +100 -0
- package/dist/providers/terminal-bridge.js.map +1 -0
- package/dist/providers/vercel-directory.d.ts +2 -0
- package/dist/providers/vercel-directory.js +7 -0
- package/dist/providers/vercel-directory.js.map +1 -0
- package/dist/providers/vercel-files.js +3 -2
- package/dist/providers/vercel-files.js.map +1 -1
- package/dist/providers/vercel-network.d.ts +2 -0
- package/dist/providers/vercel-network.js +18 -0
- package/dist/providers/vercel-network.js.map +1 -0
- package/dist/providers/vercel.d.ts +1 -0
- package/dist/providers/vercel.js +5 -1
- package/dist/providers/vercel.js.map +1 -1
- package/dist/providers/vercel.types.d.ts +2 -0
- package/dist/providers/vercel.types.js.map +1 -1
- package/dist/providers/versions.constants.d.ts +1 -0
- package/dist/providers/versions.constants.js +1 -0
- package/dist/providers/versions.constants.js.map +1 -1
- package/package.json +5 -1
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { task } from "./task.js";
|
|
3
|
+
import { gateActions } from "./gates.constants.js";
|
|
4
|
+
import { validateGate } from "./gate-validation.js";
|
|
5
|
+
function gateTask(kind, options) {
|
|
6
|
+
validateGate({ kind, prompt: options.prompt, actors: options.actors });
|
|
7
|
+
return task({
|
|
8
|
+
key: options.key,
|
|
9
|
+
after: options.after ?? [],
|
|
10
|
+
gate: Object.freeze({
|
|
11
|
+
kind,
|
|
12
|
+
prompt: options.prompt,
|
|
13
|
+
actors: Object.freeze([...options.actors]),
|
|
14
|
+
}),
|
|
15
|
+
perform() {
|
|
16
|
+
throw new Error("Workflow gates must be scheduled with a checkpoint");
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
export function approvalTask(options) {
|
|
21
|
+
return gateTask("approval", options);
|
|
22
|
+
}
|
|
23
|
+
export function pauseTask(options) {
|
|
24
|
+
return gateTask("pause", options);
|
|
25
|
+
}
|
|
26
|
+
export async function pauseGate(item, runtime) {
|
|
27
|
+
const gate = item.gate;
|
|
28
|
+
runtime.record(item).pause = Object.freeze({
|
|
29
|
+
...gate,
|
|
30
|
+
id: randomUUID(),
|
|
31
|
+
requestedAt: new Date().toISOString(),
|
|
32
|
+
});
|
|
33
|
+
runtime.finish(item, "paused");
|
|
34
|
+
await runtime.persist();
|
|
35
|
+
}
|
|
36
|
+
export function prepareDecisions(runtime) {
|
|
37
|
+
const decisions = (runtime.options.decisions ?? []).map((decision) => Object.freeze({
|
|
38
|
+
executionId: decision.executionId,
|
|
39
|
+
key: decision.key,
|
|
40
|
+
requestId: decision.requestId,
|
|
41
|
+
action: decision.action,
|
|
42
|
+
actor: decision.actor,
|
|
43
|
+
reason: decision.reason,
|
|
44
|
+
decidedAt: new Date().toISOString(),
|
|
45
|
+
}));
|
|
46
|
+
const pending = new Map([...runtime.records.keys()].map((item) => [item.key, item]));
|
|
47
|
+
const seen = new Set();
|
|
48
|
+
for (const decision of decisions) {
|
|
49
|
+
const item = pending.get(decision.key);
|
|
50
|
+
const record = item && runtime.record(item);
|
|
51
|
+
const request = record?.pause;
|
|
52
|
+
if (!record ||
|
|
53
|
+
!request ||
|
|
54
|
+
record.status !== "paused" ||
|
|
55
|
+
record.decision ||
|
|
56
|
+
decision.executionId !== runtime.executionId ||
|
|
57
|
+
decision.requestId !== request.id ||
|
|
58
|
+
seen.has(decision.key) ||
|
|
59
|
+
typeof decision.actor !== "string" ||
|
|
60
|
+
!request.actors.includes(decision.actor) ||
|
|
61
|
+
typeof decision.reason !== "string" ||
|
|
62
|
+
!decision.reason.trim() ||
|
|
63
|
+
![gateActions[request.kind], "reject"].includes(decision.action))
|
|
64
|
+
throw new Error(`Invalid or unauthorized workflow decision: ${decision.key}`);
|
|
65
|
+
seen.add(decision.key);
|
|
66
|
+
}
|
|
67
|
+
return decisions;
|
|
68
|
+
}
|
|
69
|
+
export function applyDecisions(runtime, decisions) {
|
|
70
|
+
const pending = new Map([...runtime.records.keys()].map((item) => [item.key, item]));
|
|
71
|
+
for (const decision of decisions) {
|
|
72
|
+
const item = pending.get(decision.key);
|
|
73
|
+
const record = runtime.record(item);
|
|
74
|
+
record.decision = decision;
|
|
75
|
+
if (decision.action === "reject") {
|
|
76
|
+
record.error = `${item.key} rejected by ${decision.actor}: ${decision.reason}`;
|
|
77
|
+
runtime.errors.push(new Error(record.error));
|
|
78
|
+
runtime.finish(item, "rejected");
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
runtime.values.set(item, decision);
|
|
82
|
+
runtime.finish(item, "done");
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=gates.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gates.js","sourceRoot":"","sources":["../../../src/domain/workflow/gates.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport type { Task, WorkflowExecutionState } from \"../workflow.types.ts\";\nimport type {\n WorkflowDecisionRecord,\n WorkflowGate,\n WorkflowGateOptions,\n} from \"./gates.types.ts\";\nimport { task } from \"./task.ts\";\nimport { gateActions } from \"./gates.constants.ts\";\nimport { validateGate } from \"./gate-validation.ts\";\n\nfunction gateTask(\n kind: WorkflowGate[\"kind\"],\n options: WorkflowGateOptions,\n): Task<WorkflowDecisionRecord> {\n validateGate({ kind, prompt: options.prompt, actors: options.actors });\n return task({\n key: options.key,\n after: options.after ?? [],\n gate: Object.freeze({\n kind,\n prompt: options.prompt,\n actors: Object.freeze([...options.actors]),\n }),\n perform() {\n throw new Error(\"Workflow gates must be scheduled with a checkpoint\");\n },\n });\n}\n\nexport function approvalTask(\n options: WorkflowGateOptions,\n): Task<WorkflowDecisionRecord> {\n return gateTask(\"approval\", options);\n}\n\nexport function pauseTask(\n options: WorkflowGateOptions,\n): Task<WorkflowDecisionRecord> {\n return gateTask(\"pause\", options);\n}\n\nexport async function pauseGate(\n item: Task,\n runtime: WorkflowExecutionState,\n): Promise<void> {\n const gate = item.gate!;\n runtime.record(item).pause = Object.freeze({\n ...gate,\n id: randomUUID(),\n requestedAt: new Date().toISOString(),\n });\n runtime.finish(item, \"paused\");\n await runtime.persist();\n}\n\nexport function prepareDecisions(\n runtime: WorkflowExecutionState,\n): readonly WorkflowDecisionRecord[] {\n const decisions = (runtime.options.decisions ?? []).map((decision) =>\n Object.freeze({\n executionId: decision.executionId,\n key: decision.key,\n requestId: decision.requestId,\n action: decision.action,\n actor: decision.actor,\n reason: decision.reason,\n decidedAt: new Date().toISOString(),\n }),\n );\n const pending = new Map(\n [...runtime.records.keys()].map((item) => [item.key, item]),\n );\n const seen = new Set<string>();\n for (const decision of decisions) {\n const item = pending.get(decision.key);\n const record = item && runtime.record(item);\n const request = record?.pause;\n if (\n !record ||\n !request ||\n record.status !== \"paused\" ||\n record.decision ||\n decision.executionId !== runtime.executionId ||\n decision.requestId !== request.id ||\n seen.has(decision.key) ||\n typeof decision.actor !== \"string\" ||\n !request.actors.includes(decision.actor) ||\n typeof decision.reason !== \"string\" ||\n !decision.reason.trim() ||\n ![gateActions[request.kind], \"reject\"].includes(decision.action)\n )\n throw new Error(\n `Invalid or unauthorized workflow decision: ${decision.key}`,\n );\n seen.add(decision.key);\n }\n return decisions;\n}\n\nexport function applyDecisions(\n runtime: WorkflowExecutionState,\n decisions: readonly WorkflowDecisionRecord[],\n): void {\n const pending = new Map(\n [...runtime.records.keys()].map((item) => [item.key, item]),\n );\n for (const decision of decisions) {\n const item = pending.get(decision.key)!;\n const record = runtime.record(item);\n record.decision = decision;\n if (decision.action === \"reject\") {\n record.error = `${item.key} rejected by ${decision.actor}: ${decision.reason}`;\n runtime.errors.push(new Error(record.error));\n runtime.finish(item, \"rejected\");\n continue;\n }\n runtime.values.set(item, decision);\n runtime.finish(item, \"done\");\n }\n}\n"]}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Task } from "../workflow.types.ts";
|
|
2
|
+
export interface WorkflowGate {
|
|
3
|
+
readonly kind: "approval" | "pause";
|
|
4
|
+
readonly prompt: string;
|
|
5
|
+
readonly actors: readonly string[];
|
|
6
|
+
}
|
|
7
|
+
export interface WorkflowGateOptions {
|
|
8
|
+
readonly key: string;
|
|
9
|
+
readonly after?: readonly Task[];
|
|
10
|
+
readonly prompt: string;
|
|
11
|
+
readonly actors: readonly string[];
|
|
12
|
+
}
|
|
13
|
+
export interface WorkflowPauseRequest extends WorkflowGate {
|
|
14
|
+
readonly id: string;
|
|
15
|
+
readonly requestedAt: string;
|
|
16
|
+
}
|
|
17
|
+
export interface WorkflowDecision {
|
|
18
|
+
readonly executionId: string;
|
|
19
|
+
readonly key: string;
|
|
20
|
+
readonly requestId: string;
|
|
21
|
+
readonly action: "approve" | "resume" | "reject";
|
|
22
|
+
readonly actor: string;
|
|
23
|
+
readonly reason: string;
|
|
24
|
+
}
|
|
25
|
+
export interface WorkflowDecisionRecord extends WorkflowDecision {
|
|
26
|
+
readonly decidedAt: string;
|
|
27
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gates.types.js","sourceRoot":"","sources":["../../../src/domain/workflow/gates.types.ts"],"names":[],"mappings":"","sourcesContent":["import type { Task } from \"../workflow.types.ts\";\n\nexport interface WorkflowGate {\n readonly kind: \"approval\" | \"pause\";\n readonly prompt: string;\n readonly actors: readonly string[];\n}\n\nexport interface WorkflowGateOptions {\n readonly key: string;\n readonly after?: readonly Task[];\n readonly prompt: string;\n readonly actors: readonly string[];\n}\n\nexport interface WorkflowPauseRequest extends WorkflowGate {\n readonly id: string;\n readonly requestedAt: string;\n}\n\nexport interface WorkflowDecision {\n readonly executionId: string;\n readonly key: string;\n readonly requestId: string;\n readonly action: \"approve\" | \"resume\" | \"reject\";\n readonly actor: string;\n readonly reason: string;\n}\n\nexport interface WorkflowDecisionRecord extends WorkflowDecision {\n readonly decidedAt: string;\n}\n"]}
|
|
@@ -1,12 +1,16 @@
|
|
|
1
|
+
import { checkpointValue } from "./checkpoint-value.js";
|
|
1
2
|
import { WorkflowBudgetExceeded } from "./budget.js";
|
|
2
3
|
import { setTimeout as delay } from "node:timers/promises";
|
|
3
4
|
export async function runTask(item, runtime) {
|
|
4
5
|
const { record, signal, context, emit, finish, values, errors, options, stop, } = runtime;
|
|
5
6
|
const state = record(item);
|
|
6
7
|
state.status = "active";
|
|
8
|
+
delete state.error;
|
|
9
|
+
delete state.finishedAt;
|
|
7
10
|
state.startedAt = new Date().toISOString();
|
|
8
11
|
emit({ type: "task", key: item.key, status: "active", attempt: 0 });
|
|
9
12
|
try {
|
|
13
|
+
await runtime.persist();
|
|
10
14
|
signal.throwIfAborted();
|
|
11
15
|
if (item.condition && !(await item.condition(context(item, 0, signal)))) {
|
|
12
16
|
signal.throwIfAborted();
|
|
@@ -14,11 +18,14 @@ export async function runTask(item, runtime) {
|
|
|
14
18
|
return;
|
|
15
19
|
}
|
|
16
20
|
const limit = item.retry?.attempts ?? 1;
|
|
17
|
-
|
|
21
|
+
const priorAttempts = state.attempts;
|
|
22
|
+
for (let cycle = 1; cycle <= limit; cycle++) {
|
|
23
|
+
const attempt = priorAttempts + cycle;
|
|
18
24
|
signal.throwIfAborted();
|
|
19
25
|
runtime.accounting.admit();
|
|
20
26
|
state.attempts = attempt;
|
|
21
27
|
emit({ type: "attempt", key: item.key, attempt });
|
|
28
|
+
await runtime.persist();
|
|
22
29
|
const deadline = new AbortController();
|
|
23
30
|
const timer = item.timeoutMs === undefined
|
|
24
31
|
? undefined
|
|
@@ -28,6 +35,8 @@ export async function runTask(item, runtime) {
|
|
|
28
35
|
taskSignal.throwIfAborted();
|
|
29
36
|
const value = await item.perform(context(item, attempt, taskSignal));
|
|
30
37
|
taskSignal.throwIfAborted();
|
|
38
|
+
if (options.checkpoint)
|
|
39
|
+
checkpointValue(value);
|
|
31
40
|
values.set(item, value);
|
|
32
41
|
finish(item, "done");
|
|
33
42
|
return;
|
|
@@ -35,7 +44,7 @@ export async function runTask(item, runtime) {
|
|
|
35
44
|
catch (error) {
|
|
36
45
|
runtime.closeAttempt(item);
|
|
37
46
|
if (signal.aborted ||
|
|
38
|
-
|
|
47
|
+
cycle === limit ||
|
|
39
48
|
item.retry?.accepts?.(error, attempt) === false)
|
|
40
49
|
throw error;
|
|
41
50
|
emit({ type: "retry", key: item.key, attempt });
|
|
@@ -59,5 +68,8 @@ export async function runTask(item, runtime) {
|
|
|
59
68
|
stop.abort(error);
|
|
60
69
|
}
|
|
61
70
|
}
|
|
71
|
+
finally {
|
|
72
|
+
await runtime.persist();
|
|
73
|
+
}
|
|
62
74
|
}
|
|
63
75
|
//# sourceMappingURL=task-runner.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"task-runner.js","sourceRoot":"","sources":["../../../src/domain/workflow/task-runner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EAAE,UAAU,IAAI,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAG3D,MAAM,CAAC,KAAK,UAAU,OAAO,CAC3B,IAAU,EACV,OAA+B;IAE/B,MAAM,EACJ,MAAM,EACN,MAAM,EACN,OAAO,EACP,IAAI,EACJ,MAAM,EACN,MAAM,EACN,MAAM,EACN,OAAO,EACP,IAAI,GACL,GAAG,OAAO,CAAC;IACZ,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;IAC3B,KAAK,CAAC,MAAM,GAAG,QAAQ,CAAC;IACxB,KAAK,CAAC,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC3C,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC;IACpE,IAAI,CAAC;QACH,MAAM,CAAC,cAAc,EAAE,CAAC;QACxB,IAAI,IAAI,CAAC,SAAS,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;YACxE,MAAM,CAAC,cAAc,EAAE,CAAC;YACxB,MAAM,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;YACxB,OAAO;QACT,CAAC;QACD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,EAAE,QAAQ,IAAI,CAAC,CAAC;QACxC,KAAK,IAAI,
|
|
1
|
+
{"version":3,"file":"task-runner.js","sourceRoot":"","sources":["../../../src/domain/workflow/task-runner.ts"],"names":[],"mappings":"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","sourcesContent":["import { checkpointValue } from \"./checkpoint-value.ts\";\nimport { WorkflowBudgetExceeded } from \"./budget.ts\";\nimport { setTimeout as delay } from \"node:timers/promises\";\nimport type { Task, WorkflowExecutionState } from \"../workflow.types.ts\";\n\nexport async function runTask(\n item: Task,\n runtime: WorkflowExecutionState,\n): Promise<void> {\n const {\n record,\n signal,\n context,\n emit,\n finish,\n values,\n errors,\n options,\n stop,\n } = runtime;\n const state = record(item);\n state.status = \"active\";\n delete state.error;\n delete state.finishedAt;\n state.startedAt = new Date().toISOString();\n emit({ type: \"task\", key: item.key, status: \"active\", attempt: 0 });\n try {\n await runtime.persist();\n signal.throwIfAborted();\n if (item.condition && !(await item.condition(context(item, 0, signal)))) {\n signal.throwIfAborted();\n finish(item, \"skipped\");\n return;\n }\n const limit = item.retry?.attempts ?? 1;\n const priorAttempts = state.attempts;\n for (let cycle = 1; cycle <= limit; cycle++) {\n const attempt = priorAttempts + cycle;\n signal.throwIfAborted();\n runtime.accounting.admit();\n state.attempts = attempt;\n emit({ type: \"attempt\", key: item.key, attempt });\n await runtime.persist();\n const deadline = new AbortController();\n const timer =\n item.timeoutMs === undefined\n ? undefined\n : setTimeout(\n () => deadline.abort(new Error(`${item.key} timed out`)),\n item.timeoutMs,\n );\n const taskSignal = AbortSignal.any([signal, deadline.signal]);\n try {\n taskSignal.throwIfAborted();\n const value = await item.perform(context(item, attempt, taskSignal));\n taskSignal.throwIfAborted();\n if (options.checkpoint) checkpointValue(value);\n values.set(item, value);\n finish(item, \"done\");\n return;\n } catch (error) {\n runtime.closeAttempt(item);\n if (\n signal.aborted ||\n cycle === limit ||\n item.retry?.accepts?.(error, attempt) === false\n )\n throw error;\n emit({ type: \"retry\", key: item.key, attempt });\n } finally {\n runtime.closeAttempt(item);\n clearTimeout(timer);\n }\n await delay(item.retry?.delayMs ?? 0, undefined, { signal });\n }\n } catch (error) {\n state.error = error instanceof Error ? error.message : String(error);\n if (\n signal.aborted ||\n (error instanceof WorkflowBudgetExceeded && runtime.accounting.exhausted)\n )\n finish(item, \"cancelled\");\n else {\n errors.push(error);\n finish(item, \"failed\");\n if (options.stopOnError !== false) stop.abort(error);\n }\n } finally {\n await runtime.persist();\n }\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usage-receipt.constants.js","sourceRoot":"","sources":["../../../src/domain/workflow/usage-receipt.constants.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,uBAAuB,GAAG,MAAM,CAAC;AAC9C,MAAM,CAAC,MAAM,qBAAqB,GAAG,GAAG,CAAC","sourcesContent":["export const maxUsageReceiptsPerTask = 65_536;\nexport const maxUsageReceiptLength = 512;\n"]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { maxUsageReceiptLength, maxUsageReceiptsPerTask, } from "./usage-receipt.constants.js";
|
|
2
|
+
export function validateUsageReceipt(receipt) {
|
|
3
|
+
if (typeof receipt !== "string" ||
|
|
4
|
+
!receipt.length ||
|
|
5
|
+
receipt.length > maxUsageReceiptLength)
|
|
6
|
+
throw new Error("Invalid workflow usage receipt");
|
|
7
|
+
}
|
|
8
|
+
export function validateUsageReceipts(receipts) {
|
|
9
|
+
if (receipts === undefined)
|
|
10
|
+
return;
|
|
11
|
+
if (!Array.isArray(receipts) || receipts.length > maxUsageReceiptsPerTask)
|
|
12
|
+
throw new Error("Invalid workflow usage receipts");
|
|
13
|
+
for (const receipt of receipts)
|
|
14
|
+
validateUsageReceipt(receipt);
|
|
15
|
+
if (new Set(receipts).size !== receipts.length)
|
|
16
|
+
throw new Error("Duplicate workflow usage receipts");
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=usage-receipt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usage-receipt.js","sourceRoot":"","sources":["../../../src/domain/workflow/usage-receipt.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,qBAAqB,EACrB,uBAAuB,GACxB,MAAM,8BAA8B,CAAC;AAEtC,MAAM,UAAU,oBAAoB,CAClC,OAAgB;IAEhB,IACE,OAAO,OAAO,KAAK,QAAQ;QAC3B,CAAC,OAAO,CAAC,MAAM;QACf,OAAO,CAAC,MAAM,GAAG,qBAAqB;QAEtC,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;AACtD,CAAC;AACD,MAAM,UAAU,qBAAqB,CAAC,QAAiB;IACrD,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO;IACnC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,QAAQ,CAAC,MAAM,GAAG,uBAAuB;QACvE,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC;IACrD,KAAK,MAAM,OAAO,IAAI,QAAQ;QAAE,oBAAoB,CAAC,OAAO,CAAC,CAAC;IAC9D,IAAI,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,MAAM;QAC5C,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC;AACzD,CAAC","sourcesContent":["import {\n maxUsageReceiptLength,\n maxUsageReceiptsPerTask,\n} from \"./usage-receipt.constants.ts\";\n\nexport function validateUsageReceipt(\n receipt: unknown,\n): asserts receipt is string {\n if (\n typeof receipt !== \"string\" ||\n !receipt.length ||\n receipt.length > maxUsageReceiptLength\n )\n throw new Error(\"Invalid workflow usage receipt\");\n}\nexport function validateUsageReceipts(receipts: unknown): void {\n if (receipts === undefined) return;\n if (!Array.isArray(receipts) || receipts.length > maxUsageReceiptsPerTask)\n throw new Error(\"Invalid workflow usage receipts\");\n for (const receipt of receipts) validateUsageReceipt(receipt);\n if (new Set(receipts).size !== receipts.length)\n throw new Error(\"Duplicate workflow usage receipts\");\n}\n"]}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { validateGate } from "./gate-validation.js";
|
|
1
2
|
export function positive(value, label) {
|
|
2
3
|
if (!Number.isSafeInteger(value) || value < 1)
|
|
3
4
|
throw new Error(`${label} must be a positive integer`);
|
|
@@ -8,6 +9,11 @@ export function validate(tasks) {
|
|
|
8
9
|
const visiting = new Set();
|
|
9
10
|
const visited = new Set();
|
|
10
11
|
for (const item of tasks) {
|
|
12
|
+
if (item.gate) {
|
|
13
|
+
validateGate(item.gate);
|
|
14
|
+
if (item.condition || item.retry || item.timeoutMs !== undefined)
|
|
15
|
+
throw new Error("Workflow gates cannot have conditions, retries or timeouts");
|
|
16
|
+
}
|
|
11
17
|
if (keys.has(item.key))
|
|
12
18
|
throw new Error(`Duplicate task: ${item.key}`);
|
|
13
19
|
keys.add(item.key);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validation.js","sourceRoot":"","sources":["../../../src/domain/workflow/validation.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"validation.js","sourceRoot":"","sources":["../../../src/domain/workflow/validation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAGpD,MAAM,UAAU,QAAQ,CAAC,KAAa,EAAE,KAAa;IACnD,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC;QAC3C,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,6BAA6B,CAAC,CAAC;AAC3D,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,KAAsB;IAC7C,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;IAC/B,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAQ,CAAC;IACjC,MAAM,OAAO,GAAG,IAAI,GAAG,EAAQ,CAAC;IAChC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;YACd,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACxB,IAAI,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,SAAS,KAAK,SAAS;gBAC9D,MAAM,IAAI,KAAK,CACb,4DAA4D,CAC7D,CAAC;QACN,CAAC;QACD,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,mBAAmB,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;QACvE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACnB,KAAK,MAAM,UAAU,IAAI,IAAI,CAAC,KAAK;YACjC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC;gBAC1B,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,CAAC,GAAG,wBAAwB,UAAU,CAAC,GAAG,EAAE,CAAC,CAAC;IAC3E,CAAC;IACD,SAAS,KAAK,CAAC,IAAU;QACvB,IAAI,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,uBAAuB,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;QAC3E,IAAI,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,OAAO;QAC9B,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACnB,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAC1B,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACtB,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACpB,CAAC;IACD,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AACvB,CAAC","sourcesContent":["import { validateGate } from \"./gate-validation.ts\";\nimport type { Task } from \"../workflow.types.ts\";\n\nexport function positive(value: number, label: string): void {\n if (!Number.isSafeInteger(value) || value < 1)\n throw new Error(`${label} must be a positive integer`);\n}\n\nexport function validate(tasks: readonly Task[]): void {\n const keys = new Set<string>();\n const members = new Set(tasks);\n const visiting = new Set<Task>();\n const visited = new Set<Task>();\n for (const item of tasks) {\n if (item.gate) {\n validateGate(item.gate);\n if (item.condition || item.retry || item.timeoutMs !== undefined)\n throw new Error(\n \"Workflow gates cannot have conditions, retries or timeouts\",\n );\n }\n if (keys.has(item.key)) throw new Error(`Duplicate task: ${item.key}`);\n keys.add(item.key);\n for (const dependency of item.after)\n if (!members.has(dependency))\n throw new Error(`${item.key}: missing dependency ${dependency.key}`);\n }\n function visit(item: Task): void {\n if (visiting.has(item)) throw new Error(`Dependency cycle at ${item.key}`);\n if (visited.has(item)) return;\n visiting.add(item);\n item.after.forEach(visit);\n visiting.delete(item);\n visited.add(item);\n }\n tasks.forEach(visit);\n}\n"]}
|
|
@@ -1,54 +1,87 @@
|
|
|
1
|
+
import { applyDecisions, pauseGate, prepareDecisions } from "./gates.js";
|
|
2
|
+
import { openCheckpoint } from "./checkpoint.js";
|
|
1
3
|
import { WorkflowFailure } from "./failure.js";
|
|
2
4
|
import { runTask } from "./task-runner.js";
|
|
3
5
|
import { positive } from "./validation.js";
|
|
4
6
|
import { workflowState } from "./workflow-state.js";
|
|
5
7
|
export async function schedule(name, tasks, options) {
|
|
8
|
+
if (!options.checkpoint &&
|
|
9
|
+
(tasks.some((item) => item.gate) || options.decisions?.length))
|
|
10
|
+
throw new Error("Workflow gates and decisions require a checkpoint");
|
|
11
|
+
if (options.decisions && !options.decisions.length)
|
|
12
|
+
throw new Error("Workflow decisions cannot be empty");
|
|
13
|
+
const checkpoint = options.checkpoint
|
|
14
|
+
? await openCheckpoint(name, tasks, options.checkpoint)
|
|
15
|
+
: undefined;
|
|
16
|
+
try {
|
|
17
|
+
return await scheduleRun(name, tasks, options, checkpoint);
|
|
18
|
+
}
|
|
19
|
+
finally {
|
|
20
|
+
await checkpoint?.release();
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
async function scheduleRun(name, tasks, options, checkpoint) {
|
|
6
24
|
const concurrency = options.concurrency ?? 1;
|
|
7
25
|
positive(concurrency, "concurrency");
|
|
8
|
-
const state = workflowState(name, tasks, options);
|
|
26
|
+
const state = workflowState(name, tasks, options, checkpoint);
|
|
9
27
|
const { executionId, signal, values, records, errors, observerErrors, record, emit, finish, } = state;
|
|
10
28
|
const active = new Map();
|
|
11
29
|
const started = Date.now();
|
|
30
|
+
const decisions = prepareDecisions(state);
|
|
12
31
|
emit({ type: "start" });
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
32
|
+
applyDecisions(state, decisions);
|
|
33
|
+
await state.persist();
|
|
34
|
+
try {
|
|
35
|
+
while (true) {
|
|
36
|
+
let changed = false;
|
|
37
|
+
for (const item of tasks) {
|
|
38
|
+
if (record(item).status !== "waiting")
|
|
39
|
+
continue;
|
|
40
|
+
if (signal.aborted || state.accounting.exhausted) {
|
|
41
|
+
finish(item, "cancelled");
|
|
42
|
+
changed = true;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
const dependencies = item.after.map((dependency) => record(dependency).status);
|
|
46
|
+
if (dependencies.some((status) => ["failed", "skipped", "cancelled", "rejected"].includes(status))) {
|
|
47
|
+
finish(item, "skipped");
|
|
48
|
+
changed = true;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (active.size >= concurrency ||
|
|
52
|
+
item.after.some((dependency) => active.has(dependency)) ||
|
|
53
|
+
!dependencies.every((status) => status === "done"))
|
|
54
|
+
continue;
|
|
55
|
+
const running = (item.gate ? pauseGate(item, state) : runTask(item, state)).finally(() => {
|
|
56
|
+
active.delete(item);
|
|
57
|
+
});
|
|
58
|
+
active.set(item, running);
|
|
20
59
|
changed = true;
|
|
21
|
-
continue;
|
|
22
60
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
finish(item, "skipped");
|
|
26
|
-
changed = true;
|
|
61
|
+
if (active.size) {
|
|
62
|
+
await Promise.race(active.values());
|
|
27
63
|
continue;
|
|
28
64
|
}
|
|
29
|
-
if (
|
|
30
|
-
|
|
31
|
-
continue;
|
|
32
|
-
const running = runTask(item, state).finally(() => {
|
|
33
|
-
active.delete(item);
|
|
34
|
-
});
|
|
35
|
-
active.set(item, running);
|
|
36
|
-
changed = true;
|
|
37
|
-
}
|
|
38
|
-
if (active.size) {
|
|
39
|
-
await Promise.race(active.values());
|
|
40
|
-
continue;
|
|
65
|
+
if (!changed)
|
|
66
|
+
break;
|
|
41
67
|
}
|
|
42
|
-
if (!changed)
|
|
43
|
-
break;
|
|
44
68
|
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
state.stop.abort(error);
|
|
71
|
+
await Promise.allSettled(active.values());
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
await state.persist();
|
|
45
75
|
if (options.signal?.aborted)
|
|
46
76
|
errors.push(options.signal.reason);
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
77
|
+
const paused = [...records.values()].some((entry) => entry.status === "paused");
|
|
78
|
+
let status = "done";
|
|
79
|
+
if (paused)
|
|
80
|
+
status = "paused";
|
|
81
|
+
if (errors.length)
|
|
82
|
+
status = "failed";
|
|
83
|
+
if (options.signal?.aborted)
|
|
84
|
+
status = "cancelled";
|
|
52
85
|
emit({ type: "finish", status, durationMs: Date.now() - started });
|
|
53
86
|
const result = Object.freeze({
|
|
54
87
|
executionId,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workflow-scheduler.js","sourceRoot":"","sources":["../../../src/domain/workflow/workflow-scheduler.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"workflow-scheduler.js","sourceRoot":"","sources":["../../../src/domain/workflow/workflow-scheduler.ts"],"names":[],"mappings":"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","sourcesContent":["import { applyDecisions, pauseGate, prepareDecisions } from \"./gates.ts\";\nimport { openCheckpoint } from \"./checkpoint.ts\";\nimport type { WorkflowCheckpointSession } from \"./checkpoint.types.ts\";\nimport type {\n Task,\n WorkflowOptions,\n WorkflowResult,\n} from \"../workflow.types.ts\";\nimport { WorkflowFailure } from \"./failure.ts\";\nimport { runTask } from \"./task-runner.ts\";\nimport { positive } from \"./validation.ts\";\nimport { workflowState } from \"./workflow-state.ts\";\n\nexport async function schedule(\n name: string,\n tasks: readonly Task[],\n options: WorkflowOptions,\n): Promise<WorkflowResult> {\n if (\n !options.checkpoint &&\n (tasks.some((item) => item.gate) || options.decisions?.length)\n )\n throw new Error(\"Workflow gates and decisions require a checkpoint\");\n if (options.decisions && !options.decisions.length)\n throw new Error(\"Workflow decisions cannot be empty\");\n const checkpoint = options.checkpoint\n ? await openCheckpoint(name, tasks, options.checkpoint)\n : undefined;\n try {\n return await scheduleRun(name, tasks, options, checkpoint);\n } finally {\n await checkpoint?.release();\n }\n}\n\nasync function scheduleRun(\n name: string,\n tasks: readonly Task[],\n options: WorkflowOptions,\n checkpoint?: WorkflowCheckpointSession,\n): Promise<WorkflowResult> {\n const concurrency = options.concurrency ?? 1;\n positive(concurrency, \"concurrency\");\n const state = workflowState(name, tasks, options, checkpoint);\n const {\n executionId,\n signal,\n values,\n records,\n errors,\n observerErrors,\n record,\n emit,\n finish,\n } = state;\n const active = new Map<Task, Promise<void>>();\n const started = Date.now();\n const decisions = prepareDecisions(state);\n emit({ type: \"start\" });\n applyDecisions(state, decisions);\n await state.persist();\n try {\n while (true) {\n let changed = false;\n for (const item of tasks) {\n if (record(item).status !== \"waiting\") continue;\n if (signal.aborted || state.accounting.exhausted) {\n finish(item, \"cancelled\");\n changed = true;\n continue;\n }\n const dependencies = item.after.map(\n (dependency) => record(dependency).status,\n );\n if (\n dependencies.some((status) =>\n [\"failed\", \"skipped\", \"cancelled\", \"rejected\"].includes(status),\n )\n ) {\n finish(item, \"skipped\");\n changed = true;\n continue;\n }\n if (\n active.size >= concurrency ||\n item.after.some((dependency) => active.has(dependency)) ||\n !dependencies.every((status) => status === \"done\")\n )\n continue;\n const running = (\n item.gate ? pauseGate(item, state) : runTask(item, state)\n ).finally(() => {\n active.delete(item);\n });\n active.set(item, running);\n changed = true;\n }\n if (active.size) {\n await Promise.race(active.values());\n continue;\n }\n if (!changed) break;\n }\n } catch (error) {\n state.stop.abort(error);\n await Promise.allSettled(active.values());\n throw error;\n }\n await state.persist();\n if (options.signal?.aborted) errors.push(options.signal.reason);\n const paused = [...records.values()].some(\n (entry) => entry.status === \"paused\",\n );\n let status: WorkflowResult[\"status\"] = \"done\";\n if (paused) status = \"paused\";\n if (errors.length) status = \"failed\";\n if (options.signal?.aborted) status = \"cancelled\";\n emit({ type: \"finish\", status, durationMs: Date.now() - started });\n const result: WorkflowResult = Object.freeze({\n executionId,\n name,\n status,\n usage: state.accounting.snapshot(),\n tasks: Object.freeze(\n [...records.values()].map((value) => Object.freeze({ ...value })),\n ),\n errors: Object.freeze(errors),\n observerErrors: Object.freeze(observerErrors),\n value<T>(item: Task<T>): T {\n if (!values.has(item))\n throw new Error(\n `${item.key} has no successful value in this execution`,\n );\n return values.get(item) as T;\n },\n unwrap() {\n if (status !== \"done\") throw new WorkflowFailure(result);\n },\n });\n return result;\n}\n"]}
|
|
@@ -1,2 +1,3 @@
|
|
|
1
|
+
import type { WorkflowCheckpointSession } from "./checkpoint.types.ts";
|
|
1
2
|
import type { Task, WorkflowExecutionState, WorkflowOptions } from "../workflow.types.ts";
|
|
2
|
-
export declare function workflowState(name: string, tasks: readonly Task[], options: WorkflowOptions): WorkflowExecutionState;
|
|
3
|
+
export declare function workflowState(name: string, tasks: readonly Task[], options: WorkflowOptions, checkpoint?: WorkflowCheckpointSession): WorkflowExecutionState;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { maxUsageReceiptsPerTask } from "./usage-receipt.constants.js";
|
|
2
|
+
import { validateUsageReceipt } from "./usage-receipt.js";
|
|
1
3
|
import { workflowAccounting } from "./budget.js";
|
|
2
4
|
import { randomUUID } from "node:crypto";
|
|
3
|
-
export function workflowState(name, tasks, options) {
|
|
4
|
-
const executionId = randomUUID();
|
|
5
|
+
export function workflowState(name, tasks, options, checkpoint) {
|
|
6
|
+
const executionId = checkpoint?.initial?.executionId ?? randomUUID();
|
|
5
7
|
const stop = new AbortController();
|
|
6
8
|
const signal = options.signal
|
|
7
9
|
? AbortSignal.any([options.signal, stop.signal])
|
|
@@ -12,12 +14,51 @@ export function workflowState(name, tasks, options) {
|
|
|
12
14
|
item,
|
|
13
15
|
{ key: item.key, status: "waiting", attempts: 0 },
|
|
14
16
|
]));
|
|
17
|
+
const saved = checkpoint?.initial;
|
|
18
|
+
if (saved) {
|
|
19
|
+
const complete = saved.records.every((entry) => ["done", "skipped"].includes(entry.status));
|
|
20
|
+
const settledPause = saved.records.some((entry) => ["paused", "rejected"].includes(entry.status)) &&
|
|
21
|
+
saved.records.every((entry) => ["done", "skipped", "paused", "rejected"].includes(entry.status) ||
|
|
22
|
+
(entry.status === "waiting" && entry.attempts === 0));
|
|
23
|
+
for (const item of tasks) {
|
|
24
|
+
const entry = saved.records.find((entry) => entry.key === item.key);
|
|
25
|
+
records.set(item, {
|
|
26
|
+
...entry,
|
|
27
|
+
...(entry.usageReceipts
|
|
28
|
+
? { usageReceipts: Object.freeze([...entry.usageReceipts]) }
|
|
29
|
+
: {}),
|
|
30
|
+
...(entry.pause
|
|
31
|
+
? {
|
|
32
|
+
pause: Object.freeze({
|
|
33
|
+
...entry.pause,
|
|
34
|
+
actors: Object.freeze([...entry.pause.actors]),
|
|
35
|
+
}),
|
|
36
|
+
}
|
|
37
|
+
: {}),
|
|
38
|
+
...(entry.decision
|
|
39
|
+
? { decision: Object.freeze({ ...entry.decision }) }
|
|
40
|
+
: {}),
|
|
41
|
+
status: ["done", "paused", "rejected"].includes(entry.status) ||
|
|
42
|
+
complete ||
|
|
43
|
+
(settledPause && entry.status === "skipped")
|
|
44
|
+
? entry.status
|
|
45
|
+
: "waiting",
|
|
46
|
+
});
|
|
47
|
+
if (entry.status === "done") {
|
|
48
|
+
const output = saved.values[item.key];
|
|
49
|
+
values.set(item, output.kind === "json" ? output.value : undefined);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
15
53
|
const errors = [], observerErrors = [];
|
|
54
|
+
for (const entry of records.values())
|
|
55
|
+
if (entry.status === "rejected")
|
|
56
|
+
errors.push(new Error(entry.error));
|
|
16
57
|
const accounting = workflowAccounting(options.budget, (error) => {
|
|
17
58
|
errors.push(error);
|
|
18
59
|
if (error.dimension !== "attempts")
|
|
19
60
|
stop.abort(error);
|
|
20
|
-
});
|
|
61
|
+
}, saved?.usage);
|
|
21
62
|
const record = (item) => records.get(item);
|
|
22
63
|
function emit(event) {
|
|
23
64
|
try {
|
|
@@ -50,22 +91,52 @@ export function workflowState(name, tasks, options) {
|
|
|
50
91
|
function context(item, attempt, taskSignal) {
|
|
51
92
|
if (attempt > 0)
|
|
52
93
|
activeAttempts.set(item, attempt);
|
|
94
|
+
function report(usage, receipt) {
|
|
95
|
+
const current = record(item);
|
|
96
|
+
if (attempt === 0 ||
|
|
97
|
+
current.status !== "active" ||
|
|
98
|
+
activeAttempts.get(item) !== attempt)
|
|
99
|
+
throw new Error("Usage must be reported during its active task attempt");
|
|
100
|
+
if (receipt !== undefined) {
|
|
101
|
+
validateUsageReceipt(receipt);
|
|
102
|
+
if (current.usageReceipts?.includes(receipt))
|
|
103
|
+
return;
|
|
104
|
+
if ((current.usageReceipts?.length ?? 0) >= maxUsageReceiptsPerTask)
|
|
105
|
+
throw new Error("Workflow usage receipt limit exceeded");
|
|
106
|
+
}
|
|
107
|
+
const previousReceipts = current.usageReceipts;
|
|
108
|
+
if (receipt !== undefined)
|
|
109
|
+
current.usageReceipts = Object.freeze([
|
|
110
|
+
...(previousReceipts ?? []),
|
|
111
|
+
receipt,
|
|
112
|
+
]);
|
|
113
|
+
try {
|
|
114
|
+
accounting.report(usage);
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
if (previousReceipts)
|
|
118
|
+
current.usageReceipts = previousReceipts;
|
|
119
|
+
else
|
|
120
|
+
delete current.usageReceipts;
|
|
121
|
+
throw error;
|
|
122
|
+
}
|
|
123
|
+
void runtime.persist().catch((error) => stop.abort(error));
|
|
124
|
+
emit({
|
|
125
|
+
type: "usage",
|
|
126
|
+
key: item.key,
|
|
127
|
+
attempt,
|
|
128
|
+
usage: Object.freeze({ ...usage }),
|
|
129
|
+
});
|
|
130
|
+
}
|
|
53
131
|
return {
|
|
54
132
|
signal: taskSignal,
|
|
55
133
|
attempt,
|
|
56
134
|
executionId,
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
accounting.report(usage);
|
|
63
|
-
emit({
|
|
64
|
-
type: "usage",
|
|
65
|
-
key: item.key,
|
|
66
|
-
attempt,
|
|
67
|
-
usage: Object.freeze({ ...usage }),
|
|
68
|
-
});
|
|
135
|
+
checkpoint: () => runtime.persist(),
|
|
136
|
+
reportUsage: (usage) => report(usage),
|
|
137
|
+
reportUsageOnce: (receipt, usage) => {
|
|
138
|
+
validateUsageReceipt(receipt);
|
|
139
|
+
report(usage, receipt);
|
|
69
140
|
},
|
|
70
141
|
value(dependency) {
|
|
71
142
|
if (!item.after.includes(dependency))
|
|
@@ -76,7 +147,10 @@ export function workflowState(name, tasks, options) {
|
|
|
76
147
|
},
|
|
77
148
|
};
|
|
78
149
|
}
|
|
79
|
-
|
|
150
|
+
const runtime = {
|
|
151
|
+
async persist() {
|
|
152
|
+
await checkpoint?.save(runtime);
|
|
153
|
+
},
|
|
80
154
|
executionId,
|
|
81
155
|
accounting,
|
|
82
156
|
closeAttempt(item) {
|
|
@@ -94,5 +168,6 @@ export function workflowState(name, tasks, options) {
|
|
|
94
168
|
context,
|
|
95
169
|
options,
|
|
96
170
|
};
|
|
171
|
+
return runtime;
|
|
97
172
|
}
|
|
98
173
|
//# sourceMappingURL=workflow-state.js.map
|