tinybeeai 0.0.0-stage → 0.16.1
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/.railway/railway.ts +83 -0
- package/.railway/tsconfig.json +13 -0
- package/AGENTS.md +102 -0
- package/Dockerfile +108 -0
- package/LICENSE +21 -0
- package/README.md +275 -2
- package/bin/localdost.js +29 -0
- package/bin/tinybeeai.js +28 -0
- package/compose.yml +63 -0
- package/demo/payloads/ci_failure.json +46 -0
- package/demo/payloads/outage.json +18 -0
- package/demo/payloads/pr_comment_edited.json +50 -0
- package/demo/payloads/pr_merged.json +80 -0
- package/demo/payloads/pr_opened.json +69 -0
- package/demo/payloads/pr_synchronize.json +69 -0
- package/demo/payloads/ticket_created.json +138 -0
- package/demo/sample-app/playwright.config.ts +20 -0
- package/demo/sample-app/server.ts +163 -0
- package/demo/sample-app/tests/checkout.spec.ts +13 -0
- package/demo/sample-app/tests/smoke.spec.ts +8 -0
- package/dist/agent/approval-retries.js +59 -0
- package/dist/agent/approval-retries.js.map +1 -0
- package/dist/agent/approvals.js +390 -0
- package/dist/agent/approvals.js.map +1 -0
- package/dist/agent/chat-bridge.js +154 -0
- package/dist/agent/chat-bridge.js.map +1 -0
- package/dist/agent/chat-lead.js +475 -0
- package/dist/agent/chat-lead.js.map +1 -0
- package/dist/agent/chat-loop.js +106 -0
- package/dist/agent/chat-loop.js.map +1 -0
- package/dist/agent/chat-mcp.js +49 -0
- package/dist/agent/chat-mcp.js.map +1 -0
- package/dist/agent/chat-tool-blocks.js +453 -0
- package/dist/agent/chat-tool-blocks.js.map +1 -0
- package/dist/agent/chat-view.js +94 -0
- package/dist/agent/chat-view.js.map +1 -0
- package/dist/agent/chat-views.js +781 -0
- package/dist/agent/chat-views.js.map +1 -0
- package/dist/agent/chat.js +1703 -0
- package/dist/agent/chat.js.map +1 -0
- package/dist/agent/destinations.js +150 -0
- package/dist/agent/destinations.js.map +1 -0
- package/dist/agent/llm-cli.js +451 -0
- package/dist/agent/llm-cli.js.map +1 -0
- package/dist/agent/llm-codex.js +493 -0
- package/dist/agent/llm-codex.js.map +1 -0
- package/dist/agent/llm-openai.js +211 -0
- package/dist/agent/llm-openai.js.map +1 -0
- package/dist/agent/llm.js +1464 -0
- package/dist/agent/llm.js.map +1 -0
- package/dist/agent/memory.js +33 -0
- package/dist/agent/memory.js.map +1 -0
- package/dist/agent/mock-views.js +93 -0
- package/dist/agent/mock-views.js.map +1 -0
- package/dist/agent/model-availability.js +207 -0
- package/dist/agent/model-availability.js.map +1 -0
- package/dist/agent/model-errors.js +188 -0
- package/dist/agent/model-errors.js.map +1 -0
- package/dist/agent/model-guard.js +311 -0
- package/dist/agent/model-guard.js.map +1 -0
- package/dist/agent/prompts.js +374 -0
- package/dist/agent/prompts.js.map +1 -0
- package/dist/agent/qa-lenses/qa-auth.md +32 -0
- package/dist/agent/qa-lenses/qa-check-then-act.md +33 -0
- package/dist/agent/qa-lenses/qa-control-has-no-effect.md +32 -0
- package/dist/agent/qa-lenses/qa-dates.md +31 -0
- package/dist/agent/qa-lenses/qa-double-submit.md +34 -0
- package/dist/agent/qa-lenses/qa-feature-flag.md +29 -0
- package/dist/agent/qa-lenses/qa-migration.md +35 -0
- package/dist/agent/qa-lenses/qa-money.md +38 -0
- package/dist/agent/qa-lenses/qa-response-shape-break.md +33 -0
- package/dist/agent/qa-lenses/qa-validation.md +29 -0
- package/dist/agent/qa-prompts.js +660 -0
- package/dist/agent/qa-prompts.js.map +1 -0
- package/dist/agent/reasoning.js +55 -0
- package/dist/agent/reasoning.js.map +1 -0
- package/dist/agent/redact.js +48 -0
- package/dist/agent/redact.js.map +1 -0
- package/dist/agent/rules.js +927 -0
- package/dist/agent/rules.js.map +1 -0
- package/dist/agent/runs.js +440 -0
- package/dist/agent/runs.js.map +1 -0
- package/dist/agent/send-retry.js +62 -0
- package/dist/agent/send-retry.js.map +1 -0
- package/dist/agent/team-notes.js +288 -0
- package/dist/agent/team-notes.js.map +1 -0
- package/dist/agent/token-meter.js +31 -0
- package/dist/agent/token-meter.js.map +1 -0
- package/dist/agent/triage.js +506 -0
- package/dist/agent/triage.js.map +1 -0
- package/dist/api.js +1047 -0
- package/dist/api.js.map +1 -0
- package/dist/approval-targets.js +107 -0
- package/dist/approval-targets.js.map +1 -0
- package/dist/bee/cloudflared.js +108 -0
- package/dist/bee/cloudflared.js.map +1 -0
- package/dist/bee/health.js +99 -0
- package/dist/bee/health.js.map +1 -0
- package/dist/bee/hive-client.js +75 -0
- package/dist/bee/hive-client.js.map +1 -0
- package/dist/bee/identity.js +39 -0
- package/dist/bee/identity.js.map +1 -0
- package/dist/bee/models.js +510 -0
- package/dist/bee/models.js.map +1 -0
- package/dist/bee/pairing.js +351 -0
- package/dist/bee/pairing.js.map +1 -0
- package/dist/bee/relay-client.js +50 -0
- package/dist/bee/relay-client.js.map +1 -0
- package/dist/bee/routes.js +214 -0
- package/dist/bee/routes.js.map +1 -0
- package/dist/bee/setup-page.js +223 -0
- package/dist/bee/setup-page.js.map +1 -0
- package/dist/bee/state.js +41 -0
- package/dist/bee/state.js.map +1 -0
- package/dist/bee/status.js +117 -0
- package/dist/bee/status.js.map +1 -0
- package/dist/bee/tunnel-manager.js +245 -0
- package/dist/bee/tunnel-manager.js.map +1 -0
- package/dist/bee/updater.js +268 -0
- package/dist/bee/updater.js.map +1 -0
- package/dist/boot.js +41 -0
- package/dist/boot.js.map +1 -0
- package/dist/call/index.js +145 -0
- package/dist/call/index.js.map +1 -0
- package/dist/case-folders.js +294 -0
- package/dist/case-folders.js.map +1 -0
- package/dist/cases.js +554 -0
- package/dist/cases.js.map +1 -0
- package/dist/chat-blocks.js +709 -0
- package/dist/chat-blocks.js.map +1 -0
- package/dist/chat-parse-util.js +38 -0
- package/dist/chat-parse-util.js.map +1 -0
- package/dist/chat-results.js +96 -0
- package/dist/chat-results.js.map +1 -0
- package/dist/chat-stats.js +193 -0
- package/dist/chat-stats.js.map +1 -0
- package/dist/chat-view-specs.js +186 -0
- package/dist/chat-view-specs.js.map +1 -0
- package/dist/cli/doctor-ops.js +361 -0
- package/dist/cli/doctor-ops.js.map +1 -0
- package/dist/cli/doctor.js +289 -0
- package/dist/cli/doctor.js.map +1 -0
- package/dist/cli/dogfood.js +70 -0
- package/dist/cli/dogfood.js.map +1 -0
- package/dist/cli/env-file.js +34 -0
- package/dist/cli/env-file.js.map +1 -0
- package/dist/cli/eval-chat-cases.js +359 -0
- package/dist/cli/eval-chat-cases.js.map +1 -0
- package/dist/cli/eval-chat-results.js +85 -0
- package/dist/cli/eval-chat-results.js.map +1 -0
- package/dist/cli/eval-chat.js +508 -0
- package/dist/cli/eval-chat.js.map +1 -0
- package/dist/cli/eval-designer.js +67 -0
- package/dist/cli/eval-designer.js.map +1 -0
- package/dist/cli/eval-qa-review.js +284 -0
- package/dist/cli/eval-qa-review.js.map +1 -0
- package/dist/cli/golden-export.js +35 -0
- package/dist/cli/golden-export.js.map +1 -0
- package/dist/cli/index.js +90 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/init.js +746 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/scan-repo.js +199 -0
- package/dist/cli/scan-repo.js.map +1 -0
- package/dist/cli/seed.js +108 -0
- package/dist/cli/seed.js.map +1 -0
- package/dist/cli/tinybee.js +247 -0
- package/dist/cli/tinybee.js.map +1 -0
- package/dist/config.js +138 -0
- package/dist/config.js.map +1 -0
- package/dist/connect/aivision-local.js +833 -0
- package/dist/connect/aivision-local.js.map +1 -0
- package/dist/connect/aivision-token.js +84 -0
- package/dist/connect/aivision-token.js.map +1 -0
- package/dist/connect/crypto.js +65 -0
- package/dist/connect/crypto.js.map +1 -0
- package/dist/connect/deps.js +110 -0
- package/dist/connect/deps.js.map +1 -0
- package/dist/connect/engines.js +144 -0
- package/dist/connect/engines.js.map +1 -0
- package/dist/connect/github-access.js +74 -0
- package/dist/connect/github-access.js.map +1 -0
- package/dist/connect/github-app.js +129 -0
- package/dist/connect/github-app.js.map +1 -0
- package/dist/connect/index.js +9 -0
- package/dist/connect/index.js.map +1 -0
- package/dist/connect/model-choices.js +201 -0
- package/dist/connect/model-choices.js.map +1 -0
- package/dist/connect/providers.js +900 -0
- package/dist/connect/providers.js.map +1 -0
- package/dist/connect/readiness.js +232 -0
- package/dist/connect/readiness.js.map +1 -0
- package/dist/connect/redact.js +16 -0
- package/dist/connect/redact.js.map +1 -0
- package/dist/connect/registry.js +406 -0
- package/dist/connect/registry.js.map +1 -0
- package/dist/connect/store.js +283 -0
- package/dist/connect/store.js.map +1 -0
- package/dist/connect/tunnel.js +134 -0
- package/dist/connect/tunnel.js.map +1 -0
- package/dist/connect/types.js +8 -0
- package/dist/connect/types.js.map +1 -0
- package/dist/connections/index.js +5 -0
- package/dist/connections/index.js.map +1 -0
- package/dist/connections/manager.js +223 -0
- package/dist/connections/manager.js.map +1 -0
- package/dist/connections/presets.js +264 -0
- package/dist/connections/presets.js.map +1 -0
- package/dist/connections/store.js +132 -0
- package/dist/connections/store.js.map +1 -0
- package/dist/connections/tools.js +206 -0
- package/dist/connections/tools.js.map +1 -0
- package/dist/db-schema.js +355 -0
- package/dist/db-schema.js.map +1 -0
- package/dist/db.js +379 -0
- package/dist/db.js.map +1 -0
- package/dist/delivery.js +49 -0
- package/dist/delivery.js.map +1 -0
- package/dist/deploy-wait.js +701 -0
- package/dist/deploy-wait.js.map +1 -0
- package/dist/disk.js +243 -0
- package/dist/disk.js.map +1 -0
- package/dist/dot-health.js +966 -0
- package/dist/dot-health.js.map +1 -0
- package/dist/dot-remove.js +246 -0
- package/dist/dot-remove.js.map +1 -0
- package/dist/events.js +38 -0
- package/dist/events.js.map +1 -0
- package/dist/evidence-pack.js +423 -0
- package/dist/evidence-pack.js.map +1 -0
- package/dist/flows/answers.js +289 -0
- package/dist/flows/answers.js.map +1 -0
- package/dist/flows/change.js +811 -0
- package/dist/flows/change.js.map +1 -0
- package/dist/flows/checkStatus.js +524 -0
- package/dist/flows/checkStatus.js.map +1 -0
- package/dist/flows/ci.js +236 -0
- package/dist/flows/ci.js.map +1 -0
- package/dist/flows/ciFailure.js +76 -0
- package/dist/flows/ciFailure.js.map +1 -0
- package/dist/flows/common.js +522 -0
- package/dist/flows/common.js.map +1 -0
- package/dist/flows/default-branch.js +29 -0
- package/dist/flows/default-branch.js.map +1 -0
- package/dist/flows/defectDraft.js +447 -0
- package/dist/flows/defectDraft.js.map +1 -0
- package/dist/flows/defects.js +623 -0
- package/dist/flows/defects.js.map +1 -0
- package/dist/flows/digest.js +295 -0
- package/dist/flows/digest.js.map +1 -0
- package/dist/flows/impact.js +82 -0
- package/dist/flows/impact.js.map +1 -0
- package/dist/flows/index.js +23 -0
- package/dist/flows/index.js.map +1 -0
- package/dist/flows/intent-sources.js +72 -0
- package/dist/flows/intent-sources.js.map +1 -0
- package/dist/flows/mergeCases.js +169 -0
- package/dist/flows/mergeCases.js.map +1 -0
- package/dist/flows/nightlyRun.js +82 -0
- package/dist/flows/nightlyRun.js.map +1 -0
- package/dist/flows/outage.js +64 -0
- package/dist/flows/outage.js.map +1 -0
- package/dist/flows/postMerge.js +5 -0
- package/dist/flows/postMerge.js.map +1 -0
- package/dist/flows/prCheck.js +5 -0
- package/dist/flows/prCheck.js.map +1 -0
- package/dist/flows/prTarget.js +66 -0
- package/dist/flows/prTarget.js.map +1 -0
- package/dist/flows/qa.js +363 -0
- package/dist/flows/qa.js.map +1 -0
- package/dist/flows/questions.js +160 -0
- package/dist/flows/questions.js.map +1 -0
- package/dist/flows/recovery.js +64 -0
- package/dist/flows/recovery.js.map +1 -0
- package/dist/flows/rerun.js +50 -0
- package/dist/flows/rerun.js.map +1 -0
- package/dist/flows/testDesign.js +294 -0
- package/dist/flows/testDesign.js.map +1 -0
- package/dist/flows/testDesignJob.js +46 -0
- package/dist/flows/testDesignJob.js.map +1 -0
- package/dist/flows/testRepo.js +228 -0
- package/dist/flows/testRepo.js.map +1 -0
- package/dist/flows/testSavings.js +381 -0
- package/dist/flows/testSavings.js.map +1 -0
- package/dist/flows/testStatus.js +343 -0
- package/dist/flows/testStatus.js.map +1 -0
- package/dist/flows/ticketContext.js +425 -0
- package/dist/flows/ticketContext.js.map +1 -0
- package/dist/flows/ticketReview.js +249 -0
- package/dist/flows/ticketReview.js.map +1 -0
- package/dist/flows/ticks.js +73 -0
- package/dist/flows/ticks.js.map +1 -0
- package/dist/hive/admin.js +122 -0
- package/dist/hive/admin.js.map +1 -0
- package/dist/hive/auth.js +130 -0
- package/dist/hive/auth.js.map +1 -0
- package/dist/hive/billing.js +278 -0
- package/dist/hive/billing.js.map +1 -0
- package/dist/hive/config.js +63 -0
- package/dist/hive/config.js.map +1 -0
- package/dist/hive/db.js +186 -0
- package/dist/hive/db.js.map +1 -0
- package/dist/hive/embed.js +76 -0
- package/dist/hive/embed.js.map +1 -0
- package/dist/hive/errors.js +17 -0
- package/dist/hive/errors.js.map +1 -0
- package/dist/hive/google.js +75 -0
- package/dist/hive/google.js.map +1 -0
- package/dist/hive/index.js +25 -0
- package/dist/hive/index.js.map +1 -0
- package/dist/hive/jwt.js +57 -0
- package/dist/hive/jwt.js.map +1 -0
- package/dist/hive/keys.js +61 -0
- package/dist/hive/keys.js.map +1 -0
- package/dist/hive/mail.js +27 -0
- package/dist/hive/mail.js.map +1 -0
- package/dist/hive/native-push.js +255 -0
- package/dist/hive/native-push.js.map +1 -0
- package/dist/hive/pairing.js +342 -0
- package/dist/hive/pairing.js.map +1 -0
- package/dist/hive/presence.js +121 -0
- package/dist/hive/presence.js.map +1 -0
- package/dist/hive/push.js +185 -0
- package/dist/hive/push.js.map +1 -0
- package/dist/hive/relay.js +205 -0
- package/dist/hive/relay.js.map +1 -0
- package/dist/hive/routes.js +228 -0
- package/dist/hive/routes.js.map +1 -0
- package/dist/hive/server.js +12 -0
- package/dist/hive/server.js.map +1 -0
- package/dist/hive/session.js +28 -0
- package/dist/hive/session.js.map +1 -0
- package/dist/hive/tenants.js +287 -0
- package/dist/hive/tenants.js.map +1 -0
- package/dist/hive/tunnel/cloudflare.js +160 -0
- package/dist/hive/tunnel/cloudflare.js.map +1 -0
- package/dist/hive/tunnel/mock.js +52 -0
- package/dist/hive/tunnel/mock.js.map +1 -0
- package/dist/hive/tunnel/provider.js +19 -0
- package/dist/hive/tunnel/provider.js.map +1 -0
- package/dist/hive/updates.js +61 -0
- package/dist/hive/updates.js.map +1 -0
- package/dist/http-error.js +11 -0
- package/dist/http-error.js.map +1 -0
- package/dist/impact/classify.js +226 -0
- package/dist/impact/classify.js.map +1 -0
- package/dist/impact/find.js +259 -0
- package/dist/impact/find.js.map +1 -0
- package/dist/impact/pr.js +54 -0
- package/dist/impact/pr.js.map +1 -0
- package/dist/impact/store.js +365 -0
- package/dist/impact/store.js.map +1 -0
- package/dist/impact/text.js +158 -0
- package/dist/impact/text.js.map +1 -0
- package/dist/index.js +288 -0
- package/dist/index.js.map +1 -0
- package/dist/integrations/adf.js +397 -0
- package/dist/integrations/adf.js.map +1 -0
- package/dist/integrations/bot-marker.js +103 -0
- package/dist/integrations/bot-marker.js.map +1 -0
- package/dist/integrations/github-issues.js +705 -0
- package/dist/integrations/github-issues.js.map +1 -0
- package/dist/integrations/github.js +1284 -0
- package/dist/integrations/github.js.map +1 -0
- package/dist/integrations/issue-create.js +60 -0
- package/dist/integrations/issue-create.js.map +1 -0
- package/dist/integrations/jira.js +279 -0
- package/dist/integrations/jira.js.map +1 -0
- package/dist/integrations/linear.js +273 -0
- package/dist/integrations/linear.js.map +1 -0
- package/dist/integrations/own-identity-warm.js +47 -0
- package/dist/integrations/own-identity-warm.js.map +1 -0
- package/dist/integrations/own-identity.js +39 -0
- package/dist/integrations/own-identity.js.map +1 -0
- package/dist/integrations/status-change.js +42 -0
- package/dist/integrations/status-change.js.map +1 -0
- package/dist/lead-defects.js +202 -0
- package/dist/lead-defects.js.map +1 -0
- package/dist/lead-digest.js +333 -0
- package/dist/lead-digest.js.map +1 -0
- package/dist/lead-release.js +286 -0
- package/dist/lead-release.js.map +1 -0
- package/dist/lead.js +328 -0
- package/dist/lead.js.map +1 -0
- package/dist/legacy-names.js +51 -0
- package/dist/legacy-names.js.map +1 -0
- package/dist/limits.js +44 -0
- package/dist/limits.js.map +1 -0
- package/dist/link-trust.js +224 -0
- package/dist/link-trust.js.map +1 -0
- package/dist/links.js +219 -0
- package/dist/links.js.map +1 -0
- package/dist/loopback.js +35 -0
- package/dist/loopback.js.map +1 -0
- package/dist/model-cap.js +60 -0
- package/dist/model-cap.js.map +1 -0
- package/dist/model-usage.js +173 -0
- package/dist/model-usage.js.map +1 -0
- package/dist/monitor/backfill.js +39 -0
- package/dist/monitor/backfill.js.map +1 -0
- package/dist/monitor/browser-probe.js +198 -0
- package/dist/monitor/browser-probe.js.map +1 -0
- package/dist/monitor/catch-up.js +31 -0
- package/dist/monitor/catch-up.js.map +1 -0
- package/dist/monitor/pollers.js +1606 -0
- package/dist/monitor/pollers.js.map +1 -0
- package/dist/monitor/probes.js +178 -0
- package/dist/monitor/probes.js.map +1 -0
- package/dist/monitor/self-health.js +228 -0
- package/dist/monitor/self-health.js.map +1 -0
- package/dist/monitor/statuspage.js +105 -0
- package/dist/monitor/statuspage.js.map +1 -0
- package/dist/nightly-scheduler.js +127 -0
- package/dist/nightly-scheduler.js.map +1 -0
- package/dist/nightly.js +141 -0
- package/dist/nightly.js.map +1 -0
- package/dist/notify/headline.js +114 -0
- package/dist/notify/headline.js.map +1 -0
- package/dist/notify/push.js +174 -0
- package/dist/notify/push.js.map +1 -0
- package/dist/object-cards.js +585 -0
- package/dist/object-cards.js.map +1 -0
- package/dist/object-links.js +47 -0
- package/dist/object-links.js.map +1 -0
- package/dist/onboarding-checks.js +141 -0
- package/dist/onboarding-checks.js.map +1 -0
- package/dist/onboarding.js +401 -0
- package/dist/onboarding.js.map +1 -0
- package/dist/path-filters.js +62 -0
- package/dist/path-filters.js.map +1 -0
- package/dist/phone/alerts.js +67 -0
- package/dist/phone/alerts.js.map +1 -0
- package/dist/phone/auth.js +240 -0
- package/dist/phone/auth.js.map +1 -0
- package/dist/phone/pairing.js +442 -0
- package/dist/phone/pairing.js.map +1 -0
- package/dist/phone/prefs.js +27 -0
- package/dist/phone/prefs.js.map +1 -0
- package/dist/phone/tailscale.js +161 -0
- package/dist/phone/tailscale.js.map +1 -0
- package/dist/pr-scope.js +441 -0
- package/dist/pr-scope.js.map +1 -0
- package/dist/proc.js +156 -0
- package/dist/proc.js.map +1 -0
- package/dist/profile/cache.js +108 -0
- package/dist/profile/cache.js.map +1 -0
- package/dist/profile/ci.js +216 -0
- package/dist/profile/ci.js.map +1 -0
- package/dist/profile/index.js +8 -0
- package/dist/profile/index.js.map +1 -0
- package/dist/profile/job.js +135 -0
- package/dist/profile/job.js.map +1 -0
- package/dist/profile/queries/go-tags.scm +47 -0
- package/dist/profile/queries/java-tags.scm +25 -0
- package/dist/profile/queries/javascript-tags.scm +104 -0
- package/dist/profile/queries/python-tags.scm +19 -0
- package/dist/profile/queries/typescript-tags.scm +28 -0
- package/dist/profile/scan-thread.js +42 -0
- package/dist/profile/scan-thread.js.map +1 -0
- package/dist/profile/scan-worker.js +16 -0
- package/dist/profile/scan-worker.js.map +1 -0
- package/dist/profile/scan.js +351 -0
- package/dist/profile/scan.js.map +1 -0
- package/dist/profile/stack.js +528 -0
- package/dist/profile/stack.js.map +1 -0
- package/dist/profile/store.js +35 -0
- package/dist/profile/store.js.map +1 -0
- package/dist/profile/tests.js +399 -0
- package/dist/profile/tests.js.map +1 -0
- package/dist/queue.js +350 -0
- package/dist/queue.js.map +1 -0
- package/dist/refs.js +756 -0
- package/dist/refs.js.map +1 -0
- package/dist/release-notes.js +272 -0
- package/dist/release-notes.js.map +1 -0
- package/dist/reports/build.js +243 -0
- package/dist/reports/build.js.map +1 -0
- package/dist/reports/index.js +211 -0
- package/dist/reports/index.js.map +1 -0
- package/dist/reports/store.js +150 -0
- package/dist/reports/store.js.map +1 -0
- package/dist/reports/ticket-titles.js +124 -0
- package/dist/reports/ticket-titles.js.map +1 -0
- package/dist/reports/trace-map.js +1278 -0
- package/dist/reports/trace-map.js.map +1 -0
- package/dist/reports/traceability.js +422 -0
- package/dist/reports/traceability.js.map +1 -0
- package/dist/repos/mirror.js +356 -0
- package/dist/repos/mirror.js.map +1 -0
- package/dist/repos-github.js +108 -0
- package/dist/repos-github.js.map +1 -0
- package/dist/repos.js +891 -0
- package/dist/repos.js.map +1 -0
- package/dist/review/backend-anthropic.js +90 -0
- package/dist/review/backend-anthropic.js.map +1 -0
- package/dist/review/backend-cli.js +248 -0
- package/dist/review/backend-cli.js.map +1 -0
- package/dist/review/backend-codex.js +81 -0
- package/dist/review/backend-codex.js.map +1 -0
- package/dist/review/backend-mock.js +686 -0
- package/dist/review/backend-mock.js.map +1 -0
- package/dist/review/backend-only.js +168 -0
- package/dist/review/backend-only.js.map +1 -0
- package/dist/review/backend.js +105 -0
- package/dist/review/backend.js.map +1 -0
- package/dist/review/baseline/render-v1.js +483 -0
- package/dist/review/baseline/render-v1.js.map +1 -0
- package/dist/review/change-summary.js +197 -0
- package/dist/review/change-summary.js.map +1 -0
- package/dist/review/changelog.js +40 -0
- package/dist/review/changelog.js.map +1 -0
- package/dist/review/check.js +494 -0
- package/dist/review/check.js.map +1 -0
- package/dist/review/checkout.js +185 -0
- package/dist/review/checkout.js.map +1 -0
- package/dist/review/classify.js +178 -0
- package/dist/review/classify.js.map +1 -0
- package/dist/review/coverage.js +347 -0
- package/dist/review/coverage.js.map +1 -0
- package/dist/review/decision-eval.js +121 -0
- package/dist/review/decision-eval.js.map +1 -0
- package/dist/review/deliver.js +104 -0
- package/dist/review/deliver.js.map +1 -0
- package/dist/review/designer-eval.js +280 -0
- package/dist/review/designer-eval.js.map +1 -0
- package/dist/review/dogfood-digest.js +136 -0
- package/dist/review/dogfood-digest.js.map +1 -0
- package/dist/review/dogfood-eval.js +486 -0
- package/dist/review/dogfood-eval.js.map +1 -0
- package/dist/review/environment.js +109 -0
- package/dist/review/environment.js.map +1 -0
- package/dist/review/eval.js +405 -0
- package/dist/review/eval.js.map +1 -0
- package/dist/review/glob.js +110 -0
- package/dist/review/glob.js.map +1 -0
- package/dist/review/golden-export.js +219 -0
- package/dist/review/golden-export.js.map +1 -0
- package/dist/review/impact.js +406 -0
- package/dist/review/impact.js.map +1 -0
- package/dist/review/imports.js +542 -0
- package/dist/review/imports.js.map +1 -0
- package/dist/review/index.js +512 -0
- package/dist/review/index.js.map +1 -0
- package/dist/review/intent.js +128 -0
- package/dist/review/intent.js.map +1 -0
- package/dist/review/lenses.js +167 -0
- package/dist/review/lenses.js.map +1 -0
- package/dist/review/linked-tickets.js +293 -0
- package/dist/review/linked-tickets.js.map +1 -0
- package/dist/review/merge.js +370 -0
- package/dist/review/merge.js.map +1 -0
- package/dist/review/on-demand.js +218 -0
- package/dist/review/on-demand.js.map +1 -0
- package/dist/review/prompt-eval.js +299 -0
- package/dist/review/prompt-eval.js.map +1 -0
- package/dist/review/prompt-variants.js +37 -0
- package/dist/review/prompt-variants.js.map +1 -0
- package/dist/review/readability-eval.js +217 -0
- package/dist/review/readability-eval.js.map +1 -0
- package/dist/review/readability.js +295 -0
- package/dist/review/readability.js.map +1 -0
- package/dist/review/render.js +2409 -0
- package/dist/review/render.js.map +1 -0
- package/dist/review/repo.js +194 -0
- package/dist/review/repo.js.map +1 -0
- package/dist/review/report-plan.js +90 -0
- package/dist/review/report-plan.js.map +1 -0
- package/dist/review/routes.js +195 -0
- package/dist/review/routes.js.map +1 -0
- package/dist/review/run-summary.js +34 -0
- package/dist/review/run-summary.js.map +1 -0
- package/dist/review/run.js +245 -0
- package/dist/review/run.js.map +1 -0
- package/dist/review/scope.js +454 -0
- package/dist/review/scope.js.map +1 -0
- package/dist/review/security.js +432 -0
- package/dist/review/security.js.map +1 -0
- package/dist/review/signals.js +550 -0
- package/dist/review/signals.js.map +1 -0
- package/dist/review/specs.js +150 -0
- package/dist/review/specs.js.map +1 -0
- package/dist/review/ste.js +133 -0
- package/dist/review/ste.js.map +1 -0
- package/dist/review/test-repo-file.js +18 -0
- package/dist/review/test-repo-file.js.map +1 -0
- package/dist/review/test-repo-lines.js +53 -0
- package/dist/review/test-repo-lines.js.map +1 -0
- package/dist/review/ticket-on-demand.js +232 -0
- package/dist/review/ticket-on-demand.js.map +1 -0
- package/dist/review/yaml.js +298 -0
- package/dist/review/yaml.js.map +1 -0
- package/dist/room.js +361 -0
- package/dist/room.js.map +1 -0
- package/dist/routes/activity.js +219 -0
- package/dist/routes/activity.js.map +1 -0
- package/dist/routes/call.js +29 -0
- package/dist/routes/call.js.map +1 -0
- package/dist/routes/cards.js +40 -0
- package/dist/routes/cards.js.map +1 -0
- package/dist/routes/cases.js +133 -0
- package/dist/routes/cases.js.map +1 -0
- package/dist/routes/connect.js +268 -0
- package/dist/routes/connect.js.map +1 -0
- package/dist/routes/connection-presets.js +32 -0
- package/dist/routes/connection-presets.js.map +1 -0
- package/dist/routes/connections.js +177 -0
- package/dist/routes/connections.js.map +1 -0
- package/dist/routes/disk.js +19 -0
- package/dist/routes/disk.js.map +1 -0
- package/dist/routes/docs.js +71 -0
- package/dist/routes/docs.js.map +1 -0
- package/dist/routes/dot-suggest.js +1002 -0
- package/dist/routes/dot-suggest.js.map +1 -0
- package/dist/routes/evidence-pack.js +16 -0
- package/dist/routes/evidence-pack.js.map +1 -0
- package/dist/routes/health.js +121 -0
- package/dist/routes/health.js.map +1 -0
- package/dist/routes/impact.js +48 -0
- package/dist/routes/impact.js.map +1 -0
- package/dist/routes/lead.js +50 -0
- package/dist/routes/lead.js.map +1 -0
- package/dist/routes/links.js +13 -0
- package/dist/routes/links.js.map +1 -0
- package/dist/routes/markdown.js +62 -0
- package/dist/routes/markdown.js.map +1 -0
- package/dist/routes/onboarding.js +47 -0
- package/dist/routes/onboarding.js.map +1 -0
- package/dist/routes/phone.js +231 -0
- package/dist/routes/phone.js.map +1 -0
- package/dist/routes/release-notes.js +30 -0
- package/dist/routes/release-notes.js.map +1 -0
- package/dist/routes/reports.js +102 -0
- package/dist/routes/reports.js.map +1 -0
- package/dist/routes/repos.js +91 -0
- package/dist/routes/repos.js.map +1 -0
- package/dist/routes/review.js +283 -0
- package/dist/routes/review.js.map +1 -0
- package/dist/routes/roles.js +114 -0
- package/dist/routes/roles.js.map +1 -0
- package/dist/routes/room.js +21 -0
- package/dist/routes/room.js.map +1 -0
- package/dist/routes/runner.js +156 -0
- package/dist/routes/runner.js.map +1 -0
- package/dist/routes/runs.js +84 -0
- package/dist/routes/runs.js.map +1 -0
- package/dist/routes/setup.js +18 -0
- package/dist/routes/setup.js.map +1 -0
- package/dist/routes/test-runs.js +220 -0
- package/dist/routes/test-runs.js.map +1 -0
- package/dist/routes/ticket-status.js +17 -0
- package/dist/routes/ticket-status.js.map +1 -0
- package/dist/routes/traceability.js +143 -0
- package/dist/routes/traceability.js.map +1 -0
- package/dist/routes/true.js +142 -0
- package/dist/routes/true.js.map +1 -0
- package/dist/routes/updates.js +70 -0
- package/dist/routes/updates.js.map +1 -0
- package/dist/runner/adapters/index.js +12 -0
- package/dist/runner/adapters/index.js.map +1 -0
- package/dist/runner/adapters/playwright.js +68 -0
- package/dist/runner/adapters/playwright.js.map +1 -0
- package/dist/runner/aivision/cases.js +172 -0
- package/dist/runner/aivision/cases.js.map +1 -0
- package/dist/runner/aivision/cli.js +201 -0
- package/dist/runner/aivision/cli.js.map +1 -0
- package/dist/runner/aivision/index.js +281 -0
- package/dist/runner/aivision/index.js.map +1 -0
- package/dist/runner/aivision/server.js +394 -0
- package/dist/runner/aivision/server.js.map +1 -0
- package/dist/runner/aivision/shared.js +140 -0
- package/dist/runner/aivision/shared.js.map +1 -0
- package/dist/runner/artifact-cap.js +189 -0
- package/dist/runner/artifact-cap.js.map +1 -0
- package/dist/runner/artifacts.js +433 -0
- package/dist/runner/artifacts.js.map +1 -0
- package/dist/runner/command.js +564 -0
- package/dist/runner/command.js.map +1 -0
- package/dist/runner/copyfiles.js +221 -0
- package/dist/runner/copyfiles.js.map +1 -0
- package/dist/runner/engines.js +32 -0
- package/dist/runner/engines.js.map +1 -0
- package/dist/runner/evidence.js +114 -0
- package/dist/runner/evidence.js.map +1 -0
- package/dist/runner/host-env.js +160 -0
- package/dist/runner/host-env.js.map +1 -0
- package/dist/runner/index.js +1221 -0
- package/dist/runner/index.js.map +1 -0
- package/dist/runner/observe.js +603 -0
- package/dist/runner/observe.js.map +1 -0
- package/dist/runner/playwright-json.js +108 -0
- package/dist/runner/playwright-json.js.map +1 -0
- package/dist/runner/pwgen/exec.js +192 -0
- package/dist/runner/pwgen/exec.js.map +1 -0
- package/dist/runner/pwgen/explore.js +111 -0
- package/dist/runner/pwgen/explore.js.map +1 -0
- package/dist/runner/pwgen/index.js +423 -0
- package/dist/runner/pwgen/index.js.map +1 -0
- package/dist/runner/pwgen/propose.js +69 -0
- package/dist/runner/pwgen/propose.js.map +1 -0
- package/dist/runner/pwgen/source.js +632 -0
- package/dist/runner/pwgen/source.js.map +1 -0
- package/dist/runner/setup.js +222 -0
- package/dist/runner/setup.js.map +1 -0
- package/dist/runner/step-reporter.js +107 -0
- package/dist/runner/step-reporter.js.map +1 -0
- package/dist/runner/test-repo.js +326 -0
- package/dist/runner/test-repo.js.map +1 -0
- package/dist/runner/typed-value.js +46 -0
- package/dist/runner/typed-value.js.map +1 -0
- package/dist/runner/verify.js +133 -0
- package/dist/runner/verify.js.map +1 -0
- package/dist/runner/worktree.js +349 -0
- package/dist/runner/worktree.js.map +1 -0
- package/dist/sandbox.js +61 -0
- package/dist/sandbox.js.map +1 -0
- package/dist/server.js +262 -0
- package/dist/server.js.map +1 -0
- package/dist/shadow-week.js +126 -0
- package/dist/shadow-week.js.map +1 -0
- package/dist/shutdown.js +92 -0
- package/dist/shutdown.js.map +1 -0
- package/dist/slack/index.js +645 -0
- package/dist/slack/index.js.map +1 -0
- package/dist/stages/config.js +147 -0
- package/dist/stages/config.js.map +1 -0
- package/dist/stages/index.js +41 -0
- package/dist/stages/index.js.map +1 -0
- package/dist/suites.js +293 -0
- package/dist/suites.js.map +1 -0
- package/dist/target-url.js +116 -0
- package/dist/target-url.js.map +1 -0
- package/dist/test-runs.js +662 -0
- package/dist/test-runs.js.map +1 -0
- package/dist/ticket-keys.js +97 -0
- package/dist/ticket-keys.js.map +1 -0
- package/dist/ticket-supersede.js +105 -0
- package/dist/ticket-supersede.js.map +1 -0
- package/dist/tinybee/ed25519.js +99 -0
- package/dist/tinybee/ed25519.js.map +1 -0
- package/dist/tinybee/models-routes.js +19 -0
- package/dist/tinybee/models-routes.js.map +1 -0
- package/dist/tinybee/pair-payload.js +41 -0
- package/dist/tinybee/pair-payload.js.map +1 -0
- package/dist/tinybee/update-manifest.js +77 -0
- package/dist/tinybee/update-manifest.js.map +1 -0
- package/dist/tinybee/well-known.js +64 -0
- package/dist/tinybee/well-known.js.map +1 -0
- package/dist/tinybee/workflows/activity.js +23 -0
- package/dist/tinybee/workflows/activity.js.map +1 -0
- package/dist/tinybee/workflows/cron.js +210 -0
- package/dist/tinybee/workflows/cron.js.map +1 -0
- package/dist/tinybee/workflows/engine.js +371 -0
- package/dist/tinybee/workflows/engine.js.map +1 -0
- package/dist/tinybee/workflows/from-text.js +203 -0
- package/dist/tinybee/workflows/from-text.js.map +1 -0
- package/dist/tinybee/workflows/routes.js +189 -0
- package/dist/tinybee/workflows/routes.js.map +1 -0
- package/dist/tinybee/workflows/scheduler.js +100 -0
- package/dist/tinybee/workflows/scheduler.js.map +1 -0
- package/dist/tinybee/workflows/skills.js +227 -0
- package/dist/tinybee/workflows/skills.js.map +1 -0
- package/dist/tinybee/workflows/store.js +205 -0
- package/dist/tinybee/workflows/store.js.map +1 -0
- package/dist/tinybee/workflows/types.js +180 -0
- package/dist/tinybee/workflows/types.js.map +1 -0
- package/dist/tinybee/workflows/yaml.js +323 -0
- package/dist/tinybee/workflows/yaml.js.map +1 -0
- package/dist/true/api.js +495 -0
- package/dist/true/api.js.map +1 -0
- package/dist/true/client.js +292 -0
- package/dist/true/client.js.map +1 -0
- package/dist/true/clock.js +36 -0
- package/dist/true/clock.js.map +1 -0
- package/dist/true/events.js +125 -0
- package/dist/true/events.js.map +1 -0
- package/dist/true/hosts.js +98 -0
- package/dist/true/hosts.js.map +1 -0
- package/dist/true/index.js +29 -0
- package/dist/true/index.js.map +1 -0
- package/dist/true/key.js +54 -0
- package/dist/true/key.js.map +1 -0
- package/dist/true/line.js +69 -0
- package/dist/true/line.js.map +1 -0
- package/dist/true/mask.js +31 -0
- package/dist/true/mask.js.map +1 -0
- package/dist/true/names.js +114 -0
- package/dist/true/names.js.map +1 -0
- package/dist/true/outbox.js +233 -0
- package/dist/true/outbox.js.map +1 -0
- package/dist/true/replay.js +164 -0
- package/dist/true/replay.js.map +1 -0
- package/dist/true/settings.js +123 -0
- package/dist/true/settings.js.map +1 -0
- package/dist/true/status.js +189 -0
- package/dist/true/status.js.map +1 -0
- package/dist/true/sync-cases.js +222 -0
- package/dist/true/sync-cases.js.map +1 -0
- package/dist/true/sync-manual.js +295 -0
- package/dist/true/sync-manual.js.map +1 -0
- package/dist/true/sync-runs.js +109 -0
- package/dist/true/sync-runs.js.map +1 -0
- package/dist/true/worker.js +414 -0
- package/dist/true/worker.js.map +1 -0
- package/dist/types.js +32 -0
- package/dist/types.js.map +1 -0
- package/dist/updates/asks.js +38 -0
- package/dist/updates/asks.js.map +1 -0
- package/dist/updates/index.js +835 -0
- package/dist/updates/index.js.map +1 -0
- package/dist/updates/owner.js +24 -0
- package/dist/updates/owner.js.map +1 -0
- package/dist/updates/prefs.js +118 -0
- package/dist/updates/prefs.js.map +1 -0
- package/dist/updates/route.js +78 -0
- package/dist/updates/route.js.map +1 -0
- package/dist/updates/sources.js +345 -0
- package/dist/updates/sources.js.map +1 -0
- package/dist/updates/words.js +452 -0
- package/dist/updates/words.js.map +1 -0
- package/dist/version.js +53 -0
- package/dist/version.js.map +1 -0
- package/dist/view-compute.js +382 -0
- package/dist/view-compute.js.map +1 -0
- package/dist/view-spec.js +894 -0
- package/dist/view-spec.js.map +1 -0
- package/dist/web/apple-touch-icon.png +0 -0
- package/dist/web/assets/ApprovalCard-Br_z5z7G.css +1 -0
- package/dist/web/assets/ApprovalCard-CfGhGFo_.js +3 -0
- package/dist/web/assets/CardFromRef-BaTGmZ9c.js +2 -0
- package/dist/web/assets/CardFromRef-DO7i2uDd.css +1 -0
- package/dist/web/assets/Cases-8l_6UcLh.js +2 -0
- package/dist/web/assets/Cases-CpfED0uB.css +1 -0
- package/dist/web/assets/Chat-BKDiR0qt.js +3 -0
- package/dist/web/assets/Chat-DxjdyYUu.css +1 -0
- package/dist/web/assets/ConfirmDialog-CIMveD-b.js +2 -0
- package/dist/web/assets/ConnectSheet-Cm5vbZdR.js +4 -0
- package/dist/web/assets/Docs-HKGuY0WE.js +2 -0
- package/dist/web/assets/Docs-fodKx8S7.css +1 -0
- package/dist/web/assets/DotDetail-DskS3NWe.js +2 -0
- package/dist/web/assets/DotDetail-OWRSD_1x.css +1 -0
- package/dist/web/assets/DotStage-BZDclfzV.css +1 -0
- package/dist/web/assets/DotStage-CtMNzCN3.js +2 -0
- package/dist/web/assets/Dots-BqCsS34m.js +2 -0
- package/dist/web/assets/EvidencePack-DQ3kCJX4.js +2 -0
- package/dist/web/assets/EvidencePack-DV_9ETsA.css +1 -0
- package/dist/web/assets/FailedDeliveries-1F2kFcs9.js +2 -0
- package/dist/web/assets/FailedDeliveries-DXa2_VvI.css +1 -0
- package/dist/web/assets/Feed-5wiHRTFK.js +2 -0
- package/dist/web/assets/GfmView-C-NYaduC.js +4 -0
- package/dist/web/assets/ImpactList-C-RNuDB0.js +2 -0
- package/dist/web/assets/ImpactList-C612dFdw.css +1 -0
- package/dist/web/assets/Inbox-kc1bKVL_.js +2 -0
- package/dist/web/assets/JoinScreen-C3L4wmP3.js +2 -0
- package/dist/web/assets/JoinScreen-COeAUv5v.css +1 -0
- package/dist/web/assets/LeadDialog-N3Zul9Rw.js +8 -0
- package/dist/web/assets/LeadDialog-xyWjAuAS.css +1 -0
- package/dist/web/assets/ListeningLine-BRFNaczY.js +2 -0
- package/dist/web/assets/ListeningLine-pN8CgyiK.css +1 -0
- package/dist/web/assets/Mascots-pIyGpxtD.js +2 -0
- package/dist/web/assets/ModelChoices-BxO1zyyr.js +2 -0
- package/dist/web/assets/ModelChoices-DQJnVxfs.css +1 -0
- package/dist/web/assets/NewDotDialog-CDPUWmgN.js +2 -0
- package/dist/web/assets/NewDotDialog-DrPPqkSZ.css +1 -0
- package/dist/web/assets/NewTestRunDialog-BQBthVHE.js +2 -0
- package/dist/web/assets/NoBreakNames-Csxdqdt6.js +2 -0
- package/dist/web/assets/Pair-omex_K7H.js +2 -0
- package/dist/web/assets/PushSection-CmqHlE_2.js +2 -0
- package/dist/web/assets/ReleaseNotes-B6VqFewP.js +2 -0
- package/dist/web/assets/ReleaseNotes-DBJUL7Vm.css +1 -0
- package/dist/web/assets/ReportPage-BHS1xWEt.js +162 -0
- package/dist/web/assets/Reports-BEHnDIb1.js +2 -0
- package/dist/web/assets/ReviewChangeDialog-BVSt-cw-.js +2 -0
- package/dist/web/assets/ReviewChangeDialog-BbIlHRx0.css +1 -0
- package/dist/web/assets/Room-BoG_uge4.js +2 -0
- package/dist/web/assets/Room-evAfiW_e.css +1 -0
- package/dist/web/assets/RunActions-ZY7yf1AF.js +2 -0
- package/dist/web/assets/RunDetail-C8St20GQ.css +1 -0
- package/dist/web/assets/RunDetail-qADXjBvd.js +9 -0
- package/dist/web/assets/RunRow-BKEJv8ib.js +2 -0
- package/dist/web/assets/RunnerTry-Dgpp67-s.js +6 -0
- package/dist/web/assets/RunnerTry-DsweWOfO.css +1 -0
- package/dist/web/assets/Settings-fs-QJwNM.js +2 -0
- package/dist/web/assets/Setup-DI6hBEP-.js +2 -0
- package/dist/web/assets/TestRows-wh03T5Li.js +4 -0
- package/dist/web/assets/TestRun-DTOyF-Pv.js +2 -0
- package/dist/web/assets/TestRuns-ggDjm1Q8.js +2 -0
- package/dist/web/assets/TinyBeeAddLaptop-bc3pmh3T.js +2 -0
- package/dist/web/assets/TinyBeeAi-CkyOzhm5.js +2 -0
- package/dist/web/assets/TinyBeeConnections-B2K_P38u.js +2 -0
- package/dist/web/assets/TinyBeeHome-DSpxX5KS.js +3 -0
- package/dist/web/assets/TinyBeePlan-BamiLziu.js +2 -0
- package/dist/web/assets/TinyBeeSignup-vHcz0Wen.js +2 -0
- package/dist/web/assets/TinyBeeSkills-By9RLUse.js +2 -0
- package/dist/web/assets/TinyBeeWorkflow-DeNO7Jsh.js +2 -0
- package/dist/web/assets/TinyBeeWorkflowEditor-B6OGC2OT.js +2 -0
- package/dist/web/assets/TinyBeeWorkflows-BN4-h6lt.js +2 -0
- package/dist/web/assets/Trace-BBCI1aIB.css +1 -0
- package/dist/web/assets/Trace-aF92UWaH.js +2 -0
- package/dist/web/assets/UpdatesSection-CCWEbkue.js +2 -0
- package/dist/web/assets/Welcome-CCcy558Q.js +2 -0
- package/dist/web/assets/activity-C3qilrDm.css +1 -0
- package/dist/web/assets/activity-CBfTSxqw.js +2 -0
- package/dist/web/assets/authedMedia-BTYt_tyu.js +2 -0
- package/dist/web/assets/beeApi-BHW8Tx1s.js +2 -0
- package/dist/web/assets/data-CmXvx3Mj.js +2 -0
- package/dist/web/assets/engines-CfdJODFj.css +1 -0
- package/dist/web/assets/format-BBZ8vFyT.js +2 -0
- package/dist/web/assets/forms-Coc7p2UF.css +1 -0
- package/dist/web/assets/hivePush-DZJDokj7.js +2 -0
- package/dist/web/assets/impactModel-MY1OFuiP.js +2 -0
- package/dist/web/assets/index-B6qKm1NW.js +13 -0
- package/dist/web/assets/index-BvKyT1X7.css +1 -0
- package/dist/web/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
- package/dist/web/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
- package/dist/web/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
- package/dist/web/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
- package/dist/web/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- package/dist/web/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- package/dist/web/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
- package/dist/web/assets/lead-BL_P_kri.css +1 -0
- package/dist/web/assets/model-BIQy5IVG.js +2 -0
- package/dist/web/assets/modelsApi-PUSySRQw.js +2 -0
- package/dist/web/assets/newDotModel-BaLLyw57.js +2 -0
- package/dist/web/assets/onboarding-bfKqnM4Z.css +1 -0
- package/dist/web/assets/phone-Rs7FoyG0.css +1 -0
- package/dist/web/assets/phoneApi-BG_1-M-D.js +3 -0
- package/dist/web/assets/releaseModel-EynYMVOC.js +2 -0
- package/dist/web/assets/releaseNotesModel-A1Rtv3cV.js +2 -0
- package/dist/web/assets/reports-BWaA1HhM.js +37 -0
- package/dist/web/assets/reports-DXwdWQsB.css +1 -0
- package/dist/web/assets/router-W6nZsQ33.js +2 -0
- package/dist/web/assets/runs-BY0Owv93.css +1 -0
- package/dist/web/assets/shadowWeekModel-BUUiww09.js +2 -0
- package/dist/web/assets/storage-yvL36AWt.js +2 -0
- package/dist/web/assets/store-IqrqHuFR.js +2 -0
- package/dist/web/assets/testruns-BzIZ0cR0.css +1 -0
- package/dist/web/assets/testruns-DyaqX4ji.js +2 -0
- package/dist/web/assets/true-9Q68cs5b.js +2 -0
- package/dist/web/assets/true-DPGBpoir.css +1 -0
- package/dist/web/assets/ui-DcmhYyYp.js +13 -0
- package/dist/web/assets/ui-DxEDgbIx.css +1 -0
- package/dist/web/assets/usePauseDot-BV60ZVy1.js +2 -0
- package/dist/web/assets/usePauseDot-NrSzMI1C.css +1 -0
- package/dist/web/assets/usePush-Dsm-TjlG.js +2 -0
- package/dist/web/assets/useRefChips-CbafNx59.js +68 -0
- package/dist/web/assets/useRestoreDot-D0B6W82H.js +2 -0
- package/dist/web/assets/view-worker-pyeVAhUT.js +2 -0
- package/dist/web/assets/workflowUi-QYsd3n9J.js +2 -0
- package/dist/web/favicon.png +0 -0
- package/dist/web/icon-192.png +0 -0
- package/dist/web/icon-512.png +0 -0
- package/dist/web/icon-maskable-512.png +0 -0
- package/dist/web/index.html +45 -0
- package/dist/web/manifest.webmanifest +23 -0
- package/dist/web/mascots/argus-attention.svg +7 -0
- package/dist/web/mascots/argus-ok.svg +7 -0
- package/dist/web/mascots/argus-outage.svg +7 -0
- package/dist/web/mascots/argus-running.svg +7 -0
- package/dist/web/mascots/bolt-attention.svg +7 -0
- package/dist/web/mascots/bolt-ok.svg +7 -0
- package/dist/web/mascots/bolt-outage.svg +7 -0
- package/dist/web/mascots/bolt-running.svg +7 -0
- package/dist/web/mascots/echo-attention.svg +7 -0
- package/dist/web/mascots/echo-ok.svg +7 -0
- package/dist/web/mascots/echo-outage.svg +7 -0
- package/dist/web/mascots/echo-running.svg +7 -0
- package/dist/web/mascots/lens-attention.svg +7 -0
- package/dist/web/mascots/lens-ok.svg +7 -0
- package/dist/web/mascots/lens-outage.svg +7 -0
- package/dist/web/mascots/lens-running.svg +7 -0
- package/dist/web/mascots/moss-attention.svg +7 -0
- package/dist/web/mascots/moss-ok.svg +7 -0
- package/dist/web/mascots/moss-outage.svg +7 -0
- package/dist/web/mascots/moss-running.svg +7 -0
- package/dist/web/mascots/nit-attention.svg +7 -0
- package/dist/web/mascots/nit-ok.svg +7 -0
- package/dist/web/mascots/nit-outage.svg +7 -0
- package/dist/web/mascots/nit-running.svg +7 -0
- package/dist/web/mascots/nova-attention.svg +7 -0
- package/dist/web/mascots/nova-ok.svg +7 -0
- package/dist/web/mascots/nova-outage.svg +7 -0
- package/dist/web/mascots/nova-running.svg +7 -0
- package/dist/web/mascots/penn-attention.svg +7 -0
- package/dist/web/mascots/penn-ok.svg +7 -0
- package/dist/web/mascots/penn-outage.svg +7 -0
- package/dist/web/mascots/penn-running.svg +7 -0
- package/dist/web/mascots/pip-attention.svg +7 -0
- package/dist/web/mascots/pip-ok.svg +7 -0
- package/dist/web/mascots/pip-outage.svg +7 -0
- package/dist/web/mascots/pip-running.svg +7 -0
- package/dist/web/mascots/ward-attention.svg +7 -0
- package/dist/web/mascots/ward-ok.svg +7 -0
- package/dist/web/mascots/ward-outage.svg +7 -0
- package/dist/web/mascots/ward-running.svg +7 -0
- package/dist/web/sw.js +98 -0
- package/dist/webhook-seen.js +53 -0
- package/dist/webhook-seen.js.map +1 -0
- package/dist/webhooks.js +625 -0
- package/dist/webhooks.js.map +1 -0
- package/dist/zip.js +63 -0
- package/dist/zip.js.map +1 -0
- package/docs/API-V2.md +532 -0
- package/docs/CHAT-EVAL.md +130 -0
- package/docs/CHAT-UI.md +416 -0
- package/docs/CONNECTIONS.md +89 -0
- package/docs/DATA-MODEL.md +518 -0
- package/docs/DEPLOY.md +577 -0
- package/docs/DOGFOOD-LEDGER.md +81 -0
- package/docs/GITHUB.md +148 -0
- package/docs/INTEGRATION-MODEL.md +200 -0
- package/docs/LEAD-DOT.md +553 -0
- package/docs/LIST-PATTERNS.md +52 -0
- package/docs/MULTI-REPO.md +766 -0
- package/docs/PARITY.md +276 -0
- package/docs/PR-REVIEW-QA.md +1266 -0
- package/docs/PR-REVIEW.md +357 -0
- package/docs/SANDBOX.md +320 -0
- package/docs/SETUP.md +776 -0
- package/docs/SLACK.md +80 -0
- package/docs/STAGES.md +619 -0
- package/docs/TRACE-MAP.md +89 -0
- package/docs/TRACKERS.md +225 -0
- package/docs/VOICE.md +36 -0
- package/docs/tinybee/00-README.md +45 -0
- package/docs/tinybee/01-PRD.md +216 -0
- package/docs/tinybee/02-architecture-pairing.md +199 -0
- package/docs/tinybee/03-local-agent.md +222 -0
- package/docs/tinybee/04-local-setup-ui.md +128 -0
- package/docs/tinybee/05-mobile-app-ux.md +199 -0
- package/docs/tinybee/06-environments-and-delivery.md +148 -0
- package/docs/tinybee/07-coding-agent-handoff.md +116 -0
- package/docs/tinybee/OPEN-QUESTIONS.md +26 -0
- package/docs/tinybee/PROGRESS.md +134 -0
- package/package.json +111 -4
|
@@ -0,0 +1,835 @@
|
|
|
1
|
+
// The dot reports like an async teammate (KAT-143). Live events become drafts (sources.ts), the router
|
|
2
|
+
// sets a level (route.ts), and this module keeps the feed and delivers:
|
|
3
|
+
//
|
|
4
|
+
// now the dot's web chat, its Slack channel or DM, and the phone, at once
|
|
5
|
+
// thread a reply where someone asked: their chat turn or their Slack thread
|
|
6
|
+
// digest held for the next digest (every few hours) or the morning note
|
|
7
|
+
// drop nothing; a passing run nobody asked about is never a message
|
|
8
|
+
//
|
|
9
|
+
// Every update has one key. The feed row is written before anything is sent, under a unique index, so
|
|
10
|
+
// a repeated event or a second source for the same thing (the probe and the outage run) sends nothing
|
|
11
|
+
// twice on any channel. Quiet hours hold the phone and Slack DMs; the morning note lists what they held.
|
|
12
|
+
//
|
|
13
|
+
// Slack and the phone can fail (a Slack error, a push error, the network). Every try is recorded on the
|
|
14
|
+
// feed row. An urgent update (level now, not a digest or morning note) retries a failed channel after
|
|
15
|
+
// 1, 5, and 15 minutes, then every 15 minutes until it goes through (KAT-271). From the fourth failed try
|
|
16
|
+
// the failure also shows on the dot page and in the Inbox, and the retries go on. The retry is written to
|
|
17
|
+
// the row before the first try of any channel, so a restart in the middle of a send still finds it.
|
|
18
|
+
// A channel never has two tries in flight, so a slow first try that still succeeds is never followed by a
|
|
19
|
+
// second send, and a channel that went through is never tried again. Digests, morning notes, and thread
|
|
20
|
+
// replies stay one-shot.
|
|
21
|
+
//
|
|
22
|
+
// A listed failure can be cleared by a person: Retry sends it again now on its failed channels, and Dismiss
|
|
23
|
+
// takes it off the list without sending and ends its retries (retryNow, dismissFailed, dismissAllFailed).
|
|
24
|
+
import { redactCode } from '../agent/redact.js';
|
|
25
|
+
import { log } from '../config.js';
|
|
26
|
+
import { sandboxOn, sandboxSkips } from '../sandbox.js';
|
|
27
|
+
import { count, get, insert, list, update } from '../db.js';
|
|
28
|
+
import { publish, subscribe } from '../events.js';
|
|
29
|
+
import { notify } from '../notify/push.js';
|
|
30
|
+
import { recordDelivery } from '../monitor/self-health.js';
|
|
31
|
+
import { postUpdate } from '../slack/index.js';
|
|
32
|
+
import { ruleFor } from '../agent/rules.js';
|
|
33
|
+
import { liveCount } from '../agent/approvals.js';
|
|
34
|
+
import { renderRefs } from '../refs.js';
|
|
35
|
+
import { openLinks } from '../link-trust.js';
|
|
36
|
+
import { digestRuns } from '../flows/digest.js';
|
|
37
|
+
import { morningText, reviewItem, TEAM_DETAILS_HEADING } from './words.js';
|
|
38
|
+
import { askForJob, askForRun } from './asks.js';
|
|
39
|
+
import { setPushAdopter, setUpdatesOwnDelivery } from './owner.js';
|
|
40
|
+
import { inQuietHours, ME, morningMinute, personWants, prefsFor } from './prefs.js';
|
|
41
|
+
import { routeUpdate } from './route.js';
|
|
42
|
+
import { updatesFromEvent } from './sources.js';
|
|
43
|
+
export { setUpdateDecider, ruleLevel, routeUpdate } from './route.js';
|
|
44
|
+
export { updatesFromEvent } from './sources.js';
|
|
45
|
+
const defaultDeps = {
|
|
46
|
+
now: () => new Date(),
|
|
47
|
+
slack: postUpdate,
|
|
48
|
+
push: async (n) => {
|
|
49
|
+
const r = await notify(n);
|
|
50
|
+
if (r.failed)
|
|
51
|
+
throw new Error(r.failed);
|
|
52
|
+
return !r.deduped;
|
|
53
|
+
},
|
|
54
|
+
attemptMs: 20_000,
|
|
55
|
+
};
|
|
56
|
+
/** Minutes to wait after the 1st, 2nd, and 3rd failed try of an urgent update. Later failures wait as long as the last. */
|
|
57
|
+
export const RETRY_MINUTES = [1, 5, 15];
|
|
58
|
+
/** Tries kept on a row per channel; older ones drop off so a long outage does not grow the row without end. */
|
|
59
|
+
const KEEP_ATTEMPTS = 10;
|
|
60
|
+
/** A try that has not answered after this long no longer blocks a retry (a hung connection). */
|
|
61
|
+
const HUNG_MS = 5 * 60_000;
|
|
62
|
+
let deps = defaultDeps;
|
|
63
|
+
export function setUpdateDeps(d) {
|
|
64
|
+
deps = d ? { ...defaultDeps, ...d } : defaultDeps;
|
|
65
|
+
}
|
|
66
|
+
const digestHours = () => {
|
|
67
|
+
const n = Number(process.env.LOCALDOST_UPDATE_DIGEST_HOURS);
|
|
68
|
+
return Number.isFinite(n) && n > 0 ? n : 3;
|
|
69
|
+
};
|
|
70
|
+
// ---------- feed ----------
|
|
71
|
+
export function feedFor(watchId, limit = 50) {
|
|
72
|
+
return list('updates', { watchId }, { orderBy: 'id DESC', limit });
|
|
73
|
+
}
|
|
74
|
+
function byKey(key) {
|
|
75
|
+
return list('updates', { key }, { limit: 1 })[0];
|
|
76
|
+
}
|
|
77
|
+
function patchUpdate(u, patch) {
|
|
78
|
+
update('updates', u.id, patch);
|
|
79
|
+
return { ...u, ...patch };
|
|
80
|
+
}
|
|
81
|
+
function context() {
|
|
82
|
+
return {
|
|
83
|
+
askForRun: (run) => askForRun(run),
|
|
84
|
+
askForJob: (id) => askForJob(id),
|
|
85
|
+
downSince: (watchId) => {
|
|
86
|
+
const rows = list('probe_state', { watchId }).filter((r) => r.down && r.downSince);
|
|
87
|
+
return rows.map((r) => r.downSince).sort()[0];
|
|
88
|
+
},
|
|
89
|
+
has: (key) => Boolean(byKey(key)),
|
|
90
|
+
dotName: (watchId) => get('watches', watchId)?.name ?? 'Your app',
|
|
91
|
+
isLead: (watchId) => get('watches', watchId)?.kind === 'lead',
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
// ---------- text ----------
|
|
95
|
+
/** Markdown to one plain line for a phone: link labels stay, link syntax and emphasis go. */
|
|
96
|
+
export function plainLine(md, max = 160) {
|
|
97
|
+
const t = md
|
|
98
|
+
.replace(/\[([^\]]+)\]\([^)\s]+\)/g, '$1')
|
|
99
|
+
.replace(/[*_`]/g, '')
|
|
100
|
+
.replace(/\s+/g, ' ')
|
|
101
|
+
.trim();
|
|
102
|
+
return t.length > max ? `${t.slice(0, max - 3).trimEnd()}...` : t;
|
|
103
|
+
}
|
|
104
|
+
function firstLine(md) {
|
|
105
|
+
return md.split('\n').find((l) => l.trim()) ?? '';
|
|
106
|
+
}
|
|
107
|
+
// ---------- delivery ----------
|
|
108
|
+
function slackTarget(w) {
|
|
109
|
+
const mode = w.updates?.slack ?? 'channel';
|
|
110
|
+
if (mode === 'off')
|
|
111
|
+
return null;
|
|
112
|
+
if (mode === 'dm') {
|
|
113
|
+
const u = w.updates?.slackUser?.trim();
|
|
114
|
+
return u ? { channel: u, person: `slack:${u}` } : null;
|
|
115
|
+
}
|
|
116
|
+
return w.slackChannel ? { channel: w.slackChannel.replace(/^#/, '') } : null;
|
|
117
|
+
}
|
|
118
|
+
function category(u) {
|
|
119
|
+
if (u.kind === 'decision')
|
|
120
|
+
return 'drafts';
|
|
121
|
+
if (u.kind === 'prod_down' || u.kind === 'prod_cause' || u.kind === 'prod_up')
|
|
122
|
+
return 'outages';
|
|
123
|
+
if (u.kind === 'bug' || u.kind === 'defect')
|
|
124
|
+
return 'bugs';
|
|
125
|
+
return undefined;
|
|
126
|
+
}
|
|
127
|
+
/** What follows "## Details" in the saved verdict of a team digest run: each member's own digest and the counts. Empty when there is none. */
|
|
128
|
+
function teamDigestDetails(runId) {
|
|
129
|
+
const summary = get('runs', runId)?.verdict?.summary ?? '';
|
|
130
|
+
const at = summary.indexOf(TEAM_DETAILS_HEADING);
|
|
131
|
+
return at < 0 ? '' : summary.slice(at + TEAM_DETAILS_HEADING.length).trim();
|
|
132
|
+
}
|
|
133
|
+
/** Posts the update to the dot's web chat as the dot, and tells open apps. */
|
|
134
|
+
async function toChat(w, u, extra = {}) {
|
|
135
|
+
const askedLine = u.origin?.channel === 'web' && u.origin.text ? `You asked: "${plainLine(u.origin.text, 80)}"\n\n` : '';
|
|
136
|
+
// Asked in the all-dots chat: the reply goes there, with the dot's name since that chat has many dots.
|
|
137
|
+
const inAll = u.origin?.channel === 'web' && u.origin.scope === 'all';
|
|
138
|
+
// Asked in a lead's chat (KAT-405): a member did the work, and the answer lands in the lead's chat with the member's name first.
|
|
139
|
+
const leadId = u.origin?.channel === 'web' && u.origin.scope === 'lead' ? u.origin.leadId : undefined;
|
|
140
|
+
const fromLine = inAll || leadId ? `From ${w.name}\n\n` : '';
|
|
141
|
+
const content = await renderRefs(openLinks(`${askedLine}${fromLine}**${u.title}**\n${u.body}`), { dotId: w.id, format: 'markdown' });
|
|
142
|
+
// The team digest of a lead keeps each member's own digest and the counts under a Details fold in the chat (KAT-405). Slack gets the body alone.
|
|
143
|
+
// The members' digests carry titles from PRs and tickets, so a link the server did not build shows its address there too (KAT-380).
|
|
144
|
+
const details = w.kind === 'lead' && u.kind === 'daily_digest' && u.runId ? teamDigestDetails(u.runId) : '';
|
|
145
|
+
const detailsText = details ? await renderRefs(openLinks(details), { dotId: w.id, format: 'markdown' }) : '';
|
|
146
|
+
const meta = {
|
|
147
|
+
id: u.id,
|
|
148
|
+
kind: u.kind,
|
|
149
|
+
level: u.level,
|
|
150
|
+
...(u.severity ? { severity: u.severity } : {}),
|
|
151
|
+
...(u.runId ? { runId: u.runId } : {}),
|
|
152
|
+
...(u.approvalId ? { approvalId: u.approvalId } : {}),
|
|
153
|
+
...(u.url ? { url: u.url } : {}),
|
|
154
|
+
...(extra.replyTo ? { replyTo: extra.replyTo } : {}),
|
|
155
|
+
...(extra.approval ? { approval: extra.approval } : {}),
|
|
156
|
+
...(detailsText ? { details: detailsText } : {}),
|
|
157
|
+
};
|
|
158
|
+
const row = insert('messages', { watchId: inAll ? 'all' : (leadId ?? w.id), role: 'assistant', content, kind: 'update', update: meta });
|
|
159
|
+
publish({ type: 'update', update: u, message: row });
|
|
160
|
+
return row;
|
|
161
|
+
}
|
|
162
|
+
/** A digest or a morning note: level now, but one quiet message that never retries. */
|
|
163
|
+
function isNote(kind) {
|
|
164
|
+
return kind === 'digest' || kind === 'morning' || kind === 'daily_digest';
|
|
165
|
+
}
|
|
166
|
+
function noticeOf(u, urgency) {
|
|
167
|
+
const cat = category(u);
|
|
168
|
+
return {
|
|
169
|
+
key: u.key,
|
|
170
|
+
title: plainLine(u.title, 80),
|
|
171
|
+
body: plainLine(firstLine(u.body), 160),
|
|
172
|
+
...(u.url ? { url: u.url } : {}),
|
|
173
|
+
urgency,
|
|
174
|
+
...(cat ? { category: cat } : {}),
|
|
175
|
+
watchId: u.watchId,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
function plan(w, u, d) {
|
|
179
|
+
const urgent = u.severity === 'critical' || u.severity === 'high';
|
|
180
|
+
const base = {
|
|
181
|
+
title: u.title,
|
|
182
|
+
body: u.body,
|
|
183
|
+
key: u.key,
|
|
184
|
+
...(u.runId ? { runId: u.runId } : {}),
|
|
185
|
+
...(u.url ? { url: u.url } : {}),
|
|
186
|
+
...(u.linkLabel ? { linkLabel: u.linkLabel } : {}),
|
|
187
|
+
};
|
|
188
|
+
if (u.level === 'thread') {
|
|
189
|
+
const o = u.origin;
|
|
190
|
+
if (o?.channel === 'slack') {
|
|
191
|
+
return {
|
|
192
|
+
chat: !d.passing,
|
|
193
|
+
slack: { post: { ...base, channel: o.slackChannel, threadTs: o.threadTs, asked: true, ...(u.approvalId ? { approvalId: u.approvalId } : {}) } },
|
|
194
|
+
push: null,
|
|
195
|
+
needsThem: false,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
if (o?.channel === 'web') {
|
|
199
|
+
// The asking turn already shows the run card as it runs; a passing result needs no second message.
|
|
200
|
+
return { chat: !d.passing, slack: null, push: urgent ? noticeOf(u, 'high') : null, needsThem: urgent };
|
|
201
|
+
}
|
|
202
|
+
// A follow-up (prod_cause): under its parent in Slack, and in the chat.
|
|
203
|
+
const parent = u.parentKey ? byKey(u.parentKey) : undefined;
|
|
204
|
+
const target = slackTarget(w);
|
|
205
|
+
const ts = parent?.slackTs;
|
|
206
|
+
const channel = parent?.slackChannel ?? target?.channel;
|
|
207
|
+
return {
|
|
208
|
+
chat: true,
|
|
209
|
+
slack: ts && channel ? { post: { ...base, channel, threadTs: ts } } : null,
|
|
210
|
+
push: null,
|
|
211
|
+
needsThem: false,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
if (u.level === 'now') {
|
|
215
|
+
const target = slackTarget(w);
|
|
216
|
+
const urgency = isNote(u.kind) ? 'low' : urgent ? 'high' : 'normal';
|
|
217
|
+
// A push a flow asked for (KAT-281): phone only. The chat and Slack have their own messages for the run.
|
|
218
|
+
if (u.kind === 'push')
|
|
219
|
+
return { chat: false, slack: null, push: noticeOf(u, u.urgency ?? 'normal'), needsThem: true };
|
|
220
|
+
return {
|
|
221
|
+
chat: true,
|
|
222
|
+
slack: target
|
|
223
|
+
? {
|
|
224
|
+
post: { ...base, channel: target.channel, ...(u.approvalId ? { approvalId: u.approvalId } : {}) },
|
|
225
|
+
...(target.person ? { person: target.person } : {}),
|
|
226
|
+
}
|
|
227
|
+
: null,
|
|
228
|
+
push: noticeOf(u, urgency),
|
|
229
|
+
needsThem: u.kind !== 'digest' && u.kind !== 'daily_digest',
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
return { chat: false, slack: null, push: null, needsThem: false };
|
|
233
|
+
}
|
|
234
|
+
// ---------- tries, retries ----------
|
|
235
|
+
/** Urgent: worth retrying. Digests and morning notes (level now, but a note) and thread replies are one-shot. */
|
|
236
|
+
function retryable(u) {
|
|
237
|
+
return u.level === 'now' && !isNote(u.kind);
|
|
238
|
+
}
|
|
239
|
+
/** Tries in flight, `<update id>:<channel>` to the start time. One per channel, so a retry never overlaps a slow try. */
|
|
240
|
+
const inflight = new Map();
|
|
241
|
+
function busy(id, channel) {
|
|
242
|
+
const at = inflight.get(`${id}:${channel}`);
|
|
243
|
+
return at !== undefined && Date.now() - at < HUNG_MS;
|
|
244
|
+
}
|
|
245
|
+
function errorText(err) {
|
|
246
|
+
const m = err instanceof Error ? err.message : String(err);
|
|
247
|
+
return m.replace(/\s+/g, ' ').trim().slice(0, 200) || 'unknown error';
|
|
248
|
+
}
|
|
249
|
+
/** Reads the row fresh and writes the change, with no await in between, so late results never overwrite each other. */
|
|
250
|
+
function mutate(id, fn) {
|
|
251
|
+
const u = get('updates', id);
|
|
252
|
+
if (!u)
|
|
253
|
+
return undefined;
|
|
254
|
+
return patchUpdate(u, fn(u));
|
|
255
|
+
}
|
|
256
|
+
function lastAttempt(u, channel) {
|
|
257
|
+
return [...(u.attempts ?? [])].reverse().find((a) => a.channel === channel);
|
|
258
|
+
}
|
|
259
|
+
/** When the next retry of a channel is due: after its last failed try, by how many tries it had. */
|
|
260
|
+
function dueAt(u, channel) {
|
|
261
|
+
const last = lastAttempt(u, channel);
|
|
262
|
+
// No try on record yet: the server stopped in the middle of the first one; the written due time stands.
|
|
263
|
+
if (!last)
|
|
264
|
+
return u.retryAt ? Date.parse(u.retryAt) : undefined;
|
|
265
|
+
if (last.ok)
|
|
266
|
+
return undefined;
|
|
267
|
+
const wait = RETRY_MINUTES[Math.min(last.n, RETRY_MINUTES.length) - 1] ?? RETRY_MINUTES[RETRY_MINUTES.length - 1];
|
|
268
|
+
return Date.parse(last.at) + wait * 60_000;
|
|
269
|
+
}
|
|
270
|
+
/** The retry fields for a set of waiting channels, kept in step with the indexed flags. */
|
|
271
|
+
function retryFields(u, retrying, failed) {
|
|
272
|
+
const dues = [...retrying].map((c) => dueAt(u, c)).filter((t) => t !== undefined);
|
|
273
|
+
return {
|
|
274
|
+
retrying: [...retrying],
|
|
275
|
+
retryPending: retrying.size > 0,
|
|
276
|
+
retryAt: dues.length ? new Date(Math.min(...dues)).toISOString() : undefined,
|
|
277
|
+
failed: [...failed],
|
|
278
|
+
failedDelivery: failed.size > 0,
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Runs one try on a channel and settles it within attemptMs. A try that does not answer in time counts as
|
|
283
|
+
* failed (the retry clock starts), but stays in flight: the retry waits for it, and if it finally succeeds
|
|
284
|
+
* the row is corrected, so a slow first try and its retry never both send.
|
|
285
|
+
*/
|
|
286
|
+
async function tryChannel(id, channel, send) {
|
|
287
|
+
const k = `${id}:${channel}`;
|
|
288
|
+
inflight.set(k, Date.now());
|
|
289
|
+
const call = (async () => send())().then((r) => ({ ok: true, r }), (err) => ({ ok: false, err }));
|
|
290
|
+
let timer;
|
|
291
|
+
const late = new Promise((resolve) => {
|
|
292
|
+
timer = setTimeout(() => resolve('late'), deps.attemptMs);
|
|
293
|
+
timer.unref?.();
|
|
294
|
+
});
|
|
295
|
+
const first = await Promise.race([call, late]);
|
|
296
|
+
clearTimeout(timer);
|
|
297
|
+
if (first === 'late') {
|
|
298
|
+
void call.then((res) => {
|
|
299
|
+
inflight.delete(k);
|
|
300
|
+
if (res.ok && res.r.sent)
|
|
301
|
+
lateSuccess(id, channel, res.r.patch);
|
|
302
|
+
else if (!res.ok)
|
|
303
|
+
log.warn(`updates: ${channel} try for "${id}" failed after the wait: ${errorText(res.err)}`);
|
|
304
|
+
});
|
|
305
|
+
return { outcome: 'failed', error: `no answer after ${Math.max(1, Math.round(deps.attemptMs / 1000))} seconds` };
|
|
306
|
+
}
|
|
307
|
+
inflight.delete(k);
|
|
308
|
+
if (!first.ok)
|
|
309
|
+
return { outcome: 'failed', error: errorText(first.err) };
|
|
310
|
+
return { outcome: first.r.sent ? 'sent' : 'skipped', ...(first.r.patch ? { patch: first.r.patch } : {}) };
|
|
311
|
+
}
|
|
312
|
+
/** A try that was counted as failed for being slow went through after all: record it, drop the retry. */
|
|
313
|
+
function lateSuccess(id, channel, patch) {
|
|
314
|
+
const row = mutate(id, (u) => {
|
|
315
|
+
const attempts = [...(u.attempts ?? [])];
|
|
316
|
+
for (let i = attempts.length - 1; i >= 0; i--) {
|
|
317
|
+
const a = attempts[i];
|
|
318
|
+
if (a.channel !== channel)
|
|
319
|
+
continue;
|
|
320
|
+
const { error: _error, ...rest } = a;
|
|
321
|
+
attempts[i] = { ...rest, ok: true };
|
|
322
|
+
break;
|
|
323
|
+
}
|
|
324
|
+
const next = { ...u, attempts };
|
|
325
|
+
const retrying = new Set(u.retrying ?? []);
|
|
326
|
+
const failed = new Set(u.failed ?? []);
|
|
327
|
+
retrying.delete(channel);
|
|
328
|
+
failed.delete(channel);
|
|
329
|
+
return { ...patch, attempts, delivered: [...new Set([...u.delivered, channel])], ...retryFields(next, retrying, failed) };
|
|
330
|
+
});
|
|
331
|
+
if (row) {
|
|
332
|
+
publish({ type: 'update', update: row });
|
|
333
|
+
recordDelivery(row.watchId, channel, true);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
/** Records a try on the row and sets what happens next for an urgent update. Returns the fresh row. */
|
|
337
|
+
function applyTry(id, channel, t, at) {
|
|
338
|
+
const row = mutate(id, (u) => {
|
|
339
|
+
let attempts = [...(u.attempts ?? [])];
|
|
340
|
+
const n = (lastAttempt(u, channel)?.n ?? 0) + 1;
|
|
341
|
+
if (t.outcome !== 'skipped')
|
|
342
|
+
attempts.push({ channel, n, at: at.toISOString(), ok: t.outcome === 'sent', ...(t.error ? { error: t.error } : {}) });
|
|
343
|
+
// Keep the newest tries of this channel; `n` keeps counting, so the backoff does not start over.
|
|
344
|
+
const own = attempts.filter((a) => a.channel === channel);
|
|
345
|
+
if (own.length > KEEP_ATTEMPTS) {
|
|
346
|
+
const drop = new Set(own.slice(0, own.length - KEEP_ATTEMPTS));
|
|
347
|
+
attempts = attempts.filter((a) => !drop.has(a));
|
|
348
|
+
}
|
|
349
|
+
const next = { ...u, attempts };
|
|
350
|
+
const retrying = new Set(u.retrying ?? []);
|
|
351
|
+
const failed = new Set(u.failed ?? []);
|
|
352
|
+
retrying.delete(channel);
|
|
353
|
+
const patch = { attempts };
|
|
354
|
+
if (t.outcome === 'sent') {
|
|
355
|
+
failed.delete(channel);
|
|
356
|
+
patch.delivered = [...new Set([...u.delivered, channel])];
|
|
357
|
+
Object.assign(patch, t.patch);
|
|
358
|
+
}
|
|
359
|
+
else if (t.outcome === 'failed' && retryable(u)) {
|
|
360
|
+
// Retries go on until the channel delivers once (KAT-271). From the 4th failed try the failure is also listed.
|
|
361
|
+
retrying.add(channel);
|
|
362
|
+
if (n > RETRY_MINUTES.length)
|
|
363
|
+
failed.add(channel);
|
|
364
|
+
}
|
|
365
|
+
return { ...patch, ...retryFields(next, retrying, failed) };
|
|
366
|
+
});
|
|
367
|
+
if (row && t.outcome === 'failed') {
|
|
368
|
+
if (row.failed?.includes(channel))
|
|
369
|
+
log.warn(`updates: "${row.key}" still not sent on ${channel}, trying again later: ${t.error}`);
|
|
370
|
+
publish({ type: 'update', update: row });
|
|
371
|
+
}
|
|
372
|
+
// Self-health: the same error on a channel three tries in a row raises an outage on the dot (dogfood ledger).
|
|
373
|
+
if (row && t.outcome !== 'skipped')
|
|
374
|
+
recordDelivery(row.watchId, channel, t.outcome === 'sent', t.error, at);
|
|
375
|
+
return row;
|
|
376
|
+
}
|
|
377
|
+
function gateFor(w, u, p, channel, needsThem, now) {
|
|
378
|
+
const note = isNote(u.kind);
|
|
379
|
+
const personal = (person) => {
|
|
380
|
+
const prefs = prefsFor(person);
|
|
381
|
+
// A digest or morning note counts as digest for the person's level, though it goes out at once.
|
|
382
|
+
const as = note ? 'digest' : u.level === 'thread' ? 'now' : u.level;
|
|
383
|
+
if (!personWants(prefs, as, { needsThem }))
|
|
384
|
+
return 'skip';
|
|
385
|
+
return inQuietHours(prefs, now) ? 'hold' : 'send';
|
|
386
|
+
};
|
|
387
|
+
if (channel === 'slack') {
|
|
388
|
+
if (!p.slack)
|
|
389
|
+
return 'skip';
|
|
390
|
+
return p.slack.person ? personal(p.slack.person) : 'send';
|
|
391
|
+
}
|
|
392
|
+
// The phone follows the push_notify rule, the person's level, and quiet hours.
|
|
393
|
+
if (!p.push || ruleFor(w, 'push_notify') !== 'act')
|
|
394
|
+
return 'skip';
|
|
395
|
+
// One phone alert per run outcome: a push a flow asked for and the router's own update for the same run never both go out.
|
|
396
|
+
if (u.kind !== 'push' && OUTCOME_KINDS.has(u.kind) && phoneAlreadyTold(u))
|
|
397
|
+
return 'skip';
|
|
398
|
+
return personal(ME);
|
|
399
|
+
}
|
|
400
|
+
/** Update kinds that report how a run ended. A flow's own push about the same run covers them on the phone. */
|
|
401
|
+
const OUTCOME_KINDS = new Set(['bug', 'defect', 'result', 'prod_down', 'prod_up', 'daily_digest']);
|
|
402
|
+
/** True when the phone has this update's alert, is about to get it, or quiet hours hold it. */
|
|
403
|
+
function phoneStanding(u) {
|
|
404
|
+
return u.delivered.includes('push') || Boolean(u.retrying?.includes('push')) || Boolean(u.held?.includes('push'));
|
|
405
|
+
}
|
|
406
|
+
/** Whether another update of the same run, of the other family (a flow's push or a run outcome), already has the phone. */
|
|
407
|
+
function phoneAlreadyTold(u) {
|
|
408
|
+
if (!u.runId)
|
|
409
|
+
return false;
|
|
410
|
+
const mine = u.kind === 'push';
|
|
411
|
+
return list('updates', { watchId: u.watchId, runId: u.runId }).some((r) => r.id !== u.id && (mine ? OUTCOME_KINDS.has(r.kind) : r.kind === 'push') && phoneStanding(r));
|
|
412
|
+
}
|
|
413
|
+
function senderFor(w, u, p, channel) {
|
|
414
|
+
if (channel === 'slack') {
|
|
415
|
+
const post = p.slack.post;
|
|
416
|
+
return async () => {
|
|
417
|
+
const res = await deps.slack(w, post);
|
|
418
|
+
if (res.ok)
|
|
419
|
+
return { sent: true, patch: { slackChannel: res.channel, ...(res.ts ? { slackTs: res.ts } : {}) } };
|
|
420
|
+
log.info(`updates: Slack post "${u.key}" not sent: ${res.reason}`);
|
|
421
|
+
return { sent: false };
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
const notice = p.push;
|
|
425
|
+
return async () => ({ sent: await deps.push(notice) });
|
|
426
|
+
}
|
|
427
|
+
async function deliver(w, u, d) {
|
|
428
|
+
const p = plan(w, u, d);
|
|
429
|
+
const now = deps.now();
|
|
430
|
+
const note = isNote(u.kind);
|
|
431
|
+
const needsThem = p.needsThem || (note && Boolean(d.needsThem));
|
|
432
|
+
const urgent = retryable(u);
|
|
433
|
+
const held = [];
|
|
434
|
+
// Decide every channel and write the retry before anything is awaited (KAT-271). A restart in the middle of
|
|
435
|
+
// the first try of one channel then still leaves the other channel, and this one, waiting for a retry.
|
|
436
|
+
const gates = { slack: gateFor(w, u, p, 'slack', needsThem, now), push: gateFor(w, u, p, 'push', needsThem, now) };
|
|
437
|
+
if (urgent) {
|
|
438
|
+
const waiting = ['slack', 'push'].filter((c) => gates[c] === 'send');
|
|
439
|
+
if (waiting.length)
|
|
440
|
+
mutate(u.id, (r) => {
|
|
441
|
+
const at = new Date(now.getTime() + deps.attemptMs + RETRY_MINUTES[0] * 60_000).toISOString();
|
|
442
|
+
return { retrying: [...new Set([...(r.retrying ?? []), ...waiting])], retryPending: true, retryAt: r.retryAt && r.retryAt < at ? r.retryAt : at };
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
if (p.chat) {
|
|
446
|
+
try {
|
|
447
|
+
const replyTo = u.origin?.channel === 'web' ? u.origin.messageId : undefined;
|
|
448
|
+
await toChat(w, u, { ...(replyTo ? { replyTo } : {}), ...(d.approval ? { approval: d.approval } : {}) });
|
|
449
|
+
mutate(u.id, (r) => ({ delivered: [...new Set([...r.delivered, 'chat'])] }));
|
|
450
|
+
}
|
|
451
|
+
catch (err) {
|
|
452
|
+
log.warn(`updates: chat post failed: ${String(err)}`);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
for (const channel of ['slack', 'push']) {
|
|
456
|
+
const gate = gates[channel];
|
|
457
|
+
if (gate === 'hold') {
|
|
458
|
+
held.push(channel);
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
if (gate !== 'send')
|
|
462
|
+
continue;
|
|
463
|
+
const t = await tryChannel(u.id, channel, senderFor(w, u, p, channel));
|
|
464
|
+
if (t.outcome === 'failed')
|
|
465
|
+
log.warn(`updates: ${channel} for "${u.key}" failed: ${t.error}`);
|
|
466
|
+
applyTry(u.id, channel, t, now);
|
|
467
|
+
}
|
|
468
|
+
const fresh = held.length ? mutate(u.id, () => ({ held })) : get('updates', u.id);
|
|
469
|
+
return fresh ?? u;
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* Sends again what failed on an urgent update: each waiting channel whose backoff has passed, unless a
|
|
473
|
+
* try is still in flight on it. The plan is rebuilt from the row, and the channel's gate is asked again, so
|
|
474
|
+
* quiet hours that began since hold the retry for the morning note and a person who turned the phone off
|
|
475
|
+
* hears nothing. Returns the number of retries that went through.
|
|
476
|
+
*/
|
|
477
|
+
export async function retryRound(now = deps.now()) {
|
|
478
|
+
let sent = 0;
|
|
479
|
+
for (const row of list('updates', { retryPending: true }, { orderBy: 'id ASC', limit: 100 })) {
|
|
480
|
+
const w = get('watches', row.watchId);
|
|
481
|
+
if (!w || w.archived) {
|
|
482
|
+
mutate(row.id, (u) => retryFields(u, new Set(), new Set(u.failed ?? [])));
|
|
483
|
+
continue;
|
|
484
|
+
}
|
|
485
|
+
// A decision that was approved, rejected, or expired needs no alert any more.
|
|
486
|
+
if (row.kind === 'decision' && row.approvalId && get('approvals', row.approvalId)?.status !== 'pending') {
|
|
487
|
+
mutate(row.id, (u) => retryFields(u, new Set(), new Set()));
|
|
488
|
+
continue;
|
|
489
|
+
}
|
|
490
|
+
for (const channel of row.retrying ?? []) {
|
|
491
|
+
const u = get('updates', row.id);
|
|
492
|
+
if (!u || !u.retrying?.includes(channel))
|
|
493
|
+
continue;
|
|
494
|
+
if (u.delivered.includes(channel)) {
|
|
495
|
+
mutate(u.id, (r) => retryFields(r, new Set((r.retrying ?? []).filter((c) => c !== channel)), new Set(r.failed ?? [])));
|
|
496
|
+
continue;
|
|
497
|
+
}
|
|
498
|
+
const due = dueAt(u, channel);
|
|
499
|
+
if (due === undefined || due > now.getTime() || busy(u.id, channel))
|
|
500
|
+
continue;
|
|
501
|
+
const p = plan(w, u, {});
|
|
502
|
+
const gate = gateFor(w, u, p, channel, p.needsThem, now);
|
|
503
|
+
if (gate !== 'send') {
|
|
504
|
+
mutate(u.id, (r) => ({
|
|
505
|
+
...(gate === 'hold' ? { held: [...new Set([...(r.held ?? []), channel])] } : {}),
|
|
506
|
+
...retryFields(r, new Set((r.retrying ?? []).filter((c) => c !== channel)), new Set(r.failed ?? [])),
|
|
507
|
+
}));
|
|
508
|
+
continue;
|
|
509
|
+
}
|
|
510
|
+
const t = await tryChannel(u.id, channel, senderFor(w, u, p, channel));
|
|
511
|
+
if (t.outcome === 'failed')
|
|
512
|
+
log.warn(`updates: retry on ${channel} for "${u.key}" failed: ${t.error}`);
|
|
513
|
+
applyTry(u.id, channel, t, now);
|
|
514
|
+
if (t.outcome === 'sent')
|
|
515
|
+
sent++;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
return sent;
|
|
519
|
+
}
|
|
520
|
+
/** Urgent updates that failed on a channel at least three retries in a row, newest first. Shown on the dot page and in the Inbox. */
|
|
521
|
+
export function failedDeliveries(watchId, limit = 50) {
|
|
522
|
+
return list('updates', { failedDelivery: true, ...(watchId ? { watchId } : {}) }, { orderBy: 'id DESC', limit });
|
|
523
|
+
}
|
|
524
|
+
/** How many alerts are in the failed list, for all dots or one. `failedDeliveries` returns at most a page of them. */
|
|
525
|
+
export function countFailedDeliveries(watchId) {
|
|
526
|
+
return count('updates', { failedDelivery: true, ...(watchId ? { watchId } : {}) });
|
|
527
|
+
}
|
|
528
|
+
/** Why a failed alert cannot be sent or dismissed. `code` maps to the HTTP status in the route. */
|
|
529
|
+
export class FailedAlertError extends Error {
|
|
530
|
+
code;
|
|
531
|
+
constructor(code, message) {
|
|
532
|
+
super(message);
|
|
533
|
+
this.code = code;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
/** True when the update is a decision whose approval is no longer pending (approved, rejected, expired, superseded). */
|
|
537
|
+
export function approvalSettled(u) {
|
|
538
|
+
return u.kind === 'decision' && Boolean(u.approvalId) && get('approvals', u.approvalId)?.status !== 'pending';
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* Sends a failed urgent update again now (the Retry button), on its failed channels only. It uses the same
|
|
542
|
+
* plan, sender, and try as the timed retries, so the attempts history grows and a success corrects the row.
|
|
543
|
+
* A person asked, so quiet hours do not hold it. A channel the person turned off, or that has no target
|
|
544
|
+
* any more, is not sent and is reported. A decision whose approval was settled is refused: nobody needs it.
|
|
545
|
+
*/
|
|
546
|
+
export async function retryNow(id, now = deps.now()) {
|
|
547
|
+
const row = get('updates', id);
|
|
548
|
+
if (!row)
|
|
549
|
+
throw new FailedAlertError('not_found', 'update not found');
|
|
550
|
+
if (!row.failedDelivery || !row.failed?.length)
|
|
551
|
+
throw new FailedAlertError('not_failed', 'This alert is not in the failed list.');
|
|
552
|
+
const w = get('watches', row.watchId);
|
|
553
|
+
if (!w || w.archived)
|
|
554
|
+
throw new FailedAlertError('archived', 'The dot is archived. Restore it to send this alert.');
|
|
555
|
+
if (approvalSettled(row))
|
|
556
|
+
throw new FailedAlertError('settled', 'The approval is already settled, so this alert is not worth sending.');
|
|
557
|
+
const sent = [];
|
|
558
|
+
const failed = [];
|
|
559
|
+
for (const channel of [...row.failed]) {
|
|
560
|
+
const u = get('updates', id);
|
|
561
|
+
if (!u || u.delivered.includes(channel))
|
|
562
|
+
continue;
|
|
563
|
+
if (busy(id, channel)) {
|
|
564
|
+
failed.push({ channel, error: 'A try is still running. Wait a moment and retry.' });
|
|
565
|
+
continue;
|
|
566
|
+
}
|
|
567
|
+
const p = plan(w, u, {});
|
|
568
|
+
if (gateFor(w, u, p, channel, p.needsThem, now) === 'skip') {
|
|
569
|
+
failed.push({ channel, error: `${channel === 'slack' ? 'Slack' : 'The phone'} is turned off for this dot, so nothing was sent.` });
|
|
570
|
+
continue;
|
|
571
|
+
}
|
|
572
|
+
const t = await tryChannel(id, channel, senderFor(w, u, p, channel));
|
|
573
|
+
if (t.outcome === 'failed')
|
|
574
|
+
log.warn(`updates: manual retry on ${channel} for "${u.key}" failed: ${t.error}`);
|
|
575
|
+
applyTry(id, channel, t, now);
|
|
576
|
+
if (t.outcome === 'sent')
|
|
577
|
+
sent.push(channel);
|
|
578
|
+
else
|
|
579
|
+
failed.push({ channel, error: t.error ?? 'The send was skipped.' });
|
|
580
|
+
}
|
|
581
|
+
const fresh = get('updates', id) ?? row;
|
|
582
|
+
publish({ type: 'update', update: fresh });
|
|
583
|
+
return { update: fresh, sent, failed, cleared: !fresh.failedDelivery };
|
|
584
|
+
}
|
|
585
|
+
/** The fields that end a failed alert's retries and take it off the failed list. */
|
|
586
|
+
function dismissPatch(by, at) {
|
|
587
|
+
return {
|
|
588
|
+
failed: [],
|
|
589
|
+
failedDelivery: false,
|
|
590
|
+
retrying: [],
|
|
591
|
+
retryPending: false,
|
|
592
|
+
retryAt: undefined,
|
|
593
|
+
dismissedBy: by,
|
|
594
|
+
dismissedAt: at.toISOString(),
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* Takes one failed alert off the failed list without sending (the Dismiss button). The retries stop too,
|
|
599
|
+
* or the next failed try would list it again. The feed row, the chat message, and the attempts stay.
|
|
600
|
+
*/
|
|
601
|
+
export function dismissFailed(id, by, now = deps.now()) {
|
|
602
|
+
const row = get('updates', id);
|
|
603
|
+
if (!row)
|
|
604
|
+
throw new FailedAlertError('not_found', 'update not found');
|
|
605
|
+
if (!row.failedDelivery)
|
|
606
|
+
throw new FailedAlertError('not_failed', 'This alert is not in the failed list.');
|
|
607
|
+
const next = patchUpdate(row, dismissPatch(by, now));
|
|
608
|
+
publish({ type: 'update', update: next });
|
|
609
|
+
return next;
|
|
610
|
+
}
|
|
611
|
+
/** Dismisses every failed alert, or those of one dot, or those created before `olderThan`. Returns the ones it cleared. */
|
|
612
|
+
export function dismissAllFailed(by, opts = {}, now = deps.now()) {
|
|
613
|
+
const before = opts.olderThan ? Date.parse(opts.olderThan) : undefined;
|
|
614
|
+
const rows = failedDeliveries(opts.dotId, 100_000).filter((u) => before === undefined || Date.parse(u.createdAt) < before);
|
|
615
|
+
return rows.map((u) => {
|
|
616
|
+
const next = patchUpdate(u, dismissPatch(by, now));
|
|
617
|
+
publish({ type: 'update', update: next });
|
|
618
|
+
return next;
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
/** Stores and delivers one draft. Returns the feed row, or null for a repeat or a dropped draft. */
|
|
622
|
+
export async function handleDraft(d, decision) {
|
|
623
|
+
if (byKey(d.key))
|
|
624
|
+
return null;
|
|
625
|
+
const w = get('watches', d.watchId);
|
|
626
|
+
if (!w || w.archived)
|
|
627
|
+
return null;
|
|
628
|
+
const route = decision ?? (await routeUpdate(d));
|
|
629
|
+
if (route.level === 'drop')
|
|
630
|
+
return null;
|
|
631
|
+
let row;
|
|
632
|
+
try {
|
|
633
|
+
row = insert('updates', {
|
|
634
|
+
watchId: d.watchId,
|
|
635
|
+
kind: d.kind,
|
|
636
|
+
level: route.level,
|
|
637
|
+
key: d.key,
|
|
638
|
+
// Run errors can carry tokens or passwords: the stored text is masked once, so chat, Slack, and push
|
|
639
|
+
// all send the masked version.
|
|
640
|
+
title: redactCode(d.title),
|
|
641
|
+
body: redactCode(d.body),
|
|
642
|
+
reason: route.reason,
|
|
643
|
+
delivered: [],
|
|
644
|
+
...(d.url ? { url: d.url } : {}),
|
|
645
|
+
...(d.linkLabel ? { linkLabel: d.linkLabel } : {}),
|
|
646
|
+
...(d.severity ? { severity: d.severity } : {}),
|
|
647
|
+
...(d.reproduced !== undefined ? { reproduced: d.reproduced } : {}),
|
|
648
|
+
...(d.urgency ? { urgency: d.urgency } : {}),
|
|
649
|
+
...(d.runId ? { runId: d.runId } : {}),
|
|
650
|
+
...(d.approvalId ? { approvalId: d.approvalId } : {}),
|
|
651
|
+
...(d.jobId ? { jobId: d.jobId } : {}),
|
|
652
|
+
...(d.origin ? { origin: d.origin } : {}),
|
|
653
|
+
...(d.parentKey ? { parentKey: d.parentKey } : {}),
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
catch {
|
|
657
|
+
// The unique key: another event for the same thing got there first.
|
|
658
|
+
return null;
|
|
659
|
+
}
|
|
660
|
+
if (route.level === 'digest')
|
|
661
|
+
return row;
|
|
662
|
+
return deliver(w, row, d);
|
|
663
|
+
}
|
|
664
|
+
/** Drafts for one event, handled in order. */
|
|
665
|
+
export async function handleEvent(e) {
|
|
666
|
+
if (e.type === 'update')
|
|
667
|
+
return [];
|
|
668
|
+
const drafts = updatesFromEvent(e, context(), (id) => get('runs', id));
|
|
669
|
+
const out = [];
|
|
670
|
+
for (const d of drafts) {
|
|
671
|
+
const r = await handleDraft(d);
|
|
672
|
+
if (r)
|
|
673
|
+
out.push(r);
|
|
674
|
+
}
|
|
675
|
+
return out;
|
|
676
|
+
}
|
|
677
|
+
// ---------- digests and the morning note ----------
|
|
678
|
+
/** How long after the due minute the morning note may still go out. */
|
|
679
|
+
const MORNING_WINDOW_MIN = 6 * 60;
|
|
680
|
+
function stateOf(watchId) {
|
|
681
|
+
return get('update_state', watchId) ?? insert('update_state', { id: watchId });
|
|
682
|
+
}
|
|
683
|
+
function localDay(d) {
|
|
684
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
|
|
685
|
+
}
|
|
686
|
+
/** Feed rows waiting for a digest: digest-level ones, plus urgent ones quiet hours held from the phone. */
|
|
687
|
+
export function pendingFor(watchId) {
|
|
688
|
+
return list('updates', { watchId, digestedAt: null }, { orderBy: 'id ASC', limit: 500 }).filter((u) => u.level === 'digest' || (u.held?.length ?? 0) > 0);
|
|
689
|
+
}
|
|
690
|
+
/** The digest lines for a set of feed rows: one line per update with its link, then one count of the checks that started. */
|
|
691
|
+
export function digestLines(rows) {
|
|
692
|
+
const picked = rows.filter((u) => u.kind === 'picked_up').length;
|
|
693
|
+
const lines = rows
|
|
694
|
+
.filter((u) => u.kind !== 'picked_up')
|
|
695
|
+
.map((u) => `- ${plainLine(u.title, 120)}${u.url ? ` [${u.linkLabel ?? 'Open details'}](${u.url})` : ''}`);
|
|
696
|
+
if (picked)
|
|
697
|
+
lines.push(`- Started ${picked === 1 ? '1 check' : `${picked} checks`}.`);
|
|
698
|
+
return lines;
|
|
699
|
+
}
|
|
700
|
+
async function sendNote(w, kind, key, title, body, needsThem, rows) {
|
|
701
|
+
const out = await handleDraft({ watchId: w.id, kind, key, title, body, url: `/#/chat/${encodeURIComponent(w.id)}`, needsThem });
|
|
702
|
+
const at = deps.now().toISOString();
|
|
703
|
+
for (const r of rows)
|
|
704
|
+
patchUpdate(r, { digestedAt: at });
|
|
705
|
+
return out;
|
|
706
|
+
}
|
|
707
|
+
/** A digest round for one dot when one is due and something waits. */
|
|
708
|
+
export async function digestRound(w, now = deps.now()) {
|
|
709
|
+
const st = stateOf(w.id);
|
|
710
|
+
if (!st.lastDigestAt) {
|
|
711
|
+
update('update_state', w.id, { lastDigestAt: now.toISOString() });
|
|
712
|
+
return null;
|
|
713
|
+
}
|
|
714
|
+
if (now.getTime() - Date.parse(st.lastDigestAt) < digestHours() * 3600_000)
|
|
715
|
+
return null;
|
|
716
|
+
if (inQuietHours(prefsFor(ME), now))
|
|
717
|
+
return null;
|
|
718
|
+
const rows = pendingFor(w.id).filter((u) => u.level === 'digest');
|
|
719
|
+
update('update_state', w.id, { lastDigestAt: now.toISOString() });
|
|
720
|
+
if (!rows.length)
|
|
721
|
+
return null;
|
|
722
|
+
const hours = Math.round((now.getTime() - Date.parse(st.lastDigestAt)) / 3600_000);
|
|
723
|
+
const title = `${w.name}: ${rows.length === 1 ? '1 update' : `${rows.length} updates`} from the last ${hours === 1 ? 'hour' : `${hours} hours`}`;
|
|
724
|
+
return sendNote(w, 'digest', `digest:${w.id}:${now.toISOString().slice(0, 13)}`, title, digestLines(rows).join('\n'), false, rows);
|
|
725
|
+
}
|
|
726
|
+
/** The morning note: what quiet hours held, what waits for the digest, and decisions still open. */
|
|
727
|
+
export async function morningNote(w, now = deps.now()) {
|
|
728
|
+
const prefs = prefsFor(ME);
|
|
729
|
+
// Due when quiet hours end (08:00 with none), and for a few hours after, so a server that was busy or
|
|
730
|
+
// restarted at that minute still says good morning, and never while quiet hours still hold the phone.
|
|
731
|
+
const minute = now.getHours() * 60 + now.getMinutes();
|
|
732
|
+
const due = morningMinute(prefs);
|
|
733
|
+
if (minute < due || minute >= due + MORNING_WINDOW_MIN || inQuietHours(prefs, now))
|
|
734
|
+
return null;
|
|
735
|
+
const day = localDay(now);
|
|
736
|
+
const st = stateOf(w.id);
|
|
737
|
+
if (st.lastMorningDay === day)
|
|
738
|
+
return null;
|
|
739
|
+
update('update_state', w.id, { lastMorningDay: day });
|
|
740
|
+
const rows = pendingFor(w.id);
|
|
741
|
+
const held = rows.filter((u) => (u.held?.length ?? 0) > 0);
|
|
742
|
+
const waiting = liveCount(w.id);
|
|
743
|
+
// Since yesterday's morning, from the runs themselves (the same counts the daily digest uses).
|
|
744
|
+
const since = new Date(now.getTime() - 24 * 3600_000).toISOString();
|
|
745
|
+
const runs = digestRuns(list('runs', { watchId: w.id }).filter((r) => r.createdAt >= since));
|
|
746
|
+
if (!rows.length && !waiting)
|
|
747
|
+
return null;
|
|
748
|
+
// What needs the reader first: items to review, then what quiet hours held. "All green" never sits next to an open risk.
|
|
749
|
+
const open = list('approvals', { watchId: w.id, status: 'pending' }).filter((a) => !a.expiresAt || Date.parse(a.expiresAt) > now.getTime());
|
|
750
|
+
const items = open.slice(0, waiting).map(reviewItem);
|
|
751
|
+
while (items.length < waiting)
|
|
752
|
+
items.push('an item to review');
|
|
753
|
+
const note = morningText([...items, ...held.map((u) => plainLine(u.title, 80))], runs);
|
|
754
|
+
return sendNote(w, 'morning', `morning:${w.id}:${day}`, note.title, note.body, held.length > 0 || waiting > 0, rows);
|
|
755
|
+
}
|
|
756
|
+
export async function tick(now = deps.now()) {
|
|
757
|
+
try {
|
|
758
|
+
await retryRound(now);
|
|
759
|
+
}
|
|
760
|
+
catch (err) {
|
|
761
|
+
log.warn(`updates: retries failed: ${String(err)}`);
|
|
762
|
+
}
|
|
763
|
+
for (const w of list('watches', { paused: false })) {
|
|
764
|
+
if (w.archived)
|
|
765
|
+
continue;
|
|
766
|
+
try {
|
|
767
|
+
// The morning note first: it carries what waited, so the digest round finds nothing left.
|
|
768
|
+
await morningNote(w, now);
|
|
769
|
+
await digestRound(w, now);
|
|
770
|
+
}
|
|
771
|
+
catch (err) {
|
|
772
|
+
log.warn(`updates: digest for "${w.name}" failed: ${String(err)}`);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
// ---------- pushes other flows ask for (KAT-281) ----------
|
|
777
|
+
/**
|
|
778
|
+
* A phone push a flow sent through the push_notify rule (a digest ready, a rerun result, a QA review alert).
|
|
779
|
+
* The router makes no message of its own for most of them, so it sends them here: once per key, with retries,
|
|
780
|
+
* quiet hours, and the person's level. When the router already told the phone about the same run, or the
|
|
781
|
+
* run's own update is about to, this one is not sent, so the phone never gets two alerts for one run.
|
|
782
|
+
*/
|
|
783
|
+
export async function adoptPush(p) {
|
|
784
|
+
const draft = {
|
|
785
|
+
watchId: p.watchId,
|
|
786
|
+
kind: 'push',
|
|
787
|
+
key: `push:${p.key}`,
|
|
788
|
+
title: p.title,
|
|
789
|
+
body: p.body,
|
|
790
|
+
urgency: p.urgency,
|
|
791
|
+
...(p.url ? { url: p.url } : {}),
|
|
792
|
+
...(p.runId ? { runId: p.runId } : {}),
|
|
793
|
+
};
|
|
794
|
+
if (phoneAlreadyTold({ id: '', watchId: p.watchId, kind: 'push', ...(p.runId ? { runId: p.runId } : {}) }))
|
|
795
|
+
return null;
|
|
796
|
+
return handleDraft(draft, { level: 'now', reason: 'A flow asked for this push through the push_notify rule.' });
|
|
797
|
+
}
|
|
798
|
+
// ---------- start ----------
|
|
799
|
+
/** Subscribes to live events and runs the digest clock. Returns stop. */
|
|
800
|
+
export function startUpdates() {
|
|
801
|
+
// Sandbox mode (KAT-286): no digests and no morning notes. Direct senders stay, and they are refused at push and Slack.
|
|
802
|
+
if (sandboxOn()) {
|
|
803
|
+
sandboxSkips('the update router and its digests');
|
|
804
|
+
return () => undefined;
|
|
805
|
+
}
|
|
806
|
+
setUpdatesOwnDelivery(true);
|
|
807
|
+
setPushAdopter(async (p) => {
|
|
808
|
+
try {
|
|
809
|
+
await adoptPush(p);
|
|
810
|
+
}
|
|
811
|
+
catch (err) {
|
|
812
|
+
log.warn(`updates: push "${p.key}" failed: ${String(err)}`);
|
|
813
|
+
}
|
|
814
|
+
});
|
|
815
|
+
let chain = Promise.resolve();
|
|
816
|
+
const unsub = subscribe((e) => {
|
|
817
|
+
if (e.type !== 'run' && e.type !== 'approval' && e.type !== 'picked_up' && e.type !== 'dot')
|
|
818
|
+
return;
|
|
819
|
+
// One at a time, in event order, so a follow-up finds the message it follows.
|
|
820
|
+
chain = chain.then(() => handleEvent(e)).catch((err) => log.warn(`updates: ${String(err)}`));
|
|
821
|
+
});
|
|
822
|
+
const t = setInterval(() => void tick(), 60_000);
|
|
823
|
+
t.unref?.();
|
|
824
|
+
// Retries that were waiting when the server stopped go out soon after it starts, not a minute later.
|
|
825
|
+
const boot = setTimeout(() => void retryRound().catch((err) => log.warn(`updates: retries failed: ${String(err)}`)), 5_000);
|
|
826
|
+
boot.unref?.();
|
|
827
|
+
return () => {
|
|
828
|
+
unsub();
|
|
829
|
+
clearInterval(t);
|
|
830
|
+
clearTimeout(boot);
|
|
831
|
+
setUpdatesOwnDelivery(false);
|
|
832
|
+
setPushAdopter(null);
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
//# sourceMappingURL=index.js.map
|