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
package/dist/flows.js
ADDED
|
@@ -0,0 +1,968 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flows (v81): a canvas of zones that work moves through. Each zone is one
|
|
3
|
+
* stage of a team's process; each card is one piece of work. When a card
|
|
4
|
+
* is routed or dropped into a zone, the zone's step runs:
|
|
5
|
+
*
|
|
6
|
+
* - inbox — a holding zone: cards wait for someone to move them.
|
|
7
|
+
* - task — build it: files the card's work as an ordinary task, so the
|
|
8
|
+
* existing plan → approve → build → verify pipeline (with its
|
|
9
|
+
* approvals and agent fence) does the work; the card moves on
|
|
10
|
+
* when the result is ready, or down its failure path.
|
|
11
|
+
* - report — research it: files a report task (no code changes) and
|
|
12
|
+
* keeps the report on the card for later zones.
|
|
13
|
+
* - approval — a person decides: approve moves on; send back (with a
|
|
14
|
+
* note) takes the failure path, and a send back to a build
|
|
15
|
+
* zone becomes a revision of the card's work.
|
|
16
|
+
* - notify — posts a message to the project's chat and moves on.
|
|
17
|
+
* - draft — (v86) Claude writes something from the card (a reply, a
|
|
18
|
+
* summary, a note) in seconds, with no repository and no
|
|
19
|
+
* tools. Nothing is sent: a later zone shows it to a person
|
|
20
|
+
* and a later step posts it ({{stage.<id>}}).
|
|
21
|
+
* - sort — (v85) Jev, a decision model reached through OpenRouter,
|
|
22
|
+
* reads the card and picks one of the zone's answers; the
|
|
23
|
+
* card goes where that answer leads, or down the not-sure
|
|
24
|
+
* path when Jev is less sure than the zone asks. It can also
|
|
25
|
+
* note a few scores or yes/no answers on the card.
|
|
26
|
+
* - request — (v87) calls an address on the web (an API) with the card's
|
|
27
|
+
* details, and keeps what it answers; a refusal takes the
|
|
28
|
+
* failure path. Secrets ride headers only ({{secret.NAME}}).
|
|
29
|
+
* - email — (v87) sends an email from the address in Settings → Email;
|
|
30
|
+
* {{card.email}} is the first address the card mentions.
|
|
31
|
+
* - tool — (v87) calls one tool of one of the project's MCP servers
|
|
32
|
+
* (Settings → Tools), with arguments filled from the card.
|
|
33
|
+
* - done — the end.
|
|
34
|
+
*
|
|
35
|
+
* The engine is deterministic and model-free: it runs in the worker's pass
|
|
36
|
+
* beside routines, and no card ever skips an approval the task itself needs.
|
|
37
|
+
*/
|
|
38
|
+
import { createHash } from "node:crypto";
|
|
39
|
+
export const FLOW_STAGE_KINDS = ["inbox", "task", "report", "approval", "check", "update", "notify", "sort", "draft", "request", "email", "tool", "wait", "teammate", "done"];
|
|
40
|
+
export const FLOW_COLORS = ["slate", "blue", "violet", "amber", "green", "rose"];
|
|
41
|
+
/** What each kind is called and does, in the words the canvas uses. */
|
|
42
|
+
export const FLOW_KIND_WORDS = {
|
|
43
|
+
inbox: { label: "Holding", about: "Cards wait here until someone moves them." },
|
|
44
|
+
task: { label: "Build", about: "An agent does the work as a task, with the usual approvals and checks." },
|
|
45
|
+
report: { label: "Research", about: "An agent investigates and writes a report. No code changes." },
|
|
46
|
+
approval: { label: "Person decides", about: "Someone approves, or sends it back with a note." },
|
|
47
|
+
check: { label: "Run a script", about: "Runs one of the project's scripts (shell, Python or Node) with no AI. It gets the card; what it prints is passed on, and it can pick where the card goes next. If it fails, the card takes its failure path." },
|
|
48
|
+
update: { label: "Update where it came from", about: "Comments on the GitHub or Linear issue the card came from (and can close it), or answers in the chat thread it came from. Other cards pass straight through." },
|
|
49
|
+
notify: { label: "Message", about: "Posts a message to the project's chat, then moves on." },
|
|
50
|
+
request: { label: "Web request", about: "Calls an address on the web, like an API, with the card's details, and keeps what it answers. If it fails, the card takes its failure path." },
|
|
51
|
+
email: { label: "Send email", about: "Sends an email from your address in Settings → Email. {{card.email}} is the first email address the card mentions." },
|
|
52
|
+
tool: { label: "Use a tool", about: "Calls one of the project's tools (its connected MCP servers), like posting to Slack or adding a page to Notion." },
|
|
53
|
+
draft: { label: "Draft", about: "Claude writes a reply, summary or note from the card in seconds. Nothing is sent until a later step sends it." },
|
|
54
|
+
sort: { label: "Sort", about: "Jev reads the card in under a second and sends it where its answer leads. Cards it isn't sure about take the not-sure path." },
|
|
55
|
+
wait: { label: "Wait", about: "Waits for a reply to the card's email, or for a set time. A reply moves the card on; if none comes in time, it takes the no-reply path." },
|
|
56
|
+
teammate: { label: "Teammate handles it", about: "An AI teammate reads the card, decides where it goes within its rules, and writes what the next zones send. When its rules say to ask, it asks you first." },
|
|
57
|
+
done: { label: "Done", about: "The end of the flow." },
|
|
58
|
+
};
|
|
59
|
+
const ID = /^[a-z0-9][a-z0-9-]{0,31}$/;
|
|
60
|
+
/** A project script's name: short, lowercase, dashes — how zones and chat refer to it. */
|
|
61
|
+
export const SCRIPT_NAME = /^[a-z0-9][a-z0-9-]{0,39}$/;
|
|
62
|
+
/** The languages a project script is written in (v90). */
|
|
63
|
+
export const SCRIPT_LANGUAGES = ["shell", "python", "node"];
|
|
64
|
+
export const LANGUAGE_WORDS = { shell: "Shell", python: "Python", node: "Node" };
|
|
65
|
+
const text = (value, cap) => {
|
|
66
|
+
if (value === null || value === undefined)
|
|
67
|
+
return null;
|
|
68
|
+
if (typeof value !== "string")
|
|
69
|
+
throw new Error("Zone text must be plain text.");
|
|
70
|
+
const trimmed = value.trim();
|
|
71
|
+
if (trimmed.length > cap)
|
|
72
|
+
throw new Error(`Zone text is up to ${cap} characters.`);
|
|
73
|
+
if (/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/.test(trimmed))
|
|
74
|
+
throw new Error("Zone text cannot contain control characters.");
|
|
75
|
+
return trimmed === "" ? null : trimmed;
|
|
76
|
+
};
|
|
77
|
+
const coordinate = (value, min, max, fallback) => typeof value === "number" && Number.isFinite(value) ? Math.round(Math.min(max, Math.max(min, value))) : fallback;
|
|
78
|
+
/** How an answer is named to Jev: its words as a short key. */
|
|
79
|
+
export const sortKeyOf = (answer) => answer.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40) || "answer";
|
|
80
|
+
/** The default for "sure enough to act alone". */
|
|
81
|
+
export const SORT_SURE_AT = 0.8;
|
|
82
|
+
const METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
|
|
83
|
+
const HEADER = /^[A-Za-z0-9][A-Za-z0-9-]{0,63}$/;
|
|
84
|
+
/** A web request's settings, checked: an http(s) address whose scheme and host are written out, not filled in. */
|
|
85
|
+
function validateRequest(input, title) {
|
|
86
|
+
const raw = (input ?? {});
|
|
87
|
+
const method = METHODS.includes(raw["method"]) ? raw["method"] : "POST";
|
|
88
|
+
const url = text(raw["url"], 2000);
|
|
89
|
+
if (url === null)
|
|
90
|
+
throw new Error(`Zone ${title}: give the address it calls.`);
|
|
91
|
+
const origin = /^(https?):\/\/([^/?#]*)/i.exec(url);
|
|
92
|
+
if (origin === null)
|
|
93
|
+
throw new Error(`Zone ${title}: the address must start with https:// or http://.`);
|
|
94
|
+
if (origin[2].includes("{{") || origin[2].includes("@") || origin[2] === "")
|
|
95
|
+
throw new Error(`Zone ${title}: write the address's host out in full; fill-ins go after it.`);
|
|
96
|
+
const headers = {};
|
|
97
|
+
for (const [name, value] of Object.entries((raw["headers"] ?? {}))) {
|
|
98
|
+
if (!HEADER.test(name))
|
|
99
|
+
throw new Error(`Zone ${title}: “${name}” isn't a header name.`);
|
|
100
|
+
const said = text(value, 500);
|
|
101
|
+
if (said !== null)
|
|
102
|
+
headers[name] = said;
|
|
103
|
+
}
|
|
104
|
+
if (Object.keys(headers).length > 10)
|
|
105
|
+
throw new Error(`Zone ${title}: up to 10 headers.`);
|
|
106
|
+
return { method, url, headers, body: method === "GET" || method === "DELETE" ? null : text(raw["body"], 8000) };
|
|
107
|
+
}
|
|
108
|
+
/** An email's settings, checked. */
|
|
109
|
+
function validateEmail(input, title) {
|
|
110
|
+
const raw = (input ?? {});
|
|
111
|
+
const to = text(raw["to"], 500), subject = text(raw["subject"], 200), body = text(raw["body"], 8000);
|
|
112
|
+
if (to === null)
|
|
113
|
+
throw new Error(`Zone ${title}: say who it goes to, for example {{card.email}}.`);
|
|
114
|
+
if (subject === null)
|
|
115
|
+
throw new Error(`Zone ${title}: give the email a subject.`);
|
|
116
|
+
if (body === null)
|
|
117
|
+
throw new Error(`Zone ${title}: write what the email says.`);
|
|
118
|
+
return { to, subject, body };
|
|
119
|
+
}
|
|
120
|
+
/** A tool call's settings, checked: the arguments are a JSON object. */
|
|
121
|
+
function validateTool(input, title) {
|
|
122
|
+
const raw = (input ?? {});
|
|
123
|
+
const server = text(raw["server"], 64), name = text(raw["name"], 100);
|
|
124
|
+
if (server === null || name === null)
|
|
125
|
+
throw new Error(`Zone ${title}: choose the tool it uses.`);
|
|
126
|
+
const args = text(raw["args"], 4000) ?? "{}";
|
|
127
|
+
let parsed;
|
|
128
|
+
try {
|
|
129
|
+
parsed = JSON.parse(args);
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
throw new Error(`Zone ${title}: the arguments aren't valid JSON.`);
|
|
133
|
+
}
|
|
134
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
|
|
135
|
+
throw new Error(`Zone ${title}: the arguments are a JSON object, like {"text": "{{stage.draft}}"}.`);
|
|
136
|
+
return { server, name, args };
|
|
137
|
+
}
|
|
138
|
+
/** A sort zone's settings, checked. Answers name zones by id; `ids` says which exist. */
|
|
139
|
+
function validateSort(input, title) {
|
|
140
|
+
const raw = (input ?? {});
|
|
141
|
+
const question = text(raw["question"], 300);
|
|
142
|
+
if (question === null)
|
|
143
|
+
throw new Error(`Zone ${title}: write the question it sorts by.`);
|
|
144
|
+
const answers = Array.isArray(raw["answers"]) ? raw["answers"] : [];
|
|
145
|
+
if (answers.length < 2 || answers.length > 12)
|
|
146
|
+
throw new Error(`Zone ${title}: give it 2 to 12 answers.`);
|
|
147
|
+
const keys = new Set();
|
|
148
|
+
const checked = answers.map(one => {
|
|
149
|
+
const answer = one;
|
|
150
|
+
const name = text(answer["answer"], 40);
|
|
151
|
+
if (name === null)
|
|
152
|
+
throw new Error(`Zone ${title}: every answer needs a name.`);
|
|
153
|
+
const means = text(answer["means"], 200) ?? name;
|
|
154
|
+
const to = text(answer["to"], 32);
|
|
155
|
+
if (to === null)
|
|
156
|
+
throw new Error(`Zone ${title}: say where “${name}” goes.`);
|
|
157
|
+
const key = sortKeyOf(name);
|
|
158
|
+
if (keys.has(key))
|
|
159
|
+
throw new Error(`Zone ${title}: two answers are called ${name}.`);
|
|
160
|
+
keys.add(key);
|
|
161
|
+
return { answer: name, means, to };
|
|
162
|
+
});
|
|
163
|
+
const sureAt = typeof raw["sureAt"] === "number" && Number.isFinite(raw["sureAt"]) ? Math.round(Math.min(0.99, Math.max(0.5, raw["sureAt"])) * 100) / 100 : SORT_SURE_AT;
|
|
164
|
+
const notes = Array.isArray(raw["notes"]) ? raw["notes"] : [];
|
|
165
|
+
if (notes.length > 3)
|
|
166
|
+
throw new Error(`Zone ${title}: it can note up to 3 other things.`);
|
|
167
|
+
const noteIds = new Set(["route"]);
|
|
168
|
+
const checkedNotes = notes.map(one => {
|
|
169
|
+
const note = one;
|
|
170
|
+
const noteQuestion = text(note["question"], 300);
|
|
171
|
+
if (noteQuestion === null)
|
|
172
|
+
throw new Error(`Zone ${title}: every extra note needs a question.`);
|
|
173
|
+
const kind = note["kind"] === "score" ? "score" : "yes-no";
|
|
174
|
+
let id = typeof note["id"] === "string" && ID.test(note["id"]) ? note["id"] : sortKeyOf(noteQuestion).slice(0, 24);
|
|
175
|
+
for (let n = 2; noteIds.has(id); n++)
|
|
176
|
+
id = `${sortKeyOf(noteQuestion).slice(0, 20)}-${n}`;
|
|
177
|
+
noteIds.add(id);
|
|
178
|
+
const levels = kind === "score" ? (Array.isArray(note["levels"]) ? note["levels"] : []).map(level => text(level, 120)).filter((level) => level !== null) : null;
|
|
179
|
+
if (levels !== null && (levels.length < 2 || levels.length > 10))
|
|
180
|
+
throw new Error(`Zone ${title}: a score needs 2 to 10 levels, lowest first.`);
|
|
181
|
+
return { id, kind, question: noteQuestion, levels };
|
|
182
|
+
});
|
|
183
|
+
return { question, answers: checked, sureAt, notes: checkedNotes };
|
|
184
|
+
}
|
|
185
|
+
/** The longest a zone waits or a limit runs: 30 days. */
|
|
186
|
+
export const LONGEST_WAIT_MINUTES = 30 * 24 * 60;
|
|
187
|
+
const UNITS = [[/^(m|min|mins|minute|minutes)$/, 1], [/^(h|hr|hrs|hour|hours)$/, 60], [/^(d|day|days)$/, 24 * 60], [/^(w|wk|wks|week|weeks)$/, 7 * 24 * 60]];
|
|
188
|
+
/** "3 days", "4 hours", "90 minutes", "1 week" (or a number of minutes) as minutes; null when it isn't one. */
|
|
189
|
+
export function durationMinutes(value) {
|
|
190
|
+
if (typeof value === "number")
|
|
191
|
+
return Number.isInteger(value) && value >= 1 && value <= LONGEST_WAIT_MINUTES ? value : null;
|
|
192
|
+
if (typeof value !== "string")
|
|
193
|
+
return null;
|
|
194
|
+
const match = /^\s*([0-9]{1,5}(?:\.[0-9]+)?)\s*([a-z]+)\s*$/i.exec(value);
|
|
195
|
+
const unit = match === null ? undefined : UNITS.find(([words]) => words.test(match[2].toLowerCase()));
|
|
196
|
+
if (match === null || unit === undefined)
|
|
197
|
+
return null;
|
|
198
|
+
const minutes = Math.round(Number(match[1]) * unit[1]);
|
|
199
|
+
return minutes >= 1 && minutes <= LONGEST_WAIT_MINUTES ? minutes : null;
|
|
200
|
+
}
|
|
201
|
+
/** Minutes in the words people use: "3 days", "1 day 4 hours", "45 minutes". */
|
|
202
|
+
export function durationWords(minutes) {
|
|
203
|
+
const plural = (n, word) => `${n} ${word}${n === 1 ? "" : "s"}`;
|
|
204
|
+
if (minutes % (7 * 24 * 60) === 0)
|
|
205
|
+
return plural(minutes / (7 * 24 * 60), "week");
|
|
206
|
+
const days = Math.floor(minutes / (24 * 60)), hours = Math.floor((minutes % (24 * 60)) / 60), rest = minutes % 60;
|
|
207
|
+
return [days > 0 ? plural(days, "day") : "", hours > 0 ? plural(hours, "hour") : "", rest > 0 ? plural(rest, "minute") : ""].filter(one => one !== "").join(" ");
|
|
208
|
+
}
|
|
209
|
+
/** A Wait zone's settings, checked. */
|
|
210
|
+
function validateWait(input, title) {
|
|
211
|
+
const raw = (input ?? {});
|
|
212
|
+
const minutes = durationMinutes(raw["minutes"]);
|
|
213
|
+
if (minutes === null)
|
|
214
|
+
throw new Error(`Zone ${title}: say how long it waits, from 1 minute to 30 days.`);
|
|
215
|
+
return { for: raw["for"] === "time" ? "time" : "reply", minutes };
|
|
216
|
+
}
|
|
217
|
+
/** A zone's time limit, checked; null when it has none. Only Holding and "Person decides" zones move a card on. */
|
|
218
|
+
function validateLimit(input, kind, title) {
|
|
219
|
+
if (input === undefined || input === null)
|
|
220
|
+
return null;
|
|
221
|
+
const raw = input;
|
|
222
|
+
if (kind === "wait" || kind === "done")
|
|
223
|
+
throw new Error(`Zone ${title}: ${kind === "wait" ? "a Wait zone has its own time" : "the end has no time limit"}.`);
|
|
224
|
+
const minutes = durationMinutes(raw["minutes"]);
|
|
225
|
+
if (minutes === null)
|
|
226
|
+
throw new Error(`Zone ${title}: say how long a card may wait, from 1 minute to 30 days.`);
|
|
227
|
+
const to = text(raw["to"], 32);
|
|
228
|
+
if (to !== null && kind !== "inbox" && kind !== "approval")
|
|
229
|
+
throw new Error(`Zone ${title}: only Holding and "Person decides" zones move a card on when it waits too long; other zones remind.`);
|
|
230
|
+
return { minutes, to };
|
|
231
|
+
}
|
|
232
|
+
/** Who decides at an approval zone: its named person, the flow's owner, or null for anyone who approves on the project. */
|
|
233
|
+
export function deciderOf(stage, flow) {
|
|
234
|
+
return stage.toOwner === true ? flow.owner : stage.approver;
|
|
235
|
+
}
|
|
236
|
+
/** A flow as drawn on the canvas, checked whole. Throws in plain words. */
|
|
237
|
+
export function validateFlowDefinition(input) {
|
|
238
|
+
const raw = input;
|
|
239
|
+
if (raw === null || typeof raw !== "object" || !Array.isArray(raw.stages))
|
|
240
|
+
throw new Error("A flow is a list of zones.");
|
|
241
|
+
if (raw.stages.length === 0 || raw.stages.length > 24)
|
|
242
|
+
throw new Error("A flow has 1 to 24 zones.");
|
|
243
|
+
const stages = raw.stages.map((one, index) => {
|
|
244
|
+
const stage = one;
|
|
245
|
+
const id = typeof stage["id"] === "string" ? stage["id"] : "";
|
|
246
|
+
if (!ID.test(id))
|
|
247
|
+
throw new Error("Each zone needs a short id: lowercase letters, numbers and dashes.");
|
|
248
|
+
const kind = stage["kind"];
|
|
249
|
+
if (!FLOW_STAGE_KINDS.includes(kind))
|
|
250
|
+
throw new Error(`Zone ${id}: choose what it does.`);
|
|
251
|
+
const title = text(stage["title"], 60);
|
|
252
|
+
if (title === null)
|
|
253
|
+
throw new Error(`Zone ${id} needs a name.`);
|
|
254
|
+
const zone = (stage["zone"] ?? {});
|
|
255
|
+
const color = FLOW_COLORS.includes(zone["color"]) ? zone["color"] : "slate";
|
|
256
|
+
const planning = stage["planning"] === "required" || stage["planning"] === "skip" || stage["planning"] === "auto" ? stage["planning"] : null;
|
|
257
|
+
return {
|
|
258
|
+
id, title, kind: kind,
|
|
259
|
+
zone: { x: coordinate(zone["x"], -20000, 20000, index * 320), y: coordinate(zone["y"], -20000, 20000, 0), w: coordinate(zone["w"], 220, 1200, 280), h: coordinate(zone["h"], 160, 1600, 360), color },
|
|
260
|
+
instructions: text(stage["instructions"], 4000),
|
|
261
|
+
planning: kind === "task" ? planning ?? "auto" : null,
|
|
262
|
+
approver: kind === "approval" && stage["toOwner"] !== true ? text(stage["approver"], 64) : null,
|
|
263
|
+
...(kind === "approval" && stage["toOwner"] === true ? { toOwner: true } : {}),
|
|
264
|
+
message: text(stage["message"], 1000),
|
|
265
|
+
close: kind === "update" ? stage["close"] !== false : null,
|
|
266
|
+
script: kind === "check" ? text(stage["script"], 40) : null,
|
|
267
|
+
...(kind === "check" ? validateCode(stage, title) : {}),
|
|
268
|
+
sort: kind === "sort" ? validateSort(stage["sort"], title) : null,
|
|
269
|
+
...(kind === "request" ? { request: validateRequest(stage["request"], title) } : {}),
|
|
270
|
+
...(kind === "email" ? { email: validateEmail(stage["email"], title) } : {}),
|
|
271
|
+
...(kind === "tool" ? { tool: validateTool(stage["tool"], title) } : {}),
|
|
272
|
+
...(kind === "wait" ? { wait: validateWait(stage["wait"], title) } : {}),
|
|
273
|
+
...((kind === "approval" || kind === "teammate") && stage["teammate"] !== undefined && stage["teammate"] !== null && stage["teammate"] !== "" ? { teammate: validateTeammate(stage["teammate"], title) } : {}),
|
|
274
|
+
...(kind === "teammate" ? validateRoutes(stage, title) : {}),
|
|
275
|
+
...(kind === "teammate" && stage["reply"] === true ? { reply: true } : {}),
|
|
276
|
+
...(() => { const limit = validateLimit(stage["limit"], kind, title); return limit === null ? {} : { limit }; })(),
|
|
277
|
+
next: kind === "sort" ? null : text(stage["next"], 32),
|
|
278
|
+
onFail: text(stage["onFail"], 32),
|
|
279
|
+
};
|
|
280
|
+
});
|
|
281
|
+
const ids = new Set();
|
|
282
|
+
for (const stage of stages) {
|
|
283
|
+
if (ids.has(stage.id))
|
|
284
|
+
throw new Error(`Two zones are called ${stage.id}.`);
|
|
285
|
+
ids.add(stage.id);
|
|
286
|
+
}
|
|
287
|
+
for (const stage of stages) {
|
|
288
|
+
for (const target of [stage.next, stage.onFail, stage.limit?.to ?? null, ...(stage.sort?.answers.map(one => one.to) ?? []), ...(stage.routes?.map(one => one.to) ?? [])])
|
|
289
|
+
if (target !== null && !ids.has(target))
|
|
290
|
+
throw new Error(`Zone ${stage.title} points at a zone that no longer exists.`);
|
|
291
|
+
if (stage.limit?.to === stage.id)
|
|
292
|
+
throw new Error(`Zone ${stage.title}: a card that waits too long can't move back into the same zone.`);
|
|
293
|
+
if (stage.routes?.some(one => one.to === stage.id))
|
|
294
|
+
throw new Error(`Zone ${stage.title}: an answer can't send cards back into the same zone.`);
|
|
295
|
+
if (stage.sort !== null && stage.sort.answers.some(one => one.to === stage.id))
|
|
296
|
+
throw new Error(`Zone ${stage.title}: an answer can't send cards back into the same zone.`);
|
|
297
|
+
if ((stage.kind === "task" || stage.kind === "report") && stage.instructions === null)
|
|
298
|
+
throw new Error(`Zone ${stage.title}: say what the agent should do.`);
|
|
299
|
+
if (stage.kind === "draft" && stage.instructions === null)
|
|
300
|
+
throw new Error(`Zone ${stage.title}: say what Claude should write.`);
|
|
301
|
+
if (stage.kind === "notify" && stage.message === null)
|
|
302
|
+
throw new Error(`Zone ${stage.title}: write the message to post.`);
|
|
303
|
+
if (stage.kind === "check" && (stage.script === null || !SCRIPT_NAME.test(stage.script)))
|
|
304
|
+
throw new Error(`Zone ${stage.title}: choose which script it runs.`);
|
|
305
|
+
if (stage.kind === "done" && (stage.next !== null || stage.onFail !== null))
|
|
306
|
+
throw new Error(`Zone ${stage.title} is the end; it can't lead anywhere.`);
|
|
307
|
+
if (stage.kind === "teammate" && stage.teammate === undefined)
|
|
308
|
+
throw new Error(`Zone ${stage.title}: choose which teammate handles it.`);
|
|
309
|
+
}
|
|
310
|
+
const start = typeof raw.start === "string" && ids.has(raw.start) ? raw.start : stages[0].id;
|
|
311
|
+
return { version: 1, start, stages };
|
|
312
|
+
}
|
|
313
|
+
/** A teammate's handle on a zone (v92): which teammate works it. Whether it exists is the project's to say, when a card arrives. */
|
|
314
|
+
function validateTeammate(value, title) {
|
|
315
|
+
if (typeof value !== "string" || !/^[a-z0-9][a-z0-9-]{0,31}$/.test(value))
|
|
316
|
+
throw new Error(`Zone ${title}: choose a teammate by its short name, like maya.`);
|
|
317
|
+
return value;
|
|
318
|
+
}
|
|
319
|
+
/** The answers a zone may pick, each with the zone it leads to (a script's "goto:" line, or a teammate's choice). */
|
|
320
|
+
function validateRoutes(stage, title) {
|
|
321
|
+
const rawRoutes = stage["routes"] === undefined || stage["routes"] === null ? [] : stage["routes"];
|
|
322
|
+
if (!Array.isArray(rawRoutes) || rawRoutes.length > 12)
|
|
323
|
+
throw new Error(`Zone ${title}: pick from up to 12 answers.`);
|
|
324
|
+
const seen = new Set();
|
|
325
|
+
const routes = rawRoutes.map(one => {
|
|
326
|
+
const row = (one ?? {});
|
|
327
|
+
const answer = typeof row["answer"] === "string" ? row["answer"].trim() : "";
|
|
328
|
+
const to = typeof row["to"] === "string" ? row["to"].trim() : "";
|
|
329
|
+
if (!/^[^\n]{1,40}$/.test(answer) || to === "")
|
|
330
|
+
throw new Error(`Zone ${title}: each answer needs a short name and the zone it leads to.`);
|
|
331
|
+
if (seen.has(answer.toLowerCase()))
|
|
332
|
+
throw new Error(`Zone ${title}: two answers are called ${answer}.`);
|
|
333
|
+
seen.add(answer.toLowerCase());
|
|
334
|
+
return { answer, to };
|
|
335
|
+
});
|
|
336
|
+
return routes.length === 0 ? {} : { routes };
|
|
337
|
+
}
|
|
338
|
+
/** A code zone's settings (v90): where it runs, its answers, and its secrets. Throws in plain words. */
|
|
339
|
+
function validateCode(stage, title) {
|
|
340
|
+
// Left out, it runs where every script ran before v90: in a copy of the card's work (and the zone's digest stays the same).
|
|
341
|
+
const runIn = stage["runIn"] === "folder" || stage["runIn"] === "copy" ? stage["runIn"] : stage["runIn"] === undefined || stage["runIn"] === null ? undefined : null;
|
|
342
|
+
if (runIn === null)
|
|
343
|
+
throw new Error(`Zone ${title}: choose where the script runs: a clean folder or a copy of the card's work.`);
|
|
344
|
+
const rawRoutes = stage["routes"] === undefined || stage["routes"] === null ? [] : stage["routes"];
|
|
345
|
+
if (!Array.isArray(rawRoutes) || rawRoutes.length > 12)
|
|
346
|
+
throw new Error(`Zone ${title}: a script picks from up to 12 answers.`);
|
|
347
|
+
const seen = new Set();
|
|
348
|
+
const routes = rawRoutes.map(one => {
|
|
349
|
+
const row = (one ?? {});
|
|
350
|
+
const answer = typeof row["answer"] === "string" ? row["answer"].trim() : "";
|
|
351
|
+
const to = typeof row["to"] === "string" ? row["to"].trim() : "";
|
|
352
|
+
if (!/^[^\n]{1,40}$/.test(answer) || to === "")
|
|
353
|
+
throw new Error(`Zone ${title}: each answer needs a short name and the zone it leads to.`);
|
|
354
|
+
if (seen.has(answer.toLowerCase()))
|
|
355
|
+
throw new Error(`Zone ${title}: two answers are called ${answer}.`);
|
|
356
|
+
seen.add(answer.toLowerCase());
|
|
357
|
+
return { answer, to };
|
|
358
|
+
});
|
|
359
|
+
const rawSecrets = stage["secrets"] === undefined || stage["secrets"] === null ? [] : stage["secrets"];
|
|
360
|
+
if (!Array.isArray(rawSecrets) || rawSecrets.length > 10 || rawSecrets.some(one => typeof one !== "string" || !/^[A-Z][A-Z0-9_]{0,39}$/.test(one)))
|
|
361
|
+
throw new Error(`Zone ${title}: name up to 10 saved secrets in capitals, like API_TOKEN.`);
|
|
362
|
+
return { ...(runIn === undefined ? {} : { runIn }), ...(routes.length === 0 ? {} : { routes }), ...(rawSecrets.length === 0 ? {} : { secrets: [...new Set(rawSecrets)] }) };
|
|
363
|
+
}
|
|
364
|
+
/** What a card's work is held to: the zones' steps and paths, never where they sit on the canvas. */
|
|
365
|
+
export function flowDigest(definition) {
|
|
366
|
+
const terms = definition.stages.map(({ zone: _zone, ...rest }) => rest);
|
|
367
|
+
return createHash("sha256").update(JSON.stringify({ start: definition.start, terms })).digest("hex").slice(0, 32);
|
|
368
|
+
}
|
|
369
|
+
/** Fill a zone's text from the card: title, description, the latest note and earlier zones' reports. */
|
|
370
|
+
export function fillFlowText(template, card, encode = value => value) {
|
|
371
|
+
return template.replace(/\{\{\s*(card\.title|card\.description|card\.email|note|stage\.([a-z0-9-]+))\s*\}\}/g, (_match, key, stage) => encode(fillOne(key, stage, card))).trim();
|
|
372
|
+
}
|
|
373
|
+
/** The first email address a card mentions, or "" — what {{card.email}} holds. */
|
|
374
|
+
export function cardEmailOf(card) {
|
|
375
|
+
return /[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,}/.exec(`${card.title}\n${card.description ?? ""}`)?.[0] ?? "";
|
|
376
|
+
}
|
|
377
|
+
function fillOne(key, stage, card) {
|
|
378
|
+
{
|
|
379
|
+
if (key === "card.title")
|
|
380
|
+
return card.title;
|
|
381
|
+
if (key === "card.description")
|
|
382
|
+
return card.description ?? "";
|
|
383
|
+
if (key === "card.email")
|
|
384
|
+
return cardEmailOf(card);
|
|
385
|
+
if (key === "note")
|
|
386
|
+
return card.note ?? "";
|
|
387
|
+
return stage === undefined ? "" : card.outputs[stage] ?? "";
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
const KIND_COLORS = { inbox: "slate", task: "blue", report: "violet", approval: "amber", check: "blue", update: "green", notify: "green", sort: "violet", draft: "violet", request: "blue", email: "green", tool: "blue", wait: "slate", teammate: "violet", done: "green" };
|
|
391
|
+
/** A step id as the lead may write it (sort_by_hand, Sort-By-Hand) in the one form zones use. */
|
|
392
|
+
const idOf = (value) => value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 32);
|
|
393
|
+
const slugOf = (title) => title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 28) || "zone";
|
|
394
|
+
const overlaps = (a, b) => a.x < b.x + b.w && b.x < a.x + a.w && a.y < b.y + b.h && b.y < a.y + a.h;
|
|
395
|
+
/** A wait step's settings: what it waits for and how long (3 days when neither the step nor the zone it keeps says). */
|
|
396
|
+
function waitFromStep(step, old, title) {
|
|
397
|
+
const minutes = step.wait === undefined ? old?.minutes ?? 3 * 24 * 60 : durationMinutes(step.wait);
|
|
398
|
+
if (minutes === null)
|
|
399
|
+
throw new Error(`Step ${title}: say how long it waits, like "3 days" or "4 hours" (up to 30 days).`);
|
|
400
|
+
return { for: step.waitFor ?? old?.for ?? "reply", minutes };
|
|
401
|
+
}
|
|
402
|
+
/** A step's time limit: the one it gives, the one its zone had, or none ("none" removes one). */
|
|
403
|
+
function limitFromStep(step, old, title, find) {
|
|
404
|
+
if (step.remindAfter === undefined)
|
|
405
|
+
return old === null ? null : { ...old, ...(step.thenMoveTo === undefined ? {} : { to: step.thenMoveTo.trim() === "" ? null : find(step.thenMoveTo) }) };
|
|
406
|
+
if (typeof step.remindAfter === "string" && /^\s*(none|never|no|off)?\s*$/i.test(step.remindAfter))
|
|
407
|
+
return null;
|
|
408
|
+
const minutes = durationMinutes(step.remindAfter);
|
|
409
|
+
if (minutes === null)
|
|
410
|
+
throw new Error(`Step ${title}: say when to remind, like "2 days" (up to 30 days).`);
|
|
411
|
+
return { minutes, to: typeof step.thenMoveTo === "string" && step.thenMoveTo.trim() !== "" ? find(step.thenMoveTo) : step.thenMoveTo === undefined ? old?.to ?? null : null };
|
|
412
|
+
}
|
|
413
|
+
/** What a draft zone asks for when the steps leave it out. */
|
|
414
|
+
export const DRAFT_DEFAULT = "Write a short, friendly reply to the person who sent this card, in plain words.";
|
|
415
|
+
/** What an agent is asked when the steps leave it out: the card, any send-back note, and every earlier research step's report. */
|
|
416
|
+
function defaultInstructions(kind, earlier) {
|
|
417
|
+
const notes = earlier.filter(one => one.kind === "report").map(one => `\n\nNotes from ${one.title}:\n{{stage.${one.id}}}`).join("");
|
|
418
|
+
return kind === "task"
|
|
419
|
+
? `{{card.title}}\n\n{{card.description}}${notes}\n\nChanges asked for (if any): {{note}}`
|
|
420
|
+
: `Investigate this and write a short, clear report with a summary first: {{card.title}}\n\n{{card.description}}${notes}\n\nFeedback to address (if any): {{note}}`;
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* A flow from an ordered list of steps: ids from names, each step leading
|
|
424
|
+
* to the next, a Done zone at the end when none is listed, and a decision
|
|
425
|
+
* sending work back to the nearest earlier step that does work. New zones
|
|
426
|
+
* are laid out in rows; zones kept from `previous` keep their place.
|
|
427
|
+
*/
|
|
428
|
+
export function flowFromSteps(input, previous = null) {
|
|
429
|
+
if (!Array.isArray(input) || input.length === 0)
|
|
430
|
+
throw new Error("List the flow's steps in order.");
|
|
431
|
+
if (input.length > 24)
|
|
432
|
+
throw new Error("A flow has 1 to 24 steps.");
|
|
433
|
+
const kept = new Map((previous?.stages ?? []).map(one => [one.id, one]));
|
|
434
|
+
const used = new Set();
|
|
435
|
+
const drafts = input.map((raw, index) => {
|
|
436
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw))
|
|
437
|
+
throw new Error(`Step ${index + 1} isn't a step.`);
|
|
438
|
+
const step = raw;
|
|
439
|
+
const asked = typeof step.id === "string" ? idOf(step.id) : "";
|
|
440
|
+
const old = asked !== "" && !used.has(asked) ? kept.get(asked) ?? null : null;
|
|
441
|
+
const title = typeof step.title === "string" && step.title.trim() !== "" ? step.title.trim() : old?.title ?? "";
|
|
442
|
+
if (title === "")
|
|
443
|
+
throw new Error(`Step ${index + 1} needs a name.`);
|
|
444
|
+
const kind = step.kind ?? old?.kind;
|
|
445
|
+
if (!FLOW_STAGE_KINDS.includes(kind))
|
|
446
|
+
throw new Error(`Step ${title}: choose what it does.`);
|
|
447
|
+
// A new step keeps an id it is given (so the other steps can point at it by that id); otherwise its name makes one.
|
|
448
|
+
const given = ID.test(asked) && !used.has(asked) ? asked : null;
|
|
449
|
+
let id = old?.id ?? given ?? slugOf(title);
|
|
450
|
+
for (let n = 2; old === null && used.has(id); n++)
|
|
451
|
+
id = `${slugOf(title).slice(0, 25)}-${n}`;
|
|
452
|
+
used.add(id);
|
|
453
|
+
const same = old !== null && old.kind === kind;
|
|
454
|
+
return { step, old: same ? old : null, id, title, kind: kind };
|
|
455
|
+
});
|
|
456
|
+
if (!drafts.some(one => one.kind === "done")) {
|
|
457
|
+
let id = "done";
|
|
458
|
+
for (let n = 2; used.has(id); n++)
|
|
459
|
+
id = `done-${n}`;
|
|
460
|
+
drafts.push({ step: {}, old: kept.get(id)?.kind === "done" ? kept.get(id) : null, id, title: "Done", kind: "done" });
|
|
461
|
+
}
|
|
462
|
+
const find = (ref, from) => {
|
|
463
|
+
const wanted = ref.trim().toLowerCase();
|
|
464
|
+
const hit = drafts.find(one => one.id === ref.trim() || one.id === idOf(ref) || one.title.toLowerCase() === wanted);
|
|
465
|
+
if (hit === undefined)
|
|
466
|
+
throw new Error(`Step ${from}: there's no step called ${ref}.`);
|
|
467
|
+
return hit.id;
|
|
468
|
+
};
|
|
469
|
+
const stages = [];
|
|
470
|
+
drafts.forEach(({ step, old, id, title, kind }, index) => {
|
|
471
|
+
const earlier = stages.slice();
|
|
472
|
+
const following = drafts.slice(index + 1).find(() => true) ?? null;
|
|
473
|
+
const next = kind === "done" || kind === "sort" ? null
|
|
474
|
+
: typeof step.next === "string" && step.next.trim() !== "" ? find(step.next, title)
|
|
475
|
+
: following?.id ?? drafts.find(one => one.kind === "done").id;
|
|
476
|
+
const keptFail = old?.onFail !== null && old?.onFail !== undefined && drafts.some(one => one.id === old.onFail) ? old.onFail : null;
|
|
477
|
+
const worker = [...earlier].reverse().find(one => one.kind === "task" || one.kind === "report");
|
|
478
|
+
const notSure = kind === "sort" && typeof step.ifNotSure === "string" && step.ifNotSure.trim() !== "" ? step.ifNotSure
|
|
479
|
+
: kind === "wait" && typeof step.ifNoReply === "string" && step.ifNoReply.trim() !== "" ? step.ifNoReply : step.ifFails;
|
|
480
|
+
const onFail = kind === "done" ? null
|
|
481
|
+
: typeof notSure === "string" && notSure.trim() !== "" ? find(notSure, title)
|
|
482
|
+
: keptFail ?? (kind === "approval" ? worker?.id ?? (drafts[0].id === id ? null : drafts[0].id) : null);
|
|
483
|
+
// "owner": whoever owns the flow when the card arrives.
|
|
484
|
+
const toOwner = kind === "approval" && (step.decider === undefined ? old?.toOwner === true : typeof step.decider === "string" && /^(owner|the owner|flow owner|the flow owner|the flow's owner)$/i.test(step.decider.trim()));
|
|
485
|
+
const approver = kind !== "approval" || toOwner ? null
|
|
486
|
+
: step.decider === undefined ? old?.approver ?? null
|
|
487
|
+
: step.decider === null || /^(anyone|any approver|anybody)$/i.test(step.decider.trim()) ? null : step.decider.trim();
|
|
488
|
+
stages.push({
|
|
489
|
+
id, title, kind,
|
|
490
|
+
zone: old?.zone ?? { x: 0, y: 0, w: 260, h: kind === "done" || kind === "notify" ? 220 : 300, color: KIND_COLORS[kind] },
|
|
491
|
+
// A kept step on our default instructions gets the default again, so it reads any research step added before it.
|
|
492
|
+
instructions: kind === "teammate" ? step.instructions?.trim() || old?.instructions || "Read the card and decide what happens next."
|
|
493
|
+
: kind === "draft" ? step.instructions?.trim() || old?.instructions || DRAFT_DEFAULT
|
|
494
|
+
: kind === "task" || kind === "report"
|
|
495
|
+
? step.instructions?.trim() || (old?.instructions && old.instructions !== defaultInstructions(kind, previous.stages.slice(0, previous.stages.indexOf(old))) ? old.instructions : defaultInstructions(kind, earlier))
|
|
496
|
+
: null,
|
|
497
|
+
planning: kind === "task" ? step.planning ?? old?.planning ?? "auto" : null,
|
|
498
|
+
approver,
|
|
499
|
+
...(toOwner ? { toOwner: true } : {}),
|
|
500
|
+
message: kind === "notify" ? step.message?.trim() || old?.message || (drafts.find(one => one.id === next)?.kind === "done" ? "Finished: {{card.title}}" : "Update on {{card.title}}")
|
|
501
|
+
: kind === "update" ? step.message?.trim() || old?.message || "Done: {{card.title}}" : null,
|
|
502
|
+
close: kind === "update" ? step.close ?? old?.close ?? true : null,
|
|
503
|
+
script: kind === "check" ? step.script?.trim() || old?.script || null : null,
|
|
504
|
+
// Left out, a code step runs in a copy of the project (the card's work, or the main branch), as scripts always have.
|
|
505
|
+
...(kind === "check" ? {
|
|
506
|
+
...(step.runIn !== undefined ? { runIn: step.runIn } : old?.runIn === undefined ? {} : { runIn: old.runIn }),
|
|
507
|
+
...(step.routes !== undefined ? step.routes.length === 0 ? {} : { routes: step.routes.map(one => ({ answer: String(one.answer ?? "").trim(), to: find(String(one.goesTo ?? ""), title) })) } : old?.routes === undefined ? {} : { routes: old.routes }),
|
|
508
|
+
...(step.secrets !== undefined ? step.secrets.length === 0 ? {} : { secrets: step.secrets } : old?.secrets === undefined ? {} : { secrets: old.secrets }),
|
|
509
|
+
} : {}),
|
|
510
|
+
sort: kind === "sort" ? sortFromStep(step, old?.sort ?? null, ref => find(ref, title)) : null,
|
|
511
|
+
...(kind === "request" ? { request: { method: step.method ?? old?.request?.method ?? "POST", url: step.url ?? old?.request?.url ?? "", headers: step.headers ?? old?.request?.headers ?? {}, body: step.body ?? old?.request?.body ?? null } } : {}),
|
|
512
|
+
...(kind === "email" ? { email: { to: step.to ?? old?.email?.to ?? "{{card.email}}", subject: step.subject ?? old?.email?.subject ?? "Re: {{card.title}}", body: step.body ?? old?.email?.body ?? "" } } : {}),
|
|
513
|
+
...(kind === "tool" ? { tool: { server: step.server ?? old?.tool?.server ?? "", name: step.tool ?? old?.tool?.name ?? "", args: typeof step.args === "string" ? step.args : step.args !== undefined ? JSON.stringify(step.args) : old?.tool?.args ?? "{}" } } : {}),
|
|
514
|
+
...(kind === "wait" ? { wait: waitFromStep(step, old?.wait ?? null, title) } : {}),
|
|
515
|
+
// v92: "nobody" (or "none") takes a teammate off an approval step.
|
|
516
|
+
...((kind === "approval" || kind === "teammate") && (step.teammate === undefined ? old?.teammate !== undefined : !/^(nobody|none|no one)$/i.test(step.teammate.trim())) ? { teammate: idOf(step.teammate ?? old.teammate) } : {}),
|
|
517
|
+
...(kind === "teammate" ? step.routes !== undefined ? step.routes.length === 0 ? {} : { routes: step.routes.map(one => ({ answer: String(one.answer ?? "").trim(), to: find(String(one.goesTo ?? ""), title) })) } : old?.routes === undefined ? {} : { routes: old.routes } : {}),
|
|
518
|
+
...(kind === "teammate" && (step.reply ?? old?.reply) === true ? { reply: true } : {}),
|
|
519
|
+
...(() => { const limit = kind === "wait" || kind === "done" ? null : limitFromStep(step, old?.limit ?? null, title, ref => find(ref, title)); return limit === null ? {} : { limit }; })(),
|
|
520
|
+
next, onFail,
|
|
521
|
+
});
|
|
522
|
+
});
|
|
523
|
+
// {{stage.<ref>}} in a step's words names a step as the lead wrote it (draftReply, Draft reply):
|
|
524
|
+
// it is rewritten to that step's id, the same way the steps themselves are named.
|
|
525
|
+
const refs = (text) => text.replace(/\{\{\s*stage\.([A-Za-z0-9_ -]{1,60}?)\s*\}\}/g, (whole, ref) => {
|
|
526
|
+
const hit = drafts.find(one => one.id === ref) ?? drafts.find(one => one.id === idOf(ref) || one.id.replace(/-/g, "") === idOf(ref).replace(/-/g, "") || one.title.toLowerCase() === ref.trim().toLowerCase());
|
|
527
|
+
return hit === undefined ? whole : `{{stage.${hit.id}}}`;
|
|
528
|
+
});
|
|
529
|
+
for (const stage of stages) {
|
|
530
|
+
if (stage.instructions !== null)
|
|
531
|
+
stage.instructions = refs(stage.instructions);
|
|
532
|
+
if (stage.message !== null)
|
|
533
|
+
stage.message = refs(stage.message);
|
|
534
|
+
if (stage.email !== undefined)
|
|
535
|
+
stage.email = { to: refs(stage.email.to), subject: refs(stage.email.subject), body: refs(stage.email.body) };
|
|
536
|
+
if (stage.request !== undefined)
|
|
537
|
+
stage.request = { ...stage.request, url: refs(stage.request.url), headers: Object.fromEntries(Object.entries(stage.request.headers).map(([key, value]) => [key, refs(value)])), body: stage.request.body === null ? null : refs(stage.request.body) };
|
|
538
|
+
if (stage.tool !== undefined)
|
|
539
|
+
stage.tool = { ...stage.tool, args: refs(stage.tool.args) };
|
|
540
|
+
}
|
|
541
|
+
// Lay out the new zones: in rows of four on a new flow, beside the step before them on an edited one.
|
|
542
|
+
// A new flow that branches (a sort, or a script's answers) is laid out in columns by step instead,
|
|
543
|
+
// each branch below the one before, so no arrow crosses a zone.
|
|
544
|
+
const placed = stages.filter(one => kept.get(one.id)?.zone === one.zone).map(one => one.zone);
|
|
545
|
+
const branches = previous === null && stages.some(one => (one.sort?.answers.length ?? 0) > 0 || (one.routes?.length ?? 0) > 0);
|
|
546
|
+
const columns = new Map();
|
|
547
|
+
if (branches) {
|
|
548
|
+
const queue = [stages[0].id];
|
|
549
|
+
columns.set(stages[0].id, 0);
|
|
550
|
+
while (queue.length > 0) {
|
|
551
|
+
const id = queue.shift(), stage = stages.find(one => one.id === id);
|
|
552
|
+
for (const to of [...(stage.sort?.answers.map(one => one.to) ?? []), ...(stage.routes?.map(one => one.to) ?? []), stage.next, stage.onFail]) {
|
|
553
|
+
if (to === null || to === "" || columns.has(to))
|
|
554
|
+
continue;
|
|
555
|
+
columns.set(to, columns.get(id) + 1);
|
|
556
|
+
queue.push(to);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
for (const stage of stages)
|
|
560
|
+
if (!columns.has(stage.id))
|
|
561
|
+
columns.set(stage.id, Math.max(0, ...columns.values()) + 1);
|
|
562
|
+
}
|
|
563
|
+
const rows = new Map();
|
|
564
|
+
stages.forEach((stage, index) => {
|
|
565
|
+
if (placed.includes(stage.zone))
|
|
566
|
+
return;
|
|
567
|
+
let at;
|
|
568
|
+
if (branches) {
|
|
569
|
+
const column = columns.get(stage.id), row = rows.get(column) ?? 0;
|
|
570
|
+
rows.set(column, row + 1);
|
|
571
|
+
at = { ...stage.zone, x: column * 360, y: row * 380 };
|
|
572
|
+
}
|
|
573
|
+
else if (previous === null) {
|
|
574
|
+
const row = Math.floor(index / 4), column = index % 4;
|
|
575
|
+
at = { ...stage.zone, x: (row % 2 === 0 ? column : 3 - column) * 300, y: row * 380 };
|
|
576
|
+
}
|
|
577
|
+
else {
|
|
578
|
+
const before = index > 0 ? stages[index - 1].zone : null;
|
|
579
|
+
at = { ...stage.zone, x: before === null ? 0 : before.x + 300, y: before === null ? Math.max(0, ...placed.map(one => one.y + one.h + 80)) : before.y };
|
|
580
|
+
for (let tries = 0; tries < 40 && placed.some(one => overlaps(one, at)); tries++)
|
|
581
|
+
at = { ...at, y: at.y + 380 };
|
|
582
|
+
}
|
|
583
|
+
stage.zone = at;
|
|
584
|
+
placed.push(at);
|
|
585
|
+
});
|
|
586
|
+
return validateFlowDefinition({ version: 1, start: stages[0].id, stages });
|
|
587
|
+
}
|
|
588
|
+
/** A sort step as the lead describes it: answers name the steps they go to; what it leaves out carries over from the zone it keeps. */
|
|
589
|
+
function sortFromStep(step, old, find) {
|
|
590
|
+
const answers = Array.isArray(step.answers) && step.answers.length > 0
|
|
591
|
+
? step.answers.map(one => ({ answer: String(one?.answer ?? "").trim(), means: String(one?.means ?? "").trim() || String(one?.answer ?? "").trim(), to: typeof one?.goesTo === "string" && one.goesTo.trim() !== "" ? find(one.goesTo) : "" }))
|
|
592
|
+
: old?.answers ?? [];
|
|
593
|
+
// A percentage (80) or a fraction (0.8).
|
|
594
|
+
const sure = typeof step.sureAt === "number" && Number.isFinite(step.sureAt) ? (step.sureAt > 1 ? step.sureAt / 100 : step.sureAt) : old?.sureAt ?? SORT_SURE_AT;
|
|
595
|
+
const notes = Array.isArray(step.alsoNote) ? step.alsoNote.map(one => ({ id: "", kind: one?.kind === "score" ? "score" : "yes-no", question: String(one?.question ?? "").trim(), levels: one?.kind === "score" ? (one.levels ?? []).map(String) : null }))
|
|
596
|
+
: old?.notes ?? [];
|
|
597
|
+
return { question: step.question?.trim() || old?.question || "", answers, sureAt: sure, notes };
|
|
598
|
+
}
|
|
599
|
+
/** What confirming a new or changed flow means, in the canvas's words: each step's job and path, what changed, and that the usual approvals still apply. */
|
|
600
|
+
export function flowTerms(definition, previous) {
|
|
601
|
+
const titleOf = (id) => definition.stages.find(one => one.id === id)?.title ?? "nowhere";
|
|
602
|
+
// "Then → Go ahead?" ends a sentence already.
|
|
603
|
+
const to = (id) => { const title = titleOf(id); return /[.?!]$/.test(title) ? title : `${title}.`; };
|
|
604
|
+
// Fill-ins read as what they will hold.
|
|
605
|
+
const plain = (text) => text.replace(/\{\{\s*(card\.title|card\.description|card\.email|note|stage\.([a-z0-9-]+))\s*\}\}/g, (_match, key, stage) => key === "card.title" ? "[card title]" : key === "card.description" ? "[card details]" : key === "card.email" ? "[the card's email address]" : key === "note" ? "[send-back note]" : `[${titleOf(stage ?? null)} report]`);
|
|
606
|
+
const same = (a, b) => JSON.stringify({ ...a, zone: null }) === JSON.stringify({ ...b, zone: null });
|
|
607
|
+
const describe = (stage, index, mark) => {
|
|
608
|
+
const lines = [`${index + 1}. ${stage.title} — ${FLOW_KIND_WORDS[stage.kind].label}${mark}`];
|
|
609
|
+
if (stage.kind === "task" || stage.kind === "report") {
|
|
610
|
+
// Instructions the steps left to us are said in words; the operator's own are shown as written.
|
|
611
|
+
const earlier = definition.stages.slice(0, index);
|
|
612
|
+
const reports = earlier.filter(one => one.kind === "report").map(one => one.title);
|
|
613
|
+
const notes = reports.length === 0 ? "" : `, using the notes from ${reports.join(" and ")}`;
|
|
614
|
+
lines.push(stage.instructions !== defaultInstructions(stage.kind, earlier) ? `The agent is asked: ${plain(stage.instructions ?? "")}`
|
|
615
|
+
: stage.kind === "task" ? `The agent builds what the card asks${notes}, plus any note it was sent back with.`
|
|
616
|
+
: `The agent looks into the card and writes a short report${notes}, plus any note it was sent back with.`);
|
|
617
|
+
}
|
|
618
|
+
if (stage.kind === "task" && stage.planning !== "auto")
|
|
619
|
+
lines.push(stage.planning === "required" ? "Plans first." : "Builds without a plan.");
|
|
620
|
+
if (stage.kind === "draft")
|
|
621
|
+
lines.push(`Claude writes: ${plain(stage.instructions ?? "")}`, `Then → ${to(stage.next)}`);
|
|
622
|
+
else if (stage.kind === "request" && stage.request !== undefined) {
|
|
623
|
+
const headers = Object.keys(stage.request.headers);
|
|
624
|
+
lines.push(`Calls ${stage.request.method} ${plain(stage.request.url)}${headers.length === 0 ? "" : ` with the ${headers.join(", ")} header${headers.length === 1 ? "" : "s"}`}${stage.request.body === null ? "" : `, sending: ${plain(stage.request.body).slice(0, 300)}`}`, `Then → ${to(stage.next)}${stage.onFail === null ? " If it fails → waits there." : ` If it fails → ${to(stage.onFail)}`}`);
|
|
625
|
+
}
|
|
626
|
+
else if (stage.kind === "email" && stage.email !== undefined)
|
|
627
|
+
lines.push(`Emails ${plain(stage.email.to)}: “${plain(stage.email.subject)}”`, plain(stage.email.body).slice(0, 400), `Then → ${to(stage.next)}${stage.onFail === null ? " If it can't be sent → waits there." : ` If it can't be sent → ${to(stage.onFail)}`}`);
|
|
628
|
+
else if (stage.kind === "tool" && stage.tool !== undefined)
|
|
629
|
+
lines.push(`Uses ${stage.tool.server} → ${stage.tool.name} with ${plain(stage.tool.args).slice(0, 400)}`, `Then → ${to(stage.next)}${stage.onFail === null ? " If it fails → waits there." : ` If it fails → ${to(stage.onFail)}`}`);
|
|
630
|
+
else if (stage.kind === "approval")
|
|
631
|
+
lines.push(`Decides: ${stage.teammate !== undefined ? `the AI teammate ${stage.teammate}, within its rules, handing hard ones to ${stage.toOwner === true ? "the flow's owner" : stage.approver ?? "anyone who approves"}` : stage.toOwner === true ? "the flow's owner, in their chat app" : stage.approver ?? "anyone who approves on this project"}. Approve → ${to(stage.next)} Send back → ${stage.onFail === null ? "not possible." : to(stage.onFail)}`);
|
|
632
|
+
else if (stage.kind === "teammate")
|
|
633
|
+
lines.push(`The AI teammate ${stage.teammate ?? "(none)"} handles it${stage.instructions === null ? "" : `: ${plain(stage.instructions)}`}`, ...(stage.routes === undefined || stage.routes.length === 0 ? [`Then → ${to(stage.next)}`] : stage.routes.map(one => `${one.answer} → ${to(one.to)}`)), `It asks the flow's owner when its rules say to.${stage.reply === true ? " It sends what it writes back to whoever asked." : ""}${stage.onFail === null ? "" : ` If it can't → ${to(stage.onFail)}`}`);
|
|
634
|
+
else if (stage.kind === "notify")
|
|
635
|
+
lines.push(`Posts: ${plain(stage.message ?? "")}`, `Then → ${to(stage.next)}`);
|
|
636
|
+
else if (stage.kind === "update")
|
|
637
|
+
lines.push(`Comments on the issue the card came from: ${plain(stage.message ?? "")}${stage.close === true ? " Then closes it (Linear: moves it to done)." : ""}`, `Then → ${to(stage.next)}${stage.onFail === null ? "" : ` If it can't → ${to(stage.onFail)}`}`);
|
|
638
|
+
else if (stage.kind === "sort" && stage.sort !== null) {
|
|
639
|
+
const percent = Math.round(stage.sort.sureAt * 100);
|
|
640
|
+
lines.push(`Jev asks: ${stage.sort.question}`, ...stage.sort.answers.map(one => `${one.answer} → ${to(one.to)}`), `Less than ${percent}% sure → ${stage.onFail === null ? "waits here for a person." : to(stage.onFail)}`);
|
|
641
|
+
if (stage.sort.notes.length > 0)
|
|
642
|
+
lines.push(`Also notes: ${stage.sort.notes.map(one => one.kind === "score" ? `${one.question} (${one.levels?.join(" / ")})` : `${one.question} (yes or no)`).join("; ")}`);
|
|
643
|
+
}
|
|
644
|
+
else if (stage.kind === "check")
|
|
645
|
+
lines.push(`Runs the project's script “${stage.script}” with no AI.`, `Passes → ${to(stage.next)}${stage.onFail === null ? " Fails → waits there." : ` Fails → ${to(stage.onFail)}`}`);
|
|
646
|
+
else if (stage.kind === "wait" && stage.wait !== undefined)
|
|
647
|
+
lines.push(stage.wait.for === "time" ? `Waits ${durationWords(stage.wait.minutes)}. Then → ${to(stage.next)}`
|
|
648
|
+
: `Waits up to ${durationWords(stage.wait.minutes)} for a reply to the card's email, from someone it was sent to. Reply → ${to(stage.next)} No reply → ${stage.onFail === null ? "stays there for a person." : to(stage.onFail)}`);
|
|
649
|
+
else if (stage.next !== null)
|
|
650
|
+
lines.push(`Then → ${to(stage.next)}${stage.onFail === null ? "" : ` If it fails → ${to(stage.onFail)}`}`);
|
|
651
|
+
if (stage.limit !== undefined)
|
|
652
|
+
lines.push(`After ${durationWords(stage.limit.minutes)} there: reminds ${stage.kind === "approval" ? "whoever decides" : "the card's owner"}${stage.limit.to === null ? "." : `, and moves it to ${to(stage.limit.to)}`}`);
|
|
653
|
+
return lines.join("\n");
|
|
654
|
+
};
|
|
655
|
+
const terms = [];
|
|
656
|
+
if (previous === null)
|
|
657
|
+
terms.push(...definition.stages.map((stage, index) => describe(stage, index, "")));
|
|
658
|
+
else {
|
|
659
|
+
terms.push(`Steps: ${definition.stages.map(one => one.title).join(" → ")}`);
|
|
660
|
+
definition.stages.forEach((stage, index) => {
|
|
661
|
+
const old = previous.stages.find(one => one.id === stage.id);
|
|
662
|
+
if (old === undefined || !same(old, stage))
|
|
663
|
+
terms.push(describe(stage, index, old === undefined ? " (new)" : " (changed)"));
|
|
664
|
+
});
|
|
665
|
+
const removed = previous.stages.filter(one => !definition.stages.some(stage => stage.id === one.id));
|
|
666
|
+
if (removed.length > 0)
|
|
667
|
+
terms.push(`Removes ${removed.map(one => one.title).join(", ")}. Any cards there go back to ${titleOf(definition.start)}.`);
|
|
668
|
+
}
|
|
669
|
+
// New cards that start in a holding zone aren't sorted until someone moves them to the sort: say so before it's confirmed.
|
|
670
|
+
const first = definition.stages.find(one => one.id === definition.start);
|
|
671
|
+
const sorter = definition.stages.find(one => one.kind === "sort");
|
|
672
|
+
if (first?.kind === "inbox" && sorter !== undefined)
|
|
673
|
+
terms.push(`New cards wait in ${first.title}, so ${sorter.title} only sorts the cards someone moves there.`);
|
|
674
|
+
terms.push("Build and research steps become ordinary tasks, so your usual approvals and checks apply.");
|
|
675
|
+
if (definition.stages.some(one => one.kind === "sort"))
|
|
676
|
+
terms.push("Sort steps send each card's title, details and earlier notes to Jev through your OpenRouter account.");
|
|
677
|
+
if (definition.stages.some(one => one.kind === "request" || one.kind === "email" || one.kind === "tool"))
|
|
678
|
+
terms.push("Web request, email and tool steps send what they're given outside this computer, with no one checking unless a decision comes before them.");
|
|
679
|
+
if (definition.stages.some(one => one.kind === "draft"))
|
|
680
|
+
terms.push("Draft steps send each card's text to Claude through the lead chat's sign-in. Nothing a draft writes is sent until a later step sends it.");
|
|
681
|
+
if (definition.stages.some(one => one.teammate !== undefined))
|
|
682
|
+
terms.push("AI teammates decide and act within their soul files' rules, reading each card through Claude on this computer's sign-in; they never approve code tasks or merges.");
|
|
683
|
+
return terms;
|
|
684
|
+
}
|
|
685
|
+
const zone = (x, y, color, h = 300) => ({ x, y, w: 260, h, color });
|
|
686
|
+
const stage = (id, title, kind, at, rest = {}) => ({ id, title, kind, zone: at, instructions: null, planning: kind === "task" ? "auto" : null, approver: null, message: null, close: kind === "update" ? true : null, script: null, sort: null, next: null, onFail: null, ...rest });
|
|
687
|
+
/** Ready-made flows: the coding flow is the whole business process around a change. */
|
|
688
|
+
export const FLOW_TEMPLATES = [
|
|
689
|
+
{
|
|
690
|
+
id: "coding",
|
|
691
|
+
label: "Coding flow",
|
|
692
|
+
about: "Triage, plan, build, review by a person, then tell the team.",
|
|
693
|
+
definition: {
|
|
694
|
+
version: 1,
|
|
695
|
+
start: "inbox",
|
|
696
|
+
stages: [
|
|
697
|
+
stage("inbox", "Inbox", "inbox", zone(0, 0, "slate"), { next: "triage" }),
|
|
698
|
+
stage("triage", "Triage", "report", zone(300, 0, "violet"), {
|
|
699
|
+
instructions: "Investigate this request in the repository and write a short triage: what is being asked, where in the code it lands, the risks, and a rough size.\n\nRequest: {{card.title}}\n{{card.description}}",
|
|
700
|
+
next: "go-ahead",
|
|
701
|
+
}),
|
|
702
|
+
stage("go-ahead", "Go ahead?", "approval", zone(600, 0, "amber"), { toOwner: true, next: "build", onFail: "inbox" }),
|
|
703
|
+
stage("build", "Build", "task", zone(900, 0, "blue"), {
|
|
704
|
+
instructions: "{{card.title}}\n\n{{card.description}}\n\nTriage notes:\n{{stage.triage}}\n\nRequested changes (if any): {{note}}",
|
|
705
|
+
next: "review",
|
|
706
|
+
}),
|
|
707
|
+
stage("review", "Review", "approval", zone(900, 380, "amber"), { toOwner: true, next: "announce", onFail: "build" }),
|
|
708
|
+
stage("announce", "Tell the team", "notify", zone(600, 380, "green", 220), { message: "Shipped: {{card.title}}", next: "done" }),
|
|
709
|
+
stage("done", "Done", "done", zone(300, 380, "green", 220)),
|
|
710
|
+
],
|
|
711
|
+
},
|
|
712
|
+
},
|
|
713
|
+
{
|
|
714
|
+
id: "research",
|
|
715
|
+
label: "Research flow",
|
|
716
|
+
about: "Research a question, have a person check it, then share it.",
|
|
717
|
+
definition: {
|
|
718
|
+
version: 1,
|
|
719
|
+
start: "inbox",
|
|
720
|
+
stages: [
|
|
721
|
+
stage("inbox", "Questions", "inbox", zone(0, 0, "slate"), { next: "research" }),
|
|
722
|
+
stage("research", "Research", "report", zone(300, 0, "violet"), {
|
|
723
|
+
instructions: "Research this and write a clear, sourced answer with a short summary first.\n\nQuestion: {{card.title}}\n{{card.description}}\n\nFeedback to address (if any): {{note}}",
|
|
724
|
+
next: "check",
|
|
725
|
+
}),
|
|
726
|
+
stage("check", "Check", "approval", zone(600, 0, "amber"), { toOwner: true, next: "share", onFail: "research" }),
|
|
727
|
+
stage("share", "Share", "notify", zone(900, 0, "green", 220), { message: "Answered: {{card.title}}", next: "done" }),
|
|
728
|
+
stage("done", "Done", "done", zone(900, 250, "green", 220)),
|
|
729
|
+
],
|
|
730
|
+
},
|
|
731
|
+
},
|
|
732
|
+
{
|
|
733
|
+
id: "triage",
|
|
734
|
+
label: "Issue triage",
|
|
735
|
+
about: "Jev sorts new issues into bugs, feature ideas and questions, and says how urgent each is. Bugs get fixed; questions get researched and a reply drafted. The flow's owner approves both in their chat app.",
|
|
736
|
+
definition: {
|
|
737
|
+
version: 1,
|
|
738
|
+
start: "sort",
|
|
739
|
+
stages: [
|
|
740
|
+
stage("sort", "Sort new issues", "sort", zone(0, 0, "violet"), {
|
|
741
|
+
sort: {
|
|
742
|
+
question: "What kind of request is this?",
|
|
743
|
+
answers: [
|
|
744
|
+
{ answer: "Bug", means: "Something is broken, crashes or behaves wrongly", to: "fix" },
|
|
745
|
+
{ answer: "Feature", means: "A request for something new, or to change how something works", to: "ideas" },
|
|
746
|
+
{ answer: "Question", means: "Someone asking how to do something, or asking for help", to: "answer" },
|
|
747
|
+
{ answer: "Something else", means: "Anything that isn't a bug, a feature request or a question", to: "by-hand" },
|
|
748
|
+
],
|
|
749
|
+
sureAt: 0.8,
|
|
750
|
+
notes: [{ id: "urgency", kind: "score", question: "How urgent is this?", levels: ["Routine: no deadline and nothing is blocked", "Soon: it hurts, but there's a workaround", "Now: something is blocked, broken for many people, or there's a deadline"] }],
|
|
751
|
+
},
|
|
752
|
+
onFail: "by-hand",
|
|
753
|
+
}),
|
|
754
|
+
stage("fix", "Fix it", "task", zone(420, 0, "blue"), { instructions: "{{card.title}}\n\n{{card.description}}\n\nChanges asked for (if any): {{note}}", next: "review" }),
|
|
755
|
+
stage("review", "Review the fix", "approval", zone(840, 0, "amber"), { toOwner: true, next: "close", onFail: "fix" }),
|
|
756
|
+
stage("close", "Close the issue", "update", zone(1260, 0, "green", 220), { message: "Fixed: {{card.title}}. Thanks for the report!", next: "done" }),
|
|
757
|
+
stage("by-hand", "Sort by hand", "inbox", zone(0, 400, "slate")),
|
|
758
|
+
stage("answer", "Answer it", "report", zone(420, 340, "violet"), {
|
|
759
|
+
instructions: "Answer this question for the person who asked: clearly, briefly and in plain words, using what is in the repository.\n\nQuestion: {{card.title}}\n{{card.description}}\n\nFeedback to address (if any): {{note}}",
|
|
760
|
+
next: "write-reply",
|
|
761
|
+
}),
|
|
762
|
+
stage("write-reply", "Write the reply", "draft", zone(840, 340, "violet"), {
|
|
763
|
+
instructions: "Write a short, friendly reply to the person who asked, answering their question from the research notes. Plain words; say what to do next if anything.",
|
|
764
|
+
next: "check-answer",
|
|
765
|
+
}),
|
|
766
|
+
stage("check-answer", "Check the reply", "approval", zone(1260, 340, "amber"), { toOwner: true, next: "reply", onFail: "write-reply" }),
|
|
767
|
+
stage("reply", "Reply on the issue", "update", zone(1680, 340, "green", 220), { message: "{{stage.write-reply}}", next: "done" }),
|
|
768
|
+
stage("ideas", "Feature ideas", "inbox", zone(420, 680, "slate")),
|
|
769
|
+
stage("done", "Done", "done", zone(2100, 170, "green", 220)),
|
|
770
|
+
],
|
|
771
|
+
},
|
|
772
|
+
},
|
|
773
|
+
{
|
|
774
|
+
id: "spam-filter",
|
|
775
|
+
label: "Spam filter",
|
|
776
|
+
about: "Jev screens what a public form or webhook brings in. Genuine requests wait in Requests (another flow can start from there); spam and abuse are filtered out.",
|
|
777
|
+
definition: {
|
|
778
|
+
version: 1,
|
|
779
|
+
start: "screen",
|
|
780
|
+
stages: [
|
|
781
|
+
stage("screen", "Screen", "sort", zone(0, 0, "violet"), {
|
|
782
|
+
sort: {
|
|
783
|
+
question: "Is this a genuine request from a person?",
|
|
784
|
+
answers: [
|
|
785
|
+
{ answer: "Genuine", means: "A real question, report or request from a person, even if it is short or badly written", to: "requests" },
|
|
786
|
+
{ answer: "Spam", means: "Advertising, SEO or crypto offers, lists of links, gibberish, or anything automated", to: "filtered" },
|
|
787
|
+
{ answer: "Abusive", means: "Harassment, threats, slurs or hateful content", to: "filtered" },
|
|
788
|
+
],
|
|
789
|
+
sureAt: 0.9,
|
|
790
|
+
notes: [],
|
|
791
|
+
},
|
|
792
|
+
onFail: "by-hand",
|
|
793
|
+
}),
|
|
794
|
+
stage("requests", "Requests", "inbox", zone(420, 0, "slate")),
|
|
795
|
+
stage("by-hand", "Check by hand", "inbox", zone(0, 400, "amber")),
|
|
796
|
+
stage("filtered", "Filtered out", "done", zone(420, 340, "rose", 220)),
|
|
797
|
+
],
|
|
798
|
+
},
|
|
799
|
+
},
|
|
800
|
+
{
|
|
801
|
+
id: "lead-routing",
|
|
802
|
+
label: "Lead routing",
|
|
803
|
+
about: "Jev sorts new enquiries by what they want and notes how ready they are to buy. New projects ping the team straight away.",
|
|
804
|
+
definition: {
|
|
805
|
+
version: 1,
|
|
806
|
+
start: "sort",
|
|
807
|
+
stages: [
|
|
808
|
+
stage("sort", "Sort", "sort", zone(0, 0, "violet"), {
|
|
809
|
+
sort: {
|
|
810
|
+
question: "What is this enquiry mainly about?",
|
|
811
|
+
answers: [
|
|
812
|
+
{ answer: "New project", means: "They want something new built, designed or set up", to: "tell-team" },
|
|
813
|
+
{ answer: "Existing work", means: "A client asking about work that is already underway or delivered", to: "clients" },
|
|
814
|
+
{ answer: "Partnership", means: "A proposed partnership, referral or reselling arrangement", to: "partners" },
|
|
815
|
+
{ answer: "Not a fit", means: "Job seekers, vendors selling to us, students, or anyone who isn't a potential client", to: "not-a-fit" },
|
|
816
|
+
],
|
|
817
|
+
sureAt: 0.8,
|
|
818
|
+
notes: [
|
|
819
|
+
{ id: "ready", kind: "score", question: "How ready are they to buy?", levels: ["Just looking: no clear need yet", "Planning: a clear need, but no timeline or budget", "Ready: they name a budget, a date or a decision"] },
|
|
820
|
+
{ id: "when", kind: "yes-no", question: "Do they say when they need it?", levels: null },
|
|
821
|
+
],
|
|
822
|
+
},
|
|
823
|
+
onFail: "by-hand",
|
|
824
|
+
}),
|
|
825
|
+
stage("tell-team", "Tell the team", "notify", zone(420, 0, "green", 220), { message: "New project enquiry: {{card.title}}. {{stage.sort}}", next: "projects" }),
|
|
826
|
+
stage("projects", "New projects", "inbox", zone(840, 0, "slate")),
|
|
827
|
+
stage("by-hand", "Sort by hand", "inbox", zone(0, 400, "amber")),
|
|
828
|
+
stage("clients", "Existing clients", "inbox", zone(420, 340, "slate")),
|
|
829
|
+
stage("partners", "Partnerships", "inbox", zone(420, 680, "slate")),
|
|
830
|
+
stage("not-a-fit", "Not a fit", "done", zone(420, 1020, "rose", 220)),
|
|
831
|
+
],
|
|
832
|
+
},
|
|
833
|
+
},
|
|
834
|
+
{
|
|
835
|
+
id: "effort-routing",
|
|
836
|
+
label: "Effort routing",
|
|
837
|
+
about: "Jev sends small, clear changes straight to a build, and big or unclear ones through a plan and a person first. When it isn't sure, it takes the careful path.",
|
|
838
|
+
definition: {
|
|
839
|
+
version: 1,
|
|
840
|
+
start: "size",
|
|
841
|
+
stages: [
|
|
842
|
+
stage("size", "Size it", "sort", zone(0, 0, "violet"), {
|
|
843
|
+
sort: {
|
|
844
|
+
question: "How much work is this change?",
|
|
845
|
+
answers: [
|
|
846
|
+
{ answer: "Small", means: "A small, clear change in one or two places: a typo, a label, a setting or an obvious fix", to: "quick" },
|
|
847
|
+
{ answer: "Big", means: "Touches many places, needs design choices, changes stored data, or is unclear", to: "plan" },
|
|
848
|
+
],
|
|
849
|
+
sureAt: 0.85,
|
|
850
|
+
notes: [],
|
|
851
|
+
},
|
|
852
|
+
onFail: "plan",
|
|
853
|
+
}),
|
|
854
|
+
stage("quick", "Build it", "task", zone(420, 0, "blue"), { planning: "skip", instructions: "{{card.title}}\n\n{{card.description}}\n\nChanges asked for (if any): {{note}}", next: "check-quick" }),
|
|
855
|
+
stage("check-quick", "Review", "approval", zone(840, 0, "amber"), { toOwner: true, next: "done", onFail: "quick" }),
|
|
856
|
+
stage("plan", "Plan it", "report", zone(420, 340, "violet"), {
|
|
857
|
+
instructions: "Investigate this change and write a short plan: what is asked, which files it touches, the risks, the open questions and a rough size.\n\nRequest: {{card.title}}\n{{card.description}}\n\nFeedback to address (if any): {{note}}",
|
|
858
|
+
next: "go-ahead",
|
|
859
|
+
}),
|
|
860
|
+
stage("go-ahead", "Go ahead?", "approval", zone(840, 340, "amber"), { toOwner: true, next: "build", onFail: "plan" }),
|
|
861
|
+
stage("build", "Build it carefully", "task", zone(1260, 340, "blue"), { planning: "required", instructions: "{{card.title}}\n\n{{card.description}}\n\nThe agreed plan:\n{{stage.plan}}\n\nChanges asked for (if any): {{note}}", next: "review" }),
|
|
862
|
+
stage("review", "Review", "approval", zone(1680, 340, "amber"), { toOwner: true, next: "done", onFail: "build" }),
|
|
863
|
+
stage("done", "Done", "done", zone(1680, 0, "green", 220)),
|
|
864
|
+
],
|
|
865
|
+
},
|
|
866
|
+
},
|
|
867
|
+
{
|
|
868
|
+
id: "exception-routing",
|
|
869
|
+
label: "Exception routing",
|
|
870
|
+
about: "Jev sorts customer problems (order changes, invoice problems, deliveries) to the team that owns each, and notes how urgent each is and whether they want money back.",
|
|
871
|
+
definition: {
|
|
872
|
+
version: 1,
|
|
873
|
+
start: "sort",
|
|
874
|
+
stages: [
|
|
875
|
+
stage("sort", "Sort", "sort", zone(0, 0, "violet"), {
|
|
876
|
+
sort: {
|
|
877
|
+
question: "What kind of problem is this?",
|
|
878
|
+
answers: [
|
|
879
|
+
{ answer: "Order change", means: "The customer wants to change, cancel or add to an order", to: "orders" },
|
|
880
|
+
{ answer: "Invoice problem", means: "A wrong amount, a duplicate charge, missing details or a disputed invoice", to: "billing" },
|
|
881
|
+
{ answer: "Delivery problem", means: "An order that is late, lost, damaged or wrong", to: "delivery" },
|
|
882
|
+
{ answer: "Something else", means: "Anything that isn't about an order, an invoice or a delivery", to: "by-hand" },
|
|
883
|
+
],
|
|
884
|
+
sureAt: 0.8,
|
|
885
|
+
notes: [
|
|
886
|
+
{ id: "urgency", kind: "score", question: "How urgent is it?", levels: ["Routine: no deadline", "Soon: the customer is waiting on it", "Now: money is at stake or a deadline is named"] },
|
|
887
|
+
{ id: "refund", kind: "yes-no", question: "Is the customer asking for money back?", levels: null },
|
|
888
|
+
],
|
|
889
|
+
},
|
|
890
|
+
onFail: "by-hand",
|
|
891
|
+
}),
|
|
892
|
+
stage("orders", "Orders team", "inbox", zone(420, 0, "blue"), { next: "done" }),
|
|
893
|
+
stage("billing", "Billing team", "inbox", zone(420, 340, "amber"), { next: "done" }),
|
|
894
|
+
stage("delivery", "Delivery team", "inbox", zone(420, 680, "green"), { next: "done" }),
|
|
895
|
+
stage("by-hand", "Sort by hand", "inbox", zone(0, 400, "slate")),
|
|
896
|
+
stage("done", "Done", "done", zone(840, 340, "green", 220)),
|
|
897
|
+
],
|
|
898
|
+
},
|
|
899
|
+
},
|
|
900
|
+
{
|
|
901
|
+
id: "email-replies",
|
|
902
|
+
label: "Email replies",
|
|
903
|
+
about: "Claude drafts a reply to each question, the flow's owner approves or edits it in their chat app, and it's emailed to whoever asked. Share its button as a form that asks for an email address.",
|
|
904
|
+
definition: {
|
|
905
|
+
version: 1,
|
|
906
|
+
start: "write",
|
|
907
|
+
stages: [
|
|
908
|
+
stage("write", "Write the reply", "draft", zone(0, 0, "violet"), {
|
|
909
|
+
instructions: "Write a short, friendly reply to the person who asked, answering their question in plain words. Sign off as the team.",
|
|
910
|
+
next: "check",
|
|
911
|
+
}),
|
|
912
|
+
stage("check", "Check the reply", "approval", zone(420, 0, "amber"), { toOwner: true, next: "send", onFail: "write" }),
|
|
913
|
+
stage("send", "Email it", "email", zone(840, 0, "green"), { email: { to: "{{card.email}}", subject: "Re: {{card.title}}", body: "{{stage.write}}" }, next: "done", onFail: "no-address" }),
|
|
914
|
+
stage("no-address", "No email address", "inbox", zone(840, 380, "amber")),
|
|
915
|
+
stage("done", "Done", "done", zone(1260, 0, "green", 220)),
|
|
916
|
+
],
|
|
917
|
+
},
|
|
918
|
+
},
|
|
919
|
+
{
|
|
920
|
+
id: "follow-up",
|
|
921
|
+
label: "Reply and follow up",
|
|
922
|
+
about: "Claude drafts a reply, the owner approves it, and it's emailed. If they don't answer in 3 days, a short nudge goes out in the same thread; replies land in “They replied”. Needs the inbox in Settings → Email.",
|
|
923
|
+
definition: {
|
|
924
|
+
version: 1,
|
|
925
|
+
start: "write",
|
|
926
|
+
stages: [
|
|
927
|
+
stage("write", "Write the reply", "draft", zone(0, 0, "violet"), {
|
|
928
|
+
instructions: "Write a short, friendly reply to the person who wrote in, answering in plain words. Sign off as the team.",
|
|
929
|
+
next: "check",
|
|
930
|
+
}),
|
|
931
|
+
stage("check", "Check the reply", "approval", zone(420, 0, "amber"), { toOwner: true, next: "send", onFail: "write" }),
|
|
932
|
+
stage("send", "Email it", "email", zone(840, 0, "green"), { email: { to: "{{card.email}}", subject: "Re: {{card.title}}", body: "{{stage.write}}" }, next: "wait", onFail: "not-sent" }),
|
|
933
|
+
stage("not-sent", "Couldn't email", "inbox", zone(840, 420, "amber")),
|
|
934
|
+
stage("wait", "Wait for an answer", "wait", zone(1260, 0, "slate"), { wait: { for: "reply", minutes: 3 * 24 * 60 }, next: "replied", onFail: "nudge" }),
|
|
935
|
+
stage("replied", "They replied", "inbox", zone(1680, 0, "slate"), { limit: { minutes: 24 * 60, to: null } }),
|
|
936
|
+
stage("nudge", "Nudge", "email", zone(1260, 420, "green"), {
|
|
937
|
+
email: { to: "{{card.email}}", subject: "Re: {{card.title}}", body: "Hi, just checking you saw our reply about “{{card.title}}”. Happy to help if anything's unclear." },
|
|
938
|
+
next: "wait-again", onFail: "not-sent",
|
|
939
|
+
}),
|
|
940
|
+
stage("wait-again", "Wait again", "wait", zone(1680, 420, "slate"), { wait: { for: "reply", minutes: 4 * 24 * 60 }, next: "replied", onFail: "no-answer" }),
|
|
941
|
+
stage("no-answer", "No answer", "done", zone(2100, 420, "green", 220)),
|
|
942
|
+
],
|
|
943
|
+
},
|
|
944
|
+
},
|
|
945
|
+
{
|
|
946
|
+
id: "stalled-decisions",
|
|
947
|
+
label: "Decisions that don't stall",
|
|
948
|
+
about: "The owner decides each request. If they haven't in a day they're reminded and it goes to anyone who can approve; that decision gets a reminder after 2 days.",
|
|
949
|
+
definition: {
|
|
950
|
+
version: 1,
|
|
951
|
+
start: "requests",
|
|
952
|
+
stages: [
|
|
953
|
+
stage("requests", "Requests", "inbox", zone(0, 0, "slate"), { next: "decide" }),
|
|
954
|
+
stage("decide", "Owner decides", "approval", zone(420, 0, "amber"), { toOwner: true, next: "approved", onFail: "declined", limit: { minutes: 24 * 60, to: "anyone" } }),
|
|
955
|
+
stage("anyone", "Anyone decides", "approval", zone(420, 420, "amber"), { next: "approved", onFail: "declined", limit: { minutes: 2 * 24 * 60, to: null } }),
|
|
956
|
+
stage("approved", "Tell the team", "notify", zone(840, 0, "green", 220), { message: "Approved: {{card.title}}", next: "done" }),
|
|
957
|
+
stage("done", "Done", "done", zone(1260, 0, "green", 220)),
|
|
958
|
+
stage("declined", "Declined", "done", zone(840, 420, "green", 220)),
|
|
959
|
+
],
|
|
960
|
+
},
|
|
961
|
+
},
|
|
962
|
+
{
|
|
963
|
+
id: "blank",
|
|
964
|
+
label: "Blank flow",
|
|
965
|
+
about: "An inbox and a done zone to build from.",
|
|
966
|
+
definition: { version: 1, start: "inbox", stages: [stage("inbox", "Inbox", "inbox", zone(0, 0, "slate"), { next: "done" }), stage("done", "Done", "done", zone(300, 0, "green"))] },
|
|
967
|
+
},
|
|
968
|
+
];
|