toolroll 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +1113 -0
- package/THIRD_PARTY_NOTICES.md +449 -0
- package/dist/accent-colors.d.ts +48 -0
- package/dist/accent-colors.js +122 -0
- package/dist/action-ledger.d.ts +26 -0
- package/dist/action-ledger.js +75 -0
- package/dist/agent-fence.d.ts +42 -0
- package/dist/agent-fence.js +183 -0
- package/dist/agentconfig.d.ts +216 -0
- package/dist/agentconfig.js +454 -0
- package/dist/api-tokens.d.ts +16 -0
- package/dist/api-tokens.js +27 -0
- package/dist/approval-policy.d.ts +69 -0
- package/dist/approval-policy.js +137 -0
- package/dist/approval-rules-ui.d.ts +25 -0
- package/dist/approval-rules-ui.js +39 -0
- package/dist/assignment-adapters.d.ts +180 -0
- package/dist/assignment-adapters.js +239 -0
- package/dist/assignment-brief.d.ts +71 -0
- package/dist/assignment-brief.js +129 -0
- package/dist/assignment-delivery.d.ts +56 -0
- package/dist/assignment-delivery.js +160 -0
- package/dist/assignment-presentation.d.ts +26 -0
- package/dist/assignment-presentation.js +80 -0
- package/dist/assignment-status.d.ts +62 -0
- package/dist/assignment-status.js +152 -0
- package/dist/assignment-ui.d.ts +71 -0
- package/dist/assignment-ui.js +103 -0
- package/dist/assignment.d.ts +222 -0
- package/dist/assignment.js +399 -0
- package/dist/attest.d.ts +56 -0
- package/dist/attest.js +153 -0
- package/dist/backend.d.ts +97 -0
- package/dist/backend.js +166 -0
- package/dist/backup-ui.d.ts +22 -0
- package/dist/backup-ui.js +59 -0
- package/dist/backup.d.ts +84 -0
- package/dist/backup.js +421 -0
- package/dist/beads.d.ts +34 -0
- package/dist/beads.js +135 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +22 -0
- package/dist/board.d.ts +150 -0
- package/dist/board.js +210 -0
- package/dist/boot-identity.d.ts +63 -0
- package/dist/boot-identity.js +99 -0
- package/dist/browser/THIRD_PARTY_NOTICES.txt +2295 -0
- package/dist/browser/workspace.css +4 -0
- package/dist/browser/workspace.js +225 -0
- package/dist/browser-crew.d.ts +11 -0
- package/dist/browser-crew.js +50 -0
- package/dist/browser-shell.d.ts +8 -0
- package/dist/browser-shell.js +60 -0
- package/dist/browser-workspace.d.ts +780 -0
- package/dist/browser-workspace.js +47 -0
- package/dist/budget-alerts.d.ts +19 -0
- package/dist/budget-alerts.js +46 -0
- package/dist/builder.d.ts +370 -0
- package/dist/builder.js +3262 -0
- package/dist/capscan.d.ts +35 -0
- package/dist/capscan.js +113 -0
- package/dist/chat-acceptance.d.ts +71 -0
- package/dist/chat-acceptance.js +120 -0
- package/dist/chat-actions.d.ts +265 -0
- package/dist/chat-actions.js +1273 -0
- package/dist/chat-channel.d.ts +109 -0
- package/dist/chat-channel.js +652 -0
- package/dist/chat-continuity.d.ts +32 -0
- package/dist/chat-continuity.js +317 -0
- package/dist/chat-controls.d.ts +113 -0
- package/dist/chat-controls.js +53 -0
- package/dist/chat-delivery-state.d.ts +159 -0
- package/dist/chat-delivery-state.js +309 -0
- package/dist/chat-delivery.d.ts +38 -0
- package/dist/chat-delivery.js +693 -0
- package/dist/chat-display.d.ts +1 -0
- package/dist/chat-display.js +18 -0
- package/dist/chat-evidence.d.ts +123 -0
- package/dist/chat-evidence.js +207 -0
- package/dist/chat-flow.d.ts +44 -0
- package/dist/chat-flow.js +215 -0
- package/dist/chat-inbox.d.ts +57 -0
- package/dist/chat-inbox.js +123 -0
- package/dist/chat-polish.d.ts +16 -0
- package/dist/chat-polish.js +135 -0
- package/dist/chat-review.d.ts +46 -0
- package/dist/chat-review.js +98 -0
- package/dist/chat-rooms.d.ts +123 -0
- package/dist/chat-rooms.js +216 -0
- package/dist/chat-task-actions.d.ts +44 -0
- package/dist/chat-task-actions.js +79 -0
- package/dist/check-progress.d.ts +51 -0
- package/dist/check-progress.js +290 -0
- package/dist/child-database.d.ts +12 -0
- package/dist/child-database.js +30 -0
- package/dist/claim.d.ts +688 -0
- package/dist/claim.js +1740 -0
- package/dist/cli.d.ts +137 -0
- package/dist/cli.js +1461 -0
- package/dist/codex-limits.d.ts +15 -0
- package/dist/codex-limits.js +145 -0
- package/dist/coding-context.d.ts +55 -0
- package/dist/coding-context.js +92 -0
- package/dist/coding-handoff.d.ts +57 -0
- package/dist/coding-handoff.js +277 -0
- package/dist/coding-provider.d.ts +72 -0
- package/dist/coding-provider.js +424 -0
- package/dist/coding-shipping-ui.d.ts +4 -0
- package/dist/coding-shipping-ui.js +12 -0
- package/dist/coding-types.d.ts +62 -0
- package/dist/coding-types.js +1 -0
- package/dist/coding-ui.d.ts +17 -0
- package/dist/coding-ui.js +364 -0
- package/dist/coding-update.d.ts +15 -0
- package/dist/coding-update.js +149 -0
- package/dist/coding-workspace.d.ts +124 -0
- package/dist/coding-workspace.js +768 -0
- package/dist/container-state.d.ts +5 -0
- package/dist/container-state.js +62 -0
- package/dist/containment.d.ts +197 -0
- package/dist/containment.js +559 -0
- package/dist/contest.d.ts +318 -0
- package/dist/contest.js +753 -0
- package/dist/control-setup.d.ts +35 -0
- package/dist/control-setup.js +40 -0
- package/dist/control-ui.d.ts +29 -0
- package/dist/control-ui.js +53 -0
- package/dist/controller-service.d.ts +1 -0
- package/dist/controller-service.js +19 -0
- package/dist/controller-supervisor.d.ts +30 -0
- package/dist/controller-supervisor.js +104 -0
- package/dist/converse.d.ts +304 -0
- package/dist/converse.js +849 -0
- package/dist/coordinator-proposals.d.ts +29 -0
- package/dist/coordinator-proposals.js +137 -0
- package/dist/coordinator.d.ts +186 -0
- package/dist/coordinator.js +447 -0
- package/dist/credentials-ui.d.ts +25 -0
- package/dist/credentials-ui.js +48 -0
- package/dist/daemon.d.ts +209 -0
- package/dist/daemon.js +604 -0
- package/dist/decision.d.ts +120 -0
- package/dist/decision.js +388 -0
- package/dist/demo.d.ts +55 -0
- package/dist/demo.js +992 -0
- package/dist/desktop-access.d.ts +28 -0
- package/dist/desktop-access.js +88 -0
- package/dist/desktop-bundle.d.ts +16 -0
- package/dist/desktop-bundle.js +74 -0
- package/dist/desktop-host.d.ts +54 -0
- package/dist/desktop-host.js +508 -0
- package/dist/desktop-update-gate.d.ts +12 -0
- package/dist/desktop-update-gate.js +91 -0
- package/dist/desktop-update-recovery.d.ts +14 -0
- package/dist/desktop-update-recovery.js +243 -0
- package/dist/desktop-update.d.ts +110 -0
- package/dist/desktop-update.js +740 -0
- package/dist/discord-api.d.ts +20 -0
- package/dist/discord-api.js +138 -0
- package/dist/discord-chat.d.ts +16 -0
- package/dist/discord-chat.js +381 -0
- package/dist/discord-settings.d.ts +7 -0
- package/dist/discord-settings.js +75 -0
- package/dist/discord.d.ts +10 -0
- package/dist/discord.js +169 -0
- package/dist/discover.d.ts +75 -0
- package/dist/discover.js +150 -0
- package/dist/dispatch.d.ts +100 -0
- package/dist/dispatch.js +311 -0
- package/dist/dispose.d.ts +161 -0
- package/dist/dispose.js +803 -0
- package/dist/email-settings.d.ts +34 -0
- package/dist/email-settings.js +50 -0
- package/dist/envelope.d.ts +47 -0
- package/dist/envelope.js +105 -0
- package/dist/evidence-pack.d.ts +185 -0
- package/dist/evidence-pack.js +258 -0
- package/dist/evidence.d.ts +345 -0
- package/dist/evidence.js +782 -0
- package/dist/exec.d.ts +285 -0
- package/dist/exec.js +1841 -0
- package/dist/exhaustion.d.ts +85 -0
- package/dist/exhaustion.js +141 -0
- package/dist/export-ui.d.ts +4 -0
- package/dist/export-ui.js +17 -0
- package/dist/export.d.ts +48 -0
- package/dist/export.js +305 -0
- package/dist/flow-actions.d.ts +77 -0
- package/dist/flow-actions.js +203 -0
- package/dist/flow-code.d.ts +50 -0
- package/dist/flow-code.js +159 -0
- package/dist/flow-draft.d.ts +38 -0
- package/dist/flow-draft.js +80 -0
- package/dist/flow-engine.d.ts +66 -0
- package/dist/flow-engine.js +416 -0
- package/dist/flow-insights.d.ts +95 -0
- package/dist/flow-insights.js +149 -0
- package/dist/flow-live.d.ts +24 -0
- package/dist/flow-live.js +150 -0
- package/dist/flow-people.d.ts +24 -0
- package/dist/flow-people.js +91 -0
- package/dist/flow-replies.d.ts +48 -0
- package/dist/flow-replies.js +113 -0
- package/dist/flow-scripts.d.ts +38 -0
- package/dist/flow-scripts.js +73 -0
- package/dist/flow-secrets.d.ts +13 -0
- package/dist/flow-secrets.js +45 -0
- package/dist/flow-sort.d.ts +82 -0
- package/dist/flow-sort.js +153 -0
- package/dist/flow-steps.d.ts +40 -0
- package/dist/flow-steps.js +408 -0
- package/dist/flow-triggers.d.ts +244 -0
- package/dist/flow-triggers.js +959 -0
- package/dist/flows-ui.d.ts +20 -0
- package/dist/flows-ui.js +175 -0
- package/dist/flows.d.ts +229 -0
- package/dist/flows.js +968 -0
- package/dist/fonts.d.ts +19 -0
- package/dist/fonts.js +19 -0
- package/dist/gaps.d.ts +35 -0
- package/dist/gaps.js +101 -0
- package/dist/gate-failure.d.ts +40 -0
- package/dist/gate-failure.js +66 -0
- package/dist/git.d.ts +54 -0
- package/dist/git.js +94 -0
- package/dist/google-mail.d.ts +58 -0
- package/dist/google-mail.js +151 -0
- package/dist/grant.d.ts +136 -0
- package/dist/grant.js +238 -0
- package/dist/graph.d.ts +164 -0
- package/dist/graph.js +383 -0
- package/dist/guides.d.ts +22 -0
- package/dist/guides.js +363 -0
- package/dist/held.d.ts +150 -0
- package/dist/held.js +799 -0
- package/dist/invoke.d.ts +112 -0
- package/dist/invoke.js +780 -0
- package/dist/issues.d.ts +41 -0
- package/dist/issues.js +131 -0
- package/dist/job-object-helper.ps1 +252 -0
- package/dist/jsonl-discriminants.d.ts +11 -0
- package/dist/jsonl-discriminants.js +93 -0
- package/dist/keys.d.ts +104 -0
- package/dist/keys.js +229 -0
- package/dist/kits-ui.d.ts +17 -0
- package/dist/kits-ui.js +46 -0
- package/dist/kits.d.ts +86 -0
- package/dist/kits.js +215 -0
- package/dist/knowledge-cli.d.ts +101 -0
- package/dist/knowledge-cli.js +92 -0
- package/dist/knowledge-ui.d.ts +24 -0
- package/dist/knowledge-ui.js +40 -0
- package/dist/lead-context.d.ts +8 -0
- package/dist/lead-context.js +56 -0
- package/dist/lead-follow.d.ts +22 -0
- package/dist/lead-follow.js +167 -0
- package/dist/lead-status.d.ts +75 -0
- package/dist/lead-status.js +285 -0
- package/dist/ledger-chain.d.ts +46 -0
- package/dist/ledger-chain.js +187 -0
- package/dist/ledger-csv.d.ts +4 -0
- package/dist/ledger-csv.js +11 -0
- package/dist/ledger-view.d.ts +24 -0
- package/dist/ledger-view.js +58 -0
- package/dist/limits-ui.d.ts +26 -0
- package/dist/limits-ui.js +67 -0
- package/dist/link.d.ts +72 -0
- package/dist/link.js +217 -0
- package/dist/live.d.ts +96 -0
- package/dist/live.js +366 -0
- package/dist/liveness.d.ts +30 -0
- package/dist/liveness.js +42 -0
- package/dist/log.d.ts +7 -0
- package/dist/log.js +25 -0
- package/dist/mailbox.d.ts +57 -0
- package/dist/mailbox.js +150 -0
- package/dist/maintenance.d.ts +11 -0
- package/dist/maintenance.js +35 -0
- package/dist/mate-cli.d.ts +52 -0
- package/dist/mate-cli.js +345 -0
- package/dist/mate-contract.d.ts +10 -0
- package/dist/mate-contract.js +30 -0
- package/dist/mate-doors.d.ts +69 -0
- package/dist/mate-doors.js +548 -0
- package/dist/mate-progress.d.ts +29 -0
- package/dist/mate-progress.js +121 -0
- package/dist/mate-tools.d.ts +94 -0
- package/dist/mate-tools.js +1878 -0
- package/dist/mate.d.ts +92 -0
- package/dist/mate.js +435 -0
- package/dist/mcp-connect.d.ts +103 -0
- package/dist/mcp-connect.js +252 -0
- package/dist/mcp.d.ts +27 -0
- package/dist/mcp.js +651 -0
- package/dist/memory-cli.d.ts +466 -0
- package/dist/memory-cli.js +111 -0
- package/dist/memory-pass.d.ts +160 -0
- package/dist/memory-pass.js +409 -0
- package/dist/metrics.d.ts +3 -0
- package/dist/metrics.js +56 -0
- package/dist/mobile-viewport.d.ts +3 -0
- package/dist/mobile-viewport.js +37 -0
- package/dist/model-catalog.d.ts +118 -0
- package/dist/model-catalog.js +376 -0
- package/dist/models-cli.d.ts +102 -0
- package/dist/models-cli.js +66 -0
- package/dist/models-ui.d.ts +34 -0
- package/dist/models-ui.js +68 -0
- package/dist/modes.d.ts +80 -0
- package/dist/modes.js +182 -0
- package/dist/monitoring-settings.d.ts +44 -0
- package/dist/monitoring-settings.js +173 -0
- package/dist/monitoring-ui.d.ts +20 -0
- package/dist/monitoring-ui.js +51 -0
- package/dist/monitoring.d.ts +34 -0
- package/dist/monitoring.js +221 -0
- package/dist/names.d.ts +67 -0
- package/dist/names.js +102 -0
- package/dist/observations.d.ts +40 -0
- package/dist/observations.js +211 -0
- package/dist/oidc.d.ts +71 -0
- package/dist/oidc.js +182 -0
- package/dist/onboard.d.ts +108 -0
- package/dist/onboard.js +325 -0
- package/dist/openrouter-models.d.ts +30 -0
- package/dist/openrouter-models.js +120 -0
- package/dist/operate.d.ts +118 -0
- package/dist/operate.js +11321 -0
- package/dist/peek-cli.d.ts +97 -0
- package/dist/peek-cli.js +215 -0
- package/dist/peek.d.ts +134 -0
- package/dist/peek.js +578 -0
- package/dist/phase-routing.d.ts +307 -0
- package/dist/phase-routing.js +658 -0
- package/dist/plan-auto.d.ts +14 -0
- package/dist/plan-auto.js +92 -0
- package/dist/plan.d.ts +186 -0
- package/dist/plan.js +401 -0
- package/dist/planner-source.d.ts +251 -0
- package/dist/planner-source.js +460 -0
- package/dist/planner.d.ts +75 -0
- package/dist/planner.js +992 -0
- package/dist/policy-ui.d.ts +20 -0
- package/dist/policy-ui.js +41 -0
- package/dist/policy.d.ts +111 -0
- package/dist/policy.js +231 -0
- package/dist/prepared-evidence.d.ts +16 -0
- package/dist/prepared-evidence.js +96 -0
- package/dist/pricing.d.ts +45 -0
- package/dist/pricing.js +77 -0
- package/dist/principal.d.ts +42 -0
- package/dist/principal.js +82 -0
- package/dist/probe.d.ts +46 -0
- package/dist/probe.js +79 -0
- package/dist/process-custody.d.ts +17 -0
- package/dist/process-custody.js +91 -0
- package/dist/process-liveness.d.ts +10 -0
- package/dist/process-liveness.js +81 -0
- package/dist/process-recovery-anchor.d.ts +75 -0
- package/dist/process-recovery-anchor.js +188 -0
- package/dist/process-recovery-coalition.d.ts +25 -0
- package/dist/process-recovery-coalition.js +105 -0
- package/dist/process-recovery-eligibility.d.ts +178 -0
- package/dist/process-recovery-eligibility.js +170 -0
- package/dist/process-recovery-native.d.ts +172 -0
- package/dist/process-recovery-native.js +592 -0
- package/dist/process-recovery-provenance.d.ts +163 -0
- package/dist/process-recovery-provenance.js +292 -0
- package/dist/process-recovery-services.d.ts +56 -0
- package/dist/process-recovery-services.js +191 -0
- package/dist/process-recovery-settlement.d.ts +32 -0
- package/dist/process-recovery-settlement.js +132 -0
- package/dist/process-recovery.d.ts +41 -0
- package/dist/process-recovery.js +91 -0
- package/dist/process-tree.d.ts +41 -0
- package/dist/process-tree.js +264 -0
- package/dist/project-access.d.ts +14 -0
- package/dist/project-access.js +34 -0
- package/dist/project-cli.d.ts +18 -0
- package/dist/project-cli.js +104 -0
- package/dist/project-delete-ui.d.ts +22 -0
- package/dist/project-delete-ui.js +48 -0
- package/dist/project-delete.d.ts +55 -0
- package/dist/project-delete.js +413 -0
- package/dist/project-knowledge.d.ts +111 -0
- package/dist/project-knowledge.js +241 -0
- package/dist/project-learning.d.ts +100 -0
- package/dist/project-learning.js +438 -0
- package/dist/project-memory.d.ts +81 -0
- package/dist/project-memory.js +264 -0
- package/dist/project-skills.d.ts +152 -0
- package/dist/project-skills.js +660 -0
- package/dist/project-tools.d.ts +219 -0
- package/dist/project-tools.js +796 -0
- package/dist/project.d.ts +61 -0
- package/dist/project.js +125 -0
- package/dist/prompt.d.ts +22 -0
- package/dist/prompt.js +72 -0
- package/dist/proof.d.ts +513 -0
- package/dist/proof.js +1140 -0
- package/dist/proposal.d.ts +82 -0
- package/dist/proposal.js +210 -0
- package/dist/provider-connection.d.ts +22 -0
- package/dist/provider-connection.js +98 -0
- package/dist/provider-limits.d.ts +35 -0
- package/dist/provider-limits.js +95 -0
- package/dist/provider.d.ts +279 -0
- package/dist/provider.js +935 -0
- package/dist/publish.d.ts +107 -0
- package/dist/publish.js +650 -0
- package/dist/pulls.d.ts +118 -0
- package/dist/pulls.js +240 -0
- package/dist/push.d.ts +95 -0
- package/dist/push.js +353 -0
- package/dist/quality.d.ts +8 -0
- package/dist/quality.js +6 -0
- package/dist/recipe-ui.d.ts +18 -0
- package/dist/recipe-ui.js +152 -0
- package/dist/recipes.d.ts +81 -0
- package/dist/recipes.js +332 -0
- package/dist/remote.d.ts +67 -0
- package/dist/remote.js +122 -0
- package/dist/render.d.ts +64 -0
- package/dist/render.js +587 -0
- package/dist/report-summary.d.ts +13 -0
- package/dist/report-summary.js +15 -0
- package/dist/repos.d.ts +65 -0
- package/dist/repos.js +277 -0
- package/dist/repository-context-ui.d.ts +2 -0
- package/dist/repository-context-ui.js +10 -0
- package/dist/repository-context.d.ts +67 -0
- package/dist/repository-context.js +376 -0
- package/dist/restart-certification.d.ts +134 -0
- package/dist/restart-certification.js +226 -0
- package/dist/result-actions.d.ts +28 -0
- package/dist/result-actions.js +270 -0
- package/dist/result-completion.d.ts +18 -0
- package/dist/result-completion.js +66 -0
- package/dist/result-review.d.ts +213 -0
- package/dist/result-review.js +430 -0
- package/dist/retention-ui.d.ts +12 -0
- package/dist/retention-ui.js +39 -0
- package/dist/retention.d.ts +72 -0
- package/dist/retention.js +288 -0
- package/dist/review-context.d.ts +243 -0
- package/dist/review-context.js +1043 -0
- package/dist/review-evidence.d.ts +33 -0
- package/dist/review-evidence.js +56 -0
- package/dist/reviewer.d.ts +97 -0
- package/dist/reviewer.js +202 -0
- package/dist/routine.d.ts +228 -0
- package/dist/routine.js +764 -0
- package/dist/runner.d.ts +266 -0
- package/dist/runner.js +399 -0
- package/dist/scan.d.ts +32 -0
- package/dist/scan.js +92 -0
- package/dist/scope.d.ts +695 -0
- package/dist/scope.js +1176 -0
- package/dist/scout-report.d.ts +42 -0
- package/dist/scout-report.js +108 -0
- package/dist/scout.d.ts +69 -0
- package/dist/scout.js +351 -0
- package/dist/serve.d.ts +313 -0
- package/dist/serve.js +21776 -0
- package/dist/session-brief.d.ts +8 -0
- package/dist/session-brief.js +24 -0
- package/dist/session-cli.d.ts +13 -0
- package/dist/session-cli.js +284 -0
- package/dist/session-contract.d.ts +183 -0
- package/dist/session-contract.js +122 -0
- package/dist/session-http.d.ts +19 -0
- package/dist/session-http.js +154 -0
- package/dist/session-server.d.ts +12 -0
- package/dist/session-server.js +37 -0
- package/dist/session-service.d.ts +22 -0
- package/dist/session-service.js +157 -0
- package/dist/setup-guide.d.ts +39 -0
- package/dist/setup-guide.js +93 -0
- package/dist/sign-in-guard.d.ts +45 -0
- package/dist/sign-in-guard.js +74 -0
- package/dist/skills-ui.d.ts +11 -0
- package/dist/skills-ui.js +58 -0
- package/dist/skills.d.ts +97 -0
- package/dist/skills.js +276 -0
- package/dist/slack-api.d.ts +55 -0
- package/dist/slack-api.js +229 -0
- package/dist/slack-chat.d.ts +28 -0
- package/dist/slack-chat.js +413 -0
- package/dist/slack-settings.d.ts +7 -0
- package/dist/slack-settings.js +75 -0
- package/dist/slack-state.d.ts +13 -0
- package/dist/slack-state.js +9 -0
- package/dist/slack.d.ts +13 -0
- package/dist/slack.js +167 -0
- package/dist/spend-ui.d.ts +28 -0
- package/dist/spend-ui.js +95 -0
- package/dist/spend.d.ts +142 -0
- package/dist/spend.js +313 -0
- package/dist/sqlite-runtime.d.ts +3 -0
- package/dist/sqlite-runtime.js +6 -0
- package/dist/sso-settings.d.ts +33 -0
- package/dist/sso-settings.js +103 -0
- package/dist/sso-ui.d.ts +22 -0
- package/dist/sso-ui.js +39 -0
- package/dist/storage.d.ts +15 -0
- package/dist/storage.js +84 -0
- package/dist/store.d.ts +7754 -0
- package/dist/store.js +23279 -0
- package/dist/structured-output.d.ts +58 -0
- package/dist/structured-output.js +103 -0
- package/dist/style-asset.d.ts +7 -0
- package/dist/style-asset.js +41 -0
- package/dist/subscription-chat.d.ts +46 -0
- package/dist/subscription-chat.js +275 -0
- package/dist/summary.d.ts +33 -0
- package/dist/summary.js +71 -0
- package/dist/supervisor.mjs +297 -0
- package/dist/surface.d.ts +63 -0
- package/dist/surface.js +352 -0
- package/dist/sync.d.ts +94 -0
- package/dist/sync.js +279 -0
- package/dist/task-composer.d.ts +13 -0
- package/dist/task-composer.js +52 -0
- package/dist/task-control.d.ts +165 -0
- package/dist/task-control.js +214 -0
- package/dist/task-outcome-cli.d.ts +10 -0
- package/dist/task-outcome-cli.js +92 -0
- package/dist/task-text.d.ts +30 -0
- package/dist/task-text.js +41 -0
- package/dist/team-cli.d.ts +15 -0
- package/dist/team-cli.js +618 -0
- package/dist/team-contract.d.ts +92 -0
- package/dist/team-contract.js +1 -0
- package/dist/team-http.d.ts +17 -0
- package/dist/team-http.js +144 -0
- package/dist/team-leads.d.ts +81 -0
- package/dist/team-leads.js +565 -0
- package/dist/team-runtime.d.ts +27 -0
- package/dist/team-runtime.js +264 -0
- package/dist/team-ui.d.ts +3 -0
- package/dist/team-ui.js +9 -0
- package/dist/team-updates.d.ts +8 -0
- package/dist/team-updates.js +57 -0
- package/dist/teammate-admin.d.ts +53 -0
- package/dist/teammate-admin.js +111 -0
- package/dist/teammate-desk.d.ts +60 -0
- package/dist/teammate-desk.js +154 -0
- package/dist/teammate-memory.d.ts +64 -0
- package/dist/teammate-memory.js +196 -0
- package/dist/teammate-question.d.ts +59 -0
- package/dist/teammate-question.js +175 -0
- package/dist/teammate-tools.d.ts +108 -0
- package/dist/teammate-tools.js +271 -0
- package/dist/teammate-week.d.ts +68 -0
- package/dist/teammate-week.js +132 -0
- package/dist/teammate-work.d.ts +76 -0
- package/dist/teammate-work.js +280 -0
- package/dist/teammates-ui.d.ts +20 -0
- package/dist/teammates-ui.js +166 -0
- package/dist/teammates.d.ts +180 -0
- package/dist/teammates.js +253 -0
- package/dist/teams-api.d.ts +33 -0
- package/dist/teams-api.js +204 -0
- package/dist/teams-chat.d.ts +26 -0
- package/dist/teams-chat.js +215 -0
- package/dist/teams-settings.d.ts +12 -0
- package/dist/teams-settings.js +57 -0
- package/dist/teams.d.ts +21 -0
- package/dist/teams.js +124 -0
- package/dist/telegram-flow.d.ts +49 -0
- package/dist/telegram-flow.js +110 -0
- package/dist/telegram-mate.d.ts +137 -0
- package/dist/telegram-mate.js +707 -0
- package/dist/telegram-progress.d.ts +14 -0
- package/dist/telegram-progress.js +129 -0
- package/dist/telegram-settings.d.ts +9 -0
- package/dist/telegram-settings.js +36 -0
- package/dist/telegram-status.d.ts +75 -0
- package/dist/telegram-status.js +265 -0
- package/dist/telegram-team.d.ts +85 -0
- package/dist/telegram-team.js +359 -0
- package/dist/telegram.d.ts +230 -0
- package/dist/telegram.js +1931 -0
- package/dist/templates.d.ts +65 -0
- package/dist/templates.js +118 -0
- package/dist/tool-launcher.d.ts +1 -0
- package/dist/tool-launcher.js +101 -0
- package/dist/tools-ui.d.ts +30 -0
- package/dist/tools-ui.js +40 -0
- package/dist/transitions-recipes.d.ts +1 -0
- package/dist/transitions-recipes.js +203 -0
- package/dist/tree-proof.d.ts +34 -0
- package/dist/tree-proof.js +58 -0
- package/dist/verification-evidence.d.ts +44 -0
- package/dist/verification-evidence.js +238 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +10 -0
- package/dist/webhooks.d.ts +91 -0
- package/dist/webhooks.js +283 -0
- package/dist/work-index.d.ts +68 -0
- package/dist/work-index.js +384 -0
- package/dist/work-summary.d.ts +57 -0
- package/dist/work-summary.js +116 -0
- package/dist/workspace-motion.d.ts +4 -0
- package/dist/workspace-motion.js +133 -0
- package/dist/workspace-revision.d.ts +19 -0
- package/dist/workspace-revision.js +92 -0
- package/dist/workspace-ui.d.ts +204 -0
- package/dist/workspace-ui.js +411 -0
- package/dist/worktree-notices.d.ts +13 -0
- package/dist/worktree-notices.js +17 -0
- package/dist/worktree.d.ts +249 -0
- package/dist/worktree.js +720 -0
- package/package.json +122 -0
- package/scripts/canary-assertions.mjs +42 -0
- package/scripts/crash-canary.mjs +253 -0
- package/scripts/fixtures/crash-process.mjs +74 -0
- package/scripts/fixtures/pilot-scenarios.mjs +85 -0
- package/scripts/fixtures/restart-service.mjs +27 -0
- package/scripts/launchd-certification.mjs +143 -0
- package/scripts/pilot.mjs +121 -0
- package/scripts/proof-preflight.mjs +140 -0
- package/scripts/provider-canary.mjs +379 -0
- package/scripts/recovery-canary.mjs +135 -0
- package/scripts/restart-certification.mjs +98 -0
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
import { TeamLeads } from "./team-leads.js";
|
|
2
|
+
import { proposalPreview, tooLongText } from "./chat-channel.js";
|
|
3
|
+
import { mintCardTokens, phoneLinkButton, splitParts } from "./telegram-mate.js";
|
|
4
|
+
import { MATE_MESSAGE_MAX_CHARS } from "./mate.js";
|
|
5
|
+
import { phoneText } from "./telegram-status.js";
|
|
6
|
+
/** `/team` lists, `/team <n>` chooses, `/team off` (or `private`) leaves. Any other slash text is not ours. */
|
|
7
|
+
export function teamCommand(text) {
|
|
8
|
+
const trimmed = text.trim();
|
|
9
|
+
if (/^\/team(?:@\w+)?\s*$/.test(trimmed))
|
|
10
|
+
return { kind: "list" };
|
|
11
|
+
const pick = /^\/team(?:@\w+)?\s+([1-9][0-9]{0,2})\s*$/.exec(trimmed);
|
|
12
|
+
if (pick !== null)
|
|
13
|
+
return { kind: "select", index: Number(pick[1]) };
|
|
14
|
+
if (/^\/team(?:@\w+)?\s+(?:off|private)\s*$/i.test(trimmed))
|
|
15
|
+
return { kind: "off" };
|
|
16
|
+
return /^\/team(?:@\w+)?(?:\s|$)/.test(trimmed) ? { kind: "list" } : null;
|
|
17
|
+
}
|
|
18
|
+
export const PAIR_FIRST = "Pair your phone first: open Settings → Telegram in Toolroll, then send the code here in a private chat.";
|
|
19
|
+
function domainFor(store, projects) {
|
|
20
|
+
return new TeamLeads(store, () => projects ?? []);
|
|
21
|
+
}
|
|
22
|
+
function actorOf(binding) {
|
|
23
|
+
return { name: binding.approver, generation: binding.approverGeneration };
|
|
24
|
+
}
|
|
25
|
+
function conversationRow(store, id) {
|
|
26
|
+
const row = store.handle.prepare("SELECT c.id, c.title, c.thread, c.lead, c.visibility, l.name FROM team_conversation c JOIN team_lead l ON l.id = c.lead WHERE c.id = ?").get(id);
|
|
27
|
+
return row === undefined ? null : { conversation: String(row["id"]), title: String(row["title"]), thread: Number(row["thread"]), lead: String(row["lead"]), leadName: String(row["name"]), visibility: String(row["visibility"]) };
|
|
28
|
+
}
|
|
29
|
+
/** The conversations a person may talk in, with their lead's name, as the console lists them. */
|
|
30
|
+
function conversationsFor(store, domain, actor) {
|
|
31
|
+
try {
|
|
32
|
+
const snapshot = domain.snapshot(actor);
|
|
33
|
+
return snapshot.conversations.map(conversation => ({ conversation, leadName: snapshot.leads.find(lead => lead.id === conversation.leadId)?.name ?? "lead" }));
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function managerOf(domain, actor, conversationId) {
|
|
40
|
+
try {
|
|
41
|
+
return domain.access(actor, conversationId, "manager").role === "manager";
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function audience(conversation) {
|
|
48
|
+
return conversation.visibility === "team" ? "Team" : "Private";
|
|
49
|
+
}
|
|
50
|
+
/** The `/team` listing for a private chat: everything the person can talk in, the current choice marked. */
|
|
51
|
+
function privateListing(rows, current) {
|
|
52
|
+
if (rows.length === 0)
|
|
53
|
+
return "You are not in any team conversation yet. Join one in Toolroll under Chat → Team chat, then send /team again.";
|
|
54
|
+
const lines = ["Your conversations", ""];
|
|
55
|
+
rows.forEach((row, index) => {
|
|
56
|
+
lines.push(`${index + 1}. ${phoneText(row.conversation.title, 80)} — ${audience(row.conversation)} · lead ${phoneText(row.leadName, 40)}${row.conversation.id === current ? " (current)" : ""}`);
|
|
57
|
+
});
|
|
58
|
+
lines.push("", current === null ? "This chat talks to your private assistant. Send /team <number> to talk in a conversation instead." : "Send /team <number> to switch, or /team off for your private assistant.");
|
|
59
|
+
return lines.join("\n");
|
|
60
|
+
}
|
|
61
|
+
function groupListing(rows, current) {
|
|
62
|
+
if (rows.length === 0)
|
|
63
|
+
return "No team conversation you manage is available to this group. Create one in Toolroll under Chat → Team chat, then send /team again.";
|
|
64
|
+
const lines = [current === null ? "Choose the conversation this group follows" : "This group follows a conversation", ""];
|
|
65
|
+
rows.forEach((row, index) => {
|
|
66
|
+
lines.push(`${index + 1}. ${phoneText(row.conversation.title, 80)} — lead ${phoneText(row.leadName, 40)}${row.conversation.id === current ? " (current)" : ""}`);
|
|
67
|
+
});
|
|
68
|
+
lines.push("", "Send /team <number> to follow it (managers only), or /team off to stop. Everyone in this group will read its replies.");
|
|
69
|
+
return lines.join("\n");
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Handle one inbound message that belongs to the team layer. Returns true
|
|
73
|
+
* when it was consumed here (a `/team` command in any chat, any message in
|
|
74
|
+
* a group, or a private message while a conversation is selected); false
|
|
75
|
+
* hands the update to the older personal paths untouched.
|
|
76
|
+
*/
|
|
77
|
+
export function applyTeamInbound(input) {
|
|
78
|
+
const { store, botId, now, report, message, say } = input;
|
|
79
|
+
const chat = message.chat;
|
|
80
|
+
const from = message.from;
|
|
81
|
+
if (chat === undefined || from === undefined)
|
|
82
|
+
return false;
|
|
83
|
+
const isGroup = chat.type === "group" || chat.type === "supergroup";
|
|
84
|
+
const isPrivate = chat.type === "private";
|
|
85
|
+
if (!isGroup && !isPrivate)
|
|
86
|
+
return false;
|
|
87
|
+
const chatId = String(chat.id);
|
|
88
|
+
const suspicious = message.text === undefined || message.forward_origin !== undefined || message.forward_date !== undefined ||
|
|
89
|
+
message.via_bot !== undefined || message.sender_chat !== undefined || message.caption !== undefined;
|
|
90
|
+
if (suspicious) {
|
|
91
|
+
if (isGroup) {
|
|
92
|
+
report.ignored++;
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
const text = message.text;
|
|
98
|
+
const command = teamCommand(text);
|
|
99
|
+
const binding = store.liveTelegramBindingFor(botId, String(from.id));
|
|
100
|
+
const paired = binding !== null && store.accountOf(binding.approver)?.role === "approver";
|
|
101
|
+
const teamChat = store.telegramTeamChat(botId, chatId);
|
|
102
|
+
const domain = domainFor(store, input.projects);
|
|
103
|
+
if (command !== null) {
|
|
104
|
+
report.statusReplies = (report.statusReplies ?? 0) + 1;
|
|
105
|
+
if (!paired || binding === null) {
|
|
106
|
+
say(chatId, PAIR_FIRST);
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
const actor = actorOf(binding);
|
|
110
|
+
const rows = conversationsFor(store, domain, actor).filter(row => !isGroup || (row.conversation.visibility === "team" && managerOf(domain, actor, row.conversation.id)));
|
|
111
|
+
const current = teamChat?.conversation ?? null;
|
|
112
|
+
if (command.kind === "list") {
|
|
113
|
+
say(chatId, isGroup ? groupListing(rows, current) : privateListing(rows, current));
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
if (command.kind === "off") {
|
|
117
|
+
const ended = teamChat !== null && (!isGroup || managerOf(domain, actor, teamChat.conversation) || teamChat.boundBy === actor.name) && store.unbindTelegramTeamChat(botId, chatId, actor.name, now);
|
|
118
|
+
say(chatId, ended ? (isGroup ? "This group no longer follows a conversation." : "Back to your private assistant.") : isGroup ? "This group follows nothing you can change." : "This chat already talks to your private assistant.");
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
const chosen = rows[command.index - 1];
|
|
122
|
+
if (chosen === undefined) {
|
|
123
|
+
say(chatId, `Choose a number from the list: send /team to see it.`);
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
const bound = store.bindTelegramTeamChat({ botId, chatId, kind: isGroup ? "group" : "private", conversation: chosen.conversation.id, by: actor.name, binding: binding.id }, now);
|
|
127
|
+
if (!bound.ok) {
|
|
128
|
+
say(chatId, "Another group already follows that conversation. Stop it there first, or choose a different conversation.");
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
say(chatId, isGroup
|
|
132
|
+
? `This group now follows ${phoneText(chosen.conversation.title, 80)} (lead ${phoneText(chosen.leadName, 40)}). Paired members' messages go there; everyone here reads its replies. Send /team off to stop.`
|
|
133
|
+
: `This chat now talks in ${phoneText(chosen.conversation.title, 80)} (lead ${phoneText(chosen.leadName, 40)}). Send /team off for your private assistant.`);
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
if (teamChat === null) {
|
|
137
|
+
// A group the bot merely sits in stays silent; a private chat falls through to the personal assistant.
|
|
138
|
+
if (isGroup)
|
|
139
|
+
report.ignored++;
|
|
140
|
+
return isGroup;
|
|
141
|
+
}
|
|
142
|
+
if (!paired || binding === null) {
|
|
143
|
+
// Unpaired members' chatter is nobody's message. A slash attempt gets the one hint.
|
|
144
|
+
if (text.trimStart().startsWith("/")) {
|
|
145
|
+
say(chatId, PAIR_FIRST);
|
|
146
|
+
report.statusReplies = (report.statusReplies ?? 0) + 1;
|
|
147
|
+
}
|
|
148
|
+
else
|
|
149
|
+
report.ignored++;
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
// A current sender must not queue into a destination whose sharing grant
|
|
153
|
+
// ended: it could run successfully while every reply is withheld.
|
|
154
|
+
const grant = store.liveTelegramBindingById(teamChat.binding);
|
|
155
|
+
let connected = false;
|
|
156
|
+
try {
|
|
157
|
+
if (grant !== null && grant.botId === botId && grant.approver === teamChat.boundBy &&
|
|
158
|
+
(isGroup || grant.id === binding.id)) {
|
|
159
|
+
const access = domain.access(actorOf(grant), teamChat.conversation, isGroup ? "manager" : "viewer");
|
|
160
|
+
connected = !isGroup || access.conversation.visibility === "team";
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
catch { /* Give a reconnect instruction without disclosing conversation content. */ }
|
|
164
|
+
if (!connected) {
|
|
165
|
+
say(chatId, isGroup ? "This group's connection ended. Ask a conversation manager to send /team and connect it again." : "This chat's connection ended. Send /team and choose the conversation again.");
|
|
166
|
+
report.chatRefused = (report.chatRefused ?? 0) + 1;
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
const trimmed = text.trim();
|
|
170
|
+
if (trimmed === "") {
|
|
171
|
+
report.ignored++;
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
if (trimmed.length > MATE_MESSAGE_MAX_CHARS) {
|
|
175
|
+
say(chatId, tooLongText(trimmed.length));
|
|
176
|
+
report.chatRefused = (report.chatRefused ?? 0) + 1;
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
const actor = actorOf(binding);
|
|
180
|
+
const row = conversationRow(store, teamChat.conversation);
|
|
181
|
+
if (row === null) {
|
|
182
|
+
store.unbindTelegramTeamChat(botId, chatId, "system", now);
|
|
183
|
+
report.ignored++;
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
// Membership first, in the domain's own words: a removed person is told so.
|
|
187
|
+
try {
|
|
188
|
+
domain.access(actor, row.conversation, "contributor");
|
|
189
|
+
}
|
|
190
|
+
catch (error) {
|
|
191
|
+
say(chatId, phoneText(error instanceof Error ? error.message : "You cannot talk in this conversation.", 300));
|
|
192
|
+
report.chatRefused = (report.chatRefused ?? 0) + 1;
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
195
|
+
// Consent is the person's own, per conversation, and is checked before the
|
|
196
|
+
// message is saved: an unconsented message must not sit in the shared queue.
|
|
197
|
+
const session = store.teamMateSession(actor.name, row.thread);
|
|
198
|
+
if (session === null || session.approverGeneration !== actor.generation || session.endedAt !== null) {
|
|
199
|
+
const origin = input.phoneOrigin?.() ?? null;
|
|
200
|
+
const link = phoneLinkButton(origin, { label: "Enable chat", path: `/chat?conversation=${encodeURIComponent(row.conversation)}` });
|
|
201
|
+
say(chatId, `${phoneText(row.title, 80)}: enable chat for yourself in Toolroll first (it shows the provider and limits), then send again.`, link === null ? undefined : [link]);
|
|
202
|
+
report.chatRefused = (report.chatRefused ?? 0) + 1;
|
|
203
|
+
return true;
|
|
204
|
+
}
|
|
205
|
+
const answer = domain.execute(actor, { operation: "send", args: { conversationId: row.conversation, requestId: teamRequestId(chatId, input.updateId), text: trimmed } }, now);
|
|
206
|
+
if (!answer.ok) {
|
|
207
|
+
say(chatId, phoneText(answer.message, 300));
|
|
208
|
+
report.chatRefused = (report.chatRefused ?? 0) + 1;
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
report.chatQueued = (report.chatQueued ?? 0) + 1;
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
/** The request identity a Telegram message carries into the team queue: the chat and the update, so the same update never saves twice and delivery can tell its own messages apart. */
|
|
215
|
+
export function teamRequestId(chatId, updateId) {
|
|
216
|
+
return `telegram:${chatId}:${updateId}`;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Carry new conversation messages to every chat that follows one: the
|
|
220
|
+
* lead's replies, teammates' messages (never this chat's own), and each
|
|
221
|
+
* reply's pending cards. Read from the cursor on every cycle; a failed send
|
|
222
|
+
* leaves the cursor where it was, so the next cycle retries the same
|
|
223
|
+
* message rather than skipping it.
|
|
224
|
+
*/
|
|
225
|
+
export async function deliverTeamChats(store, botId, transport, clock, report, projects, phoneOrigin, options = {}) {
|
|
226
|
+
for (const chat of store.listTelegramTeamChats(botId)) {
|
|
227
|
+
// Selection is a grant from an exact pairing. Current membership, scope,
|
|
228
|
+
// and the selection itself are rechecked after each await and before each
|
|
229
|
+
// send; another person's new pairing cannot inherit this destination.
|
|
230
|
+
const access = async () => {
|
|
231
|
+
try {
|
|
232
|
+
if (options.canDeliver !== undefined && !options.canDeliver())
|
|
233
|
+
return null;
|
|
234
|
+
const enrolled = options.readProjects === undefined ? projects : await options.readProjects();
|
|
235
|
+
if (options.canDeliver !== undefined && !options.canDeliver())
|
|
236
|
+
return null;
|
|
237
|
+
if (store.telegramTeamChat(botId, chat.chatId)?.id !== chat.id)
|
|
238
|
+
return null;
|
|
239
|
+
const binding = store.liveTelegramBindingById(chat.binding);
|
|
240
|
+
if (binding === null || binding.botId !== botId || binding.approver !== chat.boundBy ||
|
|
241
|
+
(chat.kind === "private" && binding.chatId !== chat.chatId))
|
|
242
|
+
return null;
|
|
243
|
+
const allowed = domainFor(store, enrolled).access(actorOf(binding), chat.conversation, chat.kind === "group" ? "manager" : "viewer");
|
|
244
|
+
if (chat.kind === "group" && allowed.conversation.visibility !== "team")
|
|
245
|
+
return null;
|
|
246
|
+
return allowed.conversation.projects;
|
|
247
|
+
}
|
|
248
|
+
catch {
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
if (await access() === null)
|
|
253
|
+
continue;
|
|
254
|
+
const row = conversationRow(store, chat.conversation);
|
|
255
|
+
if (row === null)
|
|
256
|
+
continue;
|
|
257
|
+
const pending = store.handle.prepare(`SELECT m.id, m.role, m.text, m.turn, q.author, q.request_id, q.status FROM mate_message m
|
|
258
|
+
LEFT JOIN team_message q ON q.message = m.id WHERE m.thread = ? AND m.id > ? ORDER BY m.id LIMIT 20`).all(row.thread, chat.cursor);
|
|
259
|
+
for (const message of pending) {
|
|
260
|
+
const id = Number(message["id"]);
|
|
261
|
+
const role = String(message["role"]);
|
|
262
|
+
const requestId = message["request_id"] === null || message["request_id"] === undefined ? null : String(message["request_id"]);
|
|
263
|
+
const ownMessage = requestId !== null && requestId.startsWith(`telegram:${chat.chatId}:`);
|
|
264
|
+
const status = message["status"] === null || message["status"] === undefined ? null : String(message["status"]);
|
|
265
|
+
const skip = role === "operator" && (ownMessage || status === "cancelled" || String(message["author"] ?? "").length === 0);
|
|
266
|
+
const prefix = `telegram-team:${chat.id}:`;
|
|
267
|
+
if (!skip) {
|
|
268
|
+
const text = role === "assistant" ? String(message["text"]) : `${phoneText(String(message["author"]), 40)}: ${String(message["text"])}`;
|
|
269
|
+
const sent = await sendParts(store, transport, chat.chatId, text, `${prefix}message:${id}`, clock, access, report);
|
|
270
|
+
if (sent !== null) {
|
|
271
|
+
report.problems.push(`team chat ${chat.chatId}: ${sent}`);
|
|
272
|
+
break;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
let complete = true;
|
|
276
|
+
if (role === "assistant" && message["turn"] !== null) {
|
|
277
|
+
const turn = Number(message["turn"]);
|
|
278
|
+
const cardBinding = cardBindingFor(store, botId, chat, turn);
|
|
279
|
+
for (const proposal of store.listMateProposals(row.thread, ["pending"]).filter(one => one.turn === turn)) {
|
|
280
|
+
const enrolled = await access();
|
|
281
|
+
if (enrolled === null) {
|
|
282
|
+
complete = false;
|
|
283
|
+
break;
|
|
284
|
+
}
|
|
285
|
+
const preview = proposalPreview(store, proposal, enrolled, "telegram");
|
|
286
|
+
if (store.serviceCursor(`${prefix}proposal:${proposal.id}`) >= splitParts(preview.text).length)
|
|
287
|
+
continue;
|
|
288
|
+
const keyboard = [];
|
|
289
|
+
let tokens = [];
|
|
290
|
+
if (preview.buttons && cardBinding !== null) {
|
|
291
|
+
const minted = mintCardTokens(store, cardBinding, proposal.id, clock(), undefined, chat.chatId);
|
|
292
|
+
keyboard.push(...minted.keyboard);
|
|
293
|
+
tokens = minted.tokens;
|
|
294
|
+
}
|
|
295
|
+
const link = preview.buttons ? null : phoneLinkButton(phoneOrigin?.() ?? null, { label: "Open in Toolroll", path: `/chat?conversation=${encodeURIComponent(row.conversation)}&proposal=${proposal.id}` });
|
|
296
|
+
if (link !== null)
|
|
297
|
+
keyboard.push(link);
|
|
298
|
+
const problem = await sendParts(store, transport, chat.chatId, preview.text, `${prefix}proposal:${proposal.id}`, clock, access, report, keyboard.length === 0 ? undefined : keyboard, tokens);
|
|
299
|
+
if (problem !== null) {
|
|
300
|
+
for (const token of tokens)
|
|
301
|
+
store.consumeTelegramProposalAction(token, clock());
|
|
302
|
+
report.problems.push(`team card ${proposal.id}: ${problem}`);
|
|
303
|
+
complete = false;
|
|
304
|
+
break;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
if (!complete || await access() === null)
|
|
309
|
+
break;
|
|
310
|
+
// Commit only after the body and every card are delivered. Confirmed
|
|
311
|
+
// parts survive a retry, so a failed card does not repeat the body.
|
|
312
|
+
store.transact(() => {
|
|
313
|
+
if (store.advanceTelegramTeamCursor(chat.id, id))
|
|
314
|
+
store.handle.prepare("DELETE FROM service_cursor WHERE key LIKE ?").run(`${prefix}%`);
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
/** Whose binding a group card's tokens ride: the person the lead was answering, else any paired member of the conversation. */
|
|
320
|
+
function cardBindingFor(store, botId, chat, turn) {
|
|
321
|
+
const author = store.handle.prepare("SELECT author FROM team_message WHERE turn_id = ?").get(turn);
|
|
322
|
+
const bindings = store.liveTelegramBindings(botId);
|
|
323
|
+
if (chat.kind === "private")
|
|
324
|
+
return bindings.find(one => one.chatId === chat.chatId) ?? null;
|
|
325
|
+
const own = author === undefined ? undefined : bindings.find(one => one.approver === String(author["author"]));
|
|
326
|
+
if (own !== undefined)
|
|
327
|
+
return own;
|
|
328
|
+
const members = new Set(store.handle.prepare("SELECT account FROM team_participant WHERE conversation = ? AND active = 1").all(chat.conversation).map(one => String(one["account"])));
|
|
329
|
+
return bindings.find(one => members.has(one.approver)) ?? null;
|
|
330
|
+
}
|
|
331
|
+
async function sendParts(store, transport, chatId, text, receipt, clock, access, report, keyboard, tokens = []) {
|
|
332
|
+
const parts = splitParts(text);
|
|
333
|
+
const delivered = store.serviceCursor(receipt);
|
|
334
|
+
for (const [index, part] of parts.entries()) {
|
|
335
|
+
if (index < delivered)
|
|
336
|
+
continue;
|
|
337
|
+
if (await access() === null)
|
|
338
|
+
return "conversation access changed; delivery stopped";
|
|
339
|
+
const last = index === parts.length - 1;
|
|
340
|
+
try {
|
|
341
|
+
const answer = await transport("sendMessage", { chat_id: chatId, text: part, link_preview_options: { is_disabled: true }, ...(last && keyboard !== undefined ? { reply_markup: { inline_keyboard: keyboard } } : {}) });
|
|
342
|
+
if (!answer.ok)
|
|
343
|
+
return answer.description ?? "Telegram did not accept the message";
|
|
344
|
+
const messageId = typeof answer.result === "object" && answer.result !== null ? answer.result.message_id : undefined;
|
|
345
|
+
if (typeof messageId !== "number")
|
|
346
|
+
return "Telegram did not confirm a message id";
|
|
347
|
+
store.transact(() => {
|
|
348
|
+
if (last && tokens.length > 0)
|
|
349
|
+
store.placeTelegramProposalActions(tokens, String(messageId));
|
|
350
|
+
store.setServiceCursor(receipt, index + 1, clock());
|
|
351
|
+
});
|
|
352
|
+
report.sent++;
|
|
353
|
+
}
|
|
354
|
+
catch (error) {
|
|
355
|
+
return `Telegram transport failed: ${error instanceof Error ? error.message : String(error)}`;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
return null;
|
|
359
|
+
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Telegram bridge: decisions out, answers back, zero tokens spent.
|
|
3
|
+
*
|
|
4
|
+
* This is channel plumbing — no LLM is anywhere in this path, and everything
|
|
5
|
+
* it renders is deterministic. The security model, in one breath: a chat is
|
|
6
|
+
* not a person, so pairing is a local authenticated act that binds one
|
|
7
|
+
* private chat AND one immutable user id to one approver generation; a
|
|
8
|
+
* button is not a command, so callback_data carries only an opaque one-time
|
|
9
|
+
* token whose meaning lives in this database where a stolen bot token
|
|
10
|
+
* cannot read it; and an answer lands in the same transaction that proves
|
|
11
|
+
* the binding is still live and consumes the token — or it does not land.
|
|
12
|
+
*
|
|
13
|
+
* What a stolen bot token CAN do is stated rather than wished away: read
|
|
14
|
+
* the decision text this installation chose to send through Telegram,
|
|
15
|
+
* repaint the bot's keyboards with deceptive labels, and race our poll for
|
|
16
|
+
* updates. It cannot mint an action token, answer as the operator, or make
|
|
17
|
+
* `answered_by` say anything the pairing did not authorize. Rotating the
|
|
18
|
+
* BotFather token plus `bridge telegram unpair` is the recovery, and both
|
|
19
|
+
* are one command.
|
|
20
|
+
*/
|
|
21
|
+
import { type Store, type Notification } from "./store.js";
|
|
22
|
+
import { type TelegramConversationOptions } from "./telegram-mate.js";
|
|
23
|
+
/** Read the enrolled project list on demand. No callback means no task data,
|
|
24
|
+
* never an implicit all-database ceiling. Shared by pass and embedded follower. */
|
|
25
|
+
export type TelegramReadProjects = () => Promise<readonly string[]>;
|
|
26
|
+
/** The environment name — and therefore the name the builder strips from agents (both names; see names.ts). */
|
|
27
|
+
export { TELEGRAM_TOKEN_ENV as TOKEN_ENV, TELEGRAM_TOKEN_ENVS as TOKEN_ENVS } from "./names.js";
|
|
28
|
+
export declare const PAIRING_TTL_MS: number;
|
|
29
|
+
export declare const CONFIRM_TTL_MS: number;
|
|
30
|
+
export declare const BRIDGE_LEASE_MS: number;
|
|
31
|
+
export declare const DELIVERY_CLAIM_MS: number;
|
|
32
|
+
/** Where Telegram pushes this bot's updates: under the public hooks address (Tailscale Funnel covers /hooks). */
|
|
33
|
+
export declare const TELEGRAM_HOOK_PATH = "/hooks/telegram";
|
|
34
|
+
/** The secret Telegram sends with every push (its header), kept beside the database; made once when asked to. */
|
|
35
|
+
export declare function telegramHookSecret(dir: string, make?: boolean): string | null;
|
|
36
|
+
/** The address Telegram pushes to: the public hooks address (https only) and /hooks/telegram; null without one. */
|
|
37
|
+
export declare function telegramPushUrl(hooksBase: string | null): string | null;
|
|
38
|
+
/** A push is Telegram's when its secret header matches ours (compared in constant time). */
|
|
39
|
+
export declare function pushedByTelegram(header: unknown, secret: string | null): boolean;
|
|
40
|
+
/** Keep one pushed update for the bridge: a JSON object with a positive integer update_id. */
|
|
41
|
+
export declare function keepPushedUpdate(store: Store, botId: string, body: Buffer, now: Date): {
|
|
42
|
+
ok: true;
|
|
43
|
+
kept: boolean;
|
|
44
|
+
} | {
|
|
45
|
+
ok: false;
|
|
46
|
+
};
|
|
47
|
+
export type TokenSource = {
|
|
48
|
+
token: string;
|
|
49
|
+
botId: string;
|
|
50
|
+
source: "env" | "file";
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Environment wins, the credential file beside the database otherwise.
|
|
54
|
+
* The file is how the CLI and the web settings card set it (0600, owner
|
|
55
|
+
* only); the env var is how people who already run keychain tooling keep
|
|
56
|
+
* it out of files entirely.
|
|
57
|
+
*/
|
|
58
|
+
export declare function loadBotToken(env: Record<string, string | undefined>, file: string): TokenSource | null;
|
|
59
|
+
/** Write the credential file, owner-only. Refuses a string that is not a bot token. */
|
|
60
|
+
export declare function saveBotToken(file: string, token: string): {
|
|
61
|
+
ok: true;
|
|
62
|
+
} | {
|
|
63
|
+
ok: false;
|
|
64
|
+
message: string;
|
|
65
|
+
};
|
|
66
|
+
export declare function clearBotToken(file: string): boolean;
|
|
67
|
+
/** The last four characters are enough to recognize a token without holding it. */
|
|
68
|
+
export declare function redactToken(token: string): string;
|
|
69
|
+
/** Scrub a token out of any text on its way to a log, an error, or a row. */
|
|
70
|
+
export declare function scrub(text: string, token: string): string;
|
|
71
|
+
/**
|
|
72
|
+
* One Bot API call. Injectable, so the suite scripts Telegram instead of
|
|
73
|
+
* dialing it. The optional signal lets a follower cancel a long poll the
|
|
74
|
+
* moment it is told to stop, instead of waiting the poll window out.
|
|
75
|
+
*/
|
|
76
|
+
export type TelegramTransport = (method: string, params: Record<string, unknown>, signal?: AbortSignal,
|
|
77
|
+
/** v64: one verified file to send as multipart — the ONLY way bytes leave. Absent, the call is JSON exactly as before. */
|
|
78
|
+
upload?: TelegramUpload) => Promise<{
|
|
79
|
+
ok: boolean;
|
|
80
|
+
result?: unknown;
|
|
81
|
+
description?: string;
|
|
82
|
+
parameters?: {
|
|
83
|
+
retry_after?: number;
|
|
84
|
+
};
|
|
85
|
+
uncertain?: boolean;
|
|
86
|
+
}>;
|
|
87
|
+
/**
|
|
88
|
+
* The one typed shape a file takes onto the wire: bytes the caller already
|
|
89
|
+
* verified, under a name and type the caller chose from the verified
|
|
90
|
+
* kind. There is no path, no URL and no `file_id` form here on purpose —
|
|
91
|
+
* Telegram never fetches anything for us, and nothing on this machine is
|
|
92
|
+
* uploaded by name.
|
|
93
|
+
*/
|
|
94
|
+
export type TelegramUpload = {
|
|
95
|
+
field: "document";
|
|
96
|
+
fileName: string;
|
|
97
|
+
contentType: "image/png" | "image/jpeg";
|
|
98
|
+
bytes: Buffer;
|
|
99
|
+
};
|
|
100
|
+
export declare function createTransport(token: string, timeoutMs?: number): TelegramTransport;
|
|
101
|
+
/** The multipart body: every param a field (objects as JSON), the file last. Exported for the adapter's own test only. */
|
|
102
|
+
export declare function multipartOf(params: Record<string, unknown>, upload: TelegramUpload): FormData;
|
|
103
|
+
export declare function hashPairingCode(code: string): string;
|
|
104
|
+
/** 128 bits, hex — pasteable, and not guessable inside any code's lifetime. */
|
|
105
|
+
export declare function mintPairingCode(): string;
|
|
106
|
+
export type BridgeReport = {
|
|
107
|
+
sent: number;
|
|
108
|
+
answered: number;
|
|
109
|
+
paired: number;
|
|
110
|
+
ignored: number;
|
|
111
|
+
/** Updates Telegram still holds beyond this pass's page budget. */
|
|
112
|
+
backlog: boolean;
|
|
113
|
+
problems: string[];
|
|
114
|
+
/** Free-text notes captured as drafts. */
|
|
115
|
+
noted?: number;
|
|
116
|
+
/** Digests sent this pass (away mode). */
|
|
117
|
+
digests?: number;
|
|
118
|
+
/** Successful replies to read-only phone commands, separate from outbox sends. */
|
|
119
|
+
statusReplies?: number;
|
|
120
|
+
/** Ordinary messages persisted for the shared assistant this pass. */
|
|
121
|
+
chatQueued?: number;
|
|
122
|
+
/** Assistant replies delivered (including a recovered earlier reply). */
|
|
123
|
+
chatAnswered?: number;
|
|
124
|
+
/** Messages settled without a reply from the assistant: refused, failed, unpaired, or answered deterministically. */
|
|
125
|
+
chatRefused?: number;
|
|
126
|
+
/** Proposal cards confirmed by a tap this pass. */
|
|
127
|
+
chatConfirmed?: number;
|
|
128
|
+
/** v98: another program asked Telegram for this bot's updates (harmless: nothing is lost). */
|
|
129
|
+
contention?: number;
|
|
130
|
+
/** v98: Telegram answered that it pushes this bot's updates to a webhook. */
|
|
131
|
+
webhookActive?: boolean;
|
|
132
|
+
/** v98: pushed updates applied this pass. */
|
|
133
|
+
pushed?: number;
|
|
134
|
+
};
|
|
135
|
+
/**
|
|
136
|
+
* One bridge pass: claim and send what the outbox holds, then read one
|
|
137
|
+
* budgeted window of updates and apply each in its own transaction. Cron
|
|
138
|
+
* calls this; running it twice concurrently loses the lease race and does
|
|
139
|
+
* nothing, which is the design working.
|
|
140
|
+
*/
|
|
141
|
+
export declare function bridgePass(store: Store, options: {
|
|
142
|
+
botId: string;
|
|
143
|
+
transport: TelegramTransport;
|
|
144
|
+
clock?: () => Date;
|
|
145
|
+
owner?: string;
|
|
146
|
+
/** false: inbound only — another channel is primary and carries the
|
|
147
|
+
* pages; taps and replies still land here. */
|
|
148
|
+
deliver?: boolean;
|
|
149
|
+
readProjects?: TelegramReadProjects;
|
|
150
|
+
/** Reload channel configuration before every outbound part. */
|
|
151
|
+
canDeliver?: () => boolean;
|
|
152
|
+
/** Ordinary text talks to the shared assistant. Absent: text that is not a command or a decision note is ignored, as before. */
|
|
153
|
+
conversation?: TelegramConversationOptions;
|
|
154
|
+
}): Promise<{
|
|
155
|
+
ok: true;
|
|
156
|
+
report: BridgeReport;
|
|
157
|
+
} | {
|
|
158
|
+
ok: false;
|
|
159
|
+
reason: "bridge-busy";
|
|
160
|
+
message: string;
|
|
161
|
+
}>;
|
|
162
|
+
/**
|
|
163
|
+
* The longest long poll the follower may ask for. `createTransport`'s HTTP
|
|
164
|
+
* timeout is 30s and must outlive the poll window, or the client would abort
|
|
165
|
+
* a poll Telegram is still honestly holding open.
|
|
166
|
+
*/
|
|
167
|
+
export declare const MAX_POLL_SECONDS = 25;
|
|
168
|
+
export type FollowReport = {
|
|
169
|
+
cycles: number;
|
|
170
|
+
sent: number;
|
|
171
|
+
answered: number;
|
|
172
|
+
paired: number;
|
|
173
|
+
ignored: number;
|
|
174
|
+
problems: string[];
|
|
175
|
+
statusReplies?: number;
|
|
176
|
+
chatQueued?: number;
|
|
177
|
+
chatAnswered?: number;
|
|
178
|
+
chatRefused?: number;
|
|
179
|
+
chatConfirmed?: number;
|
|
180
|
+
};
|
|
181
|
+
/**
|
|
182
|
+
* The follower (§M4): one actor that holds the poll lease and stays on the
|
|
183
|
+
* wire, so an answer tapped on a phone reaches the store in seconds, not at
|
|
184
|
+
* the next cron firing. `bridge telegram --follow` runs it standalone;
|
|
185
|
+
* watch embeds the same actor — the poll lease guarantees only one is
|
|
186
|
+
* live, and a cron pass overlapping it simply loses the lease race.
|
|
187
|
+
*
|
|
188
|
+
* Each cycle re-acquires the lease under the same owner — that is the
|
|
189
|
+
* fenced renewal: same generation while held, and if the lease lapsed
|
|
190
|
+
* mid-poll (a stall longer than the TTL), the re-acquire takes the next
|
|
191
|
+
* generation and the cursor rides it, so nothing this follower stamped
|
|
192
|
+
* with the old generation can move state afterwards. Transport failures
|
|
193
|
+
* back off exponentially and are counted, not hidden; cancellation aborts
|
|
194
|
+
* the in-flight long poll instead of waiting it out.
|
|
195
|
+
*/
|
|
196
|
+
/** What Telegram lists when the person taps "/". */
|
|
197
|
+
export declare const TELEGRAM_COMMANDS: {
|
|
198
|
+
command: string;
|
|
199
|
+
description: string;
|
|
200
|
+
}[];
|
|
201
|
+
export declare function followBridge(store: Store, options: {
|
|
202
|
+
botId: string;
|
|
203
|
+
transport: TelegramTransport;
|
|
204
|
+
signal: AbortSignal;
|
|
205
|
+
clock?: () => Date;
|
|
206
|
+
owner?: string;
|
|
207
|
+
/** false: inbound only; another channel is primary. */
|
|
208
|
+
deliver?: boolean;
|
|
209
|
+
readProjects?: TelegramReadProjects;
|
|
210
|
+
/** Reload channel configuration before every outbound part. */
|
|
211
|
+
canDeliver?: () => boolean;
|
|
212
|
+
/** Ordinary text talks to the shared assistant. */
|
|
213
|
+
conversation?: TelegramConversationOptions;
|
|
214
|
+
pollSeconds?: number;
|
|
215
|
+
/** One line per cycle that did something — the follower's narration hook. */
|
|
216
|
+
onCycle?: (report: BridgeReport) => void;
|
|
217
|
+
/** Injectable for tests; the default resolves early on abort. */
|
|
218
|
+
sleep?: (ms: number) => Promise<void>;
|
|
219
|
+
/** v98: have Telegram push updates to this public address (with this secret) instead of asking for them. */
|
|
220
|
+
push?: {
|
|
221
|
+
url: string;
|
|
222
|
+
secret: string;
|
|
223
|
+
} | null;
|
|
224
|
+
/** v98: how often to check Telegram still pushes to us (tests shorten it). */
|
|
225
|
+
pushCheckMs?: number;
|
|
226
|
+
}): Promise<FollowReport>;
|
|
227
|
+
/** The digest text: a header with the count and the window, then one
|
|
228
|
+
* fact per entry — its subject, then its body's first line, indented.
|
|
229
|
+
* Plain text, no buttons: nothing in a digest is tappable. */
|
|
230
|
+
export declare function digestText(rows: readonly Notification[], since: string | null, now: Date): string;
|