@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
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// The Console shell (ADR-0032): nav over a master–detail main — the fleet rail on the left, the
|
|
2
|
-
// selected workflow's Machine in the center, the Attention/Activity drawer on the right
|
|
2
|
+
// selected workflow's Machine in the center, the Attention/Activity drawer on the right, and under
|
|
3
|
+
// those two the bottom drawer with the selected run's conversations (ADR-0023). One pure
|
|
3
4
|
// render over the store plus the bootstrap's working state; Preact's diff replaces main.js's
|
|
4
5
|
// hand-rolled memo()/paint() machinery (ADR-0034). Which drawer tab the reader chose is view state
|
|
5
6
|
// like the zoom — component state, never the store's.
|
|
@@ -7,8 +8,11 @@
|
|
|
7
8
|
import { Fragment, h, type JSX } from "preact";
|
|
8
9
|
import { useState } from "preact/hooks";
|
|
9
10
|
import {
|
|
11
|
+
may,
|
|
12
|
+
olderAssemblyMark,
|
|
10
13
|
selectedRun,
|
|
11
14
|
selectedRunFailure,
|
|
15
|
+
selectedRunStarter,
|
|
12
16
|
selectedRunGates,
|
|
13
17
|
selectedRunHeld,
|
|
14
18
|
selectedRunWaiting,
|
|
@@ -16,10 +20,12 @@ import {
|
|
|
16
20
|
type Store,
|
|
17
21
|
} from "../store.ts";
|
|
18
22
|
import type { MachineDoc } from "../canvas.ts";
|
|
23
|
+
import type { LoginIssuer } from "../login.ts";
|
|
19
24
|
import { Nav } from "./nav.ts";
|
|
20
25
|
import { Fleet } from "./fleet.ts";
|
|
21
26
|
import { Drawer, type DrawerTab } from "./drawer.ts";
|
|
22
27
|
import { MachinePane } from "./machine-pane.ts";
|
|
28
|
+
import { BottomDrawer } from "./bottom-drawer.ts";
|
|
23
29
|
|
|
24
30
|
/**
|
|
25
31
|
* What the bootstrap (main.ts) hands the components: every write path in one object, so no
|
|
@@ -36,7 +42,14 @@ export type AppApi = {
|
|
|
36
42
|
startRun(name: string, body: Record<string, unknown>): Promise<string | null>;
|
|
37
43
|
/** POST one gate event; same contract as {@link AppApi.startRun}. */
|
|
38
44
|
deliverGate(runId: string, gate: string, event: string, body: Record<string, unknown>): Promise<string | null>;
|
|
45
|
+
/** POST CANCEL to a run (ADR-0002); same contract as {@link AppApi.startRun}. */
|
|
46
|
+
cancelRun(runId: string): Promise<string | null>;
|
|
47
|
+
/** POST one Steering message to an Agent slot of a run; same contract as {@link AppApi.startRun}. */
|
|
48
|
+
steer(runId: string, agent: string, text: string): Promise<string | null>;
|
|
49
|
+
/** Keep a typed credential (empty clears it) and ask `GET /whoami` what it may do. */
|
|
39
50
|
setToken(value: string): void;
|
|
51
|
+
/** Go to an issuer to log in (OIDC code + PKCE, ADR-0032). */
|
|
52
|
+
login(issuer: LoginIssuer): void;
|
|
40
53
|
/** The canvas reports its zoom readout through here (nav renders it). */
|
|
41
54
|
onScale(pct: number): void;
|
|
42
55
|
zoomIn(): void;
|
|
@@ -61,30 +74,57 @@ export function App(props: {
|
|
|
61
74
|
view: MachineView;
|
|
62
75
|
zoomPct: number;
|
|
63
76
|
initialToken: string;
|
|
77
|
+
logins: readonly LoginIssuer[];
|
|
78
|
+
loginNote: string | null;
|
|
64
79
|
schemas: ReadonlyMap<string, unknown>;
|
|
65
80
|
api: AppApi;
|
|
66
81
|
}): JSX.Element {
|
|
67
|
-
const { store, view, zoomPct, initialToken, schemas, api } = props;
|
|
82
|
+
const { store, view, zoomPct, initialToken, logins, loginNote, schemas, api } = props;
|
|
83
|
+
const run = selectedRun(store);
|
|
84
|
+
// A settled run takes no cancel and no Steering message; a live one, as its Caller's scopes allow.
|
|
85
|
+
const live = run !== null && store.runs.has(run.runId);
|
|
68
86
|
const [chosenTab, setChosenTab] = useState<DrawerTab>("attention");
|
|
69
87
|
return h(
|
|
70
88
|
Fragment,
|
|
71
89
|
null,
|
|
72
|
-
h(Nav, {
|
|
90
|
+
h(Nav, {
|
|
91
|
+
store,
|
|
92
|
+
machineId: view.doc?.id ?? null,
|
|
93
|
+
zoomPct,
|
|
94
|
+
initialToken,
|
|
95
|
+
logins,
|
|
96
|
+
loginNote,
|
|
97
|
+
onChooseTab: setChosenTab,
|
|
98
|
+
api,
|
|
99
|
+
}),
|
|
73
100
|
h(
|
|
74
101
|
"main",
|
|
75
102
|
null,
|
|
76
|
-
h(
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
103
|
+
h(
|
|
104
|
+
"div",
|
|
105
|
+
{ id: "work" },
|
|
106
|
+
h(Fleet, { store, schemas, api }),
|
|
107
|
+
h(
|
|
108
|
+
"div",
|
|
109
|
+
{ id: "upper" },
|
|
110
|
+
h(MachinePane, {
|
|
111
|
+
view,
|
|
112
|
+
run,
|
|
113
|
+
selectedNodeId: store.selectedNodeId,
|
|
114
|
+
gates: selectedRunGates(store),
|
|
115
|
+
waiting: selectedRunWaiting(store),
|
|
116
|
+
held: selectedRunHeld(store),
|
|
117
|
+
failure: selectedRunFailure(store),
|
|
118
|
+
starter: selectedRunStarter(store),
|
|
119
|
+
older: store.selectedRunId ? olderAssemblyMark(store, store.selectedRunId) : null,
|
|
120
|
+
canCancel: live && may(store, "cancel", run.workflow),
|
|
121
|
+
api,
|
|
122
|
+
}),
|
|
123
|
+
h(Drawer, { store, chosenTab, onChooseTab: setChosenTab, api }),
|
|
124
|
+
),
|
|
125
|
+
),
|
|
126
|
+
// Full width under the rail, the Machine and the right drawer (ADR-0032).
|
|
127
|
+
h(BottomDrawer, { store, api }),
|
|
88
128
|
),
|
|
89
129
|
);
|
|
90
130
|
}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
// The bottom drawer (ADR-0032, ADR-0023): the selected run's kept conversations, full width across
|
|
2
|
+
// the bottom of the page. Closed, it is a bar of tabs, one for each Agent slot of the run,
|
|
3
|
+
// with a dot on a slot whose Turn is open. Open, it shows the chosen slot's conversation and follows
|
|
4
|
+
// a live Turn: the model's words, each tool call folded with its answer, the picks, the Steering
|
|
5
|
+
// messages, the notices, the refusals, and the cut and swept marks. The Steering box is beside it,
|
|
6
|
+
// for a Caller with `steer` on a live run.
|
|
7
|
+
//
|
|
8
|
+
// The entries are store belief (main.ts reads them); the height is the viewer's own, kept in
|
|
9
|
+
// localStorage (a per-viewer convenience — the page works the same without it).
|
|
10
|
+
|
|
11
|
+
import { h, type JSX } from "preact";
|
|
12
|
+
import { useEffect, useRef, useState } from "preact/hooks";
|
|
13
|
+
import { activeSlot, may, slotConversations, slotTabs, type SlotConversation, type Store } from "../store.ts";
|
|
14
|
+
import { foldEntries, type ConversationItem } from "../conversation.ts";
|
|
15
|
+
import type { AppApi } from "./app.ts";
|
|
16
|
+
|
|
17
|
+
const HEIGHT_KEY = "jr2.console.bottom.height";
|
|
18
|
+
const HEIGHT_DEFAULT = 300;
|
|
19
|
+
const HEIGHT_MIN = 120;
|
|
20
|
+
|
|
21
|
+
function storedHeight(): number {
|
|
22
|
+
try {
|
|
23
|
+
const value = Number(localStorage.getItem(HEIGHT_KEY));
|
|
24
|
+
const kept = Number.isFinite(value) && value >= HEIGHT_MIN ? value : HEIGHT_DEFAULT;
|
|
25
|
+
// A height kept on a taller window must leave room for the Machine on this one.
|
|
26
|
+
return Math.min(kept, maxHeight());
|
|
27
|
+
} catch {
|
|
28
|
+
return HEIGHT_DEFAULT;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** The tallest the drawer goes: the Machine keeps at least 160px. */
|
|
33
|
+
function maxHeight(): number {
|
|
34
|
+
return Math.max(HEIGHT_MIN, window.innerHeight - 160);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function keepHeight(height: number): void {
|
|
38
|
+
try {
|
|
39
|
+
localStorage.setItem(HEIGHT_KEY, String(Math.round(height)));
|
|
40
|
+
} catch {
|
|
41
|
+
// no storage (a private window, or blocked): the height lasts for this page only
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function BottomDrawer(props: { store: Store; api: AppApi }): JSX.Element | null {
|
|
46
|
+
const { store, api } = props;
|
|
47
|
+
const [height, setHeight] = useState(storedHeight);
|
|
48
|
+
const run = store.selectedRunId;
|
|
49
|
+
const workflow = store.workflow;
|
|
50
|
+
const live = run !== null && store.runs.has(run);
|
|
51
|
+
const canRead = workflow !== null && may(store, "read", workflow);
|
|
52
|
+
const canSteer = live && workflow !== null && may(store, "steer", workflow);
|
|
53
|
+
// The observer's page has no conversation to show and no Agent to steer (ADR-0014).
|
|
54
|
+
if (run === null || (!canRead && !canSteer)) return null;
|
|
55
|
+
|
|
56
|
+
const tabs = slotTabs(store);
|
|
57
|
+
const slot = activeSlot(store);
|
|
58
|
+
const open = store.bottom.open;
|
|
59
|
+
const tab = tabs.find((t) => t.slot === slot);
|
|
60
|
+
|
|
61
|
+
const startResize = (e: PointerEvent): void => {
|
|
62
|
+
e.preventDefault();
|
|
63
|
+
const startY = e.clientY;
|
|
64
|
+
const startHeight = height;
|
|
65
|
+
let last = startHeight;
|
|
66
|
+
const move = (ev: PointerEvent): void => {
|
|
67
|
+
last = Math.min(maxHeight(), Math.max(HEIGHT_MIN, startHeight + (startY - ev.clientY)));
|
|
68
|
+
setHeight(last);
|
|
69
|
+
};
|
|
70
|
+
const up = (): void => {
|
|
71
|
+
removeEventListener("pointermove", move);
|
|
72
|
+
removeEventListener("pointerup", up);
|
|
73
|
+
removeEventListener("pointercancel", up);
|
|
74
|
+
keepHeight(last);
|
|
75
|
+
};
|
|
76
|
+
addEventListener("pointermove", move);
|
|
77
|
+
addEventListener("pointerup", up);
|
|
78
|
+
addEventListener("pointercancel", up);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
return h(
|
|
82
|
+
"section",
|
|
83
|
+
{
|
|
84
|
+
id: "bottom-drawer",
|
|
85
|
+
class: open ? "bottom--open" : "",
|
|
86
|
+
style: open ? { height: `${Math.min(height, maxHeight())}px` } : undefined,
|
|
87
|
+
},
|
|
88
|
+
open ? h("div", { class: "bottom-resize", title: "drag to change the height", onPointerDown: startResize }) : null,
|
|
89
|
+
h(
|
|
90
|
+
"div",
|
|
91
|
+
{ class: "bottom-tabs" },
|
|
92
|
+
h(
|
|
93
|
+
"button",
|
|
94
|
+
{
|
|
95
|
+
id: "bottom-toggle",
|
|
96
|
+
title: open ? "close the conversations" : "open the conversations",
|
|
97
|
+
onClick: () => api.dispatch({ kind: "bottom", open: !open }),
|
|
98
|
+
},
|
|
99
|
+
open ? "▾" : "▴",
|
|
100
|
+
),
|
|
101
|
+
h("span", { class: "bottom-title" }, "Conversations"),
|
|
102
|
+
tabs.length
|
|
103
|
+
? tabs.map((t) =>
|
|
104
|
+
h(
|
|
105
|
+
"button",
|
|
106
|
+
{
|
|
107
|
+
key: t.slot,
|
|
108
|
+
class: `slot-tab${t.slot === slot ? " tab--active" : ""}`,
|
|
109
|
+
title: t.open ? `${t.slot}: a Turn is open` : t.slot,
|
|
110
|
+
onClick: () => api.dispatch({ kind: "bottom", open: true, slot: t.slot }),
|
|
111
|
+
},
|
|
112
|
+
t.open ? h("span", { class: "live-dot" }) : null,
|
|
113
|
+
t.slot,
|
|
114
|
+
),
|
|
115
|
+
)
|
|
116
|
+
: h("span", { class: "dim" }, canRead ? "no Agent has a conversation in this run yet" : ""),
|
|
117
|
+
),
|
|
118
|
+
open
|
|
119
|
+
? h(
|
|
120
|
+
"div",
|
|
121
|
+
{ class: "bottom-body" },
|
|
122
|
+
h(
|
|
123
|
+
"div",
|
|
124
|
+
{ class: "conversation-pane" },
|
|
125
|
+
canRead
|
|
126
|
+
? h(Conversation, { key: `${run}/${slot ?? ""}`, conversations: slotConversations(store, slot) })
|
|
127
|
+
: h("div", { class: "dim" }, "to read a conversation, the Caller needs the read scope"),
|
|
128
|
+
),
|
|
129
|
+
canSteer && slot !== null
|
|
130
|
+
? h(SteeringBox, { key: `${run}/${slot}`, runId: run, slot, steerable: tab?.steerable ?? false, api })
|
|
131
|
+
: null,
|
|
132
|
+
)
|
|
133
|
+
: null,
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** One slot's conversations, the first first. It stays at the end while the reader is at the end,
|
|
138
|
+
* so a live Turn is followed; a reader who scrolls up stays where they are. */
|
|
139
|
+
function Conversation(props: { conversations: SlotConversation[] }): JSX.Element {
|
|
140
|
+
const { conversations } = props;
|
|
141
|
+
const ref = useRef<HTMLDivElement>(null);
|
|
142
|
+
const atEnd = useRef(true);
|
|
143
|
+
const count = conversations.reduce((n, c) => n + c.entries.length, 0);
|
|
144
|
+
useEffect(() => {
|
|
145
|
+
const el = ref.current;
|
|
146
|
+
if (el && atEnd.current) el.scrollTop = el.scrollHeight;
|
|
147
|
+
}, [count]);
|
|
148
|
+
const onScroll = (): void => {
|
|
149
|
+
const el = ref.current;
|
|
150
|
+
if (el) atEnd.current = el.scrollHeight - el.scrollTop - el.clientHeight < 24;
|
|
151
|
+
};
|
|
152
|
+
return h(
|
|
153
|
+
"div",
|
|
154
|
+
{ id: "conversation", ref, onScroll },
|
|
155
|
+
conversations.length
|
|
156
|
+
? conversations.map((c) => conversationBlock(c))
|
|
157
|
+
: h("div", { class: "dim" }, "this Agent has no kept conversation yet"),
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function conversationBlock(c: SlotConversation): JSX.Element {
|
|
162
|
+
const { row, entries, ended } = c;
|
|
163
|
+
const swept = row.swept ?? ended?.swept;
|
|
164
|
+
const cut = (row.cut || ended?.cut) && !entries.some((e) => e.kind === "cut");
|
|
165
|
+
return h(
|
|
166
|
+
"div",
|
|
167
|
+
{ key: row.instanceId, class: "conversation-block" },
|
|
168
|
+
h("div", { class: "conversation-head dim" }, `${row.agent} · ${row.instanceId}${row.open ? " · Turn open" : ""}`),
|
|
169
|
+
swept ? h("div", { class: "conv-mark conv-swept" }, `conversation swept: ${swept}`) : null,
|
|
170
|
+
foldEntries(entries).map((item, i) => itemView(item, i)),
|
|
171
|
+
cut ? h("div", { class: "conv-mark conv-cut" }, "cut: the conversation reached its size limit") : null,
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** The first line of a text, short, for a folded item's summary. */
|
|
176
|
+
function firstLine(text: string, max = 120): string {
|
|
177
|
+
const line = text.split("\n", 1)[0] ?? "";
|
|
178
|
+
return line.length > max ? `${line.slice(0, max)}…` : line;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function itemView(item: ConversationItem, i: number): JSX.Element {
|
|
182
|
+
switch (item.type) {
|
|
183
|
+
case "text":
|
|
184
|
+
return h("div", { key: i, class: "conv-text" }, item.text);
|
|
185
|
+
case "thinking":
|
|
186
|
+
return h(
|
|
187
|
+
"details",
|
|
188
|
+
{ key: i, class: "conv-thinking" },
|
|
189
|
+
h("summary", null, h("span", { class: "conv-label" }, "thinking"), ` ${firstLine(item.text)}`),
|
|
190
|
+
h("div", { class: "conv-body" }, item.text),
|
|
191
|
+
);
|
|
192
|
+
case "prompt":
|
|
193
|
+
case "notice":
|
|
194
|
+
case "steering":
|
|
195
|
+
return h(
|
|
196
|
+
"details",
|
|
197
|
+
{ key: i, class: `conv-${item.type}`, open: item.type !== "prompt" },
|
|
198
|
+
h("summary", null, h("span", { class: "conv-label" }, item.type), ` ${firstLine(item.text)}`),
|
|
199
|
+
h("div", { class: "conv-body" }, item.text),
|
|
200
|
+
);
|
|
201
|
+
case "call": {
|
|
202
|
+
const result = item.result;
|
|
203
|
+
const label = result ? (result.error ? "refused" : "pick") : "tool";
|
|
204
|
+
return h(
|
|
205
|
+
"details",
|
|
206
|
+
{ key: i, class: `conv-call${result?.error ? " conv-refused" : ""}` },
|
|
207
|
+
h(
|
|
208
|
+
"summary",
|
|
209
|
+
null,
|
|
210
|
+
h("span", { class: "conv-label" }, label),
|
|
211
|
+
` ${item.tool}`,
|
|
212
|
+
item.input ? h("span", { class: "dim" }, ` ${firstLine(item.input, 100)}`) : null,
|
|
213
|
+
),
|
|
214
|
+
item.input ? h("div", { class: "conv-body" }, item.input) : null,
|
|
215
|
+
result ? h("div", { class: "conv-body conv-answer" }, result.text) : null,
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
default:
|
|
219
|
+
return h(
|
|
220
|
+
"div",
|
|
221
|
+
{ key: i, class: `conv-mark conv-${item.type}` },
|
|
222
|
+
item.label ? `${item.label}: ${item.text}` : item.text,
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* The Steering box (ADR-0002) beside the slot it steers. What the person types is theirs
|
|
229
|
+
* (uncontrolled), and a refusal shows where they typed. A slot that the run says is not steerable
|
|
230
|
+
* shows the box and cannot send.
|
|
231
|
+
*/
|
|
232
|
+
function SteeringBox(props: { runId: string; slot: string; steerable: boolean; api: AppApi }): JSX.Element {
|
|
233
|
+
const { runId, slot, steerable, api } = props;
|
|
234
|
+
const [note, setNote] = useState<string | null>(null);
|
|
235
|
+
const [busy, setBusy] = useState(false);
|
|
236
|
+
const send = (e: Event): void => {
|
|
237
|
+
e.preventDefault();
|
|
238
|
+
const form = e.currentTarget as HTMLFormElement;
|
|
239
|
+
const area = form.elements.namedItem("text") as HTMLTextAreaElement | null;
|
|
240
|
+
const text = area?.value.trim() ?? "";
|
|
241
|
+
if (!text) return setNote("write the message");
|
|
242
|
+
setBusy(true);
|
|
243
|
+
setNote(null);
|
|
244
|
+
void api.steer(runId, slot, text).then((failure) => {
|
|
245
|
+
setBusy(false);
|
|
246
|
+
if (failure) return setNote(`not sent: ${failure}`);
|
|
247
|
+
if (area) area.value = "";
|
|
248
|
+
setNote(`sent to ${slot}: it reads the message at its next step`);
|
|
249
|
+
});
|
|
250
|
+
};
|
|
251
|
+
return h(
|
|
252
|
+
"form",
|
|
253
|
+
{ id: "steering-box", class: "schema-form steering-box", onSubmit: send },
|
|
254
|
+
h("div", { class: "dim" }, steerable ? `steer ${slot}` : `${slot} cannot be steered now`),
|
|
255
|
+
h("textarea", { name: "text", rows: 4, disabled: !steerable }),
|
|
256
|
+
h("button", { type: "submit", disabled: busy || !steerable }, "send"),
|
|
257
|
+
h("div", { class: note?.startsWith("sent to ") ? "dim" : "form-error", hidden: note === null }, note ?? ""),
|
|
258
|
+
);
|
|
259
|
+
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
// The drawer: Attention (the gate inbox) + Activity (the emit log).
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
// The drawer: Attention (the gate inbox) + Activity (the emit log). The Attention tab is there only
|
|
2
|
+
// for a Caller with `answer` on some Workflow (ADR-0032, ADR-0078) — without it the page is the
|
|
3
|
+
// observer band of ADR-0014 — and a card is only ever a fact that `GET /gates` stated: it empties
|
|
4
|
+
// when the server's next answer leaves it out, never through bookkeeping on this side.
|
|
4
5
|
|
|
5
6
|
import { h, type JSX } from "preact";
|
|
6
|
-
import { visibleGates, type GateCard, type Store } from "../store.ts";
|
|
7
|
+
import { may, visibleGates, type GateCard, type Store } from "../store.ts";
|
|
7
8
|
import { SchemaForm } from "./schema-form.ts";
|
|
8
9
|
import type { AppApi } from "./app.ts";
|
|
9
10
|
|
|
@@ -18,8 +19,8 @@ export function Drawer(props: {
|
|
|
18
19
|
api: AppApi;
|
|
19
20
|
}): JSX.Element {
|
|
20
21
|
const { store, chosenTab, onChooseTab, api } = props;
|
|
21
|
-
const unlocked = store
|
|
22
|
-
const tab = unlocked ? chosenTab : "activity"; //
|
|
22
|
+
const unlocked = may(store, "answer");
|
|
23
|
+
const tab = unlocked ? chosenTab : "activity"; // no `answer`: no Attention tab — the observer's drawer
|
|
23
24
|
return h(
|
|
24
25
|
"aside",
|
|
25
26
|
{ id: "drawer" },
|
|
@@ -94,6 +95,13 @@ function inboxCard(runId: string, workflow: string, view: GateCard, store: Store
|
|
|
94
95
|
},
|
|
95
96
|
h("span", { class: "gate-id" }, `⚑ ${view.gate}`),
|
|
96
97
|
h("span", { class: "dim" }, `${workflow} · ${runId.slice(0, 8)}`),
|
|
98
|
+
view.openedAt
|
|
99
|
+
? h(
|
|
100
|
+
"span",
|
|
101
|
+
{ class: "dim", title: view.openedAt },
|
|
102
|
+
` · open since ${new Date(view.openedAt).toLocaleTimeString()}`,
|
|
103
|
+
)
|
|
104
|
+
: null,
|
|
97
105
|
),
|
|
98
106
|
view.meta && Object.keys(view.meta).length
|
|
99
107
|
? h(
|
|
@@ -5,9 +5,23 @@
|
|
|
5
5
|
//
|
|
6
6
|
// A named Chart is a Workflow (ADR-0070): it lists under its Chart wrapper, indented, with its origin
|
|
7
7
|
// (`chart file` or `chart`) and, when a publish no longer admits it, `unrunnable` with the reason.
|
|
8
|
+
//
|
|
9
|
+
// Under the selected Workflow's live runs are its ended runs (ADR-0032): those `GET /runs` lists,
|
|
10
|
+
// with a Caller that has `read`, each with its status, and a `drifted` run with its reason. A run on
|
|
11
|
+
// an older Assembly carries the mark "older Assembly · <hash>" (ADR-0069).
|
|
8
12
|
|
|
9
13
|
import { h, type JSX } from "preact";
|
|
10
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
dispatchBadge,
|
|
16
|
+
fleetEntries,
|
|
17
|
+
fleetRuns,
|
|
18
|
+
heldAssembly,
|
|
19
|
+
may,
|
|
20
|
+
olderAssemblyMark,
|
|
21
|
+
type FleetEntry,
|
|
22
|
+
type RailRun,
|
|
23
|
+
type Store,
|
|
24
|
+
} from "../store.ts";
|
|
11
25
|
import { SchemaForm } from "./schema-form.ts";
|
|
12
26
|
import type { AppApi } from "./app.ts";
|
|
13
27
|
|
|
@@ -79,7 +93,7 @@ function workflowItem(
|
|
|
79
93
|
)
|
|
80
94
|
: null,
|
|
81
95
|
h("span", { class: "wf-count" }, String(rows.filter((r) => !r.settled).length)),
|
|
82
|
-
store
|
|
96
|
+
may(store, "start", name) && !entry.unrunnable
|
|
83
97
|
? h(
|
|
84
98
|
"button",
|
|
85
99
|
{
|
|
@@ -113,31 +127,32 @@ function startForm(name: string, schemas: ReadonlyMap<string, unknown>, api: App
|
|
|
113
127
|
);
|
|
114
128
|
}
|
|
115
129
|
|
|
116
|
-
function runRows(
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
store: Store,
|
|
120
|
-
api: AppApi,
|
|
121
|
-
): JSX.Element {
|
|
130
|
+
function runRows(name: string, rows: RailRun[], store: Store, api: AppApi): JSX.Element {
|
|
131
|
+
const live = rows.filter((r) => !r.settled);
|
|
132
|
+
const ended = rows.filter((r) => r.settled);
|
|
122
133
|
return h(
|
|
123
134
|
"ul",
|
|
124
135
|
{ class: "wf-runs" },
|
|
125
|
-
|
|
126
|
-
?
|
|
127
|
-
: h("li", { class: "empty" }, "no live runs"),
|
|
136
|
+
live.length
|
|
137
|
+
? live.map((run) => runRow(name, run, store, api))
|
|
138
|
+
: h("li", { key: "no-live", class: "empty" }, "no live runs"),
|
|
139
|
+
ended.length ? h("li", { key: "ended-head", class: "runs-head dim" }, "ended") : null,
|
|
140
|
+
ended.map((run) => runRow(name, run, store, api)),
|
|
128
141
|
);
|
|
129
142
|
}
|
|
130
143
|
|
|
131
|
-
function runRow(name: string, run:
|
|
144
|
+
function runRow(name: string, run: RailRun, store: Store, api: AppApi): JSX.Element {
|
|
132
145
|
const gates = store.gates.get(run.runId)?.gates.length ?? 0;
|
|
133
146
|
const selected = name === store.workflow && run.runId === store.selectedRunId;
|
|
134
147
|
// A dispatched run names its caller on the row (ADR-0071), off the open observation.
|
|
135
148
|
const badge = dispatchBadge(run);
|
|
149
|
+
const older = olderAssemblyMark(store, run.runId);
|
|
136
150
|
return h(
|
|
137
151
|
"li",
|
|
138
152
|
{
|
|
139
153
|
key: run.runId,
|
|
140
|
-
class: `${settled ? "settled" : ""}${selected ? " selected" : ""}`.trim(),
|
|
154
|
+
class: `${run.settled ? "settled" : ""}${selected ? " selected" : ""}`.trim(),
|
|
155
|
+
title: run.reason !== undefined ? `${run.status}: ${run.reason}` : undefined,
|
|
141
156
|
onClick: () => {
|
|
142
157
|
if (name === store.workflow) api.dispatch({ kind: "selectRun", runId: run.runId });
|
|
143
158
|
else void api.selectWorkflow(name, { push: true, runId: run.runId });
|
|
@@ -158,6 +173,14 @@ function runRow(name: string, run: ObservedRun, settled: boolean, store: Store,
|
|
|
158
173
|
gates
|
|
159
174
|
? h("span", { class: "gate-badge", title: `${gates} open gate${gates === 1 ? "" : "s"}` }, `⚑${gates}`)
|
|
160
175
|
: null,
|
|
176
|
+
older ? h("span", { class: "older-assembly dim", title: olderAssemblyTitle(store, run.runId) }, older) : null,
|
|
161
177
|
h("span", { class: `run-status status--${run.status}` }, run.status),
|
|
178
|
+
run.status === "drifted" && run.reason !== undefined ? h("div", { class: "run-reason dim" }, run.reason) : null,
|
|
162
179
|
);
|
|
163
180
|
}
|
|
181
|
+
|
|
182
|
+
/** The whole hash, for the mark's title. */
|
|
183
|
+
function olderAssemblyTitle(store: Store, runId: string): string {
|
|
184
|
+
const hash = heldAssembly(store, runId);
|
|
185
|
+
return `this run is on Assembly ${hash ?? "?"}; the current one is ${store.assembly.current ?? "?"}`;
|
|
186
|
+
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// deliberately NOT vdom-ified; elk's layout is async and pan/zoom is a 60Hz transform).
|
|
5
5
|
|
|
6
6
|
import { h, type JSX } from "preact";
|
|
7
|
-
import { useEffect, useRef } from "preact/hooks";
|
|
7
|
+
import { useEffect, useRef, useState } from "preact/hooks";
|
|
8
8
|
import {
|
|
9
9
|
dispatchStrip,
|
|
10
10
|
dispatchedStrip as dispatchedStripText,
|
|
@@ -32,9 +32,15 @@ export function MachinePane(props: {
|
|
|
32
32
|
waiting: WaitingLine[];
|
|
33
33
|
held: HeldLine[];
|
|
34
34
|
failure: string | null;
|
|
35
|
+
/** Who started the selected run (ADR-0078), as one line; null when the page does not know it. */
|
|
36
|
+
starter: string | null;
|
|
37
|
+
/** "older Assembly · <hash>" for a run on an Assembly that is not the current one (ADR-0069). */
|
|
38
|
+
older: string | null;
|
|
39
|
+
/** The page's Caller may cancel the selected run, and it is live (ADR-0032). */
|
|
40
|
+
canCancel: boolean;
|
|
35
41
|
api: AppApi;
|
|
36
42
|
}): JSX.Element {
|
|
37
|
-
const { view, run, selectedNodeId, gates, waiting, held, failure, api } = props;
|
|
43
|
+
const { view, run, selectedNodeId, gates, waiting, held, failure, starter, older, canCancel, api } = props;
|
|
38
44
|
const canvasRef = useRef<HTMLElement>(null);
|
|
39
45
|
const svgRef = useRef<SVGSVGElement>(null);
|
|
40
46
|
|
|
@@ -69,6 +75,9 @@ export function MachinePane(props: {
|
|
|
69
75
|
h("div", { id: "error", hidden: !view.note, class: view.note?.notice ? "notice" : "" }, view.note?.text ?? ""),
|
|
70
76
|
dispatchedStrip(run),
|
|
71
77
|
startedStrip(run),
|
|
78
|
+
starterStrip(starter),
|
|
79
|
+
olderStrip(older),
|
|
80
|
+
run && canCancel ? h(RunControls, { key: run.runId, runId: run.runId, api }) : null,
|
|
72
81
|
rootTransitionStrip(view.doc),
|
|
73
82
|
seatsPanel(view.doc),
|
|
74
83
|
failureStrip(failure),
|
|
@@ -98,6 +107,48 @@ function startedStrip(run: ObservedRun | null): JSX.Element | null {
|
|
|
98
107
|
return h("div", { id: "dispatched", class: "dim", style: { margin: "8px 28px 0", fontFamily: "var(--mono)" } }, text);
|
|
99
108
|
}
|
|
100
109
|
|
|
110
|
+
/** Who started the selected run (ADR-0078): a Caller, a run through a `dispatch`, or jr2. On the
|
|
111
|
+
* run's guarded status, so here only for a Caller with `read`. */
|
|
112
|
+
function starterStrip(text: string | null): JSX.Element | null {
|
|
113
|
+
if (!text) return null;
|
|
114
|
+
return h("div", { id: "started-by", class: "dim", style: { margin: "8px 28px 0", fontFamily: "var(--mono)" } }, text);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** The mark of a run on an older Assembly (ADR-0069): the graph below is the run's own Machine,
|
|
118
|
+
* not the current one. Open structure, so it shows for every viewer. */
|
|
119
|
+
function olderStrip(text: string | null): JSX.Element | null {
|
|
120
|
+
if (!text) return null;
|
|
121
|
+
return h("div", { id: "older-assembly", class: "notice-line", style: { margin: "8px 28px 0" } }, text);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* The control on the selected live run that its Caller's `cancel` opens (ADR-0032). Keyed by run,
|
|
126
|
+
* so a new selection starts clean. The Steering box is in the bottom drawer, beside the Agent slot
|
|
127
|
+
* it steers. A refusal shows where the person clicked, as the forms do.
|
|
128
|
+
*/
|
|
129
|
+
function RunControls(props: { runId: string; api: AppApi }): JSX.Element {
|
|
130
|
+
const { runId, api } = props;
|
|
131
|
+
const [note, setNote] = useState<string | null>(null);
|
|
132
|
+
const [busy, setBusy] = useState(false);
|
|
133
|
+
|
|
134
|
+
const cancel = (): void => {
|
|
135
|
+
if (!confirm(`Cancel run ${runId.slice(0, 8)}?`)) return;
|
|
136
|
+
setBusy(true);
|
|
137
|
+
setNote(null);
|
|
138
|
+
void api.cancelRun(runId).then((failure) => {
|
|
139
|
+
setBusy(false);
|
|
140
|
+
setNote(failure ? `cancel refused: ${failure}` : null);
|
|
141
|
+
});
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
return h(
|
|
145
|
+
"div",
|
|
146
|
+
{ id: "run-controls", style: { margin: "8px 28px 0", maxWidth: "720px" } },
|
|
147
|
+
h("button", { id: "cancel-run", disabled: busy, onClick: cancel }, "cancel run"),
|
|
148
|
+
h("div", { id: "run-controls-note", class: "form-error", hidden: note === null }, note ?? ""),
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
101
152
|
/** Machine-level transitions ("from any state") as the strip above the Machine — an edge from the
|
|
102
153
|
* root would lie, because the root has no box (it renders as the page). */
|
|
103
154
|
function rootTransitionStrip(doc: MachineDoc | null): JSX.Element {
|
|
@@ -192,8 +243,9 @@ function waitingStrip(waiting: WaitingLine[]): JSX.Element {
|
|
|
192
243
|
);
|
|
193
244
|
}
|
|
194
245
|
|
|
195
|
-
/** The selected run's outstanding picks (ADR-0024): the Agent, its pick, and since when it is
|
|
196
|
-
* A held Turn has no default time bound, so this is where a human sees one that waits
|
|
246
|
+
/** The selected run's outstanding picks (ADR-0024): the Agent, its pick, and since when it is
|
|
247
|
+
* outstanding. A held Turn has no default time bound, so this is where a human sees one that waits
|
|
248
|
+
* too long.
|
|
197
249
|
* Guarded like the waiting strip: here only with a live token. */
|
|
198
250
|
function heldStrip(held: HeldLine[]): JSX.Element {
|
|
199
251
|
return h(
|
|
@@ -204,7 +256,11 @@ function heldStrip(held: HeldLine[]): JSX.Element {
|
|
|
204
256
|
"li",
|
|
205
257
|
{ key: `${line.agent} ${line.pick}` },
|
|
206
258
|
h("span", { class: "held-head" }, `${line.agent || "Agent"}'s pick ${line.pick} runs`),
|
|
207
|
-
h(
|
|
259
|
+
h(
|
|
260
|
+
"span",
|
|
261
|
+
{ class: "dim", title: line.since },
|
|
262
|
+
` outstanding since ${new Date(line.since).toLocaleTimeString()}`,
|
|
263
|
+
),
|
|
208
264
|
),
|
|
209
265
|
),
|
|
210
266
|
);
|