@jr2/orchestrator 0.5.1 → 0.7.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/console/components/app.ts +55 -15
- package/console/components/bottom-drawer.ts +259 -0
- package/console/components/drawer.ts +14 -6
- package/console/components/fleet.ts +36 -13
- package/console/components/machine-pane.ts +61 -5
- package/console/components/nav.ts +54 -8
- package/console/conversation.ts +161 -0
- package/console/login.ts +123 -0
- package/console/main.ts +600 -60
- package/console/store.ts +611 -64
- package/console/style.css +231 -0
- package/docs/agents-block.md +2 -2
- package/docs/concepts/agents-and-turns.md +142 -29
- package/docs/concepts/architecture.md +60 -34
- package/docs/concepts/callers-and-hooks.md +205 -0
- package/docs/concepts/charts.md +13 -8
- package/docs/concepts/composition.md +10 -1
- package/docs/concepts/converge.md +7 -2
- package/docs/concepts/durable-runs.md +43 -16
- package/docs/concepts/effects.md +133 -50
- package/docs/concepts/gates.md +95 -25
- package/docs/concepts/machines-and-workflows.md +14 -4
- package/docs/concepts/pools-and-sources.md +193 -17
- package/docs/concepts/secrets-and-trust.md +186 -62
- package/docs/concepts/the-menu.md +14 -0
- package/docs/concepts/workspaces.md +135 -56
- package/docs/get-started/a-model-decides.md +14 -13
- package/docs/get-started/an-agent-in-a-sandbox.md +46 -35
- package/docs/get-started/connect-a-model.md +1 -1
- package/docs/get-started/create-an-instance.md +17 -14
- package/docs/get-started/have-your-assistant-write-a-machine.md +111 -40
- package/docs/get-started/install.md +4 -0
- package/docs/guides/add-tools-to-the-sandbox.md +72 -15
- package/docs/guides/answer-a-gate-from-a-forge.md +304 -0
- package/docs/guides/answer-a-gate.md +74 -37
- package/docs/guides/bind-open-parts.md +29 -12
- package/docs/guides/call-an-mcp-tool-from-a-machine.md +572 -0
- package/docs/guides/cancel-a-run.md +26 -16
- package/docs/guides/cut-a-worktree.md +560 -0
- package/docs/guides/fix-a-failed-converge.md +19 -7
- package/docs/guides/give-a-caller-a-scope.md +203 -0
- package/docs/guides/give-an-agent-mcp-tools.md +422 -0
- package/docs/guides/iterate-on-a-machine-on-kind.md +10 -9
- package/docs/guides/let-an-agent-write-a-chart.md +14 -7
- package/docs/guides/look-inside-a-sandbox.md +115 -42
- package/docs/guides/move-from-kind-to-a-shared-cluster.md +16 -0
- package/docs/guides/open-the-console.md +63 -14
- package/docs/guides/push-and-sign-a-branch.md +67 -21
- package/docs/guides/read-a-conversation.md +225 -0
- package/docs/guides/run-a-chart-by-name.md +17 -16
- package/docs/guides/run-a-command-or-call-a-url.md +84 -41
- package/docs/guides/run-one-worker-per-item.md +34 -27
- package/docs/guides/set-an-agents-access-and-dials.md +69 -22
- package/docs/guides/size-a-workspace.md +19 -6
- package/docs/guides/start-a-run-for-each-alert.md +290 -0
- package/docs/guides/start-a-run-from-a-run.md +18 -16
- package/docs/guides/start-a-run.md +24 -18
- package/docs/guides/steer-an-agent.md +332 -0
- package/docs/guides/use-a-hosted-provider.md +7 -1
- package/docs/guides/watch-a-run.md +146 -117
- package/docs/guides/write-a-hook-kind.md +238 -0
- package/docs/guides/write-a-tool-as-an-mcp-server.md +351 -0
- package/docs/guides/write-a-workflow.md +18 -8
- package/docs/llms.txt +110 -23
- package/docs/reference/authoring-surface/agent.md +35 -12
- package/docs/reference/authoring-surface/answer.md +20 -4
- package/docs/reference/authoring-surface/arg.md +28 -6
- package/docs/reference/authoring-surface/basic.md +1 -1
- package/docs/reference/authoring-surface/bearer.md +1 -1
- package/docs/reference/authoring-surface/branchname.md +15 -0
- package/docs/reference/authoring-surface/carry.md +23 -0
- package/docs/reference/authoring-surface/chartschema.md +1 -1
- package/docs/reference/authoring-surface/chartschemafor.md +1 -1
- package/docs/reference/authoring-surface/command.md +37 -22
- package/docs/reference/authoring-surface/customize.md +4 -1
- package/docs/reference/authoring-surface/defineconfig.md +1 -1
- package/docs/reference/authoring-surface/defineevent.md +1 -1
- package/docs/reference/authoring-surface/dispatch.md +5 -4
- package/docs/reference/authoring-surface/fail.md +18 -5
- package/docs/reference/authoring-surface/file.md +37 -17
- package/docs/reference/authoring-surface/frompick.md +1 -1
- package/docs/reference/authoring-surface/gate.md +118 -0
- package/docs/reference/authoring-surface/group-agents.md +15 -0
- package/docs/reference/authoring-surface/group-charts.md +16 -0
- package/docs/reference/authoring-surface/group-composition.md +25 -0
- package/docs/reference/authoring-surface/group-config.md +17 -0
- package/docs/reference/authoring-surface/group-effects.md +48 -0
- package/docs/reference/authoring-surface/group-failure.md +13 -0
- package/docs/reference/authoring-surface/group-gates.md +16 -0
- package/docs/reference/authoring-surface/group-machine-and-events.md +15 -0
- package/docs/reference/authoring-surface/group-steering.md +12 -0
- package/docs/reference/authoring-surface/group-types-these-signatures-name.md +137 -0
- package/docs/reference/authoring-surface/group-workspaces-and-repos.md +15 -0
- package/docs/reference/authoring-surface/http.md +109 -9
- package/docs/reference/authoring-surface/isfailure.md +1 -1
- package/docs/reference/authoring-surface/isready.md +14 -0
- package/docs/reference/authoring-surface/jr2setup.md +14 -4
- package/docs/reference/authoring-surface/mcp.md +107 -0
- package/docs/reference/authoring-surface/open.md +41 -8
- package/docs/reference/authoring-surface/openbody.md +1 -1
- package/docs/reference/authoring-surface/paramsof.md +23 -5
- package/docs/reference/authoring-surface/pool.md +79 -11
- package/docs/reference/authoring-surface/push.md +31 -13
- package/docs/reference/authoring-surface/readyof.md +21 -0
- package/docs/reference/authoring-surface/reread.md +19 -0
- package/docs/reference/authoring-surface/secret.md +26 -5
- package/docs/reference/authoring-surface/sentby.md +1 -1
- package/docs/reference/authoring-surface/skip.md +22 -0
- package/docs/reference/authoring-surface/source.md +59 -4
- package/docs/reference/authoring-surface/steer.md +93 -0
- package/docs/reference/authoring-surface/text.md +1 -1
- package/docs/reference/authoring-surface/type-agentdefinition.md +16 -3
- package/docs/reference/authoring-surface/type-agentmcpserver.md +54 -0
- package/docs/reference/authoring-surface/type-agentslots.md +4 -1
- package/docs/reference/authoring-surface/type-anyframepath.md +14 -0
- package/docs/reference/authoring-surface/type-anyworktreepath.md +15 -0
- package/docs/reference/authoring-surface/type-bindactors.md +14 -0
- package/docs/reference/authoring-surface/type-binding.md +6 -7
- package/docs/reference/authoring-surface/type-caller.md +40 -0
- package/docs/reference/authoring-surface/type-callerrecord.md +32 -0
- package/docs/reference/authoring-surface/type-carry.md +19 -0
- package/docs/reference/authoring-surface/type-chainlink.md +3 -3
- package/docs/reference/authoring-surface/type-commandinput.md +4 -3
- package/docs/reference/authoring-surface/type-commandoptions.md +0 -22
- package/docs/reference/authoring-surface/type-commandoutput.md +23 -5
- package/docs/reference/authoring-surface/type-customize.md +20 -3
- package/docs/reference/authoring-surface/type-delivered.md +17 -0
- package/docs/reference/authoring-surface/type-deliveredevent.md +18 -0
- package/docs/reference/authoring-surface/type-dispatchdefinition.md +4 -3
- package/docs/reference/authoring-surface/type-dispatchoutput.md +9 -2
- package/docs/reference/authoring-surface/type-doorlessinput.md +19 -0
- package/docs/reference/authoring-surface/type-effectbinding.md +12 -2
- package/docs/reference/authoring-surface/type-filebody.md +4 -8
- package/docs/reference/authoring-surface/type-framepath.md +14 -0
- package/docs/reference/authoring-surface/type-gatebinding.md +15 -0
- package/docs/reference/authoring-surface/type-gatehooks.md +12 -0
- package/docs/reference/authoring-surface/type-gatelook.md +37 -0
- package/docs/reference/authoring-surface/type-gateoutcome.md +32 -0
- package/docs/reference/authoring-surface/type-gatereply.md +19 -0
- package/docs/reference/authoring-surface/type-gateslot.md +21 -0
- package/docs/reference/authoring-surface/type-gateslotinput.md +13 -0
- package/docs/reference/authoring-surface/type-gateslotparts.md +18 -0
- package/docs/reference/authoring-surface/type-gateslots.md +12 -0
- package/docs/reference/authoring-surface/type-gatetypesat.md +13 -0
- package/docs/reference/authoring-surface/type-hostinjectedinput.md +3 -5
- package/docs/reference/authoring-surface/type-httpdefinition.md +6 -3
- package/docs/reference/authoring-surface/type-httpfileinput.md +20 -0
- package/docs/reference/authoring-surface/type-httpoutput.md +8 -2
- package/docs/reference/authoring-surface/type-jr2config.md +37 -2
- package/docs/reference/authoring-surface/type-lateroutcome.md +21 -0
- package/docs/reference/authoring-surface/type-lends.md +5 -1
- package/docs/reference/authoring-surface/type-lookoutput.md +14 -0
- package/docs/reference/authoring-surface/type-mcpcalloutput.md +78 -0
- package/docs/reference/authoring-surface/type-mcpnotreturned.md +42 -0
- package/docs/reference/authoring-surface/type-mcpoptions.md +47 -0
- package/docs/reference/authoring-surface/type-mcpoutput.md +50 -0
- package/docs/reference/authoring-surface/type-mcpreadoutput.md +53 -0
- package/docs/reference/authoring-surface/type-mcpremoteserver.md +50 -0
- package/docs/reference/authoring-surface/type-mcpserver.md +41 -0
- package/docs/reference/authoring-surface/type-mcpstdioserver.md +42 -0
- package/docs/reference/authoring-surface/type-namedsecret.md +4 -4
- package/docs/reference/authoring-surface/type-namestart.md +77 -0
- package/docs/reference/authoring-surface/type-noslotworkspaceoptions.md +1 -6
- package/docs/reference/authoring-surface/type-notworktreekeys.md +12 -0
- package/docs/reference/authoring-surface/type-openbodydeclaration.md +1 -1
- package/docs/reference/authoring-surface/type-openbodyoutput.md +1 -1
- package/docs/reference/authoring-surface/type-opencommandinput.md +7 -2
- package/docs/reference/authoring-surface/type-permissivenoslotworkspaceoptions.md +1 -6
- package/docs/reference/authoring-surface/type-permissiveworkspaceoptions.md +5 -10
- package/docs/reference/authoring-surface/type-poolnextinput.md +22 -0
- package/docs/reference/authoring-surface/type-poolspec.md +11 -6
- package/docs/reference/authoring-surface/type-pushinput.md +22 -3
- package/docs/reference/authoring-surface/type-pushoutput.md +33 -8
- package/docs/reference/authoring-surface/type-pushsourcespec.md +38 -0
- package/docs/reference/authoring-surface/type-readsourcespec.md +47 -0
- package/docs/reference/authoring-surface/type-reposlot.md +6 -6
- package/docs/reference/authoring-surface/type-reread.md +18 -0
- package/docs/reference/authoring-surface/type-runsconfig.md +41 -0
- package/docs/reference/authoring-surface/type-sandboxoptions.md +11 -4
- package/docs/reference/authoring-surface/type-sender.md +26 -1
- package/docs/reference/authoring-surface/type-sendercallerfield.md +14 -0
- package/docs/reference/authoring-surface/type-size.md +1 -1
- package/docs/reference/authoring-surface/type-skip.md +19 -0
- package/docs/reference/authoring-surface/type-slothandles.md +5 -13
- package/docs/reference/authoring-surface/type-sourcehooks.md +23 -0
- package/docs/reference/authoring-surface/type-sourcein.md +25 -0
- package/docs/reference/authoring-surface/type-sourcereply.md +18 -0
- package/docs/reference/authoring-surface/type-sourcespec.md +26 -17
- package/docs/reference/authoring-surface/type-sourceverdict.md +49 -0
- package/docs/reference/authoring-surface/type-standingpoolmachine.md +12 -0
- package/docs/reference/authoring-surface/type-standingpoolspec.md +51 -0
- package/docs/reference/authoring-surface/type-steerargs.md +20 -0
- package/docs/reference/authoring-surface/type-thinkinglevel.md +1 -1
- package/docs/reference/authoring-surface/type-usercontainer.md +1 -1
- package/docs/reference/authoring-surface/type-workspaced.md +30 -9
- package/docs/reference/authoring-surface/type-workspacehandles.md +5 -6
- package/docs/reference/authoring-surface/type-workspaceoptions.md +1 -13
- package/docs/reference/authoring-surface/type-worktreefound.md +16 -0
- package/docs/reference/authoring-surface/type-worktreeinput.md +56 -0
- package/docs/reference/authoring-surface/type-worktreekey.md +14 -0
- package/docs/reference/authoring-surface/type-worktreeoptions.md +29 -0
- package/docs/reference/authoring-surface/type-worktreeoutput.md +45 -0
- package/docs/reference/authoring-surface/type-worktreepath.md +16 -0
- package/docs/reference/authoring-surface/type-worktreeready.md +37 -0
- package/docs/reference/authoring-surface/type-worktreerefused.md +21 -0
- package/docs/reference/authoring-surface/workspace.md +66 -38
- package/docs/reference/authoring-surface/worktree.md +71 -0
- package/docs/reference/authoring-surface.md +88 -147
- package/docs/reference/cli.md +413 -74
- package/docs/reference/config.md +248 -6
- package/docs/reference/decisions/0001-operator-owned-generic-sandbox.md +10 -9
- package/docs/reference/decisions/0002-duplex-streaming-actor.md +81 -14
- package/docs/reference/decisions/0004-worktree-provisioning-model.md +27 -21
- package/docs/reference/decisions/0005-sandbox-pod-composition.md +39 -25
- package/docs/reference/decisions/0006-agent-control-surface.md +6 -3
- package/docs/reference/decisions/0007-durable-machine-state.md +1 -1
- package/docs/reference/decisions/0009-cli-and-instance-interface.md +150 -49
- package/docs/reference/decisions/0010-bdd-acceptance-tests.md +5 -3
- package/docs/reference/decisions/0011-workflow-defined-events.md +256 -12
- package/docs/reference/decisions/0012-workspace-wrapper-machine.md +39 -32
- package/docs/reference/decisions/0013-the-agent-reaches-its-machine-through-a-container-it-cannot-read.md +54 -30
- package/docs/reference/decisions/0014-observation-is-open-run-state-is-not.md +53 -27
- package/docs/reference/decisions/0015-authoring-surface-absorbs-the-mechanism.md +26 -16
- package/docs/reference/decisions/0016-agent-turn-mechanics-are-internal.md +9 -7
- package/docs/reference/decisions/0017-pool-over-a-source-port.md +226 -10
- package/docs/reference/decisions/0018-instance-agents-are-definitions-jr2-assembles-the-harness.md +15 -7
- package/docs/reference/decisions/0019-one-converging-command-against-the-current-context.md +53 -27
- package/docs/reference/decisions/0021-workspace-continuity-is-a-lease-that-answers-back.md +16 -2
- package/docs/reference/decisions/0022-observation-is-a-level-triggered-feed.md +4 -4
- package/docs/reference/decisions/0023-the-harness-prints-the-conversation.md +124 -43
- package/docs/reference/decisions/0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md +36 -38
- package/docs/reference/decisions/0025-cancel-ends-a-run-stop-parks-it.md +10 -5
- package/docs/reference/decisions/0027-the-harness-is-jr2s-own-server-flue-retires-the-wire-stays.md +16 -7
- package/docs/reference/decisions/0028-what-an-agent-may-do-to-the-workspace-is-part-of-its-definition.md +36 -19
- package/docs/reference/decisions/0029-a-menu-is-fixed-for-a-conversation-and-a-turn-is-told-its-allowed-picks.md +18 -10
- package/docs/reference/decisions/0030-a-snapshot-names-the-machine-it-was-written-under.md +3 -2
- package/docs/reference/decisions/0031-menu-only-agents-run-on-the-instance-harness.md +23 -15
- package/docs/reference/decisions/0032-the-console-unlocks-with-a-callers-credential.md +101 -0
- package/docs/reference/decisions/0033-a-machine-declares-the-input-that-starts-it.md +22 -22
- package/docs/reference/decisions/0034-the-console-renders-with-preact-and-the-server-erases-the-types.md +6 -6
- package/docs/reference/decisions/0037-an-instance-builds-its-sandbox-images-jr2-injects-the-harness.md +13 -12
- package/docs/reference/decisions/0049-a-machine-carries-its-parts-and-composes-by-invoke.md +23 -23
- package/docs/reference/decisions/0050-what-code-names-config-declares-what-the-cli-names-discovery-finds.md +6 -1
- package/docs/reference/decisions/0051-a-repo-is-a-slot-on-the-workspace-and-a-cache-on-the-node.md +57 -46
- package/docs/reference/decisions/0052-a-sandbox-lands-where-an-ordinary-pod-lands-and-the-cache-follows-it.md +5 -0
- package/docs/reference/decisions/0053-a-fetch-inside-the-pod-asks-the-node-cache-and-the-cache-asks-the-remote.md +20 -8
- package/docs/reference/decisions/0054-the-kit-ships-machines-and-a-shipped-machine-leaves-its-parts-open.md +55 -40
- package/docs/reference/decisions/0055-a-release-is-a-pushed-tag-and-the-publish-guard-is-the-credential.md +14 -13
- package/docs/reference/decisions/0057-a-turn-is-its-frame-its-dials-and-whether-it-continues.md +31 -24
- package/docs/reference/decisions/0058-a-harness-answers-the-orchestrator-alone.md +18 -10
- package/docs/reference/decisions/0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md +137 -86
- package/docs/reference/decisions/0062-jr2-briefs-the-agent-on-its-seat-never-on-its-task.md +165 -91
- package/docs/reference/decisions/0063-the-orchestrator-watches-the-cluster.md +7 -7
- package/docs/reference/decisions/0064-a-workspace-waits-for-capacity.md +2 -2
- package/docs/reference/decisions/0065-a-persist-survives-a-crash-and-history-is-kept-for-a-set-time.md +109 -0
- package/docs/reference/decisions/0066-a-model-tier-runs-a-real-model-and-a-change-to-what-it-reads-runs-the-tier.md +53 -23
- package/docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md +221 -153
- package/docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md +161 -96
- package/docs/reference/decisions/0069-the-orchestrator-is-a-kit-image-and-an-instance-publishes-its-assembly.md +32 -9
- package/docs/reference/decisions/0070-a-machine-lends-its-parts-to-a-chart.md +48 -33
- package/docs/reference/decisions/0071-a-run-starts-a-run-through-a-dispatch-effect.md +56 -30
- package/docs/reference/decisions/0072-the-docs-ship-inside-the-kit-and-an-instances-agents-md-indexes-them.md +35 -25
- package/docs/reference/decisions/0073-a-machine-fails-a-run-with-fail-and-an-uncaught-failure-ends-it-failed.md +1 -1
- package/docs/reference/decisions/0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md +296 -0
- package/docs/reference/decisions/0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md +347 -0
- package/docs/reference/decisions/0077-jr2-requires-landlock-on-every-sandbox-node.md +79 -0
- package/docs/reference/decisions/0078-a-caller-comes-from-one-authenticator-seam-and-acts-within-its-scopes.md +285 -0
- package/docs/reference/decisions/0079-an-agent-holds-mcp-tools-a-machine-calls-them-as-an-effect-and-the-machine-is-the-approval.md +198 -0
- package/docs/reference/decisions.md +9 -4
- package/docs/reference/glossary.md +138 -45
- package/docs/reference/hooks.md +1077 -0
- package/docs/reference/http-api.md +988 -123
- package/docs/reference/machines.md +0 -1
- package/docs/why.md +7 -5
- package/package.json +10 -3
- package/src/actor.ts +194 -42
- package/src/agent.ts +56 -6
- package/src/ambient.ts +3 -5
- package/src/assembly-checks.ts +530 -4
- package/src/callers.ts +245 -0
- package/src/chart.ts +238 -26
- package/src/charts.ts +9 -2
- package/src/config.ts +566 -7
- package/src/conversations.ts +453 -0
- package/src/custodian.lua +17 -5
- package/src/custodian.ts +257 -77
- package/src/customize.ts +154 -7
- package/src/dispatch-effect.ts +142 -26
- package/src/durability.ts +2 -0
- package/src/duration.ts +20 -0
- package/src/effect-checks.ts +52 -4
- package/src/effect.ts +401 -132
- package/src/effector-wire.ts +278 -30
- package/src/fail.ts +17 -4
- package/src/gate-brand.ts +16 -0
- package/src/gate.ts +634 -48
- package/src/harness-client.ts +84 -35
- package/src/harness-env.ts +33 -0
- package/src/held-secrets.ts +197 -39
- package/src/holds.ts +5 -2
- package/src/hook-kinds/discord.ts +92 -0
- package/src/hook-kinds/door.ts +107 -0
- package/src/hook-kinds/github.ts +89 -0
- package/src/hook-kinds/gitlab.ts +72 -0
- package/src/hook-kinds/hook.ts +7 -0
- package/src/hook-kinds/index.ts +465 -0
- package/src/hook-kinds/slack.ts +151 -0
- package/src/hook-kinds/webhook.ts +113 -0
- package/src/hooks.ts +506 -0
- package/src/http-effect.ts +164 -70
- package/src/http.ts +933 -99
- package/src/index.ts +30 -5
- package/src/instance.ts +196 -26
- package/src/internal.ts +23 -3
- package/src/kube-client.ts +6 -3
- package/src/machine-doc.ts +29 -5
- package/src/mcp-effect.ts +424 -0
- package/src/model-keys.ts +24 -0
- package/src/mount-digest.ts +41 -7
- package/src/names.ts +82 -7
- package/src/notices.ts +576 -59
- package/src/open-body.ts +110 -30
- package/src/open-effect.ts +17 -4
- package/src/open.ts +40 -7
- package/src/parts.ts +125 -35
- package/src/party.ts +35 -1
- package/src/pick.ts +43 -9
- package/src/pool.ts +1172 -172
- package/src/registration.ts +249 -17
- package/src/repo-identity.ts +7 -3
- package/src/repos.ts +20 -2
- package/src/run-host.ts +1614 -190
- package/src/run-list.ts +191 -0
- package/src/sandbox-effect.ts +217 -66
- package/src/sandbox-kube.ts +116 -21
- package/src/secret-placement.ts +248 -35
- package/src/server.ts +53 -4
- package/src/setup.ts +158 -38
- package/src/snapshot-store.ts +565 -48
- package/src/steer.ts +111 -0
- package/src/stub-harness.ts +6 -2
- package/src/tokens.ts +362 -46
- package/src/vocabulary.ts +23 -4
- package/src/wire.ts +27 -10
- package/src/workspace.ts +314 -259
- package/src/worktree.ts +249 -0
- package/docs/reference/authoring-surface/type-noslotspec.md +0 -26
- package/docs/reference/authoring-surface/type-workspacespec.md +0 -37
- package/docs/reference/decisions/0032-the-console-unlocks-with-the-instance-token.md +0 -61
- package/docs/reference/decisions/0065-a-persist-survives-a-crash-and-history-is-a-week.md +0 -65
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
// `@jr2/orchestrator/hooks/slack`: jr2's Slack kind. One Slack app at one address: its slash commands, its
|
|
2
|
+
// interactivity (Block Kit clicks) and its Events API.
|
|
3
|
+
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { defineHookKind, form, json, type Delivery, type HookRequest } from "./index.ts";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The schema of each event key. A key with `.*` covers a family: the command or the action id is
|
|
9
|
+
* part of the key, so one schema types `block_actions.approve` and `block_actions.reject`.
|
|
10
|
+
*/
|
|
11
|
+
export const slackEvents = {
|
|
12
|
+
/** A slash command, decoded from its form body: each field is a string. */
|
|
13
|
+
"slash_command.*": z.looseObject({ text: z.string(), user_id: z.string(), channel_id: z.string() }),
|
|
14
|
+
/** A Block Kit click, decoded from the JSON in its `payload` form field. */
|
|
15
|
+
"block_actions.*": z.looseObject({
|
|
16
|
+
user: z.looseObject({ id: z.string() }),
|
|
17
|
+
channel: z.looseObject({ id: z.string() }).optional(),
|
|
18
|
+
message: z.looseObject({ ts: z.string() }).optional(),
|
|
19
|
+
actions: z.array(z.looseObject({ action_id: z.string(), value: z.string().optional() })),
|
|
20
|
+
/** The kind's `answer` posts the text here. A click with no `response_url` fails the parse. */
|
|
21
|
+
response_url: z.string(),
|
|
22
|
+
}),
|
|
23
|
+
"event_callback.app_mention": z.looseObject({
|
|
24
|
+
event: z.looseObject({ user: z.string(), text: z.string(), channel: z.string(), ts: z.string() }),
|
|
25
|
+
}),
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const text = (v: unknown): string | undefined => (typeof v === "string" && v !== "" ? v : undefined);
|
|
29
|
+
|
|
30
|
+
const need = (v: string | undefined, what: string): string => {
|
|
31
|
+
if (v === undefined) throw new Error(`no ${what}`);
|
|
32
|
+
return v;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/** Slack's three characters that a `text` must escape. */
|
|
36
|
+
const escape = (s: string): string => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
37
|
+
|
|
38
|
+
const isForm = (req: HookRequest): boolean =>
|
|
39
|
+
(req.headers["content-type"] ?? "").toLowerCase().startsWith("application/x-www-form-urlencoded");
|
|
40
|
+
|
|
41
|
+
/** The host of a click's `response_url`. */
|
|
42
|
+
const RESPONSE_HOST = "https://hooks.slack.com/";
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* - verifier: `mac`, HMAC-SHA256 of `v0:<X-Slack-Request-Timestamp>:<raw body>`,
|
|
46
|
+
* `X-Slack-Signature: v0=<hex>`. The signature covers the timestamp, so the kind gives it as
|
|
47
|
+
* `at`, and the door's 300 s window applies (Slack asks for 5 minutes).
|
|
48
|
+
* - handshake: `url_verification` gets `{ challenge }`. The door verifies it first. (Slack's docs
|
|
49
|
+
* do not say that Slack signs it; check it at the first real setup.)
|
|
50
|
+
* - body: a command is a form; a click is a form with JSON in `payload`; an event is JSON.
|
|
51
|
+
* - event keys: `slash_command./jr2`, `block_actions.<action_id>`, `event_callback.<event.type>`.
|
|
52
|
+
* - delivery id: `event_id` for an event, `trigger_id` for a command or a click. (Slack's docs do
|
|
53
|
+
* not say that a `trigger_id` is unique for each request; the door's repeat check also uses the
|
|
54
|
+
* digest of the body.)
|
|
55
|
+
* - `onBehalfOf`: the Slack user id, never a display name.
|
|
56
|
+
* - who reads the answer: a person for a command or a click; Slack for an event (so a 503 stays a
|
|
57
|
+
* 503, and Slack sends the event again).
|
|
58
|
+
* - answer: ephemeral text. The answer to a click can only be an empty 200 (Block Kit), so the text
|
|
59
|
+
* goes to the click's `response_url` as the answer's `then`.
|
|
60
|
+
* - text limit: 4000 characters, the length that Slack's docs advise for a message's `text`. (Not
|
|
61
|
+
* checked for the answer to a command or a `response_url` post.)
|
|
62
|
+
*/
|
|
63
|
+
export const slack = defineHookKind({
|
|
64
|
+
api: 1,
|
|
65
|
+
id: "slack",
|
|
66
|
+
events: slackEvents,
|
|
67
|
+
verify: {
|
|
68
|
+
mac: {
|
|
69
|
+
alg: "sha256",
|
|
70
|
+
signed: (req) => {
|
|
71
|
+
const ts = req.headers["x-slack-request-timestamp"];
|
|
72
|
+
const sig = req.headers["x-slack-signature"];
|
|
73
|
+
if (ts === undefined || !/^\d+$/.test(ts) || !sig?.startsWith("v0=")) {
|
|
74
|
+
return { unsigned: "no Slack signature or timestamp" };
|
|
75
|
+
}
|
|
76
|
+
return { bytes: ["v0:", ts, ":", req.body], signature: { hex: sig.slice("v0=".length) }, at: Number(ts) };
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
read: (req) => {
|
|
81
|
+
if (isForm(req)) {
|
|
82
|
+
const f = form(req);
|
|
83
|
+
if (f.payload !== undefined) {
|
|
84
|
+
let p: { readonly type?: unknown; readonly trigger_id?: unknown; readonly user?: { readonly id?: unknown } };
|
|
85
|
+
try {
|
|
86
|
+
p = JSON.parse(f.payload) as typeof p;
|
|
87
|
+
} catch {
|
|
88
|
+
throw new Error("the payload field is not JSON");
|
|
89
|
+
}
|
|
90
|
+
const type = need(text(p?.type), "payload type");
|
|
91
|
+
const action = text(
|
|
92
|
+
(p as { readonly actions?: readonly { readonly action_id?: unknown }[] }).actions?.[0]?.action_id,
|
|
93
|
+
);
|
|
94
|
+
const by = text(p.user?.id);
|
|
95
|
+
return {
|
|
96
|
+
key: action ? `${type}.${action}` : type,
|
|
97
|
+
id: need(text(p.trigger_id), "trigger_id"),
|
|
98
|
+
payload: p,
|
|
99
|
+
...(by ? { onBehalfOf: by } : {}),
|
|
100
|
+
readBy: "person",
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
key: `slash_command.${need(text(f.command), "command")}`,
|
|
105
|
+
id: need(text(f.trigger_id), "trigger_id"),
|
|
106
|
+
payload: f,
|
|
107
|
+
...(text(f.user_id) ? { onBehalfOf: f.user_id } : {}),
|
|
108
|
+
readBy: "person",
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
const p = json(req) as {
|
|
112
|
+
readonly type?: unknown;
|
|
113
|
+
readonly challenge?: unknown;
|
|
114
|
+
readonly event_id?: unknown;
|
|
115
|
+
readonly event?: { readonly type?: unknown; readonly user?: unknown };
|
|
116
|
+
} | null;
|
|
117
|
+
const type = need(text(p?.type), "type");
|
|
118
|
+
if (type === "url_verification") return { handshake: { body: { challenge: text(p?.challenge) ?? "" } } };
|
|
119
|
+
const event = text(p?.event?.type);
|
|
120
|
+
const by = text(p?.event?.user);
|
|
121
|
+
return {
|
|
122
|
+
key: event ? `${type}.${event}` : type,
|
|
123
|
+
id: need(text(p?.event_id), "event_id"),
|
|
124
|
+
payload: p,
|
|
125
|
+
...(by ? { onBehalfOf: by } : {}),
|
|
126
|
+
};
|
|
127
|
+
},
|
|
128
|
+
answer: (t: string, d: Delivery) => {
|
|
129
|
+
if (d.key.startsWith("block_actions.")) {
|
|
130
|
+
const url = text((d.payload as { readonly response_url?: unknown }).response_url);
|
|
131
|
+
// The door refuses a `then` outside `posts`; a url of another host gets no post at all.
|
|
132
|
+
if (url === undefined || !url.startsWith(RESPONSE_HOST)) return { body: null };
|
|
133
|
+
return {
|
|
134
|
+
body: null,
|
|
135
|
+
then: {
|
|
136
|
+
post: url as `https://${string}`,
|
|
137
|
+
json: { response_type: "ephemeral", replace_original: false, text: escape(t) },
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
return { body: { response_type: "ephemeral", text: escape(t) } };
|
|
142
|
+
},
|
|
143
|
+
maxText: 4000,
|
|
144
|
+
posts: [RESPONSE_HOST],
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
declare module "./index.ts" {
|
|
148
|
+
interface HookKinds {
|
|
149
|
+
slack: typeof slack;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// `@jr2/orchestrator/hooks/webhook`: the general kind, for a service that has no kind of its own (ADR-0078). It
|
|
2
|
+
// makes a kind from data, through `defineHookKind`, as each other kind is made. So it gives a kind
|
|
3
|
+
// nothing that the interface does not give a kind from another package.
|
|
4
|
+
//
|
|
5
|
+
// It makes a kind, not a Hook: the service's protocol is the same for each Instance, so the
|
|
6
|
+
// Instance writes it one time, in a module of its own that `jr2.config.ts` imports for its Hooks
|
|
7
|
+
// and the Workflows import for their handlers. The Hook in the config gives only the credential
|
|
8
|
+
// and `may`.
|
|
9
|
+
//
|
|
10
|
+
// What it cannot do: a handshake, a body that is not JSON, a signature over more than the raw
|
|
11
|
+
// body, a signed timestamp, and an answer that a person reads. A service that needs one of them
|
|
12
|
+
// needs a kind module.
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
at,
|
|
16
|
+
defineHookKind,
|
|
17
|
+
json,
|
|
18
|
+
type Events,
|
|
19
|
+
type HookKindOf,
|
|
20
|
+
type HookRequest,
|
|
21
|
+
type MacAlg,
|
|
22
|
+
type Verify,
|
|
23
|
+
} from "./index.ts";
|
|
24
|
+
|
|
25
|
+
/** Where a value is in a delivery: a header (lowercase), or a dotted path in the JSON body. */
|
|
26
|
+
export type Field = { readonly header: string } | { readonly json: string };
|
|
27
|
+
|
|
28
|
+
/** How the general kind verifies. `mac` is over the raw body only. */
|
|
29
|
+
export type WebhookVerify =
|
|
30
|
+
| {
|
|
31
|
+
readonly mac: {
|
|
32
|
+
/** The header of the signature (lowercase). */
|
|
33
|
+
readonly header: string;
|
|
34
|
+
/** `sha256` when it is not given. */
|
|
35
|
+
readonly alg?: MacAlg;
|
|
36
|
+
/** The text before the signature in the header's value, such as `sha256=`. */
|
|
37
|
+
readonly prefix?: string;
|
|
38
|
+
/** `hex` when it is not given. */
|
|
39
|
+
readonly encoding?: "hex" | "base64";
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
| { readonly token: { readonly header: string } };
|
|
43
|
+
|
|
44
|
+
type VerifyOf<W extends WebhookVerify> = W extends { readonly mac: unknown }
|
|
45
|
+
? Extract<Verify, { readonly mac: unknown }>
|
|
46
|
+
: Extract<Verify, { readonly token: unknown }>;
|
|
47
|
+
|
|
48
|
+
/** The text of a field's value: a string, or a number as a string. */
|
|
49
|
+
function pick(req: HookRequest, body: unknown, f: Field): string | undefined {
|
|
50
|
+
const v = "header" in f ? req.headers[f.header.toLowerCase()] : at(body, f.json);
|
|
51
|
+
if (typeof v === "number" && Number.isFinite(v)) return String(v);
|
|
52
|
+
return typeof v === "string" && v !== "" ? v : undefined;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* A kind from data.
|
|
57
|
+
* - `id`: the kind's id. The Instance chooses it; it must not be the id of another kind.
|
|
58
|
+
* - `events`: a zod schema for each event key that a handler can hear. The door parses with it, as
|
|
59
|
+
* for each kind (`Events` in `@jr2/orchestrator/hooks`).
|
|
60
|
+
* - `verify`: `mac` over the raw body (the header, its prefix, the algorithm, the encoding), or
|
|
61
|
+
* `token` in a header.
|
|
62
|
+
* - `key`: the event key, from one field; or `{ name, action }`, which gives `<name>.<action>`, or
|
|
63
|
+
* `<name>` when the delivery has no action.
|
|
64
|
+
* - `delivery`: the delivery id. `onBehalfOf`: optional.
|
|
65
|
+
*
|
|
66
|
+
* The answer is the outcome JSON.
|
|
67
|
+
*/
|
|
68
|
+
export function webhook<const Id extends string, const E extends Events, const W extends WebhookVerify>(spec: {
|
|
69
|
+
readonly id: Id;
|
|
70
|
+
readonly events: E;
|
|
71
|
+
readonly verify: W;
|
|
72
|
+
readonly key: Field | { readonly name: Field; readonly action?: Field };
|
|
73
|
+
readonly delivery: Field;
|
|
74
|
+
readonly onBehalfOf?: Field;
|
|
75
|
+
}): HookKindOf<Id, E, VerifyOf<W>> {
|
|
76
|
+
const sv = spec.verify as WebhookVerify;
|
|
77
|
+
let verify: Verify;
|
|
78
|
+
if ("mac" in sv) {
|
|
79
|
+
const mac = sv.mac;
|
|
80
|
+
const header = mac.header.toLowerCase();
|
|
81
|
+
const prefix = mac.prefix ?? "";
|
|
82
|
+
verify = {
|
|
83
|
+
mac: {
|
|
84
|
+
alg: mac.alg ?? "sha256",
|
|
85
|
+
signed: (req) => {
|
|
86
|
+
const raw = req.headers[header];
|
|
87
|
+
if (raw === undefined || !raw.startsWith(prefix)) return { unsigned: `no ${header} header` };
|
|
88
|
+
const value = raw.slice(prefix.length);
|
|
89
|
+
return { bytes: [req.body], signature: mac.encoding === "base64" ? { base64: value } : { hex: value } };
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
} else {
|
|
94
|
+
verify = { token: { header: sv.token.header.toLowerCase() } };
|
|
95
|
+
}
|
|
96
|
+
const k = spec.key;
|
|
97
|
+
return defineHookKind({
|
|
98
|
+
api: 1,
|
|
99
|
+
id: spec.id,
|
|
100
|
+
events: spec.events,
|
|
101
|
+
verify,
|
|
102
|
+
read: (req) => {
|
|
103
|
+
const body = json(req);
|
|
104
|
+
const name = "name" in k ? pick(req, body, k.name) : pick(req, body, k);
|
|
105
|
+
const action = "name" in k && k.action ? pick(req, body, k.action) : undefined;
|
|
106
|
+
const id = pick(req, body, spec.delivery);
|
|
107
|
+
if (name === undefined) throw new Error("the delivery has no event name");
|
|
108
|
+
if (id === undefined) throw new Error("the delivery has no delivery id");
|
|
109
|
+
const by = spec.onBehalfOf ? pick(req, body, spec.onBehalfOf) : undefined;
|
|
110
|
+
return { key: action ? `${name}.${action}` : name, id, payload: body, ...(by ? { onBehalfOf: by } : {}) };
|
|
111
|
+
},
|
|
112
|
+
}) as unknown as HookKindOf<Id, E, VerifyOf<W>>;
|
|
113
|
+
}
|