toolroll 0.5.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/LICENSE +21 -0
- package/README.md +1113 -0
- package/THIRD_PARTY_NOTICES.md +449 -0
- package/dist/accent-colors.d.ts +48 -0
- package/dist/accent-colors.js +122 -0
- package/dist/action-ledger.d.ts +26 -0
- package/dist/action-ledger.js +75 -0
- package/dist/agent-fence.d.ts +42 -0
- package/dist/agent-fence.js +183 -0
- package/dist/agentconfig.d.ts +216 -0
- package/dist/agentconfig.js +454 -0
- package/dist/api-tokens.d.ts +16 -0
- package/dist/api-tokens.js +27 -0
- package/dist/approval-policy.d.ts +69 -0
- package/dist/approval-policy.js +137 -0
- package/dist/approval-rules-ui.d.ts +25 -0
- package/dist/approval-rules-ui.js +39 -0
- package/dist/assignment-adapters.d.ts +180 -0
- package/dist/assignment-adapters.js +239 -0
- package/dist/assignment-brief.d.ts +71 -0
- package/dist/assignment-brief.js +129 -0
- package/dist/assignment-delivery.d.ts +56 -0
- package/dist/assignment-delivery.js +160 -0
- package/dist/assignment-presentation.d.ts +26 -0
- package/dist/assignment-presentation.js +80 -0
- package/dist/assignment-status.d.ts +62 -0
- package/dist/assignment-status.js +152 -0
- package/dist/assignment-ui.d.ts +71 -0
- package/dist/assignment-ui.js +103 -0
- package/dist/assignment.d.ts +222 -0
- package/dist/assignment.js +399 -0
- package/dist/attest.d.ts +56 -0
- package/dist/attest.js +153 -0
- package/dist/backend.d.ts +97 -0
- package/dist/backend.js +166 -0
- package/dist/backup-ui.d.ts +22 -0
- package/dist/backup-ui.js +59 -0
- package/dist/backup.d.ts +84 -0
- package/dist/backup.js +421 -0
- package/dist/beads.d.ts +34 -0
- package/dist/beads.js +135 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +22 -0
- package/dist/board.d.ts +150 -0
- package/dist/board.js +210 -0
- package/dist/boot-identity.d.ts +63 -0
- package/dist/boot-identity.js +99 -0
- package/dist/browser/THIRD_PARTY_NOTICES.txt +2295 -0
- package/dist/browser/workspace.css +4 -0
- package/dist/browser/workspace.js +225 -0
- package/dist/browser-crew.d.ts +11 -0
- package/dist/browser-crew.js +50 -0
- package/dist/browser-shell.d.ts +8 -0
- package/dist/browser-shell.js +60 -0
- package/dist/browser-workspace.d.ts +780 -0
- package/dist/browser-workspace.js +47 -0
- package/dist/budget-alerts.d.ts +19 -0
- package/dist/budget-alerts.js +46 -0
- package/dist/builder.d.ts +370 -0
- package/dist/builder.js +3262 -0
- package/dist/capscan.d.ts +35 -0
- package/dist/capscan.js +113 -0
- package/dist/chat-acceptance.d.ts +71 -0
- package/dist/chat-acceptance.js +120 -0
- package/dist/chat-actions.d.ts +265 -0
- package/dist/chat-actions.js +1273 -0
- package/dist/chat-channel.d.ts +109 -0
- package/dist/chat-channel.js +652 -0
- package/dist/chat-continuity.d.ts +32 -0
- package/dist/chat-continuity.js +317 -0
- package/dist/chat-controls.d.ts +113 -0
- package/dist/chat-controls.js +53 -0
- package/dist/chat-delivery-state.d.ts +159 -0
- package/dist/chat-delivery-state.js +309 -0
- package/dist/chat-delivery.d.ts +38 -0
- package/dist/chat-delivery.js +693 -0
- package/dist/chat-display.d.ts +1 -0
- package/dist/chat-display.js +18 -0
- package/dist/chat-evidence.d.ts +123 -0
- package/dist/chat-evidence.js +207 -0
- package/dist/chat-flow.d.ts +44 -0
- package/dist/chat-flow.js +215 -0
- package/dist/chat-inbox.d.ts +57 -0
- package/dist/chat-inbox.js +123 -0
- package/dist/chat-polish.d.ts +16 -0
- package/dist/chat-polish.js +135 -0
- package/dist/chat-review.d.ts +46 -0
- package/dist/chat-review.js +98 -0
- package/dist/chat-rooms.d.ts +123 -0
- package/dist/chat-rooms.js +216 -0
- package/dist/chat-task-actions.d.ts +44 -0
- package/dist/chat-task-actions.js +79 -0
- package/dist/check-progress.d.ts +51 -0
- package/dist/check-progress.js +290 -0
- package/dist/child-database.d.ts +12 -0
- package/dist/child-database.js +30 -0
- package/dist/claim.d.ts +688 -0
- package/dist/claim.js +1740 -0
- package/dist/cli.d.ts +137 -0
- package/dist/cli.js +1461 -0
- package/dist/codex-limits.d.ts +15 -0
- package/dist/codex-limits.js +145 -0
- package/dist/coding-context.d.ts +55 -0
- package/dist/coding-context.js +92 -0
- package/dist/coding-handoff.d.ts +57 -0
- package/dist/coding-handoff.js +277 -0
- package/dist/coding-provider.d.ts +72 -0
- package/dist/coding-provider.js +424 -0
- package/dist/coding-shipping-ui.d.ts +4 -0
- package/dist/coding-shipping-ui.js +12 -0
- package/dist/coding-types.d.ts +62 -0
- package/dist/coding-types.js +1 -0
- package/dist/coding-ui.d.ts +17 -0
- package/dist/coding-ui.js +364 -0
- package/dist/coding-update.d.ts +15 -0
- package/dist/coding-update.js +149 -0
- package/dist/coding-workspace.d.ts +124 -0
- package/dist/coding-workspace.js +768 -0
- package/dist/container-state.d.ts +5 -0
- package/dist/container-state.js +62 -0
- package/dist/containment.d.ts +197 -0
- package/dist/containment.js +559 -0
- package/dist/contest.d.ts +318 -0
- package/dist/contest.js +753 -0
- package/dist/control-setup.d.ts +35 -0
- package/dist/control-setup.js +40 -0
- package/dist/control-ui.d.ts +29 -0
- package/dist/control-ui.js +53 -0
- package/dist/controller-service.d.ts +1 -0
- package/dist/controller-service.js +19 -0
- package/dist/controller-supervisor.d.ts +30 -0
- package/dist/controller-supervisor.js +104 -0
- package/dist/converse.d.ts +304 -0
- package/dist/converse.js +849 -0
- package/dist/coordinator-proposals.d.ts +29 -0
- package/dist/coordinator-proposals.js +137 -0
- package/dist/coordinator.d.ts +186 -0
- package/dist/coordinator.js +447 -0
- package/dist/credentials-ui.d.ts +25 -0
- package/dist/credentials-ui.js +48 -0
- package/dist/daemon.d.ts +209 -0
- package/dist/daemon.js +604 -0
- package/dist/decision.d.ts +120 -0
- package/dist/decision.js +388 -0
- package/dist/demo.d.ts +55 -0
- package/dist/demo.js +992 -0
- package/dist/desktop-access.d.ts +28 -0
- package/dist/desktop-access.js +88 -0
- package/dist/desktop-bundle.d.ts +16 -0
- package/dist/desktop-bundle.js +74 -0
- package/dist/desktop-host.d.ts +54 -0
- package/dist/desktop-host.js +508 -0
- package/dist/desktop-update-gate.d.ts +12 -0
- package/dist/desktop-update-gate.js +91 -0
- package/dist/desktop-update-recovery.d.ts +14 -0
- package/dist/desktop-update-recovery.js +243 -0
- package/dist/desktop-update.d.ts +110 -0
- package/dist/desktop-update.js +740 -0
- package/dist/discord-api.d.ts +20 -0
- package/dist/discord-api.js +138 -0
- package/dist/discord-chat.d.ts +16 -0
- package/dist/discord-chat.js +381 -0
- package/dist/discord-settings.d.ts +7 -0
- package/dist/discord-settings.js +75 -0
- package/dist/discord.d.ts +10 -0
- package/dist/discord.js +169 -0
- package/dist/discover.d.ts +75 -0
- package/dist/discover.js +150 -0
- package/dist/dispatch.d.ts +100 -0
- package/dist/dispatch.js +311 -0
- package/dist/dispose.d.ts +161 -0
- package/dist/dispose.js +803 -0
- package/dist/email-settings.d.ts +34 -0
- package/dist/email-settings.js +50 -0
- package/dist/envelope.d.ts +47 -0
- package/dist/envelope.js +105 -0
- package/dist/evidence-pack.d.ts +185 -0
- package/dist/evidence-pack.js +258 -0
- package/dist/evidence.d.ts +345 -0
- package/dist/evidence.js +782 -0
- package/dist/exec.d.ts +285 -0
- package/dist/exec.js +1841 -0
- package/dist/exhaustion.d.ts +85 -0
- package/dist/exhaustion.js +141 -0
- package/dist/export-ui.d.ts +4 -0
- package/dist/export-ui.js +17 -0
- package/dist/export.d.ts +48 -0
- package/dist/export.js +305 -0
- package/dist/flow-actions.d.ts +77 -0
- package/dist/flow-actions.js +203 -0
- package/dist/flow-code.d.ts +50 -0
- package/dist/flow-code.js +159 -0
- package/dist/flow-draft.d.ts +38 -0
- package/dist/flow-draft.js +80 -0
- package/dist/flow-engine.d.ts +66 -0
- package/dist/flow-engine.js +416 -0
- package/dist/flow-insights.d.ts +95 -0
- package/dist/flow-insights.js +149 -0
- package/dist/flow-live.d.ts +24 -0
- package/dist/flow-live.js +150 -0
- package/dist/flow-people.d.ts +24 -0
- package/dist/flow-people.js +91 -0
- package/dist/flow-replies.d.ts +48 -0
- package/dist/flow-replies.js +113 -0
- package/dist/flow-scripts.d.ts +38 -0
- package/dist/flow-scripts.js +73 -0
- package/dist/flow-secrets.d.ts +13 -0
- package/dist/flow-secrets.js +45 -0
- package/dist/flow-sort.d.ts +82 -0
- package/dist/flow-sort.js +153 -0
- package/dist/flow-steps.d.ts +40 -0
- package/dist/flow-steps.js +408 -0
- package/dist/flow-triggers.d.ts +244 -0
- package/dist/flow-triggers.js +959 -0
- package/dist/flows-ui.d.ts +20 -0
- package/dist/flows-ui.js +175 -0
- package/dist/flows.d.ts +229 -0
- package/dist/flows.js +968 -0
- package/dist/fonts.d.ts +19 -0
- package/dist/fonts.js +19 -0
- package/dist/gaps.d.ts +35 -0
- package/dist/gaps.js +101 -0
- package/dist/gate-failure.d.ts +40 -0
- package/dist/gate-failure.js +66 -0
- package/dist/git.d.ts +54 -0
- package/dist/git.js +94 -0
- package/dist/google-mail.d.ts +58 -0
- package/dist/google-mail.js +151 -0
- package/dist/grant.d.ts +136 -0
- package/dist/grant.js +238 -0
- package/dist/graph.d.ts +164 -0
- package/dist/graph.js +383 -0
- package/dist/guides.d.ts +22 -0
- package/dist/guides.js +363 -0
- package/dist/held.d.ts +150 -0
- package/dist/held.js +799 -0
- package/dist/invoke.d.ts +112 -0
- package/dist/invoke.js +780 -0
- package/dist/issues.d.ts +41 -0
- package/dist/issues.js +131 -0
- package/dist/job-object-helper.ps1 +252 -0
- package/dist/jsonl-discriminants.d.ts +11 -0
- package/dist/jsonl-discriminants.js +93 -0
- package/dist/keys.d.ts +104 -0
- package/dist/keys.js +229 -0
- package/dist/kits-ui.d.ts +17 -0
- package/dist/kits-ui.js +46 -0
- package/dist/kits.d.ts +86 -0
- package/dist/kits.js +215 -0
- package/dist/knowledge-cli.d.ts +101 -0
- package/dist/knowledge-cli.js +92 -0
- package/dist/knowledge-ui.d.ts +24 -0
- package/dist/knowledge-ui.js +40 -0
- package/dist/lead-context.d.ts +8 -0
- package/dist/lead-context.js +56 -0
- package/dist/lead-follow.d.ts +22 -0
- package/dist/lead-follow.js +167 -0
- package/dist/lead-status.d.ts +75 -0
- package/dist/lead-status.js +285 -0
- package/dist/ledger-chain.d.ts +46 -0
- package/dist/ledger-chain.js +187 -0
- package/dist/ledger-csv.d.ts +4 -0
- package/dist/ledger-csv.js +11 -0
- package/dist/ledger-view.d.ts +24 -0
- package/dist/ledger-view.js +58 -0
- package/dist/limits-ui.d.ts +26 -0
- package/dist/limits-ui.js +67 -0
- package/dist/link.d.ts +72 -0
- package/dist/link.js +217 -0
- package/dist/live.d.ts +96 -0
- package/dist/live.js +366 -0
- package/dist/liveness.d.ts +30 -0
- package/dist/liveness.js +42 -0
- package/dist/log.d.ts +7 -0
- package/dist/log.js +25 -0
- package/dist/mailbox.d.ts +57 -0
- package/dist/mailbox.js +150 -0
- package/dist/maintenance.d.ts +11 -0
- package/dist/maintenance.js +35 -0
- package/dist/mate-cli.d.ts +52 -0
- package/dist/mate-cli.js +345 -0
- package/dist/mate-contract.d.ts +10 -0
- package/dist/mate-contract.js +30 -0
- package/dist/mate-doors.d.ts +69 -0
- package/dist/mate-doors.js +548 -0
- package/dist/mate-progress.d.ts +29 -0
- package/dist/mate-progress.js +121 -0
- package/dist/mate-tools.d.ts +94 -0
- package/dist/mate-tools.js +1878 -0
- package/dist/mate.d.ts +92 -0
- package/dist/mate.js +435 -0
- package/dist/mcp-connect.d.ts +103 -0
- package/dist/mcp-connect.js +252 -0
- package/dist/mcp.d.ts +27 -0
- package/dist/mcp.js +651 -0
- package/dist/memory-cli.d.ts +466 -0
- package/dist/memory-cli.js +111 -0
- package/dist/memory-pass.d.ts +160 -0
- package/dist/memory-pass.js +409 -0
- package/dist/metrics.d.ts +3 -0
- package/dist/metrics.js +56 -0
- package/dist/mobile-viewport.d.ts +3 -0
- package/dist/mobile-viewport.js +37 -0
- package/dist/model-catalog.d.ts +118 -0
- package/dist/model-catalog.js +376 -0
- package/dist/models-cli.d.ts +102 -0
- package/dist/models-cli.js +66 -0
- package/dist/models-ui.d.ts +34 -0
- package/dist/models-ui.js +68 -0
- package/dist/modes.d.ts +80 -0
- package/dist/modes.js +182 -0
- package/dist/monitoring-settings.d.ts +44 -0
- package/dist/monitoring-settings.js +173 -0
- package/dist/monitoring-ui.d.ts +20 -0
- package/dist/monitoring-ui.js +51 -0
- package/dist/monitoring.d.ts +34 -0
- package/dist/monitoring.js +221 -0
- package/dist/names.d.ts +67 -0
- package/dist/names.js +102 -0
- package/dist/observations.d.ts +40 -0
- package/dist/observations.js +211 -0
- package/dist/oidc.d.ts +71 -0
- package/dist/oidc.js +182 -0
- package/dist/onboard.d.ts +108 -0
- package/dist/onboard.js +325 -0
- package/dist/openrouter-models.d.ts +30 -0
- package/dist/openrouter-models.js +120 -0
- package/dist/operate.d.ts +118 -0
- package/dist/operate.js +11321 -0
- package/dist/peek-cli.d.ts +97 -0
- package/dist/peek-cli.js +215 -0
- package/dist/peek.d.ts +134 -0
- package/dist/peek.js +578 -0
- package/dist/phase-routing.d.ts +307 -0
- package/dist/phase-routing.js +658 -0
- package/dist/plan-auto.d.ts +14 -0
- package/dist/plan-auto.js +92 -0
- package/dist/plan.d.ts +186 -0
- package/dist/plan.js +401 -0
- package/dist/planner-source.d.ts +251 -0
- package/dist/planner-source.js +460 -0
- package/dist/planner.d.ts +75 -0
- package/dist/planner.js +992 -0
- package/dist/policy-ui.d.ts +20 -0
- package/dist/policy-ui.js +41 -0
- package/dist/policy.d.ts +111 -0
- package/dist/policy.js +231 -0
- package/dist/prepared-evidence.d.ts +16 -0
- package/dist/prepared-evidence.js +96 -0
- package/dist/pricing.d.ts +45 -0
- package/dist/pricing.js +77 -0
- package/dist/principal.d.ts +42 -0
- package/dist/principal.js +82 -0
- package/dist/probe.d.ts +46 -0
- package/dist/probe.js +79 -0
- package/dist/process-custody.d.ts +17 -0
- package/dist/process-custody.js +91 -0
- package/dist/process-liveness.d.ts +10 -0
- package/dist/process-liveness.js +81 -0
- package/dist/process-recovery-anchor.d.ts +75 -0
- package/dist/process-recovery-anchor.js +188 -0
- package/dist/process-recovery-coalition.d.ts +25 -0
- package/dist/process-recovery-coalition.js +105 -0
- package/dist/process-recovery-eligibility.d.ts +178 -0
- package/dist/process-recovery-eligibility.js +170 -0
- package/dist/process-recovery-native.d.ts +172 -0
- package/dist/process-recovery-native.js +592 -0
- package/dist/process-recovery-provenance.d.ts +163 -0
- package/dist/process-recovery-provenance.js +292 -0
- package/dist/process-recovery-services.d.ts +56 -0
- package/dist/process-recovery-services.js +191 -0
- package/dist/process-recovery-settlement.d.ts +32 -0
- package/dist/process-recovery-settlement.js +132 -0
- package/dist/process-recovery.d.ts +41 -0
- package/dist/process-recovery.js +91 -0
- package/dist/process-tree.d.ts +41 -0
- package/dist/process-tree.js +264 -0
- package/dist/project-access.d.ts +14 -0
- package/dist/project-access.js +34 -0
- package/dist/project-cli.d.ts +18 -0
- package/dist/project-cli.js +104 -0
- package/dist/project-delete-ui.d.ts +22 -0
- package/dist/project-delete-ui.js +48 -0
- package/dist/project-delete.d.ts +55 -0
- package/dist/project-delete.js +413 -0
- package/dist/project-knowledge.d.ts +111 -0
- package/dist/project-knowledge.js +241 -0
- package/dist/project-learning.d.ts +100 -0
- package/dist/project-learning.js +438 -0
- package/dist/project-memory.d.ts +81 -0
- package/dist/project-memory.js +264 -0
- package/dist/project-skills.d.ts +152 -0
- package/dist/project-skills.js +660 -0
- package/dist/project-tools.d.ts +219 -0
- package/dist/project-tools.js +796 -0
- package/dist/project.d.ts +61 -0
- package/dist/project.js +125 -0
- package/dist/prompt.d.ts +22 -0
- package/dist/prompt.js +72 -0
- package/dist/proof.d.ts +513 -0
- package/dist/proof.js +1140 -0
- package/dist/proposal.d.ts +82 -0
- package/dist/proposal.js +210 -0
- package/dist/provider-connection.d.ts +22 -0
- package/dist/provider-connection.js +98 -0
- package/dist/provider-limits.d.ts +35 -0
- package/dist/provider-limits.js +95 -0
- package/dist/provider.d.ts +279 -0
- package/dist/provider.js +935 -0
- package/dist/publish.d.ts +107 -0
- package/dist/publish.js +650 -0
- package/dist/pulls.d.ts +118 -0
- package/dist/pulls.js +240 -0
- package/dist/push.d.ts +95 -0
- package/dist/push.js +353 -0
- package/dist/quality.d.ts +8 -0
- package/dist/quality.js +6 -0
- package/dist/recipe-ui.d.ts +18 -0
- package/dist/recipe-ui.js +152 -0
- package/dist/recipes.d.ts +81 -0
- package/dist/recipes.js +332 -0
- package/dist/remote.d.ts +67 -0
- package/dist/remote.js +122 -0
- package/dist/render.d.ts +64 -0
- package/dist/render.js +587 -0
- package/dist/report-summary.d.ts +13 -0
- package/dist/report-summary.js +15 -0
- package/dist/repos.d.ts +65 -0
- package/dist/repos.js +277 -0
- package/dist/repository-context-ui.d.ts +2 -0
- package/dist/repository-context-ui.js +10 -0
- package/dist/repository-context.d.ts +67 -0
- package/dist/repository-context.js +376 -0
- package/dist/restart-certification.d.ts +134 -0
- package/dist/restart-certification.js +226 -0
- package/dist/result-actions.d.ts +28 -0
- package/dist/result-actions.js +270 -0
- package/dist/result-completion.d.ts +18 -0
- package/dist/result-completion.js +66 -0
- package/dist/result-review.d.ts +213 -0
- package/dist/result-review.js +430 -0
- package/dist/retention-ui.d.ts +12 -0
- package/dist/retention-ui.js +39 -0
- package/dist/retention.d.ts +72 -0
- package/dist/retention.js +288 -0
- package/dist/review-context.d.ts +243 -0
- package/dist/review-context.js +1043 -0
- package/dist/review-evidence.d.ts +33 -0
- package/dist/review-evidence.js +56 -0
- package/dist/reviewer.d.ts +97 -0
- package/dist/reviewer.js +202 -0
- package/dist/routine.d.ts +228 -0
- package/dist/routine.js +764 -0
- package/dist/runner.d.ts +266 -0
- package/dist/runner.js +399 -0
- package/dist/scan.d.ts +32 -0
- package/dist/scan.js +92 -0
- package/dist/scope.d.ts +695 -0
- package/dist/scope.js +1176 -0
- package/dist/scout-report.d.ts +42 -0
- package/dist/scout-report.js +108 -0
- package/dist/scout.d.ts +69 -0
- package/dist/scout.js +351 -0
- package/dist/serve.d.ts +313 -0
- package/dist/serve.js +21776 -0
- package/dist/session-brief.d.ts +8 -0
- package/dist/session-brief.js +24 -0
- package/dist/session-cli.d.ts +13 -0
- package/dist/session-cli.js +284 -0
- package/dist/session-contract.d.ts +183 -0
- package/dist/session-contract.js +122 -0
- package/dist/session-http.d.ts +19 -0
- package/dist/session-http.js +154 -0
- package/dist/session-server.d.ts +12 -0
- package/dist/session-server.js +37 -0
- package/dist/session-service.d.ts +22 -0
- package/dist/session-service.js +157 -0
- package/dist/setup-guide.d.ts +39 -0
- package/dist/setup-guide.js +93 -0
- package/dist/sign-in-guard.d.ts +45 -0
- package/dist/sign-in-guard.js +74 -0
- package/dist/skills-ui.d.ts +11 -0
- package/dist/skills-ui.js +58 -0
- package/dist/skills.d.ts +97 -0
- package/dist/skills.js +276 -0
- package/dist/slack-api.d.ts +55 -0
- package/dist/slack-api.js +229 -0
- package/dist/slack-chat.d.ts +28 -0
- package/dist/slack-chat.js +413 -0
- package/dist/slack-settings.d.ts +7 -0
- package/dist/slack-settings.js +75 -0
- package/dist/slack-state.d.ts +13 -0
- package/dist/slack-state.js +9 -0
- package/dist/slack.d.ts +13 -0
- package/dist/slack.js +167 -0
- package/dist/spend-ui.d.ts +28 -0
- package/dist/spend-ui.js +95 -0
- package/dist/spend.d.ts +142 -0
- package/dist/spend.js +313 -0
- package/dist/sqlite-runtime.d.ts +3 -0
- package/dist/sqlite-runtime.js +6 -0
- package/dist/sso-settings.d.ts +33 -0
- package/dist/sso-settings.js +103 -0
- package/dist/sso-ui.d.ts +22 -0
- package/dist/sso-ui.js +39 -0
- package/dist/storage.d.ts +15 -0
- package/dist/storage.js +84 -0
- package/dist/store.d.ts +7754 -0
- package/dist/store.js +23279 -0
- package/dist/structured-output.d.ts +58 -0
- package/dist/structured-output.js +103 -0
- package/dist/style-asset.d.ts +7 -0
- package/dist/style-asset.js +41 -0
- package/dist/subscription-chat.d.ts +46 -0
- package/dist/subscription-chat.js +275 -0
- package/dist/summary.d.ts +33 -0
- package/dist/summary.js +71 -0
- package/dist/supervisor.mjs +297 -0
- package/dist/surface.d.ts +63 -0
- package/dist/surface.js +352 -0
- package/dist/sync.d.ts +94 -0
- package/dist/sync.js +279 -0
- package/dist/task-composer.d.ts +13 -0
- package/dist/task-composer.js +52 -0
- package/dist/task-control.d.ts +165 -0
- package/dist/task-control.js +214 -0
- package/dist/task-outcome-cli.d.ts +10 -0
- package/dist/task-outcome-cli.js +92 -0
- package/dist/task-text.d.ts +30 -0
- package/dist/task-text.js +41 -0
- package/dist/team-cli.d.ts +15 -0
- package/dist/team-cli.js +618 -0
- package/dist/team-contract.d.ts +92 -0
- package/dist/team-contract.js +1 -0
- package/dist/team-http.d.ts +17 -0
- package/dist/team-http.js +144 -0
- package/dist/team-leads.d.ts +81 -0
- package/dist/team-leads.js +565 -0
- package/dist/team-runtime.d.ts +27 -0
- package/dist/team-runtime.js +264 -0
- package/dist/team-ui.d.ts +3 -0
- package/dist/team-ui.js +9 -0
- package/dist/team-updates.d.ts +8 -0
- package/dist/team-updates.js +57 -0
- package/dist/teammate-admin.d.ts +53 -0
- package/dist/teammate-admin.js +111 -0
- package/dist/teammate-desk.d.ts +60 -0
- package/dist/teammate-desk.js +154 -0
- package/dist/teammate-memory.d.ts +64 -0
- package/dist/teammate-memory.js +196 -0
- package/dist/teammate-question.d.ts +59 -0
- package/dist/teammate-question.js +175 -0
- package/dist/teammate-tools.d.ts +108 -0
- package/dist/teammate-tools.js +271 -0
- package/dist/teammate-week.d.ts +68 -0
- package/dist/teammate-week.js +132 -0
- package/dist/teammate-work.d.ts +76 -0
- package/dist/teammate-work.js +280 -0
- package/dist/teammates-ui.d.ts +20 -0
- package/dist/teammates-ui.js +166 -0
- package/dist/teammates.d.ts +180 -0
- package/dist/teammates.js +253 -0
- package/dist/teams-api.d.ts +33 -0
- package/dist/teams-api.js +204 -0
- package/dist/teams-chat.d.ts +26 -0
- package/dist/teams-chat.js +215 -0
- package/dist/teams-settings.d.ts +12 -0
- package/dist/teams-settings.js +57 -0
- package/dist/teams.d.ts +21 -0
- package/dist/teams.js +124 -0
- package/dist/telegram-flow.d.ts +49 -0
- package/dist/telegram-flow.js +110 -0
- package/dist/telegram-mate.d.ts +137 -0
- package/dist/telegram-mate.js +707 -0
- package/dist/telegram-progress.d.ts +14 -0
- package/dist/telegram-progress.js +129 -0
- package/dist/telegram-settings.d.ts +9 -0
- package/dist/telegram-settings.js +36 -0
- package/dist/telegram-status.d.ts +75 -0
- package/dist/telegram-status.js +265 -0
- package/dist/telegram-team.d.ts +85 -0
- package/dist/telegram-team.js +359 -0
- package/dist/telegram.d.ts +230 -0
- package/dist/telegram.js +1931 -0
- package/dist/templates.d.ts +65 -0
- package/dist/templates.js +118 -0
- package/dist/tool-launcher.d.ts +1 -0
- package/dist/tool-launcher.js +101 -0
- package/dist/tools-ui.d.ts +30 -0
- package/dist/tools-ui.js +40 -0
- package/dist/transitions-recipes.d.ts +1 -0
- package/dist/transitions-recipes.js +203 -0
- package/dist/tree-proof.d.ts +34 -0
- package/dist/tree-proof.js +58 -0
- package/dist/verification-evidence.d.ts +44 -0
- package/dist/verification-evidence.js +238 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +10 -0
- package/dist/webhooks.d.ts +91 -0
- package/dist/webhooks.js +283 -0
- package/dist/work-index.d.ts +68 -0
- package/dist/work-index.js +384 -0
- package/dist/work-summary.d.ts +57 -0
- package/dist/work-summary.js +116 -0
- package/dist/workspace-motion.d.ts +4 -0
- package/dist/workspace-motion.js +133 -0
- package/dist/workspace-revision.d.ts +19 -0
- package/dist/workspace-revision.js +92 -0
- package/dist/workspace-ui.d.ts +204 -0
- package/dist/workspace-ui.js +411 -0
- package/dist/worktree-notices.d.ts +13 -0
- package/dist/worktree-notices.js +17 -0
- package/dist/worktree.d.ts +249 -0
- package/dist/worktree.js +720 -0
- package/package.json +122 -0
- package/scripts/canary-assertions.mjs +42 -0
- package/scripts/crash-canary.mjs +253 -0
- package/scripts/fixtures/crash-process.mjs +74 -0
- package/scripts/fixtures/pilot-scenarios.mjs +85 -0
- package/scripts/fixtures/restart-service.mjs +27 -0
- package/scripts/launchd-certification.mjs +143 -0
- package/scripts/pilot.mjs +121 -0
- package/scripts/proof-preflight.mjs +140 -0
- package/scripts/provider-canary.mjs +379 -0
- package/scripts/recovery-canary.mjs +135 -0
- package/scripts/restart-certification.mjs +98 -0
|
@@ -0,0 +1,1273 @@
|
|
|
1
|
+
/** Shared, exact-state actions. Models may prepare; only the existing human
|
|
2
|
+
* confirmation door executes. Protected actions use a one-use review receipt. */
|
|
3
|
+
import { publicChatText } from "./chat-display.js";
|
|
4
|
+
import { gateWords } from "./approval-policy.js";
|
|
5
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
6
|
+
import { verifiedAuthor } from "./store.js";
|
|
7
|
+
import { isVerifiedApprover, reproveApprover, } from "./principal.js";
|
|
8
|
+
import { changeSkills, importSkill, skillsView, skillsVersion, testSkill, validateSkill, } from "./project-skills.js";
|
|
9
|
+
import { changeKnowledge, knowledgeVersion, knowledgeView, } from "./project-knowledge.js";
|
|
10
|
+
import { approve, authenticateApprover, describeScope, scopeAuthorityOf, } from "./scope.js";
|
|
11
|
+
import { readVerifiedArtifact, scanForSecrets } from "./evidence.js";
|
|
12
|
+
import { readAuthModeStrict } from "./keys.js";
|
|
13
|
+
import { parseProof } from "./proof.js";
|
|
14
|
+
import { assignmentOf, checkAssignmentAsOperator } from "./assignment.js";
|
|
15
|
+
import { getDecision, recordDecision, retireDecision } from "./project-memory.js";
|
|
16
|
+
import { resumeTaskStop, taskControlOf } from "./task-control.js";
|
|
17
|
+
import { addToolTo, catalogTool, projectToolsOf, removeToolFrom, toolCommandLine, validateToolSpec } from "./project-tools.js";
|
|
18
|
+
import { deciderOf, FLOW_KIND_WORDS, flowDigest, flowTerms, validateFlowDefinition } from "./flows.js";
|
|
19
|
+
import { saveScript, scriptDigest, validateScript } from "./flow-scripts.js";
|
|
20
|
+
import { addCardToFlow, advanceFlows, cancelFlowCard, decideFlowCard, flowCardHref, flowCardText, flowDefinitionOf, moveCardInFlow } from "./flow-engine.js";
|
|
21
|
+
import { assignFlowCard, commentOnFlowCard, flowPeople, mentionsIn, watchFlowCard } from "./flow-people.js";
|
|
22
|
+
import { addFlowTriggerTo, describeTrigger, readLinearKey, removeFlowTrigger, scheduleFromWords, takesDeliveries, triggerConfigOf, validateTriggerConfig } from "./flow-triggers.js";
|
|
23
|
+
import { describeSchedule, parseSchedule } from "./routine.js";
|
|
24
|
+
import { dirname } from "node:path";
|
|
25
|
+
import { handleOf, parseSoul, SOUL_CHARS, TEAMMATE_TEMPLATES, teammateLabel } from "./teammates.js";
|
|
26
|
+
import { createTeammateFrom, labelOf, nameOf, renamedSoul, saveSoul, setTeammateState } from "./teammate-admin.js";
|
|
27
|
+
import { cleanMemory, editMemory, forgetMemory, tellTeammate } from "./teammate-memory.js";
|
|
28
|
+
import { addRoutine, removeRoutine, routineSchedule, routinesOf } from "./teammate-desk.js";
|
|
29
|
+
import { requestUndo, undoFor } from "./teammate-week.js";
|
|
30
|
+
import { kitInstalled, kitOf, KITS, setUpKitNow } from "./kits.js";
|
|
31
|
+
import { answerTeammateQuestion } from "./teammate-work.js";
|
|
32
|
+
import { callWords, checkRule, defaultRule, grantListed, revokeTool, ruleWords, setToolRules } from "./teammate-tools.js";
|
|
33
|
+
/** A tool from the lead's card: a common tool by its id, or the operator's own program or address. */
|
|
34
|
+
function toolSpecFromRequest(input) {
|
|
35
|
+
if (input["catalog"] !== undefined) {
|
|
36
|
+
const chosen = catalogTool(String(input["catalog"]));
|
|
37
|
+
if (chosen === null)
|
|
38
|
+
throw Error("Choose a tool from get_project_tools' common list.");
|
|
39
|
+
const { label: _label, ...spec } = chosen;
|
|
40
|
+
return spec;
|
|
41
|
+
}
|
|
42
|
+
const secrets = Array.isArray(input["secrets"]) ? input["secrets"].map((one) => String(one)) : [];
|
|
43
|
+
const url = typeof input["url"] === "string" ? input["url"] : null;
|
|
44
|
+
return validateToolSpec({
|
|
45
|
+
name: input["name"], transport: url === null ? "stdio" : "http", command: input["command"], args: input["args"] ?? [], url,
|
|
46
|
+
secrets: secrets.map((name) => ({ name, optional: false })), bearer: url === null ? null : secrets[0] ?? null, about: input["about"],
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
export const CHAT_ACTIONS = {
|
|
50
|
+
skill_import: { label: "Add skill", protected: true, password: false },
|
|
51
|
+
skill_enable: { label: "Enable skill", protected: false, password: false },
|
|
52
|
+
skill_disable: { label: "Disable skill", protected: false, password: false },
|
|
53
|
+
skill_restore: { label: "Restore skills", protected: false, password: false },
|
|
54
|
+
skill_test: { label: "Test skill", protected: false, password: false },
|
|
55
|
+
knowledge_instructions: {
|
|
56
|
+
label: "Save project instructions",
|
|
57
|
+
protected: false,
|
|
58
|
+
password: false,
|
|
59
|
+
},
|
|
60
|
+
knowledge_save: {
|
|
61
|
+
label: "Save reference",
|
|
62
|
+
protected: false,
|
|
63
|
+
password: false,
|
|
64
|
+
},
|
|
65
|
+
knowledge_remove: {
|
|
66
|
+
label: "Remove reference",
|
|
67
|
+
protected: false,
|
|
68
|
+
password: false,
|
|
69
|
+
},
|
|
70
|
+
knowledge_restore: {
|
|
71
|
+
label: "Restore knowledge",
|
|
72
|
+
protected: false,
|
|
73
|
+
password: false,
|
|
74
|
+
},
|
|
75
|
+
// A tool runs on this computer for every build the operator approves from now on: the password screen, like an approval.
|
|
76
|
+
tool_add: { label: "Add tool", protected: true, password: true },
|
|
77
|
+
tool_remove: { label: "Remove tool", protected: false, password: false },
|
|
78
|
+
// Flows: a drawing and cards on it. Any work a card files is an ordinary task under the usual approvals.
|
|
79
|
+
flow_create: { label: "Create flow", protected: false, password: false },
|
|
80
|
+
flow_edit: { label: "Save flow", protected: false, password: false },
|
|
81
|
+
flow_card_add: { label: "Add card", protected: false, password: false },
|
|
82
|
+
flow_card_move: { label: "Move card", protected: false, password: false },
|
|
83
|
+
flow_card_approve: { label: "Approve", protected: false, password: false },
|
|
84
|
+
flow_card_send_back: { label: "Send back", protected: false, password: false },
|
|
85
|
+
flow_card_cancel: { label: "Cancel card", protected: false, password: false },
|
|
86
|
+
flow_card_comment: { label: "Comment", protected: false, password: false },
|
|
87
|
+
flow_card_assign: { label: "Set owner", protected: false, password: false },
|
|
88
|
+
flow_card_watch: { label: "Follow card", protected: false, password: false },
|
|
89
|
+
flow_script_save: { label: "Save script", protected: false, password: false },
|
|
90
|
+
flow_trigger_add: { label: "Add trigger", protected: false, password: false },
|
|
91
|
+
flow_trigger_pause: { label: "Pause trigger", protected: false, password: false },
|
|
92
|
+
flow_trigger_resume: { label: "Turn trigger on", protected: false, password: false },
|
|
93
|
+
flow_trigger_remove: { label: "Remove trigger", protected: false, password: false },
|
|
94
|
+
teammate_create: { label: "Add teammate", protected: false, password: false },
|
|
95
|
+
teammate_soul: { label: "Update soul file", protected: false, password: false },
|
|
96
|
+
teammate_state: { label: "Change teammate", protected: false, password: false },
|
|
97
|
+
teammate_note: { label: "Tell teammate", protected: false, password: false },
|
|
98
|
+
teammate_answer: { label: "Answer teammate", protected: false, password: false },
|
|
99
|
+
teammate_tools: { label: "Change teammate's tools", protected: false, password: false },
|
|
100
|
+
teammate_memory: { label: "Change teammate's memory", protected: false, password: false },
|
|
101
|
+
teammate_routine: { label: "Change teammate's routines", protected: false, password: false },
|
|
102
|
+
teammate_undo: { label: "Undo teammate's call", protected: false, password: false },
|
|
103
|
+
kit_setup: { label: "Set up kit", protected: false, password: false },
|
|
104
|
+
decision_record: { label: "Record decision", protected: false, password: false },
|
|
105
|
+
decision_retire: { label: "Retire decision", protected: false, password: false },
|
|
106
|
+
scope_approve: { label: "Approve work", protected: true, password: true },
|
|
107
|
+
result_accept: { label: "Mark complete", protected: true, password: false },
|
|
108
|
+
task_cancel: { label: "Cancel task", protected: true, password: false },
|
|
109
|
+
task_resume: { label: "Resume task", protected: true, password: true },
|
|
110
|
+
};
|
|
111
|
+
/** Protected actions a paired phone may confirm behind its own explicit
|
|
112
|
+
* yes/cancel challenge instead of the console's secure screen. Password
|
|
113
|
+
* actions and long or redacted terms never qualify. */
|
|
114
|
+
export const CHALLENGE_ACTIONS = new Set(["result_accept"]);
|
|
115
|
+
export function isChatAction(value) {
|
|
116
|
+
return typeof value === "string" && Object.hasOwn(CHAT_ACTIONS, value);
|
|
117
|
+
}
|
|
118
|
+
const hash = (value) => createHash("sha256").update(JSON.stringify(value)).digest("hex");
|
|
119
|
+
export const CHAT_ACTION_FIELDS = {
|
|
120
|
+
skill_import: ["repo", "content", "files"],
|
|
121
|
+
skill_enable: ["repo", "version"],
|
|
122
|
+
skill_disable: ["repo", "version"],
|
|
123
|
+
skill_restore: ["repo", "restore"],
|
|
124
|
+
skill_test: ["repo", "version", "sample", "nonce"],
|
|
125
|
+
knowledge_instructions: ["repo", "instructions"],
|
|
126
|
+
knowledge_save: ["repo", "title", "content", "id"],
|
|
127
|
+
knowledge_remove: ["repo", "id"],
|
|
128
|
+
knowledge_restore: ["repo", "restore"],
|
|
129
|
+
tool_add: ["repo", "catalog", "name", "command", "args", "url", "secrets", "about"],
|
|
130
|
+
tool_remove: ["repo", "name"],
|
|
131
|
+
flow_create: ["repo", "name", "definition"],
|
|
132
|
+
flow_edit: ["flow", "name", "definition"],
|
|
133
|
+
flow_card_add: ["flow", "title", "description", "zone"],
|
|
134
|
+
flow_card_move: ["card", "zone"],
|
|
135
|
+
flow_card_approve: ["card", "note"],
|
|
136
|
+
flow_card_send_back: ["card", "note"],
|
|
137
|
+
flow_card_cancel: ["card"],
|
|
138
|
+
flow_card_comment: ["card", "note"],
|
|
139
|
+
flow_card_assign: ["card", "owner"],
|
|
140
|
+
flow_card_watch: ["card", "watching"],
|
|
141
|
+
flow_script_save: ["repo", "script"],
|
|
142
|
+
flow_trigger_add: ["flow", "trigger"],
|
|
143
|
+
flow_trigger_pause: ["trigger"],
|
|
144
|
+
flow_trigger_resume: ["trigger"],
|
|
145
|
+
flow_trigger_remove: ["trigger"],
|
|
146
|
+
teammate_create: ["repo", "template", "name", "soul"],
|
|
147
|
+
teammate_soul: ["teammate", "soul"],
|
|
148
|
+
teammate_state: ["teammate", "state"],
|
|
149
|
+
teammate_note: ["teammate", "note"],
|
|
150
|
+
teammate_answer: ["question", "choice", "text"],
|
|
151
|
+
teammate_tools: ["teammate", "tool", "change", "action", "use", "limitField", "limitOver", "undoWith"],
|
|
152
|
+
teammate_memory: ["teammate", "memory", "change", "text"],
|
|
153
|
+
teammate_routine: ["teammate", "change", "routine", "schedule", "text"],
|
|
154
|
+
teammate_undo: ["teammate", "call"],
|
|
155
|
+
kit_setup: ["repo", "kit"],
|
|
156
|
+
decision_record: ["repo", "claim", "why", "supersedes", "source"],
|
|
157
|
+
decision_retire: ["repo", "decision", "reason"],
|
|
158
|
+
scope_approve: ["task"],
|
|
159
|
+
result_accept: ["task", "run"],
|
|
160
|
+
task_cancel: ["task"],
|
|
161
|
+
task_resume: ["task", "run"],
|
|
162
|
+
};
|
|
163
|
+
const nonceHash = (value) => createHash("sha256").update(value).digest("hex");
|
|
164
|
+
function requireActor(store, who, repo) {
|
|
165
|
+
if (!store.schemaCurrent())
|
|
166
|
+
throw Error("The running build changed. Reload before proposing an action.");
|
|
167
|
+
if (!isVerifiedApprover(who) ||
|
|
168
|
+
!reproveApprover(store, who).ok ||
|
|
169
|
+
!who.repos.includes(repo) ||
|
|
170
|
+
!store.accountCanAccess(who.name, repo))
|
|
171
|
+
throw Error("This project is outside your current access.");
|
|
172
|
+
}
|
|
173
|
+
function text(input, key, cap, optional = false) {
|
|
174
|
+
const value = input[key];
|
|
175
|
+
if (optional && value === undefined)
|
|
176
|
+
return "";
|
|
177
|
+
if (typeof value !== "string" ||
|
|
178
|
+
(!optional && !value.trim()) ||
|
|
179
|
+
value.length > cap ||
|
|
180
|
+
/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f\u200b-\u200f\u202a-\u202e\u2066-\u2069\ufffd]/.test(value) ||
|
|
181
|
+
scanForSecrets(value).length)
|
|
182
|
+
throw Error(`Check the ${key} field. Credentials cannot be included in chat actions.`);
|
|
183
|
+
return value;
|
|
184
|
+
}
|
|
185
|
+
function integer(input, key) {
|
|
186
|
+
if (!Number.isSafeInteger(input[key]) || Number(input[key]) < 1)
|
|
187
|
+
throw Error(`Choose a valid ${key}.`);
|
|
188
|
+
return Number(input[key]);
|
|
189
|
+
}
|
|
190
|
+
function runtimeTerms(profile) {
|
|
191
|
+
const permission = profile.provider === "claude"
|
|
192
|
+
? profile.permissionArgv === "bypassPermissions"
|
|
193
|
+
? "Full access: tools run without asking for permission (--dangerously-skip-permissions)."
|
|
194
|
+
: profile.permissionArgv === "auto"
|
|
195
|
+
? "Auto permissions: routine commands and edits proceed; risky actions stop."
|
|
196
|
+
: "Edits proceed; commands that ask for permission are denied."
|
|
197
|
+
: profile.provider === "gemini"
|
|
198
|
+
? profile.approvalArgv === "yolo"
|
|
199
|
+
? "Full access: every tool is approved automatically (--approval-mode yolo)."
|
|
200
|
+
: "Edits are approved automatically; other tools are refused."
|
|
201
|
+
: profile.sandboxMode === "danger-full-access"
|
|
202
|
+
? "Full access: approvals and sandbox are bypassed (--dangerously-bypass-approvals-and-sandbox)."
|
|
203
|
+
: "Commands use the workspace-write sandbox.";
|
|
204
|
+
return `${profile.provider} · ${profile.model}\n${permission}\n${profile.timeoutSeconds} seconds ${profile.timeoutKind === "idle" ? "without progress" : "per attempt"}${profile.provider === "claude" ? `; at most ${profile.maxTurns} turns` : ""}. Repairs: ${profile.repairModel}, ${profile.repairTimeoutSeconds} seconds${profile.provider === "claude" ? `, at most ${profile.repairMaxTurns} turns` : ""}.`;
|
|
205
|
+
}
|
|
206
|
+
function resultOf(store, task, run, repo) {
|
|
207
|
+
const ref = store.lookupRef(task);
|
|
208
|
+
const current = store
|
|
209
|
+
.runsFor(ref.id)
|
|
210
|
+
.find((one) => one.finishedAt !== null &&
|
|
211
|
+
["builder", "repair", "scout"].includes(one.role));
|
|
212
|
+
if (!current || current.id !== run || ref.repo !== repo)
|
|
213
|
+
throw Error("This result changed. Review the current result.");
|
|
214
|
+
return current;
|
|
215
|
+
}
|
|
216
|
+
/** The flow (and card) a flow action names; its project decides who may act. */
|
|
217
|
+
function flowTargetOf(store, input) {
|
|
218
|
+
const card = input["card"] === undefined ? null : store.getFlowCard(integer(input, "card"));
|
|
219
|
+
if (input["card"] !== undefined && (card === null || card.state !== "active"))
|
|
220
|
+
throw Error("That card isn't active in a flow any more.");
|
|
221
|
+
const trigger = typeof input["trigger"] === "number" ? store.getFlowTrigger(integer(input, "trigger")) : null;
|
|
222
|
+
if (typeof input["trigger"] === "number" && (trigger === null || trigger.state === "removed"))
|
|
223
|
+
throw Error("That trigger was removed.");
|
|
224
|
+
const flow = store.getFlow(card?.flow ?? trigger?.flow ?? integer(input, "flow"));
|
|
225
|
+
const definition = flow === null ? null : flowDefinitionOf(flow);
|
|
226
|
+
if (flow === null || flow.state !== "active")
|
|
227
|
+
throw Error("That flow isn't in your projects.");
|
|
228
|
+
if (definition === null)
|
|
229
|
+
throw Error("This flow's drawing can't be read. Save it again on its canvas.");
|
|
230
|
+
return { flow, definition, card, trigger };
|
|
231
|
+
}
|
|
232
|
+
/** Where this installation keeps its files: beside the database. */
|
|
233
|
+
function configDirOf(store) {
|
|
234
|
+
const file = store.databaseFile();
|
|
235
|
+
return file === null ? null : dirname(file);
|
|
236
|
+
}
|
|
237
|
+
/** A zone named by id or by its title. */
|
|
238
|
+
function flowZoneOf(definition, input, fallback) {
|
|
239
|
+
const named = input["zone"] === undefined ? fallback : text(input, "zone", 60).trim();
|
|
240
|
+
const stage = named === null ? undefined : definition.stages.find(one => one.id === named || one.title.toLowerCase() === named.toLowerCase());
|
|
241
|
+
if (stage === undefined)
|
|
242
|
+
throw Error(`This flow has no zone called ${named}.`);
|
|
243
|
+
return stage;
|
|
244
|
+
}
|
|
245
|
+
/** A flow drawing from chat: whole, keyless, plain, and every named decider can decide on the project. */
|
|
246
|
+
function flowDrawingOf(store, input, repo) {
|
|
247
|
+
const definition = validateFlowDefinition(input["definition"]);
|
|
248
|
+
const words = JSON.stringify(definition);
|
|
249
|
+
if (scanForSecrets(words).length > 0 || /[\u200b-\u200f\u202a-\u202e\u2066-\u2069\ufffd]/.test(words))
|
|
250
|
+
throw Error("Check the steps. Credentials cannot be included in chat actions.");
|
|
251
|
+
for (const stage of definition.stages)
|
|
252
|
+
if (stage.approver !== null && !(store.listApprovers().some(one => one.name === stage.approver) && store.accountCanAccess(stage.approver, repo)))
|
|
253
|
+
throw Error(`No one called ${stage.approver} can approve on this project. Name someone who can, or let anyone who approves decide.`);
|
|
254
|
+
return definition;
|
|
255
|
+
}
|
|
256
|
+
const quoted = (value) => `“${value}”`;
|
|
257
|
+
/** No writes, including at preview. Store-derived snapshots are never accepted
|
|
258
|
+
* from a model. Rebuilding the same request before execution catches changes. */
|
|
259
|
+
export function prepareSharedAction(store, who, operation, input, root, now = new Date()) {
|
|
260
|
+
if (!isChatAction(operation))
|
|
261
|
+
throw Error("Choose an available action.");
|
|
262
|
+
const allowed = CHAT_ACTION_FIELDS[operation];
|
|
263
|
+
if (Object.keys(input).some((key) => !allowed.includes(key)))
|
|
264
|
+
throw Error("This action contains an unsupported field.");
|
|
265
|
+
const task = operation.startsWith("skill_") || operation.startsWith("knowledge_") || operation.startsWith("decision_") || operation.startsWith("tool_") || operation.startsWith("flow_") || operation.startsWith("teammate_") || operation.startsWith("kit_")
|
|
266
|
+
? null
|
|
267
|
+
: text(input, "task", 64);
|
|
268
|
+
const flowTarget = operation.startsWith("flow_") && operation !== "flow_create" && operation !== "flow_script_save" ? flowTargetOf(store, input) : null;
|
|
269
|
+
// v92: a teammate names its project; a question names it through its card's flow.
|
|
270
|
+
const mateTarget = operation.startsWith("teammate_") && operation !== "teammate_create" && operation !== "teammate_answer" ? store.getTeammate(integer(input, "teammate")) : null;
|
|
271
|
+
if (mateTarget !== null && mateTarget.state === "removed")
|
|
272
|
+
throw Error("That teammate is off the team.");
|
|
273
|
+
const questionTarget = operation === "teammate_answer" ? store.teammateQuestion(integer(input, "question")) : null;
|
|
274
|
+
const questionFlow = questionTarget === null ? null : store.getFlow(store.getFlowCard(questionTarget.card)?.flow ?? -1);
|
|
275
|
+
if (operation === "teammate_answer" && (questionTarget === null || questionFlow === null))
|
|
276
|
+
throw Error("That question isn't open any more.");
|
|
277
|
+
const repo = flowTarget !== null ? flowTarget.flow.repo : mateTarget !== null ? mateTarget.repo : questionFlow !== null ? questionFlow.repo : task === null ? text(input, "repo", 4096) : store.lookupRef(task)?.repo;
|
|
278
|
+
if (!repo)
|
|
279
|
+
throw Error("Choose an available project.");
|
|
280
|
+
requireActor(store, who, repo);
|
|
281
|
+
const request = structuredClone(input), terms = [];
|
|
282
|
+
let state, title = CHAT_ACTIONS[operation].label;
|
|
283
|
+
if (operation.startsWith("skill_")) {
|
|
284
|
+
const view = skillsView(store, repo, who.name);
|
|
285
|
+
state = {
|
|
286
|
+
identity: view.identity,
|
|
287
|
+
revision: view.revision,
|
|
288
|
+
selection: view.selection,
|
|
289
|
+
library: view.library.map((one) => one.sha),
|
|
290
|
+
};
|
|
291
|
+
if (operation === "skill_import") {
|
|
292
|
+
if (input["files"] !== undefined && input["content"] !== undefined)
|
|
293
|
+
throw Error("Choose one source for this skill.");
|
|
294
|
+
const files = input["files"] === undefined
|
|
295
|
+
? [
|
|
296
|
+
{
|
|
297
|
+
path: "SKILL.md",
|
|
298
|
+
base64: Buffer.from(text(input, "content", 12000)).toString("base64"),
|
|
299
|
+
},
|
|
300
|
+
]
|
|
301
|
+
: input["files"];
|
|
302
|
+
const skill = validateSkill(files, "Chat import");
|
|
303
|
+
request["files"] = skill.files;
|
|
304
|
+
delete request["content"];
|
|
305
|
+
title = `Add ${skill.name}`;
|
|
306
|
+
terms.push("Adds this version to your library. It will not be enabled automatically.", ...skill.files.map((file) => `${file.path}\n${Buffer.from(file.base64, "base64").toString("utf8")}`));
|
|
307
|
+
}
|
|
308
|
+
else if (operation === "skill_restore") {
|
|
309
|
+
const restore = integer(input, "restore");
|
|
310
|
+
const selection = skillsVersion(store, repo, who.name, restore);
|
|
311
|
+
state["restore"] = selection;
|
|
312
|
+
terms.push(`Restore skills version ${restore}. Future runs use this selection; existing runs keep their saved skills.`, ...Object.entries(selection).map(([name, choice]) => `${name}: ${choice.enabled ? "enabled" : "disabled"}\nVersion ${choice.sha}`));
|
|
313
|
+
}
|
|
314
|
+
else {
|
|
315
|
+
const version = text(input, "version", 64);
|
|
316
|
+
const matches = /^[a-f0-9]{20,64}$/.test(version)
|
|
317
|
+
? view.library.filter((one) => one.sha.startsWith(version))
|
|
318
|
+
: [];
|
|
319
|
+
if (matches.length !== 1)
|
|
320
|
+
throw Error("Choose an exact skill version from get_skills.");
|
|
321
|
+
const skill = matches[0];
|
|
322
|
+
request["version"] = skill.sha;
|
|
323
|
+
title = `${CHAT_ACTIONS[operation].label}: ${skill.name}`;
|
|
324
|
+
terms.push(`Version ${view.library.filter((one) => one.sha.startsWith(skill.sha.slice(0, 20))).length === 1 ? skill.sha.slice(0, 20) : skill.sha}`, operation === "skill_test"
|
|
325
|
+
? `Sample request:\n${text(input, "sample", 800)}\nCreates a report task under the existing approval rules.`
|
|
326
|
+
: "Changes skills for future runs. Existing runs keep their saved versions.");
|
|
327
|
+
if (operation === "skill_test" &&
|
|
328
|
+
!/^[a-f0-9-]{36}$/.test(text(input, "nonce", 36)))
|
|
329
|
+
throw Error("This test request has no saved identity.");
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
else if (operation.startsWith("tool_")) {
|
|
333
|
+
// The staleness fence: the project's tools as the card saw them.
|
|
334
|
+
const current = projectToolsOf(store, repo);
|
|
335
|
+
state = { tools: current.map((one) => `${one.name}:${one.digest}`) };
|
|
336
|
+
const project = repo.split(/[\\/]/).filter(Boolean).at(-1) ?? repo;
|
|
337
|
+
if (operation === "tool_add") {
|
|
338
|
+
const spec = toolSpecFromRequest(input);
|
|
339
|
+
if (current.some((one) => one.name === spec.name))
|
|
340
|
+
throw Error(`${project} already has a tool called ${spec.name}.`);
|
|
341
|
+
// The request is rewritten to exactly what was checked, so confirming re-checks the same words.
|
|
342
|
+
for (const key of ["name", "command", "args", "url", "secrets", "about"])
|
|
343
|
+
delete request[key];
|
|
344
|
+
if (input["catalog"] === undefined)
|
|
345
|
+
Object.assign(request, { name: spec.name, ...(spec.transport === "http" ? { url: spec.url } : { command: spec.command, args: spec.args }), secrets: spec.secrets.map((one) => one.name), about: spec.about });
|
|
346
|
+
const needs = spec.secrets.filter((one) => !one.optional).map((one) => one.name);
|
|
347
|
+
title = `Add ${spec.name} to ${project}`;
|
|
348
|
+
terms.push(spec.about, `Starts: ${toolCommandLine(spec)}`, "It runs on this computer with the same access as your builds. Work you approve from now on can use it; tasks approved earlier need approving again to use it.", needs.length > 0
|
|
349
|
+
? `Needs ${needs.join(" and ")}. Set ${needs.length === 1 ? "it" : "them"} on the Tools page after adding, never in chat.`
|
|
350
|
+
: "Test it on the Tools page after adding.");
|
|
351
|
+
}
|
|
352
|
+
else {
|
|
353
|
+
const name = text(input, "name", 40);
|
|
354
|
+
if (!current.some((one) => one.name === name))
|
|
355
|
+
throw Error(`${project} has no tool called ${name}.`);
|
|
356
|
+
title = `Remove ${name} from ${project}`;
|
|
357
|
+
terms.push("Builds stop using it right away. Its stored secrets are deleted.");
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
else if (operation === "kit_setup") {
|
|
361
|
+
// a starter kit — its teammate, its flow and its buttons.
|
|
362
|
+
const project = repo.split(/[\\/]/).filter(Boolean).at(-1) ?? repo;
|
|
363
|
+
const kit = kitOf(String(input["kit"] ?? ""));
|
|
364
|
+
if (kit === null)
|
|
365
|
+
throw Error(`Choose a kit: ${KITS.map(one => one.id).join(", ")}.`);
|
|
366
|
+
if (kitInstalled(store, kit, repo) !== null)
|
|
367
|
+
throw Error(`${kit.name} is already set up in ${project}.`);
|
|
368
|
+
const template = TEAMMATE_TEMPLATES.find(one => one.id === kit.teammate.template);
|
|
369
|
+
title = `Set up ${kit.name} in ${project}`;
|
|
370
|
+
terms.push(kit.promise, `Adds ${kit.teammate.handle.charAt(0).toUpperCase()}${kit.teammate.handle.slice(1)} (${template?.label.toLowerCase() ?? "a teammate"}) and the ${kit.flowName} flow${kit.buttons.length === 0 ? "" : ` with its ${kit.buttons.map(one => `“${one.label}”`).join(" and ")} button`}. Nothing goes out without your approval.`);
|
|
371
|
+
state = {};
|
|
372
|
+
}
|
|
373
|
+
else if (operation.startsWith("teammate_")) {
|
|
374
|
+
const project = repo.split(/[\\/]/).filter(Boolean).at(-1) ?? repo;
|
|
375
|
+
const mate = mateTarget;
|
|
376
|
+
if (operation === "teammate_create") {
|
|
377
|
+
const template = TEAMMATE_TEMPLATES.find(one => one.id === input["template"]);
|
|
378
|
+
const given = typeof input["soul"] === "string" && input["soul"].trim() !== "" ? text(input, "soul", SOUL_CHARS) : null;
|
|
379
|
+
if ((template === undefined) === (given === null))
|
|
380
|
+
throw Error("Start from a template, or write the whole soul file, not both.");
|
|
381
|
+
let soul = given ?? template.soul;
|
|
382
|
+
if (typeof input["name"] === "string" && input["name"].trim() !== "")
|
|
383
|
+
soul = renamedSoul(soul, text(input, "name", 40));
|
|
384
|
+
const read = parseSoul(soul);
|
|
385
|
+
if (!read.ok)
|
|
386
|
+
throw Error(read.problem);
|
|
387
|
+
if (store.teammateByHandle(repo, handleOf(read.soul.name)) !== null)
|
|
388
|
+
throw Error(`There's already a teammate called ${read.soul.name} in ${project}.`);
|
|
389
|
+
Object.assign(request, { soul });
|
|
390
|
+
delete request["template"];
|
|
391
|
+
delete request["name"];
|
|
392
|
+
state = { handle: handleOf(read.soul.name) };
|
|
393
|
+
title = `Add ${teammateLabel(read.soul)} to the team in ${project}`;
|
|
394
|
+
terms.push(soul.trim(), `${read.soul.name} decides only within these rules and asks you when they say to. Teammates never approve code tasks or merges.`);
|
|
395
|
+
}
|
|
396
|
+
else if (operation === "teammate_soul") {
|
|
397
|
+
const soul = text(input, "soul", SOUL_CHARS);
|
|
398
|
+
const read = parseSoul(soul);
|
|
399
|
+
if (!read.ok)
|
|
400
|
+
throw Error(read.problem);
|
|
401
|
+
if (handleOf(read.soul.name) !== mate.handle)
|
|
402
|
+
throw Error("Keep the teammate's name; make a new teammate for another name.");
|
|
403
|
+
if (soul.trim() === mate.soul.trim())
|
|
404
|
+
throw Error("That's already its soul file.");
|
|
405
|
+
state = { version: mate.version };
|
|
406
|
+
title = `Update ${teammateLabel(read.soul)}'s soul file`;
|
|
407
|
+
terms.push(soul.trim(), `Replaces version ${mate.version}. Its next turn reads this.`);
|
|
408
|
+
}
|
|
409
|
+
else if (operation === "teammate_state") {
|
|
410
|
+
const wanted = input["state"];
|
|
411
|
+
if (wanted !== "active" && wanted !== "paused" && wanted !== "removed")
|
|
412
|
+
throw Error("Choose pause, resume or remove.");
|
|
413
|
+
state = { state: mate.state };
|
|
414
|
+
title = `${wanted === "paused" ? "Pause" : wanted === "active" ? "Resume" : "Remove"} ${labelOf(mate)}`;
|
|
415
|
+
terms.push(wanted === "paused" ? "Its decisions go to people and the zones it handles wait until you resume it." : wanted === "active" ? "It picks up its zones' cards again." : "It leaves the team: zones that name it go to people.");
|
|
416
|
+
}
|
|
417
|
+
else if (operation === "teammate_tools") {
|
|
418
|
+
// v94: which project tools it may use, and its rule for one action.
|
|
419
|
+
const tool = text(input, "tool", 40);
|
|
420
|
+
const change = input["change"];
|
|
421
|
+
const grant = store.teammateGrant(mate.id, tool);
|
|
422
|
+
const name = nameOf(mate);
|
|
423
|
+
const grants = store.teammateGrants(mate.id);
|
|
424
|
+
const uses = grants.length === 0 ? ` ${name} doesn't use any tools yet.` : ` ${name} uses ${grants.map(one => `${one.tool} (actions: ${one.actions.map(each => each.name).join(", ")})`).join("; ")}.`;
|
|
425
|
+
if (change === "grant") {
|
|
426
|
+
const found = projectToolsOf(store, repo).find(one => one.name === tool);
|
|
427
|
+
const named = projectToolsOf(store, repo).map(one => one.name);
|
|
428
|
+
if (found === undefined)
|
|
429
|
+
throw Error(`${project} has no tool called ${tool}.${named.length === 0 ? " Add it on the Tools page first." : ` Its tools: ${named.join(", ")}.`}${uses}`);
|
|
430
|
+
if (grant !== null)
|
|
431
|
+
throw Error(`${name} can already use ${tool}.`);
|
|
432
|
+
const actions = found.lastTest?.tools ?? [];
|
|
433
|
+
if (actions.length === 0)
|
|
434
|
+
throw Error(`${tool} hasn't said what it can do yet. Test it on the Tools page first.`);
|
|
435
|
+
state = { rules: "" };
|
|
436
|
+
title = `Let ${name} use ${tool}`;
|
|
437
|
+
terms.push(...actions.map(one => `${one}: ${defaultRule({ name: one, readOnly: false }).use === "free" ? "does it" : "asks you first"}`), `${name} asks for each call on its turn; Toolroll makes it by these rules, and every call is kept on the card. Change a rule any time.`);
|
|
438
|
+
}
|
|
439
|
+
else if (change === "revoke") {
|
|
440
|
+
if (grant === null)
|
|
441
|
+
throw Error(`${name} doesn't use a tool called ${tool}.${uses}`);
|
|
442
|
+
state = { rules: JSON.stringify(grant.rules) };
|
|
443
|
+
title = `Stop ${name} using ${tool}`;
|
|
444
|
+
terms.push("Calls waiting for your approval are cancelled.");
|
|
445
|
+
}
|
|
446
|
+
else if (change === "rule") {
|
|
447
|
+
if (grant === null)
|
|
448
|
+
throw Error(`${name} doesn't use a tool called ${tool}.${uses}`);
|
|
449
|
+
const action = text(input, "action", 64);
|
|
450
|
+
const limit = input["limitField"] === undefined || input["limitField"] === null || input["limitField"] === "" ? null : { field: input["limitField"], over: input["limitOver"] };
|
|
451
|
+
const checked = checkRule(grant, action, { use: input["use"], limit, undo: input["undoWith"] ?? grant.rules[action]?.undo ?? "" });
|
|
452
|
+
if (!checked.ok)
|
|
453
|
+
throw Error(checked.said);
|
|
454
|
+
state = { rules: JSON.stringify(grant.rules) };
|
|
455
|
+
title = `${name}: ${action} — ${ruleWords(checked.rule)}`;
|
|
456
|
+
terms.push(`${tool} → ${action}: ${ruleWords(checked.rule)}.`, `Was: ${ruleWords(grant.rules[action] ?? { use: "ask" })}.`, ...(checked.rule.undo === undefined ? [] : [`A person can undo it with ${checked.rule.undo}, called with the same input.`]));
|
|
457
|
+
}
|
|
458
|
+
else
|
|
459
|
+
throw Error("Choose grant, revoke or rule.");
|
|
460
|
+
}
|
|
461
|
+
else if (operation === "teammate_undo") {
|
|
462
|
+
// v97: undo one of its tool calls with the action its manager named for that.
|
|
463
|
+
const call = store.teammateCall(integer(input, "call"));
|
|
464
|
+
const undo = call === null || call.teammate !== mate.id ? null : undoFor(store, call);
|
|
465
|
+
if (call === null || undo === null)
|
|
466
|
+
throw Error(call?.undoneBy ? `${call.undoneBy} already undid it.` : "That call can't be undone: its action has no undo set on the teammate's Tools.");
|
|
467
|
+
title = `Undo ${nameOf(mate)}'s ${call.action}`;
|
|
468
|
+
terms.push(`Undoes: ${callWords(call.tool, call.action, call.input, 300)}`, `By calling: ${callWords(call.tool, undo, call.input, 300)}`, "It's made as you, with the same input, and both calls keep their receipts.");
|
|
469
|
+
state = { call: call.id };
|
|
470
|
+
}
|
|
471
|
+
else if (operation === "teammate_routine") {
|
|
472
|
+
// v96: a routine: on a schedule, a card on its desk saying what to do; its answer goes to its manager.
|
|
473
|
+
const name = nameOf(mate);
|
|
474
|
+
if (input["change"] === "add") {
|
|
475
|
+
const schedule = routineSchedule(text(input, "schedule", 80));
|
|
476
|
+
if (schedule === null)
|
|
477
|
+
throw Error("Say the schedule like “weekdays 09:00”, “daily 17:00 Europe/London”, “monday 09:00” or “every 2 hours”.");
|
|
478
|
+
const what = text(input, "text", 200).replace(/\s+/g, " ").trim();
|
|
479
|
+
if (what === "")
|
|
480
|
+
throw Error("Say what it should do each time.");
|
|
481
|
+
Object.assign(request, { schedule, text: what });
|
|
482
|
+
title = `${name}: ${what}`.slice(0, 120);
|
|
483
|
+
terms.push(`When: ${describeSchedule(parseSchedule(schedule))}`, `What: ${what}`, `Each time, a card lands on ${name}'s desk; ${name} works it within its rules and tools, and the answer goes to ${mate.manager}.`);
|
|
484
|
+
state = { routines: routinesOf(store, mate).length };
|
|
485
|
+
}
|
|
486
|
+
else if (input["change"] === "remove") {
|
|
487
|
+
const routine = routinesOf(store, mate).find(one => one.id === integer(input, "routine"));
|
|
488
|
+
if (routine === undefined)
|
|
489
|
+
throw Error("That routine is already gone.");
|
|
490
|
+
title = `Stop ${name}'s routine`;
|
|
491
|
+
terms.push(`${describeSchedule(parseSchedule(routine.schedule) ?? { kind: "every", minutes: 60 })}: ${routine.text}`);
|
|
492
|
+
state = { routine: routine.id };
|
|
493
|
+
}
|
|
494
|
+
else
|
|
495
|
+
throw Error("Choose add or remove.");
|
|
496
|
+
}
|
|
497
|
+
else if (operation === "teammate_memory") {
|
|
498
|
+
// v95: edit or forget one thing it remembers.
|
|
499
|
+
const memory = store.teammateMemory(integer(input, "memory"));
|
|
500
|
+
if (memory === null || memory.teammate !== mate.id)
|
|
501
|
+
throw Error(`${nameOf(mate)} doesn't remember that any more.`);
|
|
502
|
+
if (input["change"] === "forget") {
|
|
503
|
+
title = `${nameOf(mate)} forgets: ${memory.text.slice(0, 60)}`;
|
|
504
|
+
terms.push(memory.text);
|
|
505
|
+
}
|
|
506
|
+
else if (input["change"] === "edit") {
|
|
507
|
+
const clean = cleanMemory(text(input, "text", 400));
|
|
508
|
+
if (!clean.ok)
|
|
509
|
+
throw Error(clean.said);
|
|
510
|
+
request["text"] = clean.text;
|
|
511
|
+
title = `Change what ${nameOf(mate)} remembers`;
|
|
512
|
+
terms.push(`Was: ${memory.text}`, `Now: ${clean.text}`);
|
|
513
|
+
}
|
|
514
|
+
else
|
|
515
|
+
throw Error("Choose edit or forget.");
|
|
516
|
+
state = { text: memory.text };
|
|
517
|
+
}
|
|
518
|
+
else if (operation === "teammate_note") {
|
|
519
|
+
const note = cleanMemory(text(input, "note", 1000));
|
|
520
|
+
if (!note.ok)
|
|
521
|
+
throw Error(note.said);
|
|
522
|
+
request["note"] = note.text;
|
|
523
|
+
state = { teammate: mate.id };
|
|
524
|
+
title = `Tell ${nameOf(mate)}`;
|
|
525
|
+
terms.push(note.text, "It keeps this in its memory: every turn reads the latest ten things people told it.");
|
|
526
|
+
}
|
|
527
|
+
else {
|
|
528
|
+
const question = questionTarget;
|
|
529
|
+
if (question.state !== "open")
|
|
530
|
+
throw Error("That question was already answered.");
|
|
531
|
+
if (question.askedOf !== who.name)
|
|
532
|
+
throw Error(`Only ${question.askedOf} can answer this one.`);
|
|
533
|
+
const choice = typeof input["choice"] === "string" ? question.options.find(one => one.id === input["choice"] || one.label.toLowerCase() === String(input["choice"]).trim().toLowerCase()) ?? null : null;
|
|
534
|
+
const said = typeof input["text"] === "string" ? text(input, "text", 2000).trim() : "";
|
|
535
|
+
if (choice === null && said === "")
|
|
536
|
+
throw Error("Pick one of its options or say the answer.");
|
|
537
|
+
Object.assign(request, { choice: choice?.id ?? null, text: said || null });
|
|
538
|
+
state = { question: question.id };
|
|
539
|
+
const mateOf = store.getTeammate(question.teammate);
|
|
540
|
+
title = `Answer ${mateOf === null ? "the teammate" : nameOf(mateOf)}`;
|
|
541
|
+
terms.push(question.question, `Your answer: ${[choice?.label, said].filter(Boolean).join(" — ")}`);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
else if (operation.startsWith("flow_")) {
|
|
545
|
+
const project = repo.split(/[\\/]/).filter(Boolean).at(-1) ?? repo;
|
|
546
|
+
if (operation === "flow_create") {
|
|
547
|
+
const name = text(input, "name", 80).trim();
|
|
548
|
+
const definition = flowDrawingOf(store, input, repo);
|
|
549
|
+
request["name"] = name;
|
|
550
|
+
request["definition"] = definition;
|
|
551
|
+
state = {};
|
|
552
|
+
title = `Create the ${name} flow in ${project}`;
|
|
553
|
+
terms.push(...flowTerms(definition, null));
|
|
554
|
+
}
|
|
555
|
+
else if (operation === "flow_edit") {
|
|
556
|
+
const { flow, definition: before } = flowTarget;
|
|
557
|
+
const name = input["name"] === undefined ? flow.name : text(input, "name", 80).trim();
|
|
558
|
+
const definition = input["definition"] === undefined ? before : flowDrawingOf(store, input, repo);
|
|
559
|
+
const redrawn = flowDigest(definition) !== flowDigest(before);
|
|
560
|
+
if (!redrawn && name === flow.name)
|
|
561
|
+
throw Error("That's the flow as it is now.");
|
|
562
|
+
Object.assign(request, { name, definition });
|
|
563
|
+
state = { flow: flow.id, revision: flow.revision };
|
|
564
|
+
title = `Change the ${flow.name} flow`;
|
|
565
|
+
if (name !== flow.name)
|
|
566
|
+
terms.push(`Renames it to ${name}.`);
|
|
567
|
+
if (redrawn)
|
|
568
|
+
terms.push(...flowTerms(definition, before));
|
|
569
|
+
}
|
|
570
|
+
else if (operation === "flow_card_add") {
|
|
571
|
+
const { flow, definition } = flowTarget;
|
|
572
|
+
const words = flowCardText(input["title"], input["description"]);
|
|
573
|
+
if ("problem" in words)
|
|
574
|
+
throw Error(words.problem);
|
|
575
|
+
const stage = flowZoneOf(definition, input, definition.start);
|
|
576
|
+
Object.assign(request, { title: words.title, zone: stage.id });
|
|
577
|
+
if (words.description === null)
|
|
578
|
+
delete request["description"];
|
|
579
|
+
else
|
|
580
|
+
request["description"] = words.description;
|
|
581
|
+
state = { flow: flow.id, revision: flow.revision };
|
|
582
|
+
title = `Add ${quoted(words.title)} to ${flow.name}`;
|
|
583
|
+
terms.push(words.description === null ? words.title : `${words.title}\n${words.description}`, `Starts in ${stage.title}: ${FLOW_KIND_WORDS[stage.kind].about}`);
|
|
584
|
+
}
|
|
585
|
+
else if (operation === "flow_script_save") {
|
|
586
|
+
const draft = validateScript((input["script"] ?? {}));
|
|
587
|
+
const current = store.flowScript(repo, draft.name);
|
|
588
|
+
if (current !== null && current.digest === scriptDigest(draft) && current.about === draft.about)
|
|
589
|
+
throw Error(`${draft.name} is already saved exactly like that.`);
|
|
590
|
+
request["script"] = draft;
|
|
591
|
+
state = { current: current?.digest ?? null, version: current?.version ?? 0 };
|
|
592
|
+
title = `${current === null ? "Save" : "Update"} the ${draft.name} script in ${project}`;
|
|
593
|
+
const language = draft.language === "python" ? "Python" : draft.language === "node" ? "Node" : "Shell";
|
|
594
|
+
terms.push(`${draft.name}: ${draft.about}`, draft.file === null ? draft.body : `Runs the project's file ${draft.file}`, `${language}, with no AI, for up to ${draft.timeoutMinutes} minutes, whenever a card reaches a zone that runs it (or a schedule does). It gets the card as data; what it prints is passed on.${current === null ? "" : ` Replaces version ${current.version} everywhere it's used.`}`);
|
|
595
|
+
}
|
|
596
|
+
else if (operation === "flow_trigger_add") {
|
|
597
|
+
const { flow, definition } = flowTarget;
|
|
598
|
+
const config = validateTriggerConfig(input["trigger"], { store, flow, definition, actor: who.name });
|
|
599
|
+
// A webhook address is a secret: it is made and shown on the flow's own Triggers panel, never through chat.
|
|
600
|
+
if (takesDeliveries(config))
|
|
601
|
+
throw Error("Webhook addresses are secrets, so they are set up on the flow's Triggers panel. From chat, use a trigger that is checked every 2 minutes.");
|
|
602
|
+
request["trigger"] = config;
|
|
603
|
+
state = { flow: flow.id, revision: flow.revision };
|
|
604
|
+
title = `Add a trigger to ${flow.name}`;
|
|
605
|
+
const zone = definition.stages.find(one => one.id === (config.zone ?? definition.start))?.title ?? "the first zone";
|
|
606
|
+
terms.push(describeTrigger(config, store), `Cards start in ${zone}.`);
|
|
607
|
+
if (config.kind === "github" && config.from === "anyone")
|
|
608
|
+
terms.push("Anyone who can open one there can write what the agent reads.");
|
|
609
|
+
if (config.kind === "linear" && readLinearKey(configDirOf(store)) === null)
|
|
610
|
+
terms.push("It needs a Linear API key, saved on the flow's Triggers panel (never in chat).");
|
|
611
|
+
if (config.kind === "github" || config.kind === "linear")
|
|
612
|
+
terms.push("Only what happens from now on counts; nothing already there is added.");
|
|
613
|
+
terms.push("Work a card starts still waits for your usual approvals.");
|
|
614
|
+
}
|
|
615
|
+
else if (operation === "flow_trigger_pause" || operation === "flow_trigger_resume" || operation === "flow_trigger_remove") {
|
|
616
|
+
const { flow, trigger } = flowTarget;
|
|
617
|
+
const config = trigger === null ? null : triggerConfigOf(trigger);
|
|
618
|
+
if (trigger === null || config === null)
|
|
619
|
+
throw Error("Choose a trigger from get_flows.");
|
|
620
|
+
if (operation === "flow_trigger_pause" && trigger.state === "paused")
|
|
621
|
+
throw Error("That trigger is already paused.");
|
|
622
|
+
if (operation === "flow_trigger_resume" && trigger.state === "active")
|
|
623
|
+
throw Error("That trigger is already on.");
|
|
624
|
+
state = { trigger: trigger.id, state: trigger.state, updatedAt: trigger.updatedAt };
|
|
625
|
+
const what = describeTrigger(config, store);
|
|
626
|
+
title = `${operation === "flow_trigger_pause" ? "Pause" : operation === "flow_trigger_resume" ? "Turn on" : "Remove"} a trigger on ${flow.name}`;
|
|
627
|
+
terms.push(what, operation === "flow_trigger_pause" ? "It stops adding cards until it's turned on again. Cards it already added stay." : operation === "flow_trigger_resume" ? "It adds cards again, starting from now." : `It stops adding cards for good${takesDeliveries(config) ? ", and its webhook address stops working" : ""}. Cards it already added stay.`);
|
|
628
|
+
}
|
|
629
|
+
else {
|
|
630
|
+
const { definition, card } = flowTarget;
|
|
631
|
+
if (card === null)
|
|
632
|
+
throw Error("Choose a card from get_flows.");
|
|
633
|
+
const at = definition.stages.find(one => one.id === card.stage);
|
|
634
|
+
state = { card: card.id, entry: card.entry, stage: card.stage };
|
|
635
|
+
if (operation === "flow_card_comment" || operation === "flow_card_assign" || operation === "flow_card_watch") {
|
|
636
|
+
const people = flowPeople(store, flowTarget.flow.repo);
|
|
637
|
+
if (operation === "flow_card_comment") {
|
|
638
|
+
const note = text(input, "note", 4000).trim();
|
|
639
|
+
const mentions = mentionsIn(note, people);
|
|
640
|
+
request["note"] = note;
|
|
641
|
+
title = `Comment on ${quoted(card.title)}`;
|
|
642
|
+
terms.push(note, mentions.length === 0 ? "Its owner and followers hear about it." : `${mentions.join(" and ")} ${mentions.length === 1 ? "is" : "are"} pinged; its owner and followers hear about it too.`);
|
|
643
|
+
}
|
|
644
|
+
else if (operation === "flow_card_assign") {
|
|
645
|
+
const said = input["owner"] === null || input["owner"] === undefined ? "" : text(input, "owner", 64).trim();
|
|
646
|
+
const owner = said === "" ? null : people.find(one => one.toLowerCase() === said.toLowerCase()) ?? null;
|
|
647
|
+
if (said !== "" && owner === null)
|
|
648
|
+
throw Error(`No one called ${said} can work on this project.`);
|
|
649
|
+
if (owner === card.owner)
|
|
650
|
+
throw Error(owner === null ? "It has no owner already." : `${owner === who.name ? "You already own" : `${owner} already owns`} it.`);
|
|
651
|
+
request["owner"] = owner;
|
|
652
|
+
state["owner"] = card.owner;
|
|
653
|
+
title = owner === null ? `Leave ${quoted(card.title)} without an owner` : `Make ${owner === who.name ? "you" : owner} the owner of ${quoted(card.title)}`;
|
|
654
|
+
terms.push(owner === null ? "No one owns it; its followers still hear about it." : `${owner === who.name ? "You" : owner} will hear when it needs ${owner === who.name ? "you" : "them"}, is sent back, fails or finishes.`);
|
|
655
|
+
}
|
|
656
|
+
else {
|
|
657
|
+
const watching = input["watching"] !== false;
|
|
658
|
+
request["watching"] = watching;
|
|
659
|
+
title = `${watching ? "Follow" : "Stop following"} ${quoted(card.title)}`;
|
|
660
|
+
terms.push(watching ? "You'll hear when it moves, is commented on, or finishes." : "You won't hear about it unless someone mentions you.");
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
else if (operation === "flow_card_move") {
|
|
664
|
+
const stage = flowZoneOf(definition, input, null);
|
|
665
|
+
if (stage.id === card.stage)
|
|
666
|
+
throw Error(`It's already in ${stage.title}.`);
|
|
667
|
+
request["zone"] = stage.id;
|
|
668
|
+
title = `Move ${quoted(card.title)} to ${stage.title}`;
|
|
669
|
+
terms.push(`From ${at?.title ?? "its zone"} to ${stage.title}.`, `${stage.title}: ${FLOW_KIND_WORDS[stage.kind].about}`);
|
|
670
|
+
}
|
|
671
|
+
else if (operation === "flow_card_cancel") {
|
|
672
|
+
title = `Take ${quoted(card.title)} out of the flow`;
|
|
673
|
+
terms.push("The card leaves the flow. Any tasks it filed stay as they are.");
|
|
674
|
+
}
|
|
675
|
+
else {
|
|
676
|
+
if (at?.kind !== "approval")
|
|
677
|
+
throw Error("That card isn't waiting for a decision.");
|
|
678
|
+
const decider = deciderOf(at, flowTarget.flow);
|
|
679
|
+
if (decider !== null && decider !== who.name)
|
|
680
|
+
throw Error(`Only ${decider} decides here.`);
|
|
681
|
+
const note = input["note"] === undefined ? "" : text(input, "note", 2000).trim();
|
|
682
|
+
const titleOf = (id) => definition.stages.find(one => one.id === id)?.title ?? null;
|
|
683
|
+
if (operation === "flow_card_approve") {
|
|
684
|
+
title = `Approve ${quoted(card.title)}`;
|
|
685
|
+
terms.push(`${at.title}: approved. ${at.next === null ? "The card is done." : `It moves to ${titleOf(at.next)}.`}`);
|
|
686
|
+
if (note !== "")
|
|
687
|
+
terms.push(`Note: ${note}`);
|
|
688
|
+
}
|
|
689
|
+
else {
|
|
690
|
+
if (at.onFail === null)
|
|
691
|
+
throw Error("This step has nowhere to send work back to.");
|
|
692
|
+
if (note === "")
|
|
693
|
+
throw Error("Say what should change.");
|
|
694
|
+
const back = definition.stages.find(one => one.id === at.onFail);
|
|
695
|
+
title = `Send ${quoted(card.title)} back`;
|
|
696
|
+
terms.push(`${at.title}: sent back to ${back?.title ?? at.onFail} with this note:\n${note}`);
|
|
697
|
+
if (back?.kind === "task" && card.primaryTask !== null)
|
|
698
|
+
terms.push("It already has a result, so the build makes a revision of that same work, with your note.");
|
|
699
|
+
}
|
|
700
|
+
if (note === "")
|
|
701
|
+
delete request["note"];
|
|
702
|
+
else
|
|
703
|
+
request["note"] = note;
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
else if (operation.startsWith("decision_")) {
|
|
708
|
+
// The staleness fence: the newest decision id and the active count, so a
|
|
709
|
+
// card drafted before a teammate recorded or retired one is refused.
|
|
710
|
+
const stamp = store.handle.prepare("SELECT COALESCE(MAX(id),0) AS newest, COUNT(*) AS active FROM project_decision WHERE repo=? AND status='active'").get(repo);
|
|
711
|
+
state = { newest: Number(stamp?.["newest"] ?? 0), active: Number(stamp?.["active"] ?? 0) };
|
|
712
|
+
if (operation === "decision_record") {
|
|
713
|
+
const claim = text(input, "claim", 240), why = text(input, "why", 2000);
|
|
714
|
+
if (!claim || !why)
|
|
715
|
+
throw Error("A decision needs the choice in one sentence and the reason.");
|
|
716
|
+
terms.push(`Decision: ${claim}`, `Why: ${why}`);
|
|
717
|
+
if (input["supersedes"] !== undefined) {
|
|
718
|
+
const older = getDecision(store, repo, who.name, integer(input, "supersedes"));
|
|
719
|
+
if (older === null || older.status !== "active")
|
|
720
|
+
throw Error("The decision being replaced is not active.");
|
|
721
|
+
terms.push(`Replaces decision ${older.id}: ${older.claim}`);
|
|
722
|
+
state["supersedes"] = older.id;
|
|
723
|
+
}
|
|
724
|
+
if (input["source"] !== undefined)
|
|
725
|
+
terms.push(`Source: ${text(input, "source", 200)}`);
|
|
726
|
+
terms.push("Records a settled choice with its reason for everyone on this project. It is context, never an instruction or a permission.");
|
|
727
|
+
}
|
|
728
|
+
else {
|
|
729
|
+
const older = getDecision(store, repo, who.name, integer(input, "decision"));
|
|
730
|
+
if (older === null || older.status !== "active")
|
|
731
|
+
throw Error("That decision is not active.");
|
|
732
|
+
const reason = text(input, "reason", 500);
|
|
733
|
+
if (!reason)
|
|
734
|
+
throw Error("Say why this decision no longer holds.");
|
|
735
|
+
terms.push(`Retire decision ${older.id}: ${older.claim}`, `Reason: ${reason}`, "The decision stays in history and stops being offered as context.");
|
|
736
|
+
state["decision"] = older.id;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
else if (operation.startsWith("knowledge_")) {
|
|
740
|
+
const view = knowledgeView(store, repo, who.name);
|
|
741
|
+
state = {
|
|
742
|
+
identity: view.identity,
|
|
743
|
+
revision: view.revision,
|
|
744
|
+
knowledge: view.knowledge,
|
|
745
|
+
};
|
|
746
|
+
if (operation === "knowledge_instructions")
|
|
747
|
+
terms.push(`Project instructions:\n${text(input, "instructions", 4000, true)}`);
|
|
748
|
+
if (operation === "knowledge_save") {
|
|
749
|
+
terms.push(`Reference: ${text(input, "title", 120)}\n${text(input, "content", 12000)}`);
|
|
750
|
+
if (input["id"] !== undefined &&
|
|
751
|
+
!view.knowledge.references.some((one) => one.id === input["id"]))
|
|
752
|
+
throw Error("That reference is no longer available.");
|
|
753
|
+
}
|
|
754
|
+
if (operation === "knowledge_remove") {
|
|
755
|
+
const reference = view.knowledge.references.find((one) => one.id === text(input, "id", 20));
|
|
756
|
+
if (!reference)
|
|
757
|
+
throw Error("That reference is no longer available.");
|
|
758
|
+
terms.push(`Remove ${reference.title}\n${reference.content}`);
|
|
759
|
+
}
|
|
760
|
+
if (operation === "knowledge_restore")
|
|
761
|
+
terms.push(`Restore version ${integer(input, "restore")}:\n${JSON.stringify(knowledgeVersion(store, repo, who.name, Number(input["restore"])), null, 2)}`);
|
|
762
|
+
terms.push("Changes project knowledge for future runs. Existing runs retain their saved context.");
|
|
763
|
+
}
|
|
764
|
+
else {
|
|
765
|
+
const ref = store.lookupRef(task), row = store.getTask(task), family = store.taskFamilyOf(task, who.repos, false);
|
|
766
|
+
if (!row || !family || family.problem || family.current.id !== task)
|
|
767
|
+
throw Error("Choose the current task.");
|
|
768
|
+
if (store.openContestFor(ref.id) !== null)
|
|
769
|
+
throw Error("Wait for the agent comparison to finish.");
|
|
770
|
+
const scope = store.getScope(task);
|
|
771
|
+
state = {
|
|
772
|
+
task,
|
|
773
|
+
updatedAt: row.updatedAt,
|
|
774
|
+
taskState: row.state,
|
|
775
|
+
scope,
|
|
776
|
+
latest: store.runsFor(ref.id)[0]?.id ?? null,
|
|
777
|
+
};
|
|
778
|
+
title = `${CHAT_ACTIONS[operation].label}: ${row.title}`;
|
|
779
|
+
if (operation === "scope_approve") {
|
|
780
|
+
if (!scope || ref.plan === "requested")
|
|
781
|
+
throw Error("Wait for the exact work and plan to be ready for approval.");
|
|
782
|
+
if (scope.digest === scope.approvedDigest)
|
|
783
|
+
throw Error("This work is already approved.");
|
|
784
|
+
const authority = scopeAuthorityOf(scope, {
|
|
785
|
+
authMode: readAuthModeStrict,
|
|
786
|
+
});
|
|
787
|
+
if (!authority.ok)
|
|
788
|
+
throw Error(authority.problem);
|
|
789
|
+
state["authMode"] = authority.authMode;
|
|
790
|
+
const race = store.activeTournamentTerms(ref.id);
|
|
791
|
+
state["race"] = race;
|
|
792
|
+
terms.push(...describeScope(scope)
|
|
793
|
+
.filter((line) => !/^\s*(reference|approved)\s/.test(line))
|
|
794
|
+
.map((line) => line
|
|
795
|
+
.trimStart()
|
|
796
|
+
.replace(/^goal\s+/, "Work: ")
|
|
797
|
+
.replace(/^not this\s+/, "Excluded: ")
|
|
798
|
+
.replace(/^touches\s+/, "Allowed files: ")), ...(authority.chain ?? [
|
|
799
|
+
{ profile: authority.profile, authMode: authority.authMode },
|
|
800
|
+
]).map((entry, index) => `${index === 0 ? "Agent permissions and limits" : "Fallback " + index}\n${runtimeTerms(entry.profile)}\n${entry.authMode === "api-key" ? "Uses your API key; spend is charged to that account." : entry.authMode === "subscription" ? "Uses your subscription login." : ""}`));
|
|
801
|
+
if (race)
|
|
802
|
+
terms.push(`Agent comparison terms:\n${JSON.stringify(race, null, 2)}`);
|
|
803
|
+
const plan = store.latestPlanArtifact(ref.id), revision = ref.revisionBriefArtifact === null
|
|
804
|
+
? null
|
|
805
|
+
: store.getArtifact(ref.revisionBriefArtifact);
|
|
806
|
+
if (ref.plan === "drafted" && !plan)
|
|
807
|
+
throw Error("The saved plan is missing.");
|
|
808
|
+
if (ref.revisionBriefArtifact !== null && !revision)
|
|
809
|
+
throw Error("The revision evidence is missing.");
|
|
810
|
+
for (const artifact of [plan, revision])
|
|
811
|
+
if (artifact) {
|
|
812
|
+
if (!root)
|
|
813
|
+
throw Error("The saved approval evidence is unavailable.");
|
|
814
|
+
const read = readVerifiedArtifact(root, artifact);
|
|
815
|
+
if (!read.ok ||
|
|
816
|
+
artifact.truncated ||
|
|
817
|
+
artifact.redacted ||
|
|
818
|
+
artifact.captureStatus === "failed")
|
|
819
|
+
throw Error("The saved approval evidence could not be verified in full.");
|
|
820
|
+
state[artifact.kind] = { id: artifact.id, sha: artifact.sha256 };
|
|
821
|
+
terms.push(`${artifact.kind === "plan" ? "Plan" : "Requested changes"}:\n${read.content.toString("utf8")}`);
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
else if (operation === "task_resume") {
|
|
825
|
+
const run = integer(input, "run"), control = taskControlOf(store, ref.id, now);
|
|
826
|
+
if (control.kind !== "paused" || control.run !== run)
|
|
827
|
+
throw Error("This attempt is not ready to resume.");
|
|
828
|
+
state["stop"] = store.stopOf(run);
|
|
829
|
+
terms.push(`Resume saved work from attempt #${run}. Only this stop's hold is released. Other holds and approval requirements still apply.`);
|
|
830
|
+
}
|
|
831
|
+
else if (operation === "task_cancel") {
|
|
832
|
+
if (row.state === "cancelled" || row.state === "done")
|
|
833
|
+
throw Error("This task cannot be cancelled now.");
|
|
834
|
+
terms.push("Cancel this task. It will no longer be scheduled. Existing work and evidence are preserved.");
|
|
835
|
+
}
|
|
836
|
+
else {
|
|
837
|
+
const run = resultOf(store, task, integer(input, "run"), repo);
|
|
838
|
+
state["run"] = { id: run.id, head: run.headRevision };
|
|
839
|
+
const assignment = assignmentOf(store, task, now, { principal: "operator", repos: who.repos }, root);
|
|
840
|
+
const receipt = assignment?.receipt ?? null;
|
|
841
|
+
if (assignment === null || receipt === null || receipt.runId !== run.id)
|
|
842
|
+
throw Error("This result changed. Open the current result before marking it complete.");
|
|
843
|
+
if (assignment.state === "complete")
|
|
844
|
+
throw Error("This result is already marked complete.");
|
|
845
|
+
if (assignment.state !== "ready-to-check")
|
|
846
|
+
throw Error(`This result is not ready to complete: ${assignment.detail}`);
|
|
847
|
+
state["receipt"] = receipt.digest;
|
|
848
|
+
terms.push(`Result #${run.id} · ${run.headRevision === null ? "no commit recorded" : `commit ${run.headRevision.slice(0, 12)}`}`, "Marks this exact result complete: you handled it. Recorded checks stay unchanged, and publication or deployment is separate.", `Checks: ${receipt.checks.detail}`);
|
|
849
|
+
if (receipt.proof !== null && receipt.proof.matrix.length > 0)
|
|
850
|
+
terms.push(`Requirements: ${receipt.proof.matrix.filter((row) => row.state === "pass").length}/${receipt.proof.matrix.length} satisfied in the saved record.`);
|
|
851
|
+
for (const line of assignment.attention)
|
|
852
|
+
terms.push(line);
|
|
853
|
+
for (const caveat of receipt.caveats)
|
|
854
|
+
if (!assignment.attention.includes(caveat))
|
|
855
|
+
terms.push(`Limitation: ${caveat}`);
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
const stamp = hash({
|
|
859
|
+
operation,
|
|
860
|
+
request,
|
|
861
|
+
repo,
|
|
862
|
+
state,
|
|
863
|
+
terms,
|
|
864
|
+
actor: who.name,
|
|
865
|
+
generation: who.generation,
|
|
866
|
+
ceiling: who.ceilingDigest,
|
|
867
|
+
});
|
|
868
|
+
return { operation, request, repo, title, terms, stamp, state };
|
|
869
|
+
}
|
|
870
|
+
export function sharedActionPayload(value) {
|
|
871
|
+
return isChatAction(value["operation"]) &&
|
|
872
|
+
typeof value["stamp"] === "string" &&
|
|
873
|
+
typeof value["repo"] === "string" &&
|
|
874
|
+
typeof value["title"] === "string" &&
|
|
875
|
+
Array.isArray(value["terms"]) &&
|
|
876
|
+
value["terms"].every((t) => typeof t === "string") &&
|
|
877
|
+
value["request"] !== null &&
|
|
878
|
+
typeof value["request"] === "object" &&
|
|
879
|
+
!Array.isArray(value["request"]) &&
|
|
880
|
+
value["state"] !== null &&
|
|
881
|
+
typeof value["state"] === "object"
|
|
882
|
+
? value
|
|
883
|
+
: null;
|
|
884
|
+
}
|
|
885
|
+
/** A shortened or redacted preview is not complete consent. This rule lives
|
|
886
|
+
* in the shared door, so a forged transport callback cannot bypass it. */
|
|
887
|
+
function sharedActionContentNeedsReview(action) {
|
|
888
|
+
const content = [action.title, ...action.terms];
|
|
889
|
+
return (action.terms.join("\n").length > 1200 ||
|
|
890
|
+
content.some((value, index) => publicChatText(value, index === 0 ? 200 : 1200) !==
|
|
891
|
+
value.replace(/\s+/g, " ").trim()));
|
|
892
|
+
}
|
|
893
|
+
export function sharedActionNeedsReview(action) {
|
|
894
|
+
return (CHAT_ACTIONS[action.operation].protected ||
|
|
895
|
+
sharedActionContentNeedsReview(action));
|
|
896
|
+
}
|
|
897
|
+
/** Whether a paired phone may confirm this protected action behind its own
|
|
898
|
+
* explicit challenge: the action is on the challenge list, needs no
|
|
899
|
+
* password, and every term fits the card unshortened. */
|
|
900
|
+
export function sharedActionAllowsChallenge(action) {
|
|
901
|
+
const config = CHAT_ACTIONS[action.operation];
|
|
902
|
+
return (CHALLENGE_ACTIONS.has(action.operation) &&
|
|
903
|
+
config.protected &&
|
|
904
|
+
!config.password &&
|
|
905
|
+
!sharedActionContentNeedsReview(action));
|
|
906
|
+
}
|
|
907
|
+
export function sharedActionReviewPath(id) {
|
|
908
|
+
return `/chat/action/${id}`;
|
|
909
|
+
}
|
|
910
|
+
function savedActionContext(store, who, id, state) {
|
|
911
|
+
if (!isVerifiedApprover(who) || !reproveApprover(store, who).ok)
|
|
912
|
+
throw Error("Your access changed. Sign in again.");
|
|
913
|
+
const proposal = store.getMateProposal(id), payload = proposal?.kind === "action"
|
|
914
|
+
? sharedActionPayload(proposal.payload)
|
|
915
|
+
: null;
|
|
916
|
+
const shared = proposal && store.handle.prepare('SELECT id FROM team_conversation WHERE thread=?').get(proposal.thread);
|
|
917
|
+
const session = shared && proposal ? store.teamMateSession(who.name, proposal.thread) : store.activeMateSession(who.name), turn = proposal ? store.getMateTurn(proposal.turn) : null;
|
|
918
|
+
if (!proposal ||
|
|
919
|
+
!payload ||
|
|
920
|
+
proposal.state !== state ||
|
|
921
|
+
(shared ? !store.canUseTeamMateThread(who.name, who.generation, proposal.thread) : store.getMateThread(proposal.thread)?.approver !== who.name))
|
|
922
|
+
throw Error("This action is no longer waiting for your review.");
|
|
923
|
+
if (!session ||
|
|
924
|
+
session.approverGeneration !== who.generation ||
|
|
925
|
+
session.ceilingDigest !== who.ceilingDigest ||
|
|
926
|
+
proposal.ceilingDigest !== who.ceilingDigest ||
|
|
927
|
+
(!shared && turn?.session !== session.id) ||
|
|
928
|
+
turn?.state !== "answered")
|
|
929
|
+
throw Error("This conversation ended or its project access changed. Ask for a fresh proposal.");
|
|
930
|
+
requireActor(store, who, payload.repo);
|
|
931
|
+
return payload;
|
|
932
|
+
}
|
|
933
|
+
export function mintSharedActionReview(store, who, id, root, now) {
|
|
934
|
+
const payload = savedActionContext(store, who, id, "pending");
|
|
935
|
+
const live = prepareSharedAction(store, who, payload.operation, payload.request, root, now);
|
|
936
|
+
if (live.stamp !== payload.stamp)
|
|
937
|
+
throw Error("This action changed. Ask for a fresh proposal.");
|
|
938
|
+
const nonce = randomBytes(24).toString("hex");
|
|
939
|
+
if (!store.mintCeremonyNonce({
|
|
940
|
+
hash: nonceHash(nonce),
|
|
941
|
+
approver: who.name,
|
|
942
|
+
subject: "chat-action",
|
|
943
|
+
subjectId: id,
|
|
944
|
+
digest: payload.stamp,
|
|
945
|
+
ttlMs: 10 * 60_000,
|
|
946
|
+
}, now).ok)
|
|
947
|
+
throw Error("Too many reviews are open. Close older reviews and try again.");
|
|
948
|
+
return { payload: live, nonce };
|
|
949
|
+
}
|
|
950
|
+
/** Called inside the proposal door's transaction, after ownership and session
|
|
951
|
+
* checks. No password or review receipt is ever part of a saved proposal. */
|
|
952
|
+
export function executeSharedAction(store, who, id, payload, now, options) {
|
|
953
|
+
const refuse = (reason, said) => ({ ok: false, reason, said });
|
|
954
|
+
if (store.isDemo())
|
|
955
|
+
return refuse("refused", "The demo authorizes nothing.");
|
|
956
|
+
let live;
|
|
957
|
+
try {
|
|
958
|
+
const saved = savedActionContext(store, who, id, "confirming");
|
|
959
|
+
if (hash(saved) !== hash(payload))
|
|
960
|
+
return refuse("refused", "This action does not match the saved proposal.");
|
|
961
|
+
live = prepareSharedAction(store, who, saved.operation, saved.request, options.root, now);
|
|
962
|
+
if (live.stamp !== saved.stamp)
|
|
963
|
+
return refuse("stale", "This action changed. Ask for a fresh proposal.");
|
|
964
|
+
}
|
|
965
|
+
catch (error) {
|
|
966
|
+
return refuse("stale", error instanceof Error
|
|
967
|
+
? error.message
|
|
968
|
+
: "This action is no longer available.");
|
|
969
|
+
}
|
|
970
|
+
payload = live;
|
|
971
|
+
const config = CHAT_ACTIONS[live.operation], req = live.request;
|
|
972
|
+
const challenged = options.via !== "web" &&
|
|
973
|
+
options.via !== "cli" &&
|
|
974
|
+
options.confirm === true &&
|
|
975
|
+
sharedActionAllowsChallenge(payload);
|
|
976
|
+
if (sharedActionNeedsReview(payload) && !challenged) {
|
|
977
|
+
if (options.via !== "web" || !options.review || options.confirm !== true)
|
|
978
|
+
return refuse("needs-confirm", "Review the complete action in the secure confirmation screen.");
|
|
979
|
+
if (!store.consumeCeremonyNonce(nonceHash(options.review.nonce), who.name, "chat-action", id, payload.stamp, now))
|
|
980
|
+
return refuse("needs-confirm", "This confirmation expired or was already used. Open the review again.");
|
|
981
|
+
if (config.password &&
|
|
982
|
+
!authenticateApprover(store, who.name, options.review.password, payload.repo).ok)
|
|
983
|
+
return refuse("needs-confirm", "Confirm with your password in the secure screen.");
|
|
984
|
+
}
|
|
985
|
+
try {
|
|
986
|
+
return store.savepoint(() => {
|
|
987
|
+
const task = typeof req["task"] === "string" ? req["task"] : null, repo = payload.repo, actor = who.name;
|
|
988
|
+
let taskId = task;
|
|
989
|
+
if (payload.operation === "skill_import")
|
|
990
|
+
importSkill(store, repo, actor, req["files"], "Chat import", now);
|
|
991
|
+
else if (payload.operation === "skill_test")
|
|
992
|
+
taskId = testSkill(store, {
|
|
993
|
+
repo,
|
|
994
|
+
actor,
|
|
995
|
+
sha: String(req["version"]),
|
|
996
|
+
sample: String(req["sample"]),
|
|
997
|
+
nonce: String(req["nonce"]),
|
|
998
|
+
}, now).id;
|
|
999
|
+
else if (payload.operation.startsWith("skill_"))
|
|
1000
|
+
changeSkills(store, {
|
|
1001
|
+
repo,
|
|
1002
|
+
actor,
|
|
1003
|
+
identity: String(payload.state["identity"]),
|
|
1004
|
+
revision: Number(payload.state["revision"]),
|
|
1005
|
+
action: payload.operation.slice(6),
|
|
1006
|
+
...(req["version"] === undefined
|
|
1007
|
+
? {}
|
|
1008
|
+
: { sha: String(req["version"]) }),
|
|
1009
|
+
...(req["restore"] === undefined
|
|
1010
|
+
? {}
|
|
1011
|
+
: { restore: Number(req["restore"]) }),
|
|
1012
|
+
}, now);
|
|
1013
|
+
else if (payload.operation.startsWith("flow_")) {
|
|
1014
|
+
const done = runFlowAction(store, payload, actor, who.repos, options.root, now);
|
|
1015
|
+
return { ok: true, taskId: null, said: done.said, href: done.href };
|
|
1016
|
+
}
|
|
1017
|
+
else if (payload.operation === "kit_setup") {
|
|
1018
|
+
const kit = kitOf(String(req["kit"]));
|
|
1019
|
+
const made = setUpKitNow(store, kit, repo, actor, now, null, true);
|
|
1020
|
+
if (!made.ok)
|
|
1021
|
+
throw Error(made.said);
|
|
1022
|
+
return { ok: true, taskId: null, said: `${made.said} Try it with a sample card from its page.`, href: `/kits/${kit.id}?repo=${encodeURIComponent(repo)}` };
|
|
1023
|
+
}
|
|
1024
|
+
else if (payload.operation.startsWith("teammate_")) {
|
|
1025
|
+
const done = runTeammateAction(store, payload, actor, now);
|
|
1026
|
+
return { ok: true, taskId: null, said: done.said, href: done.href };
|
|
1027
|
+
}
|
|
1028
|
+
else if (payload.operation === "tool_add") {
|
|
1029
|
+
const added = addToolTo(store, repo, toolSpecFromRequest(req), req["catalog"] === undefined ? "the lead, confirmed by you" : "the common tools list", actor, now);
|
|
1030
|
+
if (!added.ok)
|
|
1031
|
+
throw Error(added.message);
|
|
1032
|
+
}
|
|
1033
|
+
else if (payload.operation === "tool_remove") {
|
|
1034
|
+
if (!removeToolFrom(store, repo, String(req["name"]), actor, now))
|
|
1035
|
+
throw Error("That tool was already removed.");
|
|
1036
|
+
}
|
|
1037
|
+
else if (payload.operation === "decision_record")
|
|
1038
|
+
recordDecision(store, { repo, actor, draft: { claim: String(req["claim"]), why: String(req["why"]), sourceKind: "conversation",
|
|
1039
|
+
...(req["source"] === undefined ? {} : { sourceRef: String(req["source"]) }), ...(payload.state["supersedes"] === undefined ? {} : { supersedes: Number(payload.state["supersedes"]) }) } }, now);
|
|
1040
|
+
else if (payload.operation === "decision_retire")
|
|
1041
|
+
retireDecision(store, { repo, actor, id: Number(payload.state["decision"]), reason: String(req["reason"]) }, now);
|
|
1042
|
+
else if (payload.operation.startsWith("knowledge_"))
|
|
1043
|
+
changeKnowledge(store, {
|
|
1044
|
+
repo,
|
|
1045
|
+
actor,
|
|
1046
|
+
identity: String(payload.state["identity"]),
|
|
1047
|
+
revision: Number(payload.state["revision"]),
|
|
1048
|
+
action: payload.operation.slice(10),
|
|
1049
|
+
draft: Object.fromEntries(["instructions", "title", "content", "id"]
|
|
1050
|
+
.filter((k) => req[k] !== undefined)
|
|
1051
|
+
.map((k) => [k, req[k]])),
|
|
1052
|
+
...(req["restore"] === undefined
|
|
1053
|
+
? {}
|
|
1054
|
+
: { restore: Number(req["restore"]) }),
|
|
1055
|
+
}, now);
|
|
1056
|
+
else if (payload.operation === "scope_approve") {
|
|
1057
|
+
const scope = store.getScope(task), race = store.activeTournamentTerms(store.lookupRef(task).id);
|
|
1058
|
+
const result = approve(store, task, actor, now, scope.digest, options.review.password);
|
|
1059
|
+
if (!result.ok) {
|
|
1060
|
+
// v102: the project's approval rules speak for themselves.
|
|
1061
|
+
if (result.reason === "second-approver")
|
|
1062
|
+
throw Error(gateWords({ verdict: "vote", have: result.have, need: 2, already: result.already }));
|
|
1063
|
+
if (result.reason === "requester" || result.reason === "person-required")
|
|
1064
|
+
throw Error(gateWords({ verdict: "refuse", reason: result.reason }));
|
|
1065
|
+
if (result.reason === "policy")
|
|
1066
|
+
throw Error(result.message);
|
|
1067
|
+
throw Error(`Approval refused: ${result.reason}.`);
|
|
1068
|
+
}
|
|
1069
|
+
if (race &&
|
|
1070
|
+
!store.approveTournamentTerms(race.id, actor, race.raceDigest, now))
|
|
1071
|
+
throw Error("The comparison terms changed. Nothing was approved.");
|
|
1072
|
+
}
|
|
1073
|
+
else if (payload.operation === "result_accept") {
|
|
1074
|
+
const completed = checkAssignmentAsOperator(store, task, String(payload.state["receipt"] ?? ""), who, now, options.root);
|
|
1075
|
+
if (!completed.ok)
|
|
1076
|
+
throw Error(completed.message);
|
|
1077
|
+
}
|
|
1078
|
+
else if (payload.operation === "task_cancel") {
|
|
1079
|
+
const result = store.cancelTask(task, now);
|
|
1080
|
+
if (!result.ok)
|
|
1081
|
+
throw Error(`Task was not cancelled: ${result.reason}.`);
|
|
1082
|
+
}
|
|
1083
|
+
else if (payload.operation === "task_resume") {
|
|
1084
|
+
const result = resumeTaskStop(store, {
|
|
1085
|
+
taskId: task,
|
|
1086
|
+
runId: Number(req["run"]),
|
|
1087
|
+
by: verifiedAuthor(actor),
|
|
1088
|
+
via: "web",
|
|
1089
|
+
}, now);
|
|
1090
|
+
if (!result.ok)
|
|
1091
|
+
throw Error(result.detail);
|
|
1092
|
+
return {
|
|
1093
|
+
ok: true,
|
|
1094
|
+
taskId,
|
|
1095
|
+
said: `Stop released. ${result.gate ? "Other requirements still prevent the next attempt." : "The next attempt may start."}`,
|
|
1096
|
+
};
|
|
1097
|
+
}
|
|
1098
|
+
const said = payload.operation === "skill_test"
|
|
1099
|
+
? "Skill test created. Existing approval rules apply."
|
|
1100
|
+
: payload.operation === "result_accept"
|
|
1101
|
+
? "Marked complete. The recorded checks are unchanged."
|
|
1102
|
+
: payload.operation === "scope_approve"
|
|
1103
|
+
? "The exact work is approved."
|
|
1104
|
+
: payload.operation === "task_cancel"
|
|
1105
|
+
? "Task cancelled."
|
|
1106
|
+
: payload.operation === "skill_import"
|
|
1107
|
+
? "Skill added to the library."
|
|
1108
|
+
: payload.operation === "skill_enable"
|
|
1109
|
+
? "Skill enabled for future runs."
|
|
1110
|
+
: payload.operation === "skill_disable"
|
|
1111
|
+
? "Skill disabled for future runs."
|
|
1112
|
+
: payload.operation === "skill_restore"
|
|
1113
|
+
? "Saved skills restored."
|
|
1114
|
+
: payload.operation === "decision_record"
|
|
1115
|
+
? "Decision recorded for this project."
|
|
1116
|
+
: payload.operation === "decision_retire"
|
|
1117
|
+
? "Decision retired; its history stays."
|
|
1118
|
+
: payload.operation === "tool_add"
|
|
1119
|
+
? "Tool added for work you approve from now on. Set any secrets it needs and test it on the Tools page."
|
|
1120
|
+
: payload.operation === "tool_remove"
|
|
1121
|
+
? "Tool removed from every build."
|
|
1122
|
+
: payload.operation === "knowledge_remove"
|
|
1123
|
+
? "Reference removed."
|
|
1124
|
+
: payload.operation === "knowledge_restore"
|
|
1125
|
+
? "Saved project knowledge restored."
|
|
1126
|
+
: "Project knowledge saved.";
|
|
1127
|
+
return { ok: true, taskId, said };
|
|
1128
|
+
});
|
|
1129
|
+
}
|
|
1130
|
+
catch (error) {
|
|
1131
|
+
return refuse("refused", error instanceof Error
|
|
1132
|
+
? error.message
|
|
1133
|
+
: "The action could not be completed.");
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
/** A confirmed teammate action (v92), through the same helpers the Teammates pages use. */
|
|
1137
|
+
function runTeammateAction(store, payload, actor, now) {
|
|
1138
|
+
const req = payload.request;
|
|
1139
|
+
if (payload.operation === "teammate_create") {
|
|
1140
|
+
const made = createTeammateFrom(store, { repo: payload.repo, soul: String(req["soul"]), by: actor }, now);
|
|
1141
|
+
if (!made.ok)
|
|
1142
|
+
throw Error(made.said);
|
|
1143
|
+
return { said: made.said, href: `/teammates/${made.id}` };
|
|
1144
|
+
}
|
|
1145
|
+
if (payload.operation === "teammate_answer") {
|
|
1146
|
+
const question = store.teammateQuestion(Number(req["question"]));
|
|
1147
|
+
const answered = answerTeammateQuestion(store, Number(req["question"]), { choice: req["choice"] === null ? null : String(req["choice"]), text: req["text"] === null ? null : String(req["text"]), by: actor, via: "chat" }, now);
|
|
1148
|
+
if (!answered.ok)
|
|
1149
|
+
throw Error(answered.said);
|
|
1150
|
+
return { said: answered.said, href: question === null ? "/teammates" : `/teammates/${question.teammate}` };
|
|
1151
|
+
}
|
|
1152
|
+
const mate = store.getTeammate(Number(req["teammate"]));
|
|
1153
|
+
if (mate === null || mate.state === "removed")
|
|
1154
|
+
throw Error("That teammate is off the team.");
|
|
1155
|
+
if (payload.operation === "teammate_undo") {
|
|
1156
|
+
const asked = requestUndo(store, mate, Number(req["call"]), actor, now);
|
|
1157
|
+
if (!asked.ok)
|
|
1158
|
+
throw Error(asked.said);
|
|
1159
|
+
return { said: `${asked.said} The receipt on the card shows how it went.`, href: `/teammates/${mate.id}#week` };
|
|
1160
|
+
}
|
|
1161
|
+
if (payload.operation === "teammate_routine") {
|
|
1162
|
+
const done = req["change"] === "add" ? addRoutine(store, mate, String(req["schedule"]), String(req["text"]), actor, now, null)
|
|
1163
|
+
: removeRoutine(store, mate, Number(req["routine"]), now, null);
|
|
1164
|
+
if (!done.ok)
|
|
1165
|
+
throw Error(done.said);
|
|
1166
|
+
return { said: done.said, href: `/teammates/${mate.id}#desk` };
|
|
1167
|
+
}
|
|
1168
|
+
if (payload.operation === "teammate_memory") {
|
|
1169
|
+
const memory = store.teammateMemory(Number(req["memory"]));
|
|
1170
|
+
if (memory === null || memory.text !== payload.state["text"])
|
|
1171
|
+
throw Error("That memory changed since. Ask for a fresh proposal.");
|
|
1172
|
+
const done = req["change"] === "forget" ? forgetMemory(store, mate, memory.id, actor, now) : editMemory(store, mate, memory.id, String(req["text"]), actor, now);
|
|
1173
|
+
if (!done.ok)
|
|
1174
|
+
throw Error(done.said);
|
|
1175
|
+
return { said: done.said, href: `/teammates/${mate.id}#memory` };
|
|
1176
|
+
}
|
|
1177
|
+
if (payload.operation === "teammate_tools") {
|
|
1178
|
+
const tool = String(req["tool"]);
|
|
1179
|
+
const grant = store.teammateGrant(mate.id, tool);
|
|
1180
|
+
if ((grant === null ? "" : JSON.stringify(grant.rules)) !== payload.state["rules"])
|
|
1181
|
+
throw Error("Someone changed its tools since. Ask for a fresh proposal.");
|
|
1182
|
+
const found = projectToolsOf(store, mate.repo).find(one => one.name === tool);
|
|
1183
|
+
const done = req["change"] === "grant" ? found === undefined ? { ok: false, said: `There's no tool called ${tool} any more.` } : grantListed(store, mate, found, null, actor, now)
|
|
1184
|
+
: req["change"] === "revoke" ? revokeTool(store, mate, tool, actor, now)
|
|
1185
|
+
: setToolRules(store, mate, tool, { [String(req["action"])]: { use: req["use"], limit: req["limitField"] === undefined || req["limitField"] === null || req["limitField"] === "" ? null : { field: req["limitField"], over: req["limitOver"] },
|
|
1186
|
+
undo: req["undoWith"] ?? grant?.rules[String(req["action"])]?.undo ?? "" } }, actor, now);
|
|
1187
|
+
if (!done.ok)
|
|
1188
|
+
throw Error(done.said);
|
|
1189
|
+
return { said: done.said, href: `/teammates/${mate.id}#tools` };
|
|
1190
|
+
}
|
|
1191
|
+
if (payload.operation === "teammate_soul" && mate.version !== payload.state["version"])
|
|
1192
|
+
throw Error("Someone changed its soul file since. Ask for a fresh proposal.");
|
|
1193
|
+
const done = payload.operation === "teammate_soul" ? saveSoul(store, mate, String(req["soul"]), actor, now)
|
|
1194
|
+
: payload.operation === "teammate_state" ? setTeammateState(store, mate, req["state"], actor, now)
|
|
1195
|
+
: tellTeammate(store, mate, String(req["note"]), actor, now);
|
|
1196
|
+
if (!done.ok)
|
|
1197
|
+
throw Error(done.said);
|
|
1198
|
+
return { said: done.said, href: payload.operation === "teammate_state" && req["state"] === "removed" ? "/teammates" : `/teammates/${mate.id}` };
|
|
1199
|
+
}
|
|
1200
|
+
/** A confirmed flow action, through the same helpers the canvas uses; then
|
|
1201
|
+
* the flow moves what it can right away, as it does after a canvas change. */
|
|
1202
|
+
function runFlowAction(store, payload, actor, repos, root, now) {
|
|
1203
|
+
const req = payload.request;
|
|
1204
|
+
const settle = (flow, said, card) => {
|
|
1205
|
+
const repo = store.getFlow(flow)?.repo;
|
|
1206
|
+
if (repo !== undefined) {
|
|
1207
|
+
try {
|
|
1208
|
+
advanceFlows(store, repo, now, root === undefined ? {} : { evidenceRoot: root });
|
|
1209
|
+
}
|
|
1210
|
+
catch { /* the next worker pass retries */ }
|
|
1211
|
+
}
|
|
1212
|
+
const after = card === null ? null : store.getFlowCard(card);
|
|
1213
|
+
const filed = after !== null && after.task !== null && after.state === "active" ? " Its step filed a task under your usual approvals." : "";
|
|
1214
|
+
return { said: `${said}${filed}`, href: card === null ? `/flows/${flow}` : flowCardHref(flow, card) };
|
|
1215
|
+
};
|
|
1216
|
+
if (payload.operation === "flow_create") {
|
|
1217
|
+
const id = store.createFlow({ repo: payload.repo, name: String(req["name"]), definitionJson: JSON.stringify(req["definition"]), by: actor }, now);
|
|
1218
|
+
return { said: "Flow created. Add cards to it here or on its canvas.", href: `/flows/${id}` };
|
|
1219
|
+
}
|
|
1220
|
+
if (payload.operation === "flow_edit") {
|
|
1221
|
+
const flow = Number(payload.state["flow"]);
|
|
1222
|
+
if (!store.saveFlow(flow, { name: String(req["name"]), definitionJson: JSON.stringify(req["definition"]), sawRevision: Number(payload.state["revision"]), by: actor }, now))
|
|
1223
|
+
throw Error("Someone changed this flow. Ask for a fresh proposal.");
|
|
1224
|
+
return settle(flow, "Flow saved.", null);
|
|
1225
|
+
}
|
|
1226
|
+
if (payload.operation === "flow_card_add") {
|
|
1227
|
+
const flow = store.getFlow(Number(req["flow"]));
|
|
1228
|
+
const added = addCardToFlow(store, flow, { title: req["title"], description: req["description"] ?? null, stage: String(req["zone"]) }, actor, now);
|
|
1229
|
+
if (!added.ok)
|
|
1230
|
+
throw Error(added.message);
|
|
1231
|
+
return settle(flow.id, added.said, added.card);
|
|
1232
|
+
}
|
|
1233
|
+
if (payload.operation === "flow_script_save") {
|
|
1234
|
+
const saved = saveScript(store, payload.repo, req["script"], actor, now);
|
|
1235
|
+
if (!saved.ok)
|
|
1236
|
+
throw Error(saved.message);
|
|
1237
|
+
const first = store.listFlows([payload.repo])[0];
|
|
1238
|
+
return { said: saved.said, href: first === undefined ? "/flows" : `/flows/${first.id}` };
|
|
1239
|
+
}
|
|
1240
|
+
if (payload.operation === "flow_trigger_add") {
|
|
1241
|
+
const flow = store.getFlow(Number(req["flow"]));
|
|
1242
|
+
const made = addFlowTriggerTo(store, flow, req["trigger"], actor, now, null);
|
|
1243
|
+
if (!made.ok)
|
|
1244
|
+
throw Error(made.message);
|
|
1245
|
+
return { said: "Trigger added.", href: `/flows/${flow.id}` };
|
|
1246
|
+
}
|
|
1247
|
+
if (payload.operation.startsWith("flow_trigger_")) {
|
|
1248
|
+
const trigger = store.getFlowTrigger(Number(req["trigger"]));
|
|
1249
|
+
if (payload.operation === "flow_trigger_remove")
|
|
1250
|
+
removeFlowTrigger(store, trigger, now, configDirOf(store));
|
|
1251
|
+
else
|
|
1252
|
+
store.updateFlowTrigger(trigger.id, { state: payload.operation === "flow_trigger_pause" ? "paused" : "active" }, now);
|
|
1253
|
+
return { said: payload.operation === "flow_trigger_remove" ? "Trigger removed." : payload.operation === "flow_trigger_pause" ? "Trigger paused." : "Trigger on again.", href: `/flows/${trigger.flow}` };
|
|
1254
|
+
}
|
|
1255
|
+
const card = store.getFlowCard(Number(req["card"]));
|
|
1256
|
+
if (payload.operation === "flow_card_comment" || payload.operation === "flow_card_assign" || payload.operation === "flow_card_watch") {
|
|
1257
|
+
const done = payload.operation === "flow_card_comment" ? commentOnFlowCard(store, card, actor, req["note"], now)
|
|
1258
|
+
: payload.operation === "flow_card_assign" ? assignFlowCard(store, card, typeof req["owner"] === "string" ? req["owner"] : null, actor, now)
|
|
1259
|
+
: watchFlowCard(store, card, actor, req["watching"] !== false, now);
|
|
1260
|
+
if (!done.ok)
|
|
1261
|
+
throw Error(done.message);
|
|
1262
|
+
return { said: done.said, href: flowCardHref(card.flow, card.id) };
|
|
1263
|
+
}
|
|
1264
|
+
const acted = payload.operation === "flow_card_move" ? moveCardInFlow(store, card, String(req["zone"]), actor, now)
|
|
1265
|
+
: payload.operation === "flow_card_cancel" ? cancelFlowCard(store, card, actor, now)
|
|
1266
|
+
: (() => {
|
|
1267
|
+
const decided = decideFlowCard(store, { card: card.id, decision: payload.operation === "flow_card_approve" ? "approve" : "send-back", note: typeof req["note"] === "string" ? req["note"] : null, actor, repos, ...(root === undefined ? {} : { evidenceRoot: root }) }, now);
|
|
1268
|
+
return decided.ok ? { ok: true, said: decided.said, card: card.id } : { ok: false, message: decided.message };
|
|
1269
|
+
})();
|
|
1270
|
+
if (!acted.ok)
|
|
1271
|
+
throw Error(acted.message);
|
|
1272
|
+
return settle(card.flow, acted.said, card.id);
|
|
1273
|
+
}
|