@evermore.work/server 2026.509.0-canary.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/dist/adapters/builtin-adapter-types.d.ts +5 -0
- package/dist/adapters/builtin-adapter-types.d.ts.map +1 -0
- package/dist/adapters/builtin-adapter-types.js +17 -0
- package/dist/adapters/builtin-adapter-types.js.map +1 -0
- package/dist/adapters/codex-models.d.ts +5 -0
- package/dist/adapters/codex-models.d.ts.map +1 -0
- package/dist/adapters/codex-models.js +105 -0
- package/dist/adapters/codex-models.js.map +1 -0
- package/dist/adapters/cursor-models.d.ts +13 -0
- package/dist/adapters/cursor-models.d.ts.map +1 -0
- package/dist/adapters/cursor-models.js +148 -0
- package/dist/adapters/cursor-models.js.map +1 -0
- package/dist/adapters/http/execute.d.ts +3 -0
- package/dist/adapters/http/execute.d.ts.map +1 -0
- package/dist/adapters/http/execute.js +51 -0
- package/dist/adapters/http/execute.js.map +1 -0
- package/dist/adapters/http/execute.test.d.ts +2 -0
- package/dist/adapters/http/execute.test.d.ts.map +1 -0
- package/dist/adapters/http/execute.test.js +40 -0
- package/dist/adapters/http/execute.test.js.map +1 -0
- package/dist/adapters/http/index.d.ts +3 -0
- package/dist/adapters/http/index.d.ts.map +1 -0
- package/dist/adapters/http/index.js +20 -0
- package/dist/adapters/http/index.js.map +1 -0
- package/dist/adapters/http/test.d.ts +3 -0
- package/dist/adapters/http/test.d.ts.map +1 -0
- package/dist/adapters/http/test.js +106 -0
- package/dist/adapters/http/test.js.map +1 -0
- package/dist/adapters/index.d.ts +4 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +3 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/plugin-loader.d.ts +28 -0
- package/dist/adapters/plugin-loader.d.ts.map +1 -0
- package/dist/adapters/plugin-loader.js +196 -0
- package/dist/adapters/plugin-loader.js.map +1 -0
- package/dist/adapters/process/execute.d.ts +3 -0
- package/dist/adapters/process/execute.d.ts.map +1 -0
- package/dist/adapters/process/execute.js +70 -0
- package/dist/adapters/process/execute.js.map +1 -0
- package/dist/adapters/process/index.d.ts +3 -0
- package/dist/adapters/process/index.d.ts.map +1 -0
- package/dist/adapters/process/index.js +23 -0
- package/dist/adapters/process/index.js.map +1 -0
- package/dist/adapters/process/test.d.ts +3 -0
- package/dist/adapters/process/test.d.ts.map +1 -0
- package/dist/adapters/process/test.js +77 -0
- package/dist/adapters/process/test.js.map +1 -0
- package/dist/adapters/registry.d.ts +69 -0
- package/dist/adapters/registry.d.ts.map +1 -0
- package/dist/adapters/registry.js +566 -0
- package/dist/adapters/registry.js.map +1 -0
- package/dist/adapters/types.d.ts +2 -0
- package/dist/adapters/types.d.ts.map +1 -0
- package/dist/adapters/types.js +2 -0
- package/dist/adapters/types.js.map +1 -0
- package/dist/adapters/utils.d.ts +43 -0
- package/dist/adapters/utils.d.ts.map +1 -0
- package/dist/adapters/utils.js +52 -0
- package/dist/adapters/utils.js.map +1 -0
- package/dist/agent-auth-jwt.d.ts +14 -0
- package/dist/agent-auth-jwt.d.ts.map +1 -0
- package/dist/agent-auth-jwt.js +117 -0
- package/dist/agent-auth-jwt.js.map +1 -0
- package/dist/app.d.ts +43 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +373 -0
- package/dist/app.js.map +1 -0
- package/dist/attachment-types.d.ts +23 -0
- package/dist/attachment-types.d.ts.map +1 -0
- package/dist/attachment-types.js +91 -0
- package/dist/attachment-types.js.map +1 -0
- package/dist/auth/better-auth.d.ts +33 -0
- package/dist/auth/better-auth.d.ts.map +1 -0
- package/dist/auth/better-auth.js +133 -0
- package/dist/auth/better-auth.js.map +1 -0
- package/dist/board-claim.d.ts +23 -0
- package/dist/board-claim.d.ts.map +1 -0
- package/dist/board-claim.js +115 -0
- package/dist/board-claim.js.map +1 -0
- package/dist/config-file.d.ts +3 -0
- package/dist/config-file.d.ts.map +1 -0
- package/dist/config-file.js +16 -0
- package/dist/config-file.js.map +1 -0
- package/dist/config.d.ts +44 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +226 -0
- package/dist/config.js.map +1 -0
- package/dist/dev-runner-worktree.d.ts +15 -0
- package/dist/dev-runner-worktree.d.ts.map +1 -0
- package/dist/dev-runner-worktree.js +68 -0
- package/dist/dev-runner-worktree.js.map +1 -0
- package/dist/dev-server-status.d.ts +27 -0
- package/dist/dev-server-status.d.ts.map +1 -0
- package/dist/dev-server-status.js +74 -0
- package/dist/dev-server-status.js.map +1 -0
- package/dist/dev-watch-ignore.d.ts +2 -0
- package/dist/dev-watch-ignore.d.ts.map +1 -0
- package/dist/dev-watch-ignore.js +36 -0
- package/dist/dev-watch-ignore.js.map +1 -0
- package/dist/errors.d.ts +12 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +28 -0
- package/dist/errors.js.map +1 -0
- package/dist/home-paths.d.ts +17 -0
- package/dist/home-paths.d.ts.map +1 -0
- package/dist/home-paths.js +75 -0
- package/dist/home-paths.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +753 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/join-request-dedupe.d.ts +11 -0
- package/dist/lib/join-request-dedupe.d.ts.map +1 -0
- package/dist/lib/join-request-dedupe.js +49 -0
- package/dist/lib/join-request-dedupe.js.map +1 -0
- package/dist/log-redaction.d.ts +11 -0
- package/dist/log-redaction.d.ts.map +1 -0
- package/dist/log-redaction.js +122 -0
- package/dist/log-redaction.js.map +1 -0
- package/dist/middleware/auth.d.ts +12 -0
- package/dist/middleware/auth.d.ts.map +1 -0
- package/dist/middleware/auth.js +302 -0
- package/dist/middleware/auth.js.map +1 -0
- package/dist/middleware/board-mutation-guard.d.ts +3 -0
- package/dist/middleware/board-mutation-guard.d.ts.map +1 -0
- package/dist/middleware/board-mutation-guard.js +67 -0
- package/dist/middleware/board-mutation-guard.js.map +1 -0
- package/dist/middleware/error-handler.d.ts +17 -0
- package/dist/middleware/error-handler.d.ts.map +1 -0
- package/dist/middleware/error-handler.js +45 -0
- package/dist/middleware/error-handler.js.map +1 -0
- package/dist/middleware/http-log-policy.d.ts +2 -0
- package/dist/middleware/http-log-policy.d.ts.map +1 -0
- package/dist/middleware/http-log-policy.js +52 -0
- package/dist/middleware/http-log-policy.js.map +1 -0
- package/dist/middleware/index.d.ts +4 -0
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +4 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/middleware/logger.d.ts +4 -0
- package/dist/middleware/logger.d.ts.map +1 -0
- package/dist/middleware/logger.js +92 -0
- package/dist/middleware/logger.js.map +1 -0
- package/dist/middleware/private-hostname-guard.d.ts +11 -0
- package/dist/middleware/private-hostname-guard.d.ts.map +1 -0
- package/dist/middleware/private-hostname-guard.js +78 -0
- package/dist/middleware/private-hostname-guard.js.map +1 -0
- package/dist/middleware/validate.d.ts +4 -0
- package/dist/middleware/validate.d.ts.map +1 -0
- package/dist/middleware/validate.js +7 -0
- package/dist/middleware/validate.js.map +1 -0
- package/dist/onboarding-assets/ceo/AGENTS.md +59 -0
- package/dist/onboarding-assets/ceo/HEARTBEAT.md +85 -0
- package/dist/onboarding-assets/ceo/SOUL.md +33 -0
- package/dist/onboarding-assets/ceo/TOOLS.md +3 -0
- package/dist/onboarding-assets/default/AGENTS.md +17 -0
- package/dist/paths.d.ts +3 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +31 -0
- package/dist/paths.js.map +1 -0
- package/dist/realtime/live-events-ws.d.ts +28 -0
- package/dist/realtime/live-events-ws.d.ts.map +1 -0
- package/dist/realtime/live-events-ws.js +187 -0
- package/dist/realtime/live-events-ws.js.map +1 -0
- package/dist/redaction.d.ts +5 -0
- package/dist/redaction.d.ts.map +1 -0
- package/dist/redaction.js +98 -0
- package/dist/redaction.js.map +1 -0
- package/dist/routes/access.d.ts +82 -0
- package/dist/routes/access.d.ts.map +1 -0
- package/dist/routes/access.js +3411 -0
- package/dist/routes/access.js.map +1 -0
- package/dist/routes/activity.d.ts +3 -0
- package/dist/routes/activity.d.ts.map +1 -0
- package/dist/routes/activity.js +90 -0
- package/dist/routes/activity.js.map +1 -0
- package/dist/routes/adapters.d.ts +16 -0
- package/dist/routes/adapters.d.ts.map +1 -0
- package/dist/routes/adapters.js +527 -0
- package/dist/routes/adapters.js.map +1 -0
- package/dist/routes/agents.d.ts +6 -0
- package/dist/routes/agents.d.ts.map +1 -0
- package/dist/routes/agents.js +2753 -0
- package/dist/routes/agents.js.map +1 -0
- package/dist/routes/approvals.d.ts +6 -0
- package/dist/routes/approvals.d.ts.map +1 -0
- package/dist/routes/approvals.js +300 -0
- package/dist/routes/approvals.js.map +1 -0
- package/dist/routes/assets.d.ts +4 -0
- package/dist/routes/assets.d.ts.map +1 -0
- package/dist/routes/assets.js +309 -0
- package/dist/routes/assets.js.map +1 -0
- package/dist/routes/auth.d.ts +3 -0
- package/dist/routes/auth.d.ts.map +1 -0
- package/dist/routes/auth.js +82 -0
- package/dist/routes/auth.js.map +1 -0
- package/dist/routes/authz.d.ts +19 -0
- package/dist/routes/authz.d.ts.map +1 -0
- package/dist/routes/authz.js +75 -0
- package/dist/routes/authz.js.map +1 -0
- package/dist/routes/companies.d.ts +4 -0
- package/dist/routes/companies.d.ts.map +1 -0
- package/dist/routes/companies.js +359 -0
- package/dist/routes/companies.js.map +1 -0
- package/dist/routes/company-skills.d.ts +3 -0
- package/dist/routes/company-skills.d.ts.map +1 -0
- package/dist/routes/company-skills.js +258 -0
- package/dist/routes/company-skills.js.map +1 -0
- package/dist/routes/costs.d.ts +11 -0
- package/dist/routes/costs.d.ts.map +1 -0
- package/dist/routes/costs.js +285 -0
- package/dist/routes/costs.js.map +1 -0
- package/dist/routes/dashboard.d.ts +3 -0
- package/dist/routes/dashboard.d.ts.map +1 -0
- package/dist/routes/dashboard.js +15 -0
- package/dist/routes/dashboard.js.map +1 -0
- package/dist/routes/environment-selection.d.ts +13 -0
- package/dist/routes/environment-selection.d.ts.map +1 -0
- package/dist/routes/environment-selection.js +30 -0
- package/dist/routes/environment-selection.js.map +1 -0
- package/dist/routes/environments.d.ts +6 -0
- package/dist/routes/environments.d.ts.map +1 -0
- package/dist/routes/environments.js +401 -0
- package/dist/routes/environments.js.map +1 -0
- package/dist/routes/execution-workspaces.d.ts +3 -0
- package/dist/routes/execution-workspaces.d.ts.map +1 -0
- package/dist/routes/execution-workspaces.js +536 -0
- package/dist/routes/execution-workspaces.js.map +1 -0
- package/dist/routes/goals.d.ts +3 -0
- package/dist/routes/goals.d.ts.map +1 -0
- package/dist/routes/goals.js +101 -0
- package/dist/routes/goals.js.map +1 -0
- package/dist/routes/health.d.ts +9 -0
- package/dist/routes/health.d.ts.map +1 -0
- package/dist/routes/health.js +114 -0
- package/dist/routes/health.js.map +1 -0
- package/dist/routes/inbox-dismissals.d.ts +3 -0
- package/dist/routes/inbox-dismissals.d.ts.map +1 -0
- package/dist/routes/inbox-dismissals.js +58 -0
- package/dist/routes/inbox-dismissals.js.map +1 -0
- package/dist/routes/index.d.ts +22 -0
- package/dist/routes/index.d.ts.map +1 -0
- package/dist/routes/index.js +22 -0
- package/dist/routes/index.js.map +1 -0
- package/dist/routes/instance-database-backups.d.ts +15 -0
- package/dist/routes/instance-database-backups.d.ts.map +1 -0
- package/dist/routes/instance-database-backups.js +12 -0
- package/dist/routes/instance-database-backups.js.map +1 -0
- package/dist/routes/instance-settings.d.ts +3 -0
- package/dist/routes/instance-settings.d.ts.map +1 -0
- package/dist/routes/instance-settings.js +110 -0
- package/dist/routes/instance-settings.js.map +1 -0
- package/dist/routes/issue-tree-control.d.ts +3 -0
- package/dist/routes/issue-tree-control.d.ts.map +1 -0
- package/dist/routes/issue-tree-control.js +363 -0
- package/dist/routes/issue-tree-control.js.map +1 -0
- package/dist/routes/issues-checkout-wakeup.d.ts +9 -0
- package/dist/routes/issues-checkout-wakeup.d.ts.map +1 -0
- package/dist/routes/issues-checkout-wakeup.js +12 -0
- package/dist/routes/issues-checkout-wakeup.js.map +1 -0
- package/dist/routes/issues.d.ts +23 -0
- package/dist/routes/issues.d.ts.map +1 -0
- package/dist/routes/issues.js +3886 -0
- package/dist/routes/issues.js.map +1 -0
- package/dist/routes/llms.d.ts +3 -0
- package/dist/routes/llms.d.ts.map +1 -0
- package/dist/routes/llms.js +80 -0
- package/dist/routes/llms.js.map +1 -0
- package/dist/routes/org-chart-svg.d.ts +25 -0
- package/dist/routes/org-chart-svg.d.ts.map +1 -0
- package/dist/routes/org-chart-svg.js +656 -0
- package/dist/routes/org-chart-svg.js.map +1 -0
- package/dist/routes/plugin-ui-static.d.ts +69 -0
- package/dist/routes/plugin-ui-static.d.ts.map +1 -0
- package/dist/routes/plugin-ui-static.js +411 -0
- package/dist/routes/plugin-ui-static.js.map +1 -0
- package/dist/routes/plugins.d.ts +121 -0
- package/dist/routes/plugins.d.ts.map +1 -0
- package/dist/routes/plugins.js +2192 -0
- package/dist/routes/plugins.js.map +1 -0
- package/dist/routes/projects.d.ts +3 -0
- package/dist/routes/projects.d.ts.map +1 -0
- package/dist/routes/projects.js +566 -0
- package/dist/routes/projects.js.map +1 -0
- package/dist/routes/routines.d.ts +6 -0
- package/dist/routes/routines.d.ts.map +1 -0
- package/dist/routes/routines.js +417 -0
- package/dist/routes/routines.js.map +1 -0
- package/dist/routes/secrets.d.ts +3 -0
- package/dist/routes/secrets.d.ts.map +1 -0
- package/dist/routes/secrets.js +128 -0
- package/dist/routes/secrets.js.map +1 -0
- package/dist/routes/sidebar-badges.d.ts +3 -0
- package/dist/routes/sidebar-badges.d.ts.map +1 -0
- package/dist/routes/sidebar-badges.js +68 -0
- package/dist/routes/sidebar-badges.js.map +1 -0
- package/dist/routes/sidebar-preferences.d.ts +3 -0
- package/dist/routes/sidebar-preferences.d.ts.map +1 -0
- package/dist/routes/sidebar-preferences.js +63 -0
- package/dist/routes/sidebar-preferences.js.map +1 -0
- package/dist/routes/user-profiles.d.ts +3 -0
- package/dist/routes/user-profiles.d.ts.map +1 -0
- package/dist/routes/user-profiles.js +337 -0
- package/dist/routes/user-profiles.js.map +1 -0
- package/dist/routes/workspace-command-authz.d.ts +14 -0
- package/dist/routes/workspace-command-authz.d.ts.map +1 -0
- package/dist/routes/workspace-command-authz.js +83 -0
- package/dist/routes/workspace-command-authz.js.map +1 -0
- package/dist/routes/workspace-runtime-service-authz.d.ts +12 -0
- package/dist/routes/workspace-runtime-service-authz.d.ts.map +1 -0
- package/dist/routes/workspace-runtime-service-authz.js +96 -0
- package/dist/routes/workspace-runtime-service-authz.js.map +1 -0
- package/dist/runtime-api.d.ts +19 -0
- package/dist/runtime-api.d.ts.map +1 -0
- package/dist/runtime-api.js +137 -0
- package/dist/runtime-api.js.map +1 -0
- package/dist/secrets/external-stub-providers.d.ts +5 -0
- package/dist/secrets/external-stub-providers.d.ts.map +1 -0
- package/dist/secrets/external-stub-providers.js +21 -0
- package/dist/secrets/external-stub-providers.js.map +1 -0
- package/dist/secrets/local-encrypted-provider.d.ts +3 -0
- package/dist/secrets/local-encrypted-provider.d.ts.map +1 -0
- package/dist/secrets/local-encrypted-provider.js +116 -0
- package/dist/secrets/local-encrypted-provider.js.map +1 -0
- package/dist/secrets/provider-registry.d.ts +5 -0
- package/dist/secrets/provider-registry.d.ts.map +1 -0
- package/dist/secrets/provider-registry.js +20 -0
- package/dist/secrets/provider-registry.js.map +1 -0
- package/dist/secrets/types.d.ts +21 -0
- package/dist/secrets/types.d.ts.map +1 -0
- package/dist/secrets/types.js +2 -0
- package/dist/secrets/types.js.map +1 -0
- package/dist/services/access.d.ts +171 -0
- package/dist/services/access.d.ts.map +1 -0
- package/dist/services/access.js +522 -0
- package/dist/services/access.js.map +1 -0
- package/dist/services/activity-log.d.ts +19 -0
- package/dist/services/activity-log.d.ts.map +1 -0
- package/dist/services/activity-log.js +99 -0
- package/dist/services/activity-log.js.map +1 -0
- package/dist/services/activity.d.ts +462 -0
- package/dist/services/activity.d.ts.map +1 -0
- package/dist/services/activity.js +443 -0
- package/dist/services/activity.js.map +1 -0
- package/dist/services/adapter-plugin-store.d.ts +36 -0
- package/dist/services/adapter-plugin-store.d.ts.map +1 -0
- package/dist/services/adapter-plugin-store.js +154 -0
- package/dist/services/adapter-plugin-store.js.map +1 -0
- package/dist/services/agent-instructions.d.ts +91 -0
- package/dist/services/agent-instructions.d.ts.map +1 -0
- package/dist/services/agent-instructions.js +580 -0
- package/dist/services/agent-instructions.js.map +1 -0
- package/dist/services/agent-permissions.d.ts +6 -0
- package/dist/services/agent-permissions.d.ts.map +1 -0
- package/dist/services/agent-permissions.js +18 -0
- package/dist/services/agent-permissions.js.map +1 -0
- package/dist/services/agent-start-lock.d.ts +2 -0
- package/dist/services/agent-start-lock.d.ts.map +1 -0
- package/dist/services/agent-start-lock.js +43 -0
- package/dist/services/agent-start-lock.js.map +1 -0
- package/dist/services/agents.d.ts +2253 -0
- package/dist/services/agents.d.ts.map +1 -0
- package/dist/services/agents.js +609 -0
- package/dist/services/agents.js.map +1 -0
- package/dist/services/approvals.d.ts +546 -0
- package/dist/services/approvals.d.ts.map +1 -0
- package/dist/services/approvals.js +212 -0
- package/dist/services/approvals.js.map +1 -0
- package/dist/services/assets.d.ts +33 -0
- package/dist/services/assets.d.ts.map +1 -0
- package/dist/services/assets.js +17 -0
- package/dist/services/assets.js.map +1 -0
- package/dist/services/board-auth.d.ts +239 -0
- package/dist/services/board-auth.d.ts.map +1 -0
- package/dist/services/board-auth.js +300 -0
- package/dist/services/board-auth.js.map +1 -0
- package/dist/services/budgets.d.ts +38 -0
- package/dist/services/budgets.d.ts.map +1 -0
- package/dist/services/budgets.js +784 -0
- package/dist/services/budgets.js.map +1 -0
- package/dist/services/companies.d.ts +154 -0
- package/dist/services/companies.d.ts.map +1 -0
- package/dist/services/companies.js +267 -0
- package/dist/services/companies.js.map +1 -0
- package/dist/services/company-export-readme.d.ts +17 -0
- package/dist/services/company-export-readme.d.ts.map +1 -0
- package/dist/services/company-export-readme.js +148 -0
- package/dist/services/company-export-readme.js.map +1 -0
- package/dist/services/company-member-roles.d.ts +9 -0
- package/dist/services/company-member-roles.d.ts.map +1 -0
- package/dist/services/company-member-roles.js +46 -0
- package/dist/services/company-member-roles.js.map +1 -0
- package/dist/services/company-portability.d.ts +24 -0
- package/dist/services/company-portability.d.ts.map +1 -0
- package/dist/services/company-portability.js +4076 -0
- package/dist/services/company-portability.js.map +1 -0
- package/dist/services/company-search-rate-limit.d.ts +22 -0
- package/dist/services/company-search-rate-limit.d.ts.map +1 -0
- package/dist/services/company-search-rate-limit.js +38 -0
- package/dist/services/company-search-rate-limit.js.map +1 -0
- package/dist/services/company-search.d.ts +8 -0
- package/dist/services/company-search.d.ts.map +1 -0
- package/dist/services/company-search.js +626 -0
- package/dist/services/company-search.js.map +1 -0
- package/dist/services/company-skills.d.ts +77 -0
- package/dist/services/company-skills.d.ts.map +1 -0
- package/dist/services/company-skills.js +2120 -0
- package/dist/services/company-skills.js.map +1 -0
- package/dist/services/costs.d.ts +127 -0
- package/dist/services/costs.d.ts.map +1 -0
- package/dist/services/costs.js +409 -0
- package/dist/services/costs.js.map +1 -0
- package/dist/services/cron.d.ts +80 -0
- package/dist/services/cron.d.ts.map +1 -0
- package/dist/services/cron.js +300 -0
- package/dist/services/cron.js.map +1 -0
- package/dist/services/dashboard.d.ts +34 -0
- package/dist/services/dashboard.d.ts.map +1 -0
- package/dist/services/dashboard.js +142 -0
- package/dist/services/dashboard.js.map +1 -0
- package/dist/services/default-agent-instructions.d.ts +9 -0
- package/dist/services/default-agent-instructions.d.ts.map +1 -0
- package/dist/services/default-agent-instructions.js +20 -0
- package/dist/services/default-agent-instructions.js.map +1 -0
- package/dist/services/documents.d.ts +199 -0
- package/dist/services/documents.d.ts.map +1 -0
- package/dist/services/documents.js +411 -0
- package/dist/services/documents.js.map +1 -0
- package/dist/services/environment-config.d.ts +43 -0
- package/dist/services/environment-config.d.ts.map +1 -0
- package/dist/services/environment-config.js +388 -0
- package/dist/services/environment-config.js.map +1 -0
- package/dist/services/environment-execution-target.d.ts +21 -0
- package/dist/services/environment-execution-target.d.ts.map +1 -0
- package/dist/services/environment-execution-target.js +119 -0
- package/dist/services/environment-execution-target.js.map +1 -0
- package/dist/services/environment-probe.d.ts +9 -0
- package/dist/services/environment-probe.d.ts.map +1 -0
- package/dist/services/environment-probe.js +106 -0
- package/dist/services/environment-probe.js.map +1 -0
- package/dist/services/environment-run-orchestrator.d.ts +124 -0
- package/dist/services/environment-run-orchestrator.d.ts.map +1 -0
- package/dist/services/environment-run-orchestrator.js +392 -0
- package/dist/services/environment-run-orchestrator.js.map +1 -0
- package/dist/services/environment-runtime.d.ts +90 -0
- package/dist/services/environment-runtime.d.ts.map +1 -0
- package/dist/services/environment-runtime.js +934 -0
- package/dist/services/environment-runtime.js.map +1 -0
- package/dist/services/environments.d.ts +36 -0
- package/dist/services/environments.d.ts.map +1 -0
- package/dist/services/environments.js +260 -0
- package/dist/services/environments.js.map +1 -0
- package/dist/services/execution-workspace-policy.d.ts +30 -0
- package/dist/services/execution-workspace-policy.d.ts.map +1 -0
- package/dist/services/execution-workspace-policy.js +195 -0
- package/dist/services/execution-workspace-policy.js.map +1 -0
- package/dist/services/execution-workspaces.d.ts +30 -0
- package/dist/services/execution-workspaces.d.ts.map +1 -0
- package/dist/services/execution-workspaces.js +635 -0
- package/dist/services/execution-workspaces.js.map +1 -0
- package/dist/services/feedback-redaction.d.ts +23 -0
- package/dist/services/feedback-redaction.d.ts.map +1 -0
- package/dist/services/feedback-redaction.js +150 -0
- package/dist/services/feedback-redaction.js.map +1 -0
- package/dist/services/feedback-share-client.d.ts +9 -0
- package/dist/services/feedback-share-client.d.ts.map +1 -0
- package/dist/services/feedback-share-client.js +46 -0
- package/dist/services/feedback-share-client.js.map +1 -0
- package/dist/services/feedback.d.ts +93 -0
- package/dist/services/feedback.d.ts.map +1 -0
- package/dist/services/feedback.js +1717 -0
- package/dist/services/feedback.js.map +1 -0
- package/dist/services/finance.d.ts +93 -0
- package/dist/services/finance.d.ts.map +1 -0
- package/dist/services/finance.js +120 -0
- package/dist/services/finance.js.map +1 -0
- package/dist/services/github-fetch.d.ts +4 -0
- package/dist/services/github-fetch.d.ts.map +1 -0
- package/dist/services/github-fetch.js +23 -0
- package/dist/services/github-fetch.js.map +1 -0
- package/dist/services/goals.d.ts +433 -0
- package/dist/services/goals.d.ts.map +1 -0
- package/dist/services/goals.js +54 -0
- package/dist/services/goals.js.map +1 -0
- package/dist/services/heartbeat-run-summary.d.ts +7 -0
- package/dist/services/heartbeat-run-summary.d.ts.map +1 -0
- package/dist/services/heartbeat-run-summary.js +84 -0
- package/dist/services/heartbeat-run-summary.js.map +1 -0
- package/dist/services/heartbeat-stop-metadata.d.ts +28 -0
- package/dist/services/heartbeat-stop-metadata.d.ts.map +1 -0
- package/dist/services/heartbeat-stop-metadata.js +86 -0
- package/dist/services/heartbeat-stop-metadata.js.map +1 -0
- package/dist/services/heartbeat-stop-metadata.test.d.ts +2 -0
- package/dist/services/heartbeat-stop-metadata.test.d.ts.map +1 -0
- package/dist/services/heartbeat-stop-metadata.test.js +93 -0
- package/dist/services/heartbeat-stop-metadata.test.js.map +1 -0
- package/dist/services/heartbeat.d.ts +1484 -0
- package/dist/services/heartbeat.d.ts.map +1 -0
- package/dist/services/heartbeat.js +7557 -0
- package/dist/services/heartbeat.js.map +1 -0
- package/dist/services/hire-hook.d.ts +14 -0
- package/dist/services/hire-hook.d.ts.map +1 -0
- package/dist/services/hire-hook.js +85 -0
- package/dist/services/hire-hook.js.map +1 -0
- package/dist/services/inbox-dismissals.d.ts +22 -0
- package/dist/services/inbox-dismissals.d.ts.map +1 -0
- package/dist/services/inbox-dismissals.js +33 -0
- package/dist/services/inbox-dismissals.js.map +1 -0
- package/dist/services/index.d.ts +44 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +44 -0
- package/dist/services/index.js.map +1 -0
- package/dist/services/instance-settings.d.ts +11 -0
- package/dist/services/instance-settings.d.ts.map +1 -0
- package/dist/services/instance-settings.js +138 -0
- package/dist/services/instance-settings.js.map +1 -0
- package/dist/services/invite-grants.d.ts +15 -0
- package/dist/services/invite-grants.d.ts.map +1 -0
- package/dist/services/invite-grants.js +50 -0
- package/dist/services/invite-grants.js.map +1 -0
- package/dist/services/issue-approvals.d.ts +56 -0
- package/dist/services/issue-approvals.d.ts.map +1 -0
- package/dist/services/issue-approvals.js +153 -0
- package/dist/services/issue-approvals.js.map +1 -0
- package/dist/services/issue-assignment-wakeup.d.ts +29 -0
- package/dist/services/issue-assignment-wakeup.d.ts.map +1 -0
- package/dist/services/issue-assignment-wakeup.js +22 -0
- package/dist/services/issue-assignment-wakeup.js.map +1 -0
- package/dist/services/issue-continuation-summary.d.ts +66 -0
- package/dist/services/issue-continuation-summary.d.ts.map +1 -0
- package/dist/services/issue-continuation-summary.js +212 -0
- package/dist/services/issue-continuation-summary.js.map +1 -0
- package/dist/services/issue-execution-policy.d.ts +93 -0
- package/dist/services/issue-execution-policy.d.ts.map +1 -0
- package/dist/services/issue-execution-policy.js +838 -0
- package/dist/services/issue-execution-policy.js.map +1 -0
- package/dist/services/issue-goal-fallback.d.ts +18 -0
- package/dist/services/issue-goal-fallback.d.ts.map +1 -0
- package/dist/services/issue-goal-fallback.js +33 -0
- package/dist/services/issue-goal-fallback.js.map +1 -0
- package/dist/services/issue-liveness.d.ts +3 -0
- package/dist/services/issue-liveness.d.ts.map +1 -0
- package/dist/services/issue-liveness.js +2 -0
- package/dist/services/issue-liveness.js.map +1 -0
- package/dist/services/issue-references.d.ts +21 -0
- package/dist/services/issue-references.d.ts.map +1 -0
- package/dist/services/issue-references.js +318 -0
- package/dist/services/issue-references.js.map +1 -0
- package/dist/services/issue-thread-interactions.d.ts +76 -0
- package/dist/services/issue-thread-interactions.d.ts.map +1 -0
- package/dist/services/issue-thread-interactions.js +923 -0
- package/dist/services/issue-thread-interactions.js.map +1 -0
- package/dist/services/issue-thread-interactions.test.d.ts +2 -0
- package/dist/services/issue-thread-interactions.test.d.ts.map +1 -0
- package/dist/services/issue-thread-interactions.test.js +195 -0
- package/dist/services/issue-thread-interactions.test.js.map +1 -0
- package/dist/services/issue-tree-control.d.ts +89 -0
- package/dist/services/issue-tree-control.d.ts.map +1 -0
- package/dist/services/issue-tree-control.js +933 -0
- package/dist/services/issue-tree-control.js.map +1 -0
- package/dist/services/issues.d.ts +710 -0
- package/dist/services/issues.d.ts.map +1 -0
- package/dist/services/issues.js +3199 -0
- package/dist/services/issues.js.map +1 -0
- package/dist/services/json-schema-secret-refs.d.ts +5 -0
- package/dist/services/json-schema-secret-refs.d.ts.map +1 -0
- package/dist/services/json-schema-secret-refs.js +67 -0
- package/dist/services/json-schema-secret-refs.js.map +1 -0
- package/dist/services/live-events.d.ts +17 -0
- package/dist/services/live-events.d.ts.map +1 -0
- package/dist/services/live-events.js +33 -0
- package/dist/services/live-events.js.map +1 -0
- package/dist/services/local-service-supervisor.d.ts +56 -0
- package/dist/services/local-service-supervisor.d.ts.map +1 -0
- package/dist/services/local-service-supervisor.js +284 -0
- package/dist/services/local-service-supervisor.js.map +1 -0
- package/dist/services/plugin-capability-validator.d.ts +108 -0
- package/dist/services/plugin-capability-validator.d.ts.map +1 -0
- package/dist/services/plugin-capability-validator.js +313 -0
- package/dist/services/plugin-capability-validator.js.map +1 -0
- package/dist/services/plugin-config-validator.d.ts +26 -0
- package/dist/services/plugin-config-validator.d.ts.map +1 -0
- package/dist/services/plugin-config-validator.js +41 -0
- package/dist/services/plugin-config-validator.js.map +1 -0
- package/dist/services/plugin-database.d.ts +49 -0
- package/dist/services/plugin-database.d.ts.map +1 -0
- package/dist/services/plugin-database.js +440 -0
- package/dist/services/plugin-database.js.map +1 -0
- package/dist/services/plugin-dev-watcher.d.ts +30 -0
- package/dist/services/plugin-dev-watcher.d.ts.map +1 -0
- package/dist/services/plugin-dev-watcher.js +241 -0
- package/dist/services/plugin-dev-watcher.js.map +1 -0
- package/dist/services/plugin-environment-driver.d.ts +124 -0
- package/dist/services/plugin-environment-driver.d.ts.map +1 -0
- package/dist/services/plugin-environment-driver.js +224 -0
- package/dist/services/plugin-environment-driver.js.map +1 -0
- package/dist/services/plugin-event-bus.d.ts +149 -0
- package/dist/services/plugin-event-bus.d.ts.map +1 -0
- package/dist/services/plugin-event-bus.js +258 -0
- package/dist/services/plugin-event-bus.js.map +1 -0
- package/dist/services/plugin-host-service-cleanup.d.ts +14 -0
- package/dist/services/plugin-host-service-cleanup.d.ts.map +1 -0
- package/dist/services/plugin-host-service-cleanup.js +37 -0
- package/dist/services/plugin-host-service-cleanup.js.map +1 -0
- package/dist/services/plugin-host-services.d.ts +17 -0
- package/dist/services/plugin-host-services.d.ts.map +1 -0
- package/dist/services/plugin-host-services.js +1861 -0
- package/dist/services/plugin-host-services.js.map +1 -0
- package/dist/services/plugin-job-coordinator.d.ts +81 -0
- package/dist/services/plugin-job-coordinator.d.ts.map +1 -0
- package/dist/services/plugin-job-coordinator.js +172 -0
- package/dist/services/plugin-job-coordinator.js.map +1 -0
- package/dist/services/plugin-job-scheduler.d.ts +163 -0
- package/dist/services/plugin-job-scheduler.d.ts.map +1 -0
- package/dist/services/plugin-job-scheduler.js +454 -0
- package/dist/services/plugin-job-scheduler.js.map +1 -0
- package/dist/services/plugin-job-store.d.ts +208 -0
- package/dist/services/plugin-job-store.d.ts.map +1 -0
- package/dist/services/plugin-job-store.js +350 -0
- package/dist/services/plugin-job-store.js.map +1 -0
- package/dist/services/plugin-lifecycle.d.ts +203 -0
- package/dist/services/plugin-lifecycle.d.ts.map +1 -0
- package/dist/services/plugin-lifecycle.js +476 -0
- package/dist/services/plugin-lifecycle.js.map +1 -0
- package/dist/services/plugin-loader.d.ts +445 -0
- package/dist/services/plugin-loader.d.ts.map +1 -0
- package/dist/services/plugin-loader.js +1273 -0
- package/dist/services/plugin-loader.js.map +1 -0
- package/dist/services/plugin-local-folders.d.ts +48 -0
- package/dist/services/plugin-local-folders.d.ts.map +1 -0
- package/dist/services/plugin-local-folders.js +461 -0
- package/dist/services/plugin-local-folders.js.map +1 -0
- package/dist/services/plugin-log-retention.d.ts +20 -0
- package/dist/services/plugin-log-retention.d.ts.map +1 -0
- package/dist/services/plugin-log-retention.js +63 -0
- package/dist/services/plugin-log-retention.js.map +1 -0
- package/dist/services/plugin-managed-agents.d.ts +15 -0
- package/dist/services/plugin-managed-agents.d.ts.map +1 -0
- package/dist/services/plugin-managed-agents.js +414 -0
- package/dist/services/plugin-managed-agents.js.map +1 -0
- package/dist/services/plugin-managed-routines.d.ts +41 -0
- package/dist/services/plugin-managed-routines.d.ts.map +1 -0
- package/dist/services/plugin-managed-routines.js +416 -0
- package/dist/services/plugin-managed-routines.js.map +1 -0
- package/dist/services/plugin-manifest-validator.d.ts +79 -0
- package/dist/services/plugin-manifest-validator.d.ts.map +1 -0
- package/dist/services/plugin-manifest-validator.js +84 -0
- package/dist/services/plugin-manifest-validator.js.map +1 -0
- package/dist/services/plugin-registry.d.ts +2550 -0
- package/dist/services/plugin-registry.d.ts.map +1 -0
- package/dist/services/plugin-registry.js +581 -0
- package/dist/services/plugin-registry.js.map +1 -0
- package/dist/services/plugin-runtime-sandbox.d.ts +40 -0
- package/dist/services/plugin-runtime-sandbox.d.ts.map +1 -0
- package/dist/services/plugin-runtime-sandbox.js +154 -0
- package/dist/services/plugin-runtime-sandbox.js.map +1 -0
- package/dist/services/plugin-secrets-handler.d.ts +81 -0
- package/dist/services/plugin-secrets-handler.d.ts.map +1 -0
- package/dist/services/plugin-secrets-handler.js +231 -0
- package/dist/services/plugin-secrets-handler.js.map +1 -0
- package/dist/services/plugin-state-store.d.ts +92 -0
- package/dist/services/plugin-state-store.d.ts.map +1 -0
- package/dist/services/plugin-state-store.js +190 -0
- package/dist/services/plugin-state-store.js.map +1 -0
- package/dist/services/plugin-stream-bus.d.ts +29 -0
- package/dist/services/plugin-stream-bus.d.ts.map +1 -0
- package/dist/services/plugin-stream-bus.js +48 -0
- package/dist/services/plugin-stream-bus.js.map +1 -0
- package/dist/services/plugin-tool-dispatcher.d.ts +180 -0
- package/dist/services/plugin-tool-dispatcher.d.ts.map +1 -0
- package/dist/services/plugin-tool-dispatcher.js +224 -0
- package/dist/services/plugin-tool-dispatcher.js.map +1 -0
- package/dist/services/plugin-tool-registry.d.ts +192 -0
- package/dist/services/plugin-tool-registry.d.ts.map +1 -0
- package/dist/services/plugin-tool-registry.js +224 -0
- package/dist/services/plugin-tool-registry.js.map +1 -0
- package/dist/services/plugin-worker-manager.d.ts +262 -0
- package/dist/services/plugin-worker-manager.d.ts.map +1 -0
- package/dist/services/plugin-worker-manager.js +836 -0
- package/dist/services/plugin-worker-manager.js.map +1 -0
- package/dist/services/productivity-review.d.ts +83 -0
- package/dist/services/productivity-review.d.ts.map +1 -0
- package/dist/services/productivity-review.js +652 -0
- package/dist/services/productivity-review.js.map +1 -0
- package/dist/services/project-workspace-runtime-config.d.ts +4 -0
- package/dist/services/project-workspace-runtime-config.d.ts.map +1 -0
- package/dist/services/project-workspace-runtime-config.js +54 -0
- package/dist/services/project-workspace-runtime-config.js.map +1 -0
- package/dist/services/projects.d.ts +99 -0
- package/dist/services/projects.d.ts.map +1 -0
- package/dist/services/projects.js +879 -0
- package/dist/services/projects.js.map +1 -0
- package/dist/services/quota-windows.d.ts +9 -0
- package/dist/services/quota-windows.d.ts.map +1 -0
- package/dist/services/quota-windows.js +56 -0
- package/dist/services/quota-windows.js.map +1 -0
- package/dist/services/recovery/index.d.ts +10 -0
- package/dist/services/recovery/index.d.ts.map +1 -0
- package/dist/services/recovery/index.js +6 -0
- package/dist/services/recovery/index.js.map +1 -0
- package/dist/services/recovery/issue-graph-liveness.d.ts +85 -0
- package/dist/services/recovery/issue-graph-liveness.d.ts.map +1 -0
- package/dist/services/recovery/issue-graph-liveness.js +343 -0
- package/dist/services/recovery/issue-graph-liveness.js.map +1 -0
- package/dist/services/recovery/model-profile-hint.d.ts +8 -0
- package/dist/services/recovery/model-profile-hint.d.ts.map +1 -0
- package/dist/services/recovery/model-profile-hint.js +11 -0
- package/dist/services/recovery/model-profile-hint.js.map +1 -0
- package/dist/services/recovery/origins.d.ts +36 -0
- package/dist/services/recovery/origins.d.ts.map +1 -0
- package/dist/services/recovery/origins.js +45 -0
- package/dist/services/recovery/origins.js.map +1 -0
- package/dist/services/recovery/pause-hold-guard.d.ts +6 -0
- package/dist/services/recovery/pause-hold-guard.d.ts.map +1 -0
- package/dist/services/recovery/pause-hold-guard.js +6 -0
- package/dist/services/recovery/pause-hold-guard.js.map +1 -0
- package/dist/services/recovery/run-liveness-continuations.d.ts +50 -0
- package/dist/services/recovery/run-liveness-continuations.d.ts.map +1 -0
- package/dist/services/recovery/run-liveness-continuations.js +117 -0
- package/dist/services/recovery/run-liveness-continuations.js.map +1 -0
- package/dist/services/recovery/service.d.ts +195 -0
- package/dist/services/recovery/service.d.ts.map +1 -0
- package/dist/services/recovery/service.js +2210 -0
- package/dist/services/recovery/service.js.map +1 -0
- package/dist/services/recovery/successful-run-handoff.d.ts +87 -0
- package/dist/services/recovery/successful-run-handoff.d.ts.map +1 -0
- package/dist/services/recovery/successful-run-handoff.js +297 -0
- package/dist/services/recovery/successful-run-handoff.js.map +1 -0
- package/dist/services/recovery/successful-run-handoff.test.d.ts +2 -0
- package/dist/services/recovery/successful-run-handoff.test.d.ts.map +1 -0
- package/dist/services/recovery/successful-run-handoff.test.js +267 -0
- package/dist/services/recovery/successful-run-handoff.test.js.map +1 -0
- package/dist/services/routines.d.ts +166 -0
- package/dist/services/routines.d.ts.map +1 -0
- package/dist/services/routines.js +1937 -0
- package/dist/services/routines.js.map +1 -0
- package/dist/services/run-continuations.d.ts +3 -0
- package/dist/services/run-continuations.d.ts.map +1 -0
- package/dist/services/run-continuations.js +2 -0
- package/dist/services/run-continuations.js.map +1 -0
- package/dist/services/run-liveness.d.ts +46 -0
- package/dist/services/run-liveness.d.ts.map +1 -0
- package/dist/services/run-liveness.js +275 -0
- package/dist/services/run-liveness.js.map +1 -0
- package/dist/services/run-log-store.d.ts +34 -0
- package/dist/services/run-log-store.d.ts.map +1 -0
- package/dist/services/run-log-store.js +111 -0
- package/dist/services/run-log-store.js.map +1 -0
- package/dist/services/sandbox-provider-runtime.d.ts +132 -0
- package/dist/services/sandbox-provider-runtime.d.ts.map +1 -0
- package/dist/services/sandbox-provider-runtime.js +216 -0
- package/dist/services/sandbox-provider-runtime.js.map +1 -0
- package/dist/services/secrets.d.ts +515 -0
- package/dist/services/secrets.d.ts.map +1 -0
- package/dist/services/secrets.js +290 -0
- package/dist/services/secrets.js.map +1 -0
- package/dist/services/sidebar-badges.d.ts +14 -0
- package/dist/services/sidebar-badges.d.ts.map +1 -0
- package/dist/services/sidebar-badges.js +48 -0
- package/dist/services/sidebar-badges.js.map +1 -0
- package/dist/services/sidebar-preferences.d.ts +9 -0
- package/dist/services/sidebar-preferences.d.ts.map +1 -0
- package/dist/services/sidebar-preferences.js +82 -0
- package/dist/services/sidebar-preferences.js.map +1 -0
- package/dist/services/work-products.d.ts +14 -0
- package/dist/services/work-products.d.ts.map +1 -0
- package/dist/services/work-products.js +100 -0
- package/dist/services/work-products.js.map +1 -0
- package/dist/services/workspace-operation-log-store.d.ts +33 -0
- package/dist/services/workspace-operation-log-store.d.ts.map +1 -0
- package/dist/services/workspace-operation-log-store.js +110 -0
- package/dist/services/workspace-operation-log-store.js.map +1 -0
- package/dist/services/workspace-operations.d.ts +44 -0
- package/dist/services/workspace-operations.d.ts.map +1 -0
- package/dist/services/workspace-operations.js +211 -0
- package/dist/services/workspace-operations.js.map +1 -0
- package/dist/services/workspace-realization.d.ts +33 -0
- package/dist/services/workspace-realization.d.ts.map +1 -0
- package/dist/services/workspace-realization.js +221 -0
- package/dist/services/workspace-realization.js.map +1 -0
- package/dist/services/workspace-runtime-read-model.d.ts +92 -0
- package/dist/services/workspace-runtime-read-model.d.ts.map +1 -0
- package/dist/services/workspace-runtime-read-model.js +67 -0
- package/dist/services/workspace-runtime-read-model.js.map +1 -0
- package/dist/services/workspace-runtime.d.ts +238 -0
- package/dist/services/workspace-runtime.d.ts.map +1 -0
- package/dist/services/workspace-runtime.js +2388 -0
- package/dist/services/workspace-runtime.js.map +1 -0
- package/dist/startup-banner.d.ts +32 -0
- package/dist/startup-banner.d.ts.map +1 -0
- package/dist/startup-banner.js +118 -0
- package/dist/startup-banner.js.map +1 -0
- package/dist/storage/index.d.ts +6 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +29 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/local-disk-provider.d.ts +3 -0
- package/dist/storage/local-disk-provider.d.ts.map +1 -0
- package/dist/storage/local-disk-provider.js +79 -0
- package/dist/storage/local-disk-provider.js.map +1 -0
- package/dist/storage/provider-registry.d.ts +4 -0
- package/dist/storage/provider-registry.d.ts.map +1 -0
- package/dist/storage/provider-registry.js +15 -0
- package/dist/storage/provider-registry.js.map +1 -0
- package/dist/storage/s3-provider.d.ts +11 -0
- package/dist/storage/s3-provider.d.ts.map +1 -0
- package/dist/storage/s3-provider.js +123 -0
- package/dist/storage/s3-provider.js.map +1 -0
- package/dist/storage/service.d.ts +3 -0
- package/dist/storage/service.d.ts.map +1 -0
- package/dist/storage/service.js +120 -0
- package/dist/storage/service.js.map +1 -0
- package/dist/storage/types.d.ts +55 -0
- package/dist/storage/types.d.ts.map +1 -0
- package/dist/storage/types.js +2 -0
- package/dist/storage/types.js.map +1 -0
- package/dist/telemetry.d.ts +6 -0
- package/dist/telemetry.d.ts.map +1 -0
- package/dist/telemetry.js +20 -0
- package/dist/telemetry.js.map +1 -0
- package/dist/ui-branding.d.ts +13 -0
- package/dist/ui-branding.d.ts.map +1 -0
- package/dist/ui-branding.js +187 -0
- package/dist/ui-branding.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +5 -0
- package/dist/version.js.map +1 -0
- package/dist/vite-html-renderer.d.ts +18 -0
- package/dist/vite-html-renderer.d.ts.map +1 -0
- package/dist/vite-html-renderer.js +61 -0
- package/dist/vite-html-renderer.js.map +1 -0
- package/dist/worktree-config.d.ts +19 -0
- package/dist/worktree-config.d.ts.map +1 -0
- package/dist/worktree-config.js +368 -0
- package/dist/worktree-config.js.map +1 -0
- package/package.json +90 -0
- package/skills/diagnose-why-work-stopped/SKILL.md +161 -0
- package/skills/evermore/SKILL.md +366 -0
- package/skills/evermore/references/api-reference.md +899 -0
- package/skills/evermore/references/company-skills.md +193 -0
- package/skills/evermore/references/issue-workspaces.md +80 -0
- package/skills/evermore/references/routines.md +187 -0
- package/skills/evermore/references/workflows.md +141 -0
- package/skills/evermore-converting-plans-to-tasks/SKILL.md +42 -0
- package/skills/evermore-create-agent/SKILL.md +163 -0
- package/skills/evermore-create-agent/references/agent-instruction-templates.md +123 -0
- package/skills/evermore-create-agent/references/agents/coder.md +64 -0
- package/skills/evermore-create-agent/references/agents/qa.md +88 -0
- package/skills/evermore-create-agent/references/agents/securityengineer.md +135 -0
- package/skills/evermore-create-agent/references/agents/uxdesigner.md +115 -0
- package/skills/evermore-create-agent/references/api-reference.md +110 -0
- package/skills/evermore-create-agent/references/baseline-role-guide.md +168 -0
- package/skills/evermore-create-agent/references/draft-review-checklist.md +95 -0
- package/skills/evermore-create-plugin/SKILL.md +101 -0
- package/skills/evermore-dev/SKILL.md +267 -0
- package/skills/para-memory-files/SKILL.md +104 -0
- package/skills/para-memory-files/references/schemas.md +35 -0
- package/skills/terminal-bench-loop/SKILL.md +236 -0
- package/ui-dist/android-chrome-192x192.png +0 -0
- package/ui-dist/android-chrome-512x512.png +0 -0
- package/ui-dist/apple-touch-icon.png +0 -0
- package/ui-dist/assets/_basePickBy-Ds9oHp1_.js +1 -0
- package/ui-dist/assets/_baseUniq-CHYwQyJ_.js +1 -0
- package/ui-dist/assets/apl-B4CMkyY2.js +1 -0
- package/ui-dist/assets/arc-CiUKtBzk.js +1 -0
- package/ui-dist/assets/architectureDiagram-VXUJARFQ-CAW2b6pT.js +36 -0
- package/ui-dist/assets/asciiarmor-Df11BRmG.js +1 -0
- package/ui-dist/assets/asn1-EdZsLKOL.js +1 -0
- package/ui-dist/assets/asterisk-B-8jnY81.js +1 -0
- package/ui-dist/assets/blockDiagram-VD42YOAC-1Rk6YCcn.js +122 -0
- package/ui-dist/assets/brainfuck-C4LP7Hcl.js +1 -0
- package/ui-dist/assets/c4Diagram-YG6GDRKO-DF5RJtyZ.js +10 -0
- package/ui-dist/assets/channel-D7SqxhNi.js +1 -0
- package/ui-dist/assets/chunk-4BX2VUAB-BSZDJAxk.js +1 -0
- package/ui-dist/assets/chunk-55IACEB6-DgVOW-V3.js +1 -0
- package/ui-dist/assets/chunk-B4BG7PRW-C1sGAq6t.js +165 -0
- package/ui-dist/assets/chunk-DI55MBZ5-DZyfq3VK.js +220 -0
- package/ui-dist/assets/chunk-FMBD7UC4-D6K9nYXi.js +15 -0
- package/ui-dist/assets/chunk-QN33PNHL-BJ0Ni2l9.js +1 -0
- package/ui-dist/assets/chunk-QZHKN3VN-Cwjr0vxG.js +1 -0
- package/ui-dist/assets/chunk-TZMSLE5B-C2RGCkyV.js +1 -0
- package/ui-dist/assets/classDiagram-2ON5EDUG-Cx1PlXXb.js +1 -0
- package/ui-dist/assets/classDiagram-v2-WZHVMYZB-Cx1PlXXb.js +1 -0
- package/ui-dist/assets/clike-B9uivgTg.js +1 -0
- package/ui-dist/assets/clojure-BMjYHr_A.js +1 -0
- package/ui-dist/assets/clone-5ZE-SRxk.js +1 -0
- package/ui-dist/assets/cmake-BQqOBYOt.js +1 -0
- package/ui-dist/assets/cobol-CWcv1MsR.js +1 -0
- package/ui-dist/assets/coffeescript-S37ZYGWr.js +1 -0
- package/ui-dist/assets/commonlisp-DBKNyK5s.js +1 -0
- package/ui-dist/assets/cose-bilkent-S5V4N54A-CW0Mh3DC.js +1 -0
- package/ui-dist/assets/crystal-SjHAIU92.js +1 -0
- package/ui-dist/assets/css-BnMrqG3P.js +1 -0
- package/ui-dist/assets/cypher-C_CwsFkJ.js +1 -0
- package/ui-dist/assets/cytoscape.esm-jbPEKk2Y.js +321 -0
- package/ui-dist/assets/d-pRatUO7H.js +1 -0
- package/ui-dist/assets/dagre-6UL2VRFP-BXDbyGyw.js +4 -0
- package/ui-dist/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/ui-dist/assets/diagram-PSM6KHXK-BHW-b3P9.js +24 -0
- package/ui-dist/assets/diagram-QEK2KX5R-vjGsaMmX.js +43 -0
- package/ui-dist/assets/diagram-S2PKOQOG-BHVhRqBj.js +24 -0
- package/ui-dist/assets/diff-DbItnlRl.js +1 -0
- package/ui-dist/assets/dockerfile-BKs6k2Af.js +1 -0
- package/ui-dist/assets/dtd-DF_7sFjM.js +1 -0
- package/ui-dist/assets/dylan-DwRh75JA.js +1 -0
- package/ui-dist/assets/ebnf-CDyGwa7X.js +1 -0
- package/ui-dist/assets/ecl-Cabwm37j.js +1 -0
- package/ui-dist/assets/eiffel-CnydiIhH.js +1 -0
- package/ui-dist/assets/elm-vLlmbW-K.js +1 -0
- package/ui-dist/assets/erDiagram-Q2GNP2WA-DwsdEDB2.js +60 -0
- package/ui-dist/assets/erlang-BNw1qcRV.js +1 -0
- package/ui-dist/assets/factor-kuTfRLto.js +1 -0
- package/ui-dist/assets/fcl-Kvtd6kyn.js +1 -0
- package/ui-dist/assets/flowDiagram-NV44I4VS-Bv7drkZu.js +162 -0
- package/ui-dist/assets/forth-Ffai-XNe.js +1 -0
- package/ui-dist/assets/fortran-DYz_wnZ1.js +1 -0
- package/ui-dist/assets/ganttDiagram-JELNMOA3-DyYAGyjV.js +267 -0
- package/ui-dist/assets/gas-Bneqetm1.js +1 -0
- package/ui-dist/assets/gherkin-heZmZLOM.js +1 -0
- package/ui-dist/assets/gitGraphDiagram-V2S2FVAM-DmKJJ2X0.js +65 -0
- package/ui-dist/assets/graph-BI12oqz9.js +1 -0
- package/ui-dist/assets/groovy-D9Dt4D0W.js +1 -0
- package/ui-dist/assets/haskell-Cw1EW3IL.js +1 -0
- package/ui-dist/assets/haxe-H-WmDvRZ.js +1 -0
- package/ui-dist/assets/http-DBlCnlav.js +1 -0
- package/ui-dist/assets/idl-BEugSyMb.js +1 -0
- package/ui-dist/assets/index-B1YmgKZD.js +1 -0
- package/ui-dist/assets/index-B5EG1mbW.js +1 -0
- package/ui-dist/assets/index-B63DPkk7.js +2 -0
- package/ui-dist/assets/index-BAalUM2u.js +1 -0
- package/ui-dist/assets/index-BCyQqUEQ.js +1 -0
- package/ui-dist/assets/index-BSRsyahM.js +1 -0
- package/ui-dist/assets/index-B_Iu4zUd.js +1 -0
- package/ui-dist/assets/index-C-BdZUIH.js +1 -0
- package/ui-dist/assets/index-CGgUnSQj.js +6 -0
- package/ui-dist/assets/index-COTIEysQ.js +1 -0
- package/ui-dist/assets/index-ChSqseHR.js +1 -0
- package/ui-dist/assets/index-Ckf1hADU.js +534 -0
- package/ui-dist/assets/index-D4M1TSCO.js +3 -0
- package/ui-dist/assets/index-DCTk2CN-.js +7 -0
- package/ui-dist/assets/index-DI-wyUUr.js +1 -0
- package/ui-dist/assets/index-DNtLqZ-D.js +1 -0
- package/ui-dist/assets/index-DSRR_614.css +1 -0
- package/ui-dist/assets/index-DarmgkJv.js +1 -0
- package/ui-dist/assets/index-DdcxF71a.js +1 -0
- package/ui-dist/assets/index-DfwWaMga.js +1 -0
- package/ui-dist/assets/index-SzUviW57.js +13 -0
- package/ui-dist/assets/index-VjZhELb6.js +1 -0
- package/ui-dist/assets/index-mEcmy8wG.js +1 -0
- package/ui-dist/assets/index-q7ldlDv6.js +1 -0
- package/ui-dist/assets/infoDiagram-HS3SLOUP-CqAxMRbC.js +2 -0
- package/ui-dist/assets/init-Gi6I4Gst.js +1 -0
- package/ui-dist/assets/javascript-iXu5QeM3.js +1 -0
- package/ui-dist/assets/journeyDiagram-XKPGCS4Q-DUJiudtY.js +139 -0
- package/ui-dist/assets/julia-DuME0IfC.js +1 -0
- package/ui-dist/assets/kanban-definition-3W4ZIXB7-CiRgJ4X2.js +89 -0
- package/ui-dist/assets/katex-B95LWT_Q.js +261 -0
- package/ui-dist/assets/layout-DJ8V2pqt.js +1 -0
- package/ui-dist/assets/linear-BwnNUuyZ.js +1 -0
- package/ui-dist/assets/livescript-BwQOo05w.js +1 -0
- package/ui-dist/assets/lua-BgMRiT3U.js +1 -0
- package/ui-dist/assets/mathematica-DTrFuWx2.js +1 -0
- package/ui-dist/assets/mbox-CNhZ1qSd.js +1 -0
- package/ui-dist/assets/mermaid.core-DLCiCWj4.js +250 -0
- package/ui-dist/assets/mindmap-definition-VGOIOE7T-epIiGA01.js +68 -0
- package/ui-dist/assets/mirc-CjQqDB4T.js +1 -0
- package/ui-dist/assets/mllike-CXdrOF99.js +1 -0
- package/ui-dist/assets/modelica-Dc1JOy9r.js +1 -0
- package/ui-dist/assets/mscgen-BA5vi2Kp.js +1 -0
- package/ui-dist/assets/mumps-BT43cFF4.js +1 -0
- package/ui-dist/assets/nginx-DdIZxoE0.js +1 -0
- package/ui-dist/assets/nsis-LdVXkNf5.js +1 -0
- package/ui-dist/assets/ntriples-BfvgReVJ.js +1 -0
- package/ui-dist/assets/octave-Ck1zUtKM.js +1 -0
- package/ui-dist/assets/ordinal-Cboi1Yqb.js +1 -0
- package/ui-dist/assets/oz-BzwKVEFT.js +1 -0
- package/ui-dist/assets/pascal--L3eBynH.js +1 -0
- package/ui-dist/assets/perl-CdXCOZ3F.js +1 -0
- package/ui-dist/assets/pieDiagram-ADFJNKIX-CHx4pJ9Y.js +30 -0
- package/ui-dist/assets/pig-CevX1Tat.js +1 -0
- package/ui-dist/assets/powershell-CFHJl5sT.js +1 -0
- package/ui-dist/assets/properties-C78fOPTZ.js +1 -0
- package/ui-dist/assets/protobuf-ChK-085T.js +1 -0
- package/ui-dist/assets/pug-DeIclll2.js +1 -0
- package/ui-dist/assets/puppet-DMA9R1ak.js +1 -0
- package/ui-dist/assets/python-BuPzkPfP.js +1 -0
- package/ui-dist/assets/q-pXgVlZs6.js +1 -0
- package/ui-dist/assets/quadrantDiagram-AYHSOK5B-D0tXmGxE.js +7 -0
- package/ui-dist/assets/r-B6wPVr8A.js +1 -0
- package/ui-dist/assets/requirementDiagram-UZGBJVZJ-BgFc9Xc4.js +64 -0
- package/ui-dist/assets/rpm-CTu-6PCP.js +1 -0
- package/ui-dist/assets/ruby-B2Rjki9n.js +1 -0
- package/ui-dist/assets/sankeyDiagram-TZEHDZUN-DqntX5wV.js +10 -0
- package/ui-dist/assets/sas-B4kiWyti.js +1 -0
- package/ui-dist/assets/scheme-C41bIUwD.js +1 -0
- package/ui-dist/assets/sequenceDiagram-WL72ISMW-BfIJeGAT.js +145 -0
- package/ui-dist/assets/shell-CjFT_Tl9.js +1 -0
- package/ui-dist/assets/sieve-C3Gn_uJK.js +1 -0
- package/ui-dist/assets/simple-mode-GW_nhZxv.js +1 -0
- package/ui-dist/assets/smalltalk-CnHTOXQT.js +1 -0
- package/ui-dist/assets/solr-DehyRSwq.js +1 -0
- package/ui-dist/assets/sparql-DkYu6x3z.js +1 -0
- package/ui-dist/assets/spreadsheet-BCZA_wO0.js +1 -0
- package/ui-dist/assets/sql-D0XecflT.js +1 -0
- package/ui-dist/assets/stateDiagram-FKZM4ZOC-CBlsmcoW.js +1 -0
- package/ui-dist/assets/stateDiagram-v2-4FDKWEC3-DJtH2tCS.js +1 -0
- package/ui-dist/assets/stex-C3f8Ysf7.js +1 -0
- package/ui-dist/assets/stylus-B533Al4x.js +1 -0
- package/ui-dist/assets/swift-BzpIVaGY.js +1 -0
- package/ui-dist/assets/tcl-DVfN8rqt.js +1 -0
- package/ui-dist/assets/textile-CnDTJFAw.js +1 -0
- package/ui-dist/assets/tiddlywiki-DO-Gjzrf.js +1 -0
- package/ui-dist/assets/tiki-DGYXhP31.js +1 -0
- package/ui-dist/assets/timeline-definition-IT6M3QCI-C2XyT4Lo.js +61 -0
- package/ui-dist/assets/toml-Bm5Em-hy.js +1 -0
- package/ui-dist/assets/treemap-GDKQZRPO-DvTrxs0e.js +154 -0
- package/ui-dist/assets/troff-wAsdV37c.js +1 -0
- package/ui-dist/assets/ttcn-CfJYG6tj.js +1 -0
- package/ui-dist/assets/ttcn-cfg-B9xdYoR4.js +1 -0
- package/ui-dist/assets/turtle-B1tBg_DP.js +1 -0
- package/ui-dist/assets/vb-CmGdzxic.js +1 -0
- package/ui-dist/assets/vbscript-BuJXcnF6.js +1 -0
- package/ui-dist/assets/velocity-D8B20fx6.js +1 -0
- package/ui-dist/assets/verilog-C6RDOZhf.js +1 -0
- package/ui-dist/assets/vhdl-lSbBsy5d.js +1 -0
- package/ui-dist/assets/webidl-ZXfAyPTL.js +1 -0
- package/ui-dist/assets/xquery-DzFWVndE.js +1 -0
- package/ui-dist/assets/xychartDiagram-PRI3JC2R-pTizTbG0.js +7 -0
- package/ui-dist/assets/yacas-BJ4BC0dw.js +1 -0
- package/ui-dist/assets/z80-Hz9HOZM7.js +1 -0
- package/ui-dist/brands/opencode-logo-dark-square.svg +18 -0
- package/ui-dist/brands/opencode-logo-light-square.svg +18 -0
- package/ui-dist/favicon-16x16.png +0 -0
- package/ui-dist/favicon-32x32.png +0 -0
- package/ui-dist/favicon.ico +0 -0
- package/ui-dist/favicon.svg +9 -0
- package/ui-dist/index.html +49 -0
- package/ui-dist/site.webmanifest +30 -0
- package/ui-dist/sw.js +42 -0
- package/ui-dist/worktree-favicon-16x16.png +0 -0
- package/ui-dist/worktree-favicon-32x32.png +0 -0
- package/ui-dist/worktree-favicon.ico +0 -0
- package/ui-dist/worktree-favicon.svg +9 -0
|
@@ -0,0 +1,2210 @@
|
|
|
1
|
+
import { and, asc, desc, eq, gt, inArray, isNull, notInArray, sql } from "drizzle-orm";
|
|
2
|
+
import { DEFAULT_ISSUE_GRAPH_LIVENESS_AUTO_RECOVERY_LOOKBACK_HOURS, MAX_ISSUE_GRAPH_LIVENESS_AUTO_RECOVERY_LOOKBACK_HOURS, MIN_ISSUE_GRAPH_LIVENESS_AUTO_RECOVERY_LOOKBACK_HOURS, } from "@evermore.work/shared";
|
|
3
|
+
import { agents, agentWakeupRequests, approvals, companies, heartbeatRunEvents, heartbeatRunWatchdogDecisions, heartbeatRuns, issueApprovals, issueRelations, issueThreadInteractions, issues, } from "@evermore.work/db";
|
|
4
|
+
import { parseObject, asBoolean, asNumber } from "../../adapters/utils.js";
|
|
5
|
+
import { runningProcesses } from "../../adapters/index.js";
|
|
6
|
+
import { forbidden, notFound } from "../../errors.js";
|
|
7
|
+
import { logger } from "../../middleware/logger.js";
|
|
8
|
+
import { redactCurrentUserText } from "../../log-redaction.js";
|
|
9
|
+
import { redactSensitiveText } from "../../redaction.js";
|
|
10
|
+
import { logActivity } from "../activity-log.js";
|
|
11
|
+
import { budgetService } from "../budgets.js";
|
|
12
|
+
import { instanceSettingsService } from "../instance-settings.js";
|
|
13
|
+
import { issueTreeControlService } from "../issue-tree-control.js";
|
|
14
|
+
import { issueService } from "../issues.js";
|
|
15
|
+
import { getRunLogStore } from "../run-log-store.js";
|
|
16
|
+
import { DEFAULT_MAX_SUCCESSFUL_RUN_HANDOFF_ATTEMPTS, FINISH_SUCCESSFUL_RUN_HANDOFF_REASON, SUCCESSFUL_RUN_MISSING_STATE_REASON, buildSuccessfulRunHandoffExhaustedNotice, } from "./successful-run-handoff.js";
|
|
17
|
+
import { RECOVERY_ORIGIN_KINDS, buildIssueGraphLivenessLeafKey, isStrandedIssueRecoveryOriginKind, parseIssueGraphLivenessIncidentKey, } from "./origins.js";
|
|
18
|
+
import { classifyIssueGraphLiveness, } from "./issue-graph-liveness.js";
|
|
19
|
+
import { recoveryAssigneeAdapterOverrides, withRecoveryModelProfileHint, } from "./model-profile-hint.js";
|
|
20
|
+
import { isAutomaticRecoverySuppressedByPauseHold } from "./pause-hold-guard.js";
|
|
21
|
+
const EXECUTION_PATH_HEARTBEAT_RUN_STATUSES = ["queued", "running", "scheduled_retry"];
|
|
22
|
+
const UNSUCCESSFUL_HEARTBEAT_RUN_TERMINAL_STATUSES = ["failed", "cancelled", "timed_out"];
|
|
23
|
+
export const ACTIVE_RUN_OUTPUT_SUSPICION_THRESHOLD_MS = 60 * 60 * 1000;
|
|
24
|
+
export const ACTIVE_RUN_OUTPUT_CRITICAL_THRESHOLD_MS = 4 * 60 * 60 * 1000;
|
|
25
|
+
export const ACTIVE_RUN_OUTPUT_CONTINUE_REARM_MS = 30 * 60 * 1000;
|
|
26
|
+
const ACTIVE_RUN_OUTPUT_EVIDENCE_TAIL_BYTES = 8 * 1024;
|
|
27
|
+
const STRANDED_ISSUE_RECOVERY_ORIGIN_KIND = RECOVERY_ORIGIN_KINDS.strandedIssueRecovery;
|
|
28
|
+
const STALE_ACTIVE_RUN_EVALUATION_ORIGIN_KIND = RECOVERY_ORIGIN_KINDS.staleActiveRunEvaluation;
|
|
29
|
+
const DEFERRED_WAKE_CONTEXT_KEY = "_evermoreWakeContext";
|
|
30
|
+
function readNonEmptyString(value) {
|
|
31
|
+
return typeof value === "string" && value.trim().length > 0 ? value : null;
|
|
32
|
+
}
|
|
33
|
+
function summarizeRunFailureForIssueComment(run) {
|
|
34
|
+
if (!run)
|
|
35
|
+
return null;
|
|
36
|
+
if (readNonEmptyString(run.error) || readNonEmptyString(run.errorCode)) {
|
|
37
|
+
return " Latest retry failure details were withheld from the issue thread; inspect the linked run for evidence.";
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
function didAutomaticRecoveryFail(latestRun, expectedRetryReason) {
|
|
42
|
+
if (!latestRun)
|
|
43
|
+
return false;
|
|
44
|
+
const latestContext = parseObject(latestRun.contextSnapshot);
|
|
45
|
+
const latestRetryReason = readNonEmptyString(latestContext.retryReason);
|
|
46
|
+
return latestRetryReason === expectedRetryReason &&
|
|
47
|
+
UNSUCCESSFUL_HEARTBEAT_RUN_TERMINAL_STATUSES.includes(latestRun.status);
|
|
48
|
+
}
|
|
49
|
+
function successfulRunHandoffRecoveryEvidence(latestRun) {
|
|
50
|
+
if (!latestRun)
|
|
51
|
+
return null;
|
|
52
|
+
const context = parseObject(latestRun.contextSnapshot);
|
|
53
|
+
const wakeReason = readNonEmptyString(context.wakeReason);
|
|
54
|
+
const handoffReason = readNonEmptyString(context.handoffReason);
|
|
55
|
+
const isSuccessfulRunHandoff = wakeReason === FINISH_SUCCESSFUL_RUN_HANDOFF_REASON ||
|
|
56
|
+
handoffReason === SUCCESSFUL_RUN_MISSING_STATE_REASON ||
|
|
57
|
+
asBoolean(context.handoffRequired, false) === true;
|
|
58
|
+
if (!isSuccessfulRunHandoff)
|
|
59
|
+
return null;
|
|
60
|
+
const handoffAttempt = asNumber(context.handoffAttempt, 1);
|
|
61
|
+
const maxHandoffAttempts = asNumber(context.maxHandoffAttempts, DEFAULT_MAX_SUCCESSFUL_RUN_HANDOFF_ATTEMPTS);
|
|
62
|
+
return {
|
|
63
|
+
sourceRunId: readNonEmptyString(context.sourceRunId) ?? readNonEmptyString(context.resumeFromRunId),
|
|
64
|
+
correctiveRunId: latestRun.id,
|
|
65
|
+
missingDisposition: readNonEmptyString(context.missingDisposition) ?? "clear_next_step",
|
|
66
|
+
handoffAttempt,
|
|
67
|
+
maxHandoffAttempts,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function isExhaustedSuccessfulRunHandoff(latestRun) {
|
|
71
|
+
const evidence = successfulRunHandoffRecoveryEvidence(latestRun);
|
|
72
|
+
if (!evidence)
|
|
73
|
+
return null;
|
|
74
|
+
if (evidence.handoffAttempt < evidence.maxHandoffAttempts)
|
|
75
|
+
return { ...evidence, exhausted: false };
|
|
76
|
+
return { ...evidence, exhausted: true };
|
|
77
|
+
}
|
|
78
|
+
function issueIdFromRunContext(contextSnapshot) {
|
|
79
|
+
const context = parseObject(contextSnapshot);
|
|
80
|
+
return readNonEmptyString(context.issueId) ?? readNonEmptyString(context.taskId);
|
|
81
|
+
}
|
|
82
|
+
function issueIdFromWakePayload(payload) {
|
|
83
|
+
const parsed = parseObject(payload);
|
|
84
|
+
const nestedContext = parseObject(parsed[DEFERRED_WAKE_CONTEXT_KEY]);
|
|
85
|
+
return readNonEmptyString(parsed.issueId) ??
|
|
86
|
+
readNonEmptyString(nestedContext.issueId) ??
|
|
87
|
+
readNonEmptyString(nestedContext.taskId);
|
|
88
|
+
}
|
|
89
|
+
function issueUiLink(issue, prefix) {
|
|
90
|
+
const label = issue.identifier ?? issue.id;
|
|
91
|
+
return `[${label}](/${prefix}/issues/${label})`;
|
|
92
|
+
}
|
|
93
|
+
function runUiLink(run, prefix) {
|
|
94
|
+
return `[${run.id}](/${prefix}/agents/${run.agentId}/runs/${run.id})`;
|
|
95
|
+
}
|
|
96
|
+
function agentUiLink(agent, prefix) {
|
|
97
|
+
if (!agent)
|
|
98
|
+
return "unknown";
|
|
99
|
+
return `[${agent.name ?? agent.id}](/${prefix}/agents/${agent.id})`;
|
|
100
|
+
}
|
|
101
|
+
function formatDuration(ms) {
|
|
102
|
+
if (ms === null)
|
|
103
|
+
return "unknown";
|
|
104
|
+
const minutes = Math.floor(ms / 60_000);
|
|
105
|
+
if (minutes < 60)
|
|
106
|
+
return `${minutes}m`;
|
|
107
|
+
const hours = Math.floor(minutes / 60);
|
|
108
|
+
const remainingMinutes = minutes % 60;
|
|
109
|
+
return remainingMinutes > 0 ? `${hours}h ${remainingMinutes}m` : `${hours}h`;
|
|
110
|
+
}
|
|
111
|
+
function formatIssueLinksForComment(relations) {
|
|
112
|
+
const identifiers = [
|
|
113
|
+
...new Set(relations
|
|
114
|
+
.map((relation) => relation.identifier)
|
|
115
|
+
.filter((identifier) => Boolean(identifier))),
|
|
116
|
+
];
|
|
117
|
+
if (identifiers.length === 0)
|
|
118
|
+
return "another open issue";
|
|
119
|
+
return identifiers
|
|
120
|
+
.slice(0, 5)
|
|
121
|
+
.map((identifier) => {
|
|
122
|
+
const prefix = identifier.split("-")[0] || "EVR";
|
|
123
|
+
return `[${identifier}](/${prefix}/issues/${identifier})`;
|
|
124
|
+
})
|
|
125
|
+
.join(", ");
|
|
126
|
+
}
|
|
127
|
+
function isAgentInvokable(agent) {
|
|
128
|
+
return Boolean(agent && !["paused", "terminated", "pending_approval"].includes(agent.status));
|
|
129
|
+
}
|
|
130
|
+
function isStrandedIssueRecoveryIssue(issue) {
|
|
131
|
+
return isStrandedIssueRecoveryOriginKind(issue.originKind);
|
|
132
|
+
}
|
|
133
|
+
function isUnsuccessfulTerminalIssueRun(latestRun) {
|
|
134
|
+
return Boolean(latestRun &&
|
|
135
|
+
UNSUCCESSFUL_HEARTBEAT_RUN_TERMINAL_STATUSES.includes(latestRun.status));
|
|
136
|
+
}
|
|
137
|
+
function isSuccessfulInProgressContinuationRun(latestRun) {
|
|
138
|
+
return latestRun?.status === "succeeded";
|
|
139
|
+
}
|
|
140
|
+
function isProductiveContinuationRun(latestRun) {
|
|
141
|
+
return latestRun?.status === "succeeded" &&
|
|
142
|
+
(latestRun.livenessState === "advanced" ||
|
|
143
|
+
latestRun.livenessState === "completed" ||
|
|
144
|
+
latestRun.livenessState === "blocked" ||
|
|
145
|
+
latestRun.livenessState === "needs_followup");
|
|
146
|
+
}
|
|
147
|
+
function isRepeatedProductiveContinuationRecovery(latestRun) {
|
|
148
|
+
const latestContext = parseObject(latestRun.contextSnapshot);
|
|
149
|
+
return readNonEmptyString(latestContext.retryReason) === "issue_continuation_needed" &&
|
|
150
|
+
readNonEmptyString(latestContext.source) === "issue.productive_terminal_continuation_recovery" &&
|
|
151
|
+
isProductiveContinuationRun(latestRun);
|
|
152
|
+
}
|
|
153
|
+
function parseLivenessIncidentKey(incidentKey) {
|
|
154
|
+
if (!incidentKey)
|
|
155
|
+
return null;
|
|
156
|
+
return parseIssueGraphLivenessIncidentKey(incidentKey);
|
|
157
|
+
}
|
|
158
|
+
function livenessRecoveryLeafIssueId(finding) {
|
|
159
|
+
return finding.recoveryIssueId;
|
|
160
|
+
}
|
|
161
|
+
function livenessRecoveryLeafFingerprint(finding) {
|
|
162
|
+
return buildIssueGraphLivenessLeafKey({
|
|
163
|
+
companyId: finding.companyId,
|
|
164
|
+
state: finding.state,
|
|
165
|
+
leafIssueId: livenessRecoveryLeafIssueId(finding),
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
function livenessRecoveryLeafKey(companyId, state, leafIssueId) {
|
|
169
|
+
return buildIssueGraphLivenessLeafKey({ companyId, state, leafIssueId });
|
|
170
|
+
}
|
|
171
|
+
function isUniqueLivenessRecoveryConflict(error) {
|
|
172
|
+
if (!error || typeof error !== "object")
|
|
173
|
+
return false;
|
|
174
|
+
const maybe = error;
|
|
175
|
+
return maybe.code === "23505" &&
|
|
176
|
+
(maybe.constraint === "issues_active_liveness_recovery_incident_uq" ||
|
|
177
|
+
maybe.constraint === "issues_active_liveness_recovery_leaf_uq" ||
|
|
178
|
+
typeof maybe.message === "string" &&
|
|
179
|
+
(maybe.message.includes("issues_active_liveness_recovery_incident_uq") ||
|
|
180
|
+
maybe.message.includes("issues_active_liveness_recovery_leaf_uq")));
|
|
181
|
+
}
|
|
182
|
+
function formatDependencyPath(finding) {
|
|
183
|
+
return finding.dependencyPath
|
|
184
|
+
.map((entry) => entry.identifier ?? entry.issueId)
|
|
185
|
+
.join(" -> ");
|
|
186
|
+
}
|
|
187
|
+
function buildLivenessEscalationDescription(finding) {
|
|
188
|
+
const source = finding.dependencyPath[0];
|
|
189
|
+
const recovery = finding.dependencyPath.find((entry) => entry.issueId === finding.recoveryIssueId);
|
|
190
|
+
const selectedOwner = finding.recommendedOwnerAgentId ?? "none";
|
|
191
|
+
return [
|
|
192
|
+
"Evermore detected a harness-level issue graph liveness incident.",
|
|
193
|
+
"",
|
|
194
|
+
"## Source",
|
|
195
|
+
"",
|
|
196
|
+
`- Source issue: ${source?.identifier ?? source?.issueId ?? finding.issueId}`,
|
|
197
|
+
`- Recovery target issue: ${recovery?.identifier ?? recovery?.issueId ?? finding.recoveryIssueId}`,
|
|
198
|
+
`- Incident key: \`${finding.incidentKey}\``,
|
|
199
|
+
`- Detected invariant: \`${finding.state}\``,
|
|
200
|
+
`- Dependency path: ${formatDependencyPath(finding)}`,
|
|
201
|
+
`- Reason: ${finding.reason}`,
|
|
202
|
+
"",
|
|
203
|
+
"## Ownership",
|
|
204
|
+
"",
|
|
205
|
+
`- Selected owner agent: \`${selectedOwner}\``,
|
|
206
|
+
`- Candidate owner agents: ${finding.recommendedOwnerCandidateAgentIds.length > 0 ? finding.recommendedOwnerCandidateAgentIds.map((id) => `\`${id}\``).join(", ") : "none"}`,
|
|
207
|
+
"",
|
|
208
|
+
"## Next Action",
|
|
209
|
+
"",
|
|
210
|
+
finding.recommendedAction,
|
|
211
|
+
"",
|
|
212
|
+
"Resolve the blocked chain, then mark this escalation issue done so the original issue can resume when all blockers are cleared.",
|
|
213
|
+
].join("\n");
|
|
214
|
+
}
|
|
215
|
+
function buildLivenessOriginalIssueComment(finding, escalation) {
|
|
216
|
+
return [
|
|
217
|
+
"Evermore detected a harness-level liveness incident in this issue's dependency graph.",
|
|
218
|
+
"",
|
|
219
|
+
`- Escalation issue: ${escalation.identifier ?? escalation.id}`,
|
|
220
|
+
`- Incident key: \`${finding.incidentKey}\``,
|
|
221
|
+
`- Finding: \`${finding.state}\``,
|
|
222
|
+
`- Dependency path: ${formatDependencyPath(finding)}`,
|
|
223
|
+
`- Reason: ${finding.reason}`,
|
|
224
|
+
`- Manager action requested: ${finding.recommendedAction}`,
|
|
225
|
+
"",
|
|
226
|
+
"This issue now keeps its existing blockers and is also blocked by the escalation issue so dependency wakeups remain explicit.",
|
|
227
|
+
].join("\n");
|
|
228
|
+
}
|
|
229
|
+
export function recoveryService(db, deps) {
|
|
230
|
+
const issuesSvc = issueService(db);
|
|
231
|
+
const treeControlSvc = issueTreeControlService(db);
|
|
232
|
+
const budgets = budgetService(db);
|
|
233
|
+
const instanceSettings = instanceSettingsService(db);
|
|
234
|
+
const runLogStore = getRunLogStore();
|
|
235
|
+
const getCurrentUserRedactionOptions = async () => ({
|
|
236
|
+
enabled: (await instanceSettings.getGeneral()).censorUsernameInLogs,
|
|
237
|
+
});
|
|
238
|
+
async function getAgent(agentId) {
|
|
239
|
+
return db.select().from(agents).where(eq(agents.id, agentId)).then((rows) => rows[0] ?? null);
|
|
240
|
+
}
|
|
241
|
+
async function getLatestIssueRun(companyId, issueId) {
|
|
242
|
+
return db
|
|
243
|
+
.select({
|
|
244
|
+
id: heartbeatRuns.id,
|
|
245
|
+
agentId: heartbeatRuns.agentId,
|
|
246
|
+
status: heartbeatRuns.status,
|
|
247
|
+
error: heartbeatRuns.error,
|
|
248
|
+
errorCode: heartbeatRuns.errorCode,
|
|
249
|
+
contextSnapshot: heartbeatRuns.contextSnapshot,
|
|
250
|
+
livenessState: heartbeatRuns.livenessState,
|
|
251
|
+
})
|
|
252
|
+
.from(heartbeatRuns)
|
|
253
|
+
.where(and(eq(heartbeatRuns.companyId, companyId), sql `${heartbeatRuns.contextSnapshot} ->> 'issueId' = ${issueId}`))
|
|
254
|
+
.orderBy(desc(heartbeatRuns.createdAt), desc(heartbeatRuns.id))
|
|
255
|
+
.limit(1)
|
|
256
|
+
.then((rows) => rows[0] ?? null);
|
|
257
|
+
}
|
|
258
|
+
async function hasActiveExecutionPath(companyId, issueId) {
|
|
259
|
+
const [run, deferredWake] = await Promise.all([
|
|
260
|
+
db
|
|
261
|
+
.select({ id: heartbeatRuns.id })
|
|
262
|
+
.from(heartbeatRuns)
|
|
263
|
+
.where(and(eq(heartbeatRuns.companyId, companyId), inArray(heartbeatRuns.status, [...EXECUTION_PATH_HEARTBEAT_RUN_STATUSES]), sql `${heartbeatRuns.contextSnapshot} ->> 'issueId' = ${issueId}`))
|
|
264
|
+
.limit(1)
|
|
265
|
+
.then((rows) => rows[0] ?? null),
|
|
266
|
+
db
|
|
267
|
+
.select({ id: agentWakeupRequests.id })
|
|
268
|
+
.from(agentWakeupRequests)
|
|
269
|
+
.where(and(eq(agentWakeupRequests.companyId, companyId), eq(agentWakeupRequests.status, "deferred_issue_execution"), sql `${agentWakeupRequests.payload} ->> 'issueId' = ${issueId}`))
|
|
270
|
+
.limit(1)
|
|
271
|
+
.then((rows) => rows[0] ?? null),
|
|
272
|
+
]);
|
|
273
|
+
return Boolean(run || deferredWake);
|
|
274
|
+
}
|
|
275
|
+
async function hasQueuedIssueWake(companyId, issueId) {
|
|
276
|
+
return db
|
|
277
|
+
.select({ id: agentWakeupRequests.id })
|
|
278
|
+
.from(agentWakeupRequests)
|
|
279
|
+
.where(and(eq(agentWakeupRequests.companyId, companyId), eq(agentWakeupRequests.status, "queued"), sql `${agentWakeupRequests.payload} ->> 'issueId' = ${issueId}`))
|
|
280
|
+
.limit(1)
|
|
281
|
+
.then((rows) => Boolean(rows[0]));
|
|
282
|
+
}
|
|
283
|
+
async function enqueueStrandedIssueRecovery(input) {
|
|
284
|
+
const queued = await deps.enqueueWakeup(input.agentId, {
|
|
285
|
+
source: "automation",
|
|
286
|
+
triggerDetail: "system",
|
|
287
|
+
reason: input.reason,
|
|
288
|
+
payload: withRecoveryModelProfileHint({
|
|
289
|
+
issueId: input.issueId,
|
|
290
|
+
...(input.retryOfRunId ? { retryOfRunId: input.retryOfRunId } : {}),
|
|
291
|
+
}),
|
|
292
|
+
requestedByActorType: "system",
|
|
293
|
+
requestedByActorId: null,
|
|
294
|
+
contextSnapshot: withRecoveryModelProfileHint({
|
|
295
|
+
issueId: input.issueId,
|
|
296
|
+
taskId: input.issueId,
|
|
297
|
+
wakeReason: input.reason,
|
|
298
|
+
retryReason: input.retryReason,
|
|
299
|
+
source: input.source,
|
|
300
|
+
...(input.retryOfRunId ? { retryOfRunId: input.retryOfRunId } : {}),
|
|
301
|
+
}),
|
|
302
|
+
});
|
|
303
|
+
if (queued && input.retryOfRunId) {
|
|
304
|
+
return db
|
|
305
|
+
.update(heartbeatRuns)
|
|
306
|
+
.set({
|
|
307
|
+
retryOfRunId: input.retryOfRunId,
|
|
308
|
+
updatedAt: new Date(),
|
|
309
|
+
})
|
|
310
|
+
.where(eq(heartbeatRuns.id, queued.id))
|
|
311
|
+
.returning()
|
|
312
|
+
.then((rows) => rows[0] ?? queued);
|
|
313
|
+
}
|
|
314
|
+
return queued;
|
|
315
|
+
}
|
|
316
|
+
async function enqueueInitialAssignedTodoDispatch(issue, agentId) {
|
|
317
|
+
return deps.enqueueWakeup(agentId, {
|
|
318
|
+
source: "assignment",
|
|
319
|
+
triggerDetail: "system",
|
|
320
|
+
reason: "issue_assigned",
|
|
321
|
+
payload: withRecoveryModelProfileHint({
|
|
322
|
+
issueId: issue.id,
|
|
323
|
+
mutation: "assigned_todo_liveness_dispatch",
|
|
324
|
+
}),
|
|
325
|
+
requestedByActorType: "system",
|
|
326
|
+
requestedByActorId: null,
|
|
327
|
+
contextSnapshot: withRecoveryModelProfileHint({
|
|
328
|
+
issueId: issue.id,
|
|
329
|
+
taskId: issue.id,
|
|
330
|
+
wakeReason: "issue_assigned",
|
|
331
|
+
source: "issue.assigned_todo_liveness_dispatch",
|
|
332
|
+
}),
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
async function isInvocationBudgetBlocked(issue, agentId) {
|
|
336
|
+
const budgetBlock = await budgets.getInvocationBlock(issue.companyId, agentId, {
|
|
337
|
+
issueId: issue.id,
|
|
338
|
+
projectId: issue.projectId,
|
|
339
|
+
});
|
|
340
|
+
return Boolean(budgetBlock);
|
|
341
|
+
}
|
|
342
|
+
async function reconcileUnassignedBlockingIssues() {
|
|
343
|
+
const candidates = await db
|
|
344
|
+
.select({
|
|
345
|
+
id: issues.id,
|
|
346
|
+
companyId: issues.companyId,
|
|
347
|
+
identifier: issues.identifier,
|
|
348
|
+
status: issues.status,
|
|
349
|
+
createdByAgentId: issues.createdByAgentId,
|
|
350
|
+
})
|
|
351
|
+
.from(issueRelations)
|
|
352
|
+
.innerJoin(issues, eq(issueRelations.issueId, issues.id))
|
|
353
|
+
.where(and(eq(issueRelations.type, "blocks"), inArray(issues.status, ["todo", "blocked"]), isNull(issues.assigneeAgentId), isNull(issues.assigneeUserId), sql `${issues.createdByAgentId} is not null`, sql `exists (
|
|
354
|
+
select 1
|
|
355
|
+
from issues blocked_issue
|
|
356
|
+
where blocked_issue.id = ${issueRelations.relatedIssueId}
|
|
357
|
+
and blocked_issue.company_id = ${issues.companyId}
|
|
358
|
+
and blocked_issue.status not in ('done', 'cancelled')
|
|
359
|
+
)`));
|
|
360
|
+
let assigned = 0;
|
|
361
|
+
let skipped = 0;
|
|
362
|
+
const issueIds = [];
|
|
363
|
+
const seen = new Set();
|
|
364
|
+
for (const candidate of candidates) {
|
|
365
|
+
if (seen.has(candidate.id))
|
|
366
|
+
continue;
|
|
367
|
+
seen.add(candidate.id);
|
|
368
|
+
const creatorAgentId = candidate.createdByAgentId;
|
|
369
|
+
if (!creatorAgentId) {
|
|
370
|
+
skipped += 1;
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
const creatorAgent = await getAgent(creatorAgentId);
|
|
374
|
+
if (!creatorAgent || creatorAgent.companyId !== candidate.companyId || !isAgentInvokable(creatorAgent)) {
|
|
375
|
+
skipped += 1;
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
const relations = await issuesSvc.getRelationSummaries(candidate.id);
|
|
379
|
+
const blockingLinks = formatIssueLinksForComment(relations.blocks);
|
|
380
|
+
const updated = await issuesSvc.update(candidate.id, {
|
|
381
|
+
assigneeAgentId: creatorAgent.id,
|
|
382
|
+
assigneeUserId: null,
|
|
383
|
+
});
|
|
384
|
+
if (!updated) {
|
|
385
|
+
skipped += 1;
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
await issuesSvc.addComment(candidate.id, [
|
|
389
|
+
"## Assigned Orphan Blocker",
|
|
390
|
+
"",
|
|
391
|
+
`Evermore found this issue is blocking ${blockingLinks} but had no assignee, so no heartbeat could pick it up.`,
|
|
392
|
+
"",
|
|
393
|
+
"- Assigned it back to the agent that created the blocker.",
|
|
394
|
+
"- Next action: resolve this blocker or reassign it to the right owner.",
|
|
395
|
+
].join("\n"), {});
|
|
396
|
+
await logActivity(db, {
|
|
397
|
+
companyId: candidate.companyId,
|
|
398
|
+
actorType: "system",
|
|
399
|
+
actorId: "system",
|
|
400
|
+
agentId: null,
|
|
401
|
+
runId: null,
|
|
402
|
+
action: "issue.updated",
|
|
403
|
+
entityType: "issue",
|
|
404
|
+
entityId: candidate.id,
|
|
405
|
+
details: {
|
|
406
|
+
identifier: candidate.identifier,
|
|
407
|
+
assigneeAgentId: creatorAgent.id,
|
|
408
|
+
source: "recovery.reconcile_unassigned_blocking_issue",
|
|
409
|
+
},
|
|
410
|
+
});
|
|
411
|
+
const queued = await deps.enqueueWakeup(creatorAgent.id, {
|
|
412
|
+
source: "automation",
|
|
413
|
+
triggerDetail: "system",
|
|
414
|
+
reason: "issue_assigned",
|
|
415
|
+
payload: withRecoveryModelProfileHint({
|
|
416
|
+
issueId: candidate.id,
|
|
417
|
+
mutation: "unassigned_blocker_recovery",
|
|
418
|
+
}),
|
|
419
|
+
requestedByActorType: "system",
|
|
420
|
+
requestedByActorId: null,
|
|
421
|
+
contextSnapshot: withRecoveryModelProfileHint({
|
|
422
|
+
issueId: candidate.id,
|
|
423
|
+
taskId: candidate.id,
|
|
424
|
+
wakeReason: "issue_assigned",
|
|
425
|
+
source: "issue.unassigned_blocker_recovery",
|
|
426
|
+
}),
|
|
427
|
+
});
|
|
428
|
+
if (queued) {
|
|
429
|
+
assigned += 1;
|
|
430
|
+
issueIds.push(candidate.id);
|
|
431
|
+
}
|
|
432
|
+
else {
|
|
433
|
+
skipped += 1;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
return { assigned, skipped, issueIds };
|
|
437
|
+
}
|
|
438
|
+
async function getCompanyIssuePrefix(companyId) {
|
|
439
|
+
return db
|
|
440
|
+
.select({ issuePrefix: companies.issuePrefix })
|
|
441
|
+
.from(companies)
|
|
442
|
+
.where(eq(companies.id, companyId))
|
|
443
|
+
.then((rows) => rows[0]?.issuePrefix ?? "EVR");
|
|
444
|
+
}
|
|
445
|
+
function staleActiveRunOriginFingerprint(companyId, runId) {
|
|
446
|
+
return `stale_active_run:${companyId}:${runId}`;
|
|
447
|
+
}
|
|
448
|
+
function silenceStartedAtForRun(run) {
|
|
449
|
+
return run.lastOutputAt ?? run.processStartedAt ?? run.startedAt ?? run.createdAt ?? null;
|
|
450
|
+
}
|
|
451
|
+
function silenceAgeMsForRun(run, now = new Date()) {
|
|
452
|
+
const startedAt = silenceStartedAtForRun(run);
|
|
453
|
+
return startedAt ? Math.max(0, now.getTime() - startedAt.getTime()) : null;
|
|
454
|
+
}
|
|
455
|
+
async function latestActiveOutputQuietUntilDecision(companyId, runId, now = new Date()) {
|
|
456
|
+
const [row] = await db
|
|
457
|
+
.select()
|
|
458
|
+
.from(heartbeatRunWatchdogDecisions)
|
|
459
|
+
.where(and(eq(heartbeatRunWatchdogDecisions.companyId, companyId), eq(heartbeatRunWatchdogDecisions.runId, runId), inArray(heartbeatRunWatchdogDecisions.decision, ["snooze", "continue"]), gt(heartbeatRunWatchdogDecisions.snoozedUntil, now)))
|
|
460
|
+
.orderBy(desc(heartbeatRunWatchdogDecisions.createdAt))
|
|
461
|
+
.limit(1);
|
|
462
|
+
return row ?? null;
|
|
463
|
+
}
|
|
464
|
+
async function findOpenStaleRunEvaluation(companyId, runId) {
|
|
465
|
+
const [row] = await db
|
|
466
|
+
.select({
|
|
467
|
+
id: issues.id,
|
|
468
|
+
identifier: issues.identifier,
|
|
469
|
+
status: issues.status,
|
|
470
|
+
priority: issues.priority,
|
|
471
|
+
assigneeAgentId: issues.assigneeAgentId,
|
|
472
|
+
updatedAt: issues.updatedAt,
|
|
473
|
+
})
|
|
474
|
+
.from(issues)
|
|
475
|
+
.where(and(eq(issues.companyId, companyId), eq(issues.originKind, STALE_ACTIVE_RUN_EVALUATION_ORIGIN_KIND), eq(issues.originId, runId), isNull(issues.hiddenAt), notInArray(issues.status, ["done", "cancelled"])))
|
|
476
|
+
.limit(1);
|
|
477
|
+
return row ?? null;
|
|
478
|
+
}
|
|
479
|
+
async function buildRunOutputSilence(run, now = new Date()) {
|
|
480
|
+
const [quietUntilDecision, evaluation] = await Promise.all([
|
|
481
|
+
latestActiveOutputQuietUntilDecision(run.companyId, run.id, now),
|
|
482
|
+
findOpenStaleRunEvaluation(run.companyId, run.id),
|
|
483
|
+
]);
|
|
484
|
+
const silenceStartedAt = silenceStartedAtForRun(run);
|
|
485
|
+
const silenceAgeMs = run.status === "running" ? silenceAgeMsForRun(run, now) : null;
|
|
486
|
+
const level = run.status !== "running"
|
|
487
|
+
? "not_applicable"
|
|
488
|
+
: quietUntilDecision
|
|
489
|
+
? "snoozed"
|
|
490
|
+
: (silenceAgeMs ?? 0) >= ACTIVE_RUN_OUTPUT_CRITICAL_THRESHOLD_MS
|
|
491
|
+
? "critical"
|
|
492
|
+
: (silenceAgeMs ?? 0) >= ACTIVE_RUN_OUTPUT_SUSPICION_THRESHOLD_MS
|
|
493
|
+
? "suspicious"
|
|
494
|
+
: "ok";
|
|
495
|
+
return {
|
|
496
|
+
lastOutputAt: run.lastOutputAt ?? null,
|
|
497
|
+
lastOutputSeq: run.lastOutputSeq ?? 0,
|
|
498
|
+
lastOutputStream: (run.lastOutputStream === "stdout" || run.lastOutputStream === "stderr")
|
|
499
|
+
? run.lastOutputStream
|
|
500
|
+
: null,
|
|
501
|
+
silenceStartedAt,
|
|
502
|
+
silenceAgeMs,
|
|
503
|
+
level,
|
|
504
|
+
suspicionThresholdMs: ACTIVE_RUN_OUTPUT_SUSPICION_THRESHOLD_MS,
|
|
505
|
+
criticalThresholdMs: ACTIVE_RUN_OUTPUT_CRITICAL_THRESHOLD_MS,
|
|
506
|
+
snoozedUntil: quietUntilDecision?.snoozedUntil ?? null,
|
|
507
|
+
evaluationIssueId: evaluation?.id ?? null,
|
|
508
|
+
evaluationIssueIdentifier: evaluation?.identifier ?? null,
|
|
509
|
+
evaluationIssueAssigneeAgentId: evaluation?.assigneeAgentId ?? null,
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
function redactWatchdogEvidenceText(value, currentUserRedactionOptions) {
|
|
513
|
+
return redactSensitiveText(redactCurrentUserText(value, currentUserRedactionOptions));
|
|
514
|
+
}
|
|
515
|
+
function truncateEvidenceText(value, maxChars = 4000) {
|
|
516
|
+
if (value.length <= maxChars)
|
|
517
|
+
return value;
|
|
518
|
+
return `${value.slice(value.length - maxChars)}\n[truncated earlier evidence]`;
|
|
519
|
+
}
|
|
520
|
+
async function readRunLogTailForEvidence(run) {
|
|
521
|
+
if (!run.logStore || !run.logRef || !run.logBytes)
|
|
522
|
+
return "";
|
|
523
|
+
try {
|
|
524
|
+
const offset = Math.max(0, run.logBytes - ACTIVE_RUN_OUTPUT_EVIDENCE_TAIL_BYTES);
|
|
525
|
+
const result = await runLogStore.read({ store: run.logStore, logRef: run.logRef }, { offset, limitBytes: ACTIVE_RUN_OUTPUT_EVIDENCE_TAIL_BYTES });
|
|
526
|
+
return result.content;
|
|
527
|
+
}
|
|
528
|
+
catch (err) {
|
|
529
|
+
logger.warn({ err, runId: run.id }, "failed to read stale-run watchdog evidence tail");
|
|
530
|
+
return "";
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
async function resolveStaleRunSourceIssue(run) {
|
|
534
|
+
const issueId = issueIdFromRunContext(run.contextSnapshot);
|
|
535
|
+
if (!issueId)
|
|
536
|
+
return null;
|
|
537
|
+
const [issue] = await db
|
|
538
|
+
.select()
|
|
539
|
+
.from(issues)
|
|
540
|
+
.where(and(eq(issues.companyId, run.companyId), eq(issues.id, issueId), isNull(issues.hiddenAt)))
|
|
541
|
+
.limit(1);
|
|
542
|
+
return issue ?? null;
|
|
543
|
+
}
|
|
544
|
+
async function resolveStaleRunOwnerAgentId(input) {
|
|
545
|
+
const candidateIds = [];
|
|
546
|
+
if (input.sourceIssue?.assigneeAgentId) {
|
|
547
|
+
const sourceAssignee = await getAgent(input.sourceIssue.assigneeAgentId);
|
|
548
|
+
if (sourceAssignee?.reportsTo)
|
|
549
|
+
candidateIds.push(sourceAssignee.reportsTo);
|
|
550
|
+
}
|
|
551
|
+
if (input.runningAgent.reportsTo)
|
|
552
|
+
candidateIds.push(input.runningAgent.reportsTo);
|
|
553
|
+
const roleCandidates = await db
|
|
554
|
+
.select()
|
|
555
|
+
.from(agents)
|
|
556
|
+
.where(and(eq(agents.companyId, input.run.companyId), inArray(agents.role, ["cto", "ceo"])))
|
|
557
|
+
.orderBy(sql `case when ${agents.role} = 'cto' then 0 else 1 end`, asc(agents.createdAt));
|
|
558
|
+
candidateIds.push(...roleCandidates.map((agent) => agent.id));
|
|
559
|
+
const seen = new Set();
|
|
560
|
+
for (const agentId of candidateIds) {
|
|
561
|
+
if (seen.has(agentId))
|
|
562
|
+
continue;
|
|
563
|
+
seen.add(agentId);
|
|
564
|
+
const candidate = await getAgent(agentId);
|
|
565
|
+
if (!candidate || candidate.companyId !== input.run.companyId)
|
|
566
|
+
continue;
|
|
567
|
+
const budgetBlock = await budgets.getInvocationBlock(input.run.companyId, candidate.id, {
|
|
568
|
+
issueId: input.sourceIssue?.id ?? null,
|
|
569
|
+
projectId: input.sourceIssue?.projectId ?? null,
|
|
570
|
+
});
|
|
571
|
+
if (isAgentInvokable(candidate) && !budgetBlock)
|
|
572
|
+
return candidate.id;
|
|
573
|
+
}
|
|
574
|
+
return null;
|
|
575
|
+
}
|
|
576
|
+
async function collectStaleRunEvidence(input) {
|
|
577
|
+
const [tail, recentEvents, childIssues, blockers] = await Promise.all([
|
|
578
|
+
readRunLogTailForEvidence(input.run),
|
|
579
|
+
db
|
|
580
|
+
.select({
|
|
581
|
+
eventType: heartbeatRunEvents.eventType,
|
|
582
|
+
level: heartbeatRunEvents.level,
|
|
583
|
+
message: heartbeatRunEvents.message,
|
|
584
|
+
createdAt: heartbeatRunEvents.createdAt,
|
|
585
|
+
})
|
|
586
|
+
.from(heartbeatRunEvents)
|
|
587
|
+
.where(and(eq(heartbeatRunEvents.companyId, input.run.companyId), eq(heartbeatRunEvents.runId, input.run.id)))
|
|
588
|
+
.orderBy(desc(heartbeatRunEvents.id))
|
|
589
|
+
.limit(8),
|
|
590
|
+
input.sourceIssue
|
|
591
|
+
? db
|
|
592
|
+
.select({ id: issues.id, identifier: issues.identifier, title: issues.title, status: issues.status })
|
|
593
|
+
.from(issues)
|
|
594
|
+
.where(and(eq(issues.companyId, input.run.companyId), eq(issues.parentId, input.sourceIssue.id), isNull(issues.hiddenAt)))
|
|
595
|
+
.orderBy(desc(issues.updatedAt))
|
|
596
|
+
.limit(8)
|
|
597
|
+
: Promise.resolve([]),
|
|
598
|
+
input.sourceIssue
|
|
599
|
+
? db
|
|
600
|
+
.select({ id: issues.id, identifier: issues.identifier, title: issues.title, status: issues.status })
|
|
601
|
+
.from(issueRelations)
|
|
602
|
+
.innerJoin(issues, eq(issueRelations.issueId, issues.id))
|
|
603
|
+
.where(and(eq(issueRelations.companyId, input.run.companyId), eq(issueRelations.relatedIssueId, input.sourceIssue.id), eq(issueRelations.type, "blocks")))
|
|
604
|
+
.limit(8)
|
|
605
|
+
: Promise.resolve([]),
|
|
606
|
+
]);
|
|
607
|
+
const currentUserRedactionOptions = await getCurrentUserRedactionOptions();
|
|
608
|
+
const safeTail = truncateEvidenceText(redactWatchdogEvidenceText(tail, currentUserRedactionOptions));
|
|
609
|
+
const silenceAgeMs = silenceAgeMsForRun(input.run, input.now);
|
|
610
|
+
return {
|
|
611
|
+
safeTail,
|
|
612
|
+
silenceAgeMs,
|
|
613
|
+
recentEvents: recentEvents.reverse().map((event) => ({
|
|
614
|
+
eventType: event.eventType,
|
|
615
|
+
level: event.level,
|
|
616
|
+
createdAt: event.createdAt.toISOString(),
|
|
617
|
+
message: event.message ? truncateEvidenceText(redactWatchdogEvidenceText(event.message, currentUserRedactionOptions), 300) : null,
|
|
618
|
+
})),
|
|
619
|
+
childIssues,
|
|
620
|
+
blockers,
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
function buildStaleRunEvaluationDescription(input) {
|
|
624
|
+
const sourceIssue = input.sourceIssue
|
|
625
|
+
? issueUiLink({ identifier: input.sourceIssue.identifier, id: input.sourceIssue.id }, input.prefix)
|
|
626
|
+
: "none";
|
|
627
|
+
const recentEvents = input.evidence.recentEvents.length > 0
|
|
628
|
+
? input.evidence.recentEvents.map((event) => `- ${event.createdAt} \`${event.eventType}\`${event.level ? ` ${event.level}` : ""}: ${event.message ?? "(no message)"}`).join("\n")
|
|
629
|
+
: "- none";
|
|
630
|
+
const childIssues = input.evidence.childIssues.length > 0
|
|
631
|
+
? input.evidence.childIssues.map((issue) => `- ${issueUiLink({ identifier: issue.identifier, id: issue.id }, input.prefix)} \`${issue.status}\`: ${issue.title}`).join("\n")
|
|
632
|
+
: "- none detected";
|
|
633
|
+
const blockers = input.evidence.blockers.length > 0
|
|
634
|
+
? input.evidence.blockers.map((issue) => `- ${issueUiLink({ identifier: issue.identifier, id: issue.id }, input.prefix)} \`${issue.status}\`: ${issue.title}`).join("\n")
|
|
635
|
+
: "- none detected";
|
|
636
|
+
return [
|
|
637
|
+
`Evermore detected ${input.level} output silence on an active heartbeat run.`,
|
|
638
|
+
"",
|
|
639
|
+
"## Run",
|
|
640
|
+
"",
|
|
641
|
+
`- Run: ${runUiLink(input.run, input.prefix)}`,
|
|
642
|
+
`- Agent: ${input.runningAgent.name} (${input.runningAgent.adapterType})`,
|
|
643
|
+
`- Invocation: ${input.run.invocationSource}${input.run.triggerDetail ? ` / ${input.run.triggerDetail}` : ""}`,
|
|
644
|
+
`- Source issue: ${sourceIssue}`,
|
|
645
|
+
`- Started at: ${input.run.startedAt?.toISOString() ?? "unknown"}`,
|
|
646
|
+
`- Process started at: ${input.run.processStartedAt?.toISOString() ?? "unknown"}`,
|
|
647
|
+
`- Last output at: ${input.run.lastOutputAt?.toISOString() ?? "none recorded"}`,
|
|
648
|
+
`- Last output sequence: ${input.run.lastOutputSeq ?? 0}`,
|
|
649
|
+
`- Silent for: ${formatDuration(input.evidence.silenceAgeMs)}`,
|
|
650
|
+
`- Thresholds: suspicious after ${formatDuration(ACTIVE_RUN_OUTPUT_SUSPICION_THRESHOLD_MS)}, critical after ${formatDuration(ACTIVE_RUN_OUTPUT_CRITICAL_THRESHOLD_MS)}`,
|
|
651
|
+
`- Process metadata: pid \`${input.run.processPid ?? "unknown"}\`, process group \`${input.run.processGroupId ?? "unknown"}\`, in-memory handle \`${runningProcesses.has(input.run.id) ? "yes" : "no"}\``,
|
|
652
|
+
"",
|
|
653
|
+
"## Last Output Excerpt",
|
|
654
|
+
"",
|
|
655
|
+
input.evidence.safeTail ? `\`\`\`text\n${input.evidence.safeTail}\n\`\`\`` : "_No run-log tail was available._",
|
|
656
|
+
"",
|
|
657
|
+
"## Recent Run Events",
|
|
658
|
+
"",
|
|
659
|
+
recentEvents,
|
|
660
|
+
"",
|
|
661
|
+
"## Related Work",
|
|
662
|
+
"",
|
|
663
|
+
"Active child issues:",
|
|
664
|
+
childIssues,
|
|
665
|
+
"",
|
|
666
|
+
"Current source blockers:",
|
|
667
|
+
blockers,
|
|
668
|
+
"",
|
|
669
|
+
"## Decision Checklist",
|
|
670
|
+
"",
|
|
671
|
+
"- Continue or snooze if the run is intentionally quiet.",
|
|
672
|
+
"- Ask the run owner for context if work may be delegated outside the transcript.",
|
|
673
|
+
"- Preserve artifacts, branch state, and useful output before cancellation.",
|
|
674
|
+
"- Cancel or recover through the explicit run recovery controls when authorized.",
|
|
675
|
+
"- Close this issue as a false positive only after recording the reason.",
|
|
676
|
+
].join("\n");
|
|
677
|
+
}
|
|
678
|
+
function isUniqueStaleRunEvaluationConflict(error) {
|
|
679
|
+
if (!error || typeof error !== "object")
|
|
680
|
+
return false;
|
|
681
|
+
const maybe = error;
|
|
682
|
+
return maybe.code === "23505" &&
|
|
683
|
+
(maybe.constraint === "issues_active_stale_run_evaluation_uq" ||
|
|
684
|
+
typeof maybe.message === "string" && maybe.message.includes("issues_active_stale_run_evaluation_uq"));
|
|
685
|
+
}
|
|
686
|
+
function isUniqueStrandedIssueRecoveryConflict(error) {
|
|
687
|
+
if (!error || typeof error !== "object")
|
|
688
|
+
return false;
|
|
689
|
+
const maybe = error;
|
|
690
|
+
return maybe.code === "23505" &&
|
|
691
|
+
(maybe.constraint === "issues_active_stranded_issue_recovery_uq" ||
|
|
692
|
+
typeof maybe.message === "string" && maybe.message.includes("issues_active_stranded_issue_recovery_uq"));
|
|
693
|
+
}
|
|
694
|
+
async function ensureSourceIssueBlockedByStaleEvaluation(input) {
|
|
695
|
+
if (!input.sourceIssue || ["done", "cancelled"].includes(input.sourceIssue.status))
|
|
696
|
+
return false;
|
|
697
|
+
const blockerIds = await existingBlockerIssueIds(input.sourceIssue.companyId, input.sourceIssue.id);
|
|
698
|
+
if (blockerIds.includes(input.evaluationIssue.id))
|
|
699
|
+
return false;
|
|
700
|
+
const nextBlockerIds = [...blockerIds, input.evaluationIssue.id];
|
|
701
|
+
await issuesSvc.update(input.sourceIssue.id, {
|
|
702
|
+
...(input.sourceIssue.status === "blocked" ? {} : { status: "blocked" }),
|
|
703
|
+
blockedByIssueIds: nextBlockerIds,
|
|
704
|
+
});
|
|
705
|
+
await issuesSvc.addComment(input.sourceIssue.id, [
|
|
706
|
+
"Evermore detected critical output silence on this issue's active run.",
|
|
707
|
+
"",
|
|
708
|
+
`- Evaluation issue: ${input.evaluationIssue.identifier ?? input.evaluationIssue.id}`,
|
|
709
|
+
`- Run: \`${input.run.id}\``,
|
|
710
|
+
"",
|
|
711
|
+
"This blocks the source issue on the explicit review task without cancelling the active process.",
|
|
712
|
+
].join("\n"), { runId: input.run.id });
|
|
713
|
+
await logActivity(db, {
|
|
714
|
+
companyId: input.sourceIssue.companyId,
|
|
715
|
+
actorType: "system",
|
|
716
|
+
actorId: "system",
|
|
717
|
+
agentId: null,
|
|
718
|
+
runId: input.run.id,
|
|
719
|
+
action: "heartbeat.output_stale_escalated",
|
|
720
|
+
entityType: "issue",
|
|
721
|
+
entityId: input.sourceIssue.id,
|
|
722
|
+
details: {
|
|
723
|
+
source: "recovery.scan_silent_active_runs",
|
|
724
|
+
evaluationIssueId: input.evaluationIssue.id,
|
|
725
|
+
blockerIssueIds: nextBlockerIds,
|
|
726
|
+
},
|
|
727
|
+
});
|
|
728
|
+
return true;
|
|
729
|
+
}
|
|
730
|
+
async function createOrUpdateStaleRunEvaluation(input) {
|
|
731
|
+
const runningAgent = await getAgent(input.run.agentId);
|
|
732
|
+
if (!runningAgent || runningAgent.companyId !== input.run.companyId)
|
|
733
|
+
return { kind: "skipped" };
|
|
734
|
+
const sourceIssue = await resolveStaleRunSourceIssue(input.run);
|
|
735
|
+
const prefix = await getCompanyIssuePrefix(input.run.companyId);
|
|
736
|
+
const evidence = await collectStaleRunEvidence({
|
|
737
|
+
run: input.run,
|
|
738
|
+
runningAgent,
|
|
739
|
+
sourceIssue,
|
|
740
|
+
prefix,
|
|
741
|
+
now: input.now,
|
|
742
|
+
});
|
|
743
|
+
const level = (evidence.silenceAgeMs ?? 0) >= ACTIVE_RUN_OUTPUT_CRITICAL_THRESHOLD_MS ? "critical" : "suspicious";
|
|
744
|
+
const existing = await findOpenStaleRunEvaluation(input.run.companyId, input.run.id);
|
|
745
|
+
if (existing) {
|
|
746
|
+
if (level === "critical" && existing.priority !== "high") {
|
|
747
|
+
await issuesSvc.update(existing.id, {
|
|
748
|
+
priority: "high",
|
|
749
|
+
});
|
|
750
|
+
await issuesSvc.addComment(existing.id, [
|
|
751
|
+
"Critical output silence threshold crossed.",
|
|
752
|
+
"",
|
|
753
|
+
`- Run: \`${input.run.id}\``,
|
|
754
|
+
`- Silent for: ${formatDuration(evidence.silenceAgeMs)}`,
|
|
755
|
+
`- Last output at: ${input.run.lastOutputAt?.toISOString() ?? "none recorded"}`,
|
|
756
|
+
].join("\n"), { runId: input.run.id });
|
|
757
|
+
await ensureSourceIssueBlockedByStaleEvaluation({
|
|
758
|
+
sourceIssue,
|
|
759
|
+
evaluationIssue: existing,
|
|
760
|
+
run: input.run,
|
|
761
|
+
});
|
|
762
|
+
return { kind: "escalated", evaluationIssueId: existing.id };
|
|
763
|
+
}
|
|
764
|
+
if (level === "critical") {
|
|
765
|
+
await ensureSourceIssueBlockedByStaleEvaluation({
|
|
766
|
+
sourceIssue,
|
|
767
|
+
evaluationIssue: existing,
|
|
768
|
+
run: input.run,
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
return { kind: "existing", evaluationIssueId: existing.id };
|
|
772
|
+
}
|
|
773
|
+
const ownerAgentId = await resolveStaleRunOwnerAgentId({ run: input.run, runningAgent, sourceIssue });
|
|
774
|
+
const description = buildStaleRunEvaluationDescription({
|
|
775
|
+
run: input.run,
|
|
776
|
+
runningAgent,
|
|
777
|
+
sourceIssue,
|
|
778
|
+
prefix,
|
|
779
|
+
evidence,
|
|
780
|
+
level,
|
|
781
|
+
now: input.now,
|
|
782
|
+
});
|
|
783
|
+
let evaluation;
|
|
784
|
+
try {
|
|
785
|
+
evaluation = await issuesSvc.create(input.run.companyId, {
|
|
786
|
+
title: `Review silent active run for ${runningAgent.name}`,
|
|
787
|
+
description,
|
|
788
|
+
status: "todo",
|
|
789
|
+
priority: level === "critical" ? "high" : "medium",
|
|
790
|
+
parentId: sourceIssue && !["done", "cancelled"].includes(sourceIssue.status) ? sourceIssue.id : null,
|
|
791
|
+
projectId: sourceIssue?.projectId ?? null,
|
|
792
|
+
goalId: sourceIssue?.goalId ?? null,
|
|
793
|
+
billingCode: sourceIssue?.billingCode ?? null,
|
|
794
|
+
assigneeAgentId: ownerAgentId,
|
|
795
|
+
assigneeAdapterOverrides: recoveryAssigneeAdapterOverrides(),
|
|
796
|
+
originKind: STALE_ACTIVE_RUN_EVALUATION_ORIGIN_KIND,
|
|
797
|
+
originId: input.run.id,
|
|
798
|
+
originRunId: input.run.id,
|
|
799
|
+
originFingerprint: staleActiveRunOriginFingerprint(input.run.companyId, input.run.id),
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
catch (error) {
|
|
803
|
+
if (!isUniqueStaleRunEvaluationConflict(error))
|
|
804
|
+
throw error;
|
|
805
|
+
const raced = await findOpenStaleRunEvaluation(input.run.companyId, input.run.id);
|
|
806
|
+
if (!raced)
|
|
807
|
+
throw error;
|
|
808
|
+
return { kind: "existing", evaluationIssueId: raced.id };
|
|
809
|
+
}
|
|
810
|
+
await logActivity(db, {
|
|
811
|
+
companyId: input.run.companyId,
|
|
812
|
+
actorType: "system",
|
|
813
|
+
actorId: "system",
|
|
814
|
+
agentId: ownerAgentId,
|
|
815
|
+
runId: input.run.id,
|
|
816
|
+
action: "heartbeat.output_stale_detected",
|
|
817
|
+
entityType: "issue",
|
|
818
|
+
entityId: evaluation.id,
|
|
819
|
+
details: {
|
|
820
|
+
source: "recovery.scan_silent_active_runs",
|
|
821
|
+
level,
|
|
822
|
+
sourceIssueId: sourceIssue?.id ?? null,
|
|
823
|
+
silenceAgeMs: evidence.silenceAgeMs,
|
|
824
|
+
lastOutputAt: input.run.lastOutputAt?.toISOString() ?? null,
|
|
825
|
+
},
|
|
826
|
+
});
|
|
827
|
+
if (level === "critical") {
|
|
828
|
+
await ensureSourceIssueBlockedByStaleEvaluation({
|
|
829
|
+
sourceIssue,
|
|
830
|
+
evaluationIssue: evaluation,
|
|
831
|
+
run: input.run,
|
|
832
|
+
});
|
|
833
|
+
}
|
|
834
|
+
if (ownerAgentId) {
|
|
835
|
+
await deps.enqueueWakeup(ownerAgentId, {
|
|
836
|
+
source: "assignment",
|
|
837
|
+
triggerDetail: "system",
|
|
838
|
+
reason: "issue_assigned",
|
|
839
|
+
payload: withRecoveryModelProfileHint({
|
|
840
|
+
issueId: evaluation.id,
|
|
841
|
+
staleRunId: input.run.id,
|
|
842
|
+
sourceIssueId: sourceIssue?.id ?? null,
|
|
843
|
+
}),
|
|
844
|
+
requestedByActorType: "system",
|
|
845
|
+
requestedByActorId: null,
|
|
846
|
+
contextSnapshot: withRecoveryModelProfileHint({
|
|
847
|
+
issueId: evaluation.id,
|
|
848
|
+
taskId: evaluation.id,
|
|
849
|
+
wakeReason: "issue_assigned",
|
|
850
|
+
source: STALE_ACTIVE_RUN_EVALUATION_ORIGIN_KIND,
|
|
851
|
+
staleRunId: input.run.id,
|
|
852
|
+
sourceIssueId: sourceIssue?.id ?? null,
|
|
853
|
+
}),
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
return { kind: "created", evaluationIssueId: evaluation.id };
|
|
857
|
+
}
|
|
858
|
+
async function scanSilentActiveRuns(opts) {
|
|
859
|
+
const now = opts?.now ?? new Date();
|
|
860
|
+
const suspicionBefore = new Date(now.getTime() - ACTIVE_RUN_OUTPUT_SUSPICION_THRESHOLD_MS);
|
|
861
|
+
const candidates = await db
|
|
862
|
+
.select()
|
|
863
|
+
.from(heartbeatRuns)
|
|
864
|
+
.where(and(opts?.companyId ? eq(heartbeatRuns.companyId, opts.companyId) : undefined, eq(heartbeatRuns.status, "running"), sql `coalesce(${heartbeatRuns.lastOutputAt}, ${heartbeatRuns.processStartedAt}, ${heartbeatRuns.startedAt}, ${heartbeatRuns.createdAt}) <= ${suspicionBefore.toISOString()}::timestamptz`))
|
|
865
|
+
.orderBy(asc(heartbeatRuns.createdAt))
|
|
866
|
+
.limit(100);
|
|
867
|
+
const result = {
|
|
868
|
+
scanned: candidates.length,
|
|
869
|
+
created: 0,
|
|
870
|
+
existing: 0,
|
|
871
|
+
escalated: 0,
|
|
872
|
+
snoozed: 0,
|
|
873
|
+
skipped: 0,
|
|
874
|
+
evaluationIssueIds: [],
|
|
875
|
+
};
|
|
876
|
+
for (const run of candidates) {
|
|
877
|
+
if (await latestActiveOutputQuietUntilDecision(run.companyId, run.id, now)) {
|
|
878
|
+
result.snoozed += 1;
|
|
879
|
+
continue;
|
|
880
|
+
}
|
|
881
|
+
const outcome = await createOrUpdateStaleRunEvaluation({ run, now });
|
|
882
|
+
if (outcome.kind === "created")
|
|
883
|
+
result.created += 1;
|
|
884
|
+
else if (outcome.kind === "existing")
|
|
885
|
+
result.existing += 1;
|
|
886
|
+
else if (outcome.kind === "escalated")
|
|
887
|
+
result.escalated += 1;
|
|
888
|
+
else
|
|
889
|
+
result.skipped += 1;
|
|
890
|
+
if ("evaluationIssueId" in outcome && outcome.evaluationIssueId) {
|
|
891
|
+
result.evaluationIssueIds.push(outcome.evaluationIssueId);
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
return result;
|
|
895
|
+
}
|
|
896
|
+
async function recordWatchdogDecision(input) {
|
|
897
|
+
const [run] = await db
|
|
898
|
+
.select()
|
|
899
|
+
.from(heartbeatRuns)
|
|
900
|
+
.where(eq(heartbeatRuns.id, input.runId))
|
|
901
|
+
.limit(1);
|
|
902
|
+
if (!run)
|
|
903
|
+
throw notFound("Heartbeat run not found");
|
|
904
|
+
let evaluationIssue = null;
|
|
905
|
+
if (input.evaluationIssueId) {
|
|
906
|
+
evaluationIssue = await db
|
|
907
|
+
.select({
|
|
908
|
+
id: issues.id,
|
|
909
|
+
assigneeAgentId: issues.assigneeAgentId,
|
|
910
|
+
companyId: issues.companyId,
|
|
911
|
+
originKind: issues.originKind,
|
|
912
|
+
originId: issues.originId,
|
|
913
|
+
hiddenAt: issues.hiddenAt,
|
|
914
|
+
status: issues.status,
|
|
915
|
+
})
|
|
916
|
+
.from(issues)
|
|
917
|
+
.where(and(eq(issues.id, input.evaluationIssueId), eq(issues.companyId, run.companyId)))
|
|
918
|
+
.then((rows) => rows[0] ?? null);
|
|
919
|
+
if (!evaluationIssue)
|
|
920
|
+
throw notFound("Evaluation issue not found");
|
|
921
|
+
}
|
|
922
|
+
const boardActor = input.actor.type === "board";
|
|
923
|
+
const assignedRecoveryOwner = input.actor.type === "agent" &&
|
|
924
|
+
Boolean(input.actor.agentId) &&
|
|
925
|
+
evaluationIssue !== null &&
|
|
926
|
+
evaluationIssue.originKind === STALE_ACTIVE_RUN_EVALUATION_ORIGIN_KIND &&
|
|
927
|
+
evaluationIssue.originId === run.id &&
|
|
928
|
+
evaluationIssue.hiddenAt === null &&
|
|
929
|
+
!["done", "cancelled"].includes(evaluationIssue.status) &&
|
|
930
|
+
evaluationIssue?.assigneeAgentId === input.actor.agentId;
|
|
931
|
+
if (!boardActor && !assignedRecoveryOwner) {
|
|
932
|
+
throw forbidden("Only the board or the assigned recovery owner can record watchdog decisions");
|
|
933
|
+
}
|
|
934
|
+
if (evaluationIssue && (evaluationIssue.originKind !== STALE_ACTIVE_RUN_EVALUATION_ORIGIN_KIND ||
|
|
935
|
+
evaluationIssue.originId !== run.id)) {
|
|
936
|
+
throw forbidden("Watchdog decision evaluation issue is not bound to the target run");
|
|
937
|
+
}
|
|
938
|
+
if (input.actor.type === "agent" && !evaluationIssue) {
|
|
939
|
+
throw forbidden("Agent watchdog decisions require the target evaluation issue");
|
|
940
|
+
}
|
|
941
|
+
const createdByRunId = input.actor.type === "agent"
|
|
942
|
+
? input.actor.runId ?? input.createdByRunId ?? null
|
|
943
|
+
: input.actor.type === "board"
|
|
944
|
+
? input.actor.runId ?? input.createdByRunId ?? null
|
|
945
|
+
: null;
|
|
946
|
+
if (createdByRunId) {
|
|
947
|
+
const [creatorRun] = await db
|
|
948
|
+
.select({ id: heartbeatRuns.id, companyId: heartbeatRuns.companyId, agentId: heartbeatRuns.agentId })
|
|
949
|
+
.from(heartbeatRuns)
|
|
950
|
+
.where(eq(heartbeatRuns.id, createdByRunId))
|
|
951
|
+
.limit(1);
|
|
952
|
+
const sameCompany = creatorRun?.companyId === run.companyId;
|
|
953
|
+
const sameAgent = input.actor.type !== "agent" || creatorRun?.agentId === input.actor.agentId;
|
|
954
|
+
if (!creatorRun || !sameCompany || !sameAgent) {
|
|
955
|
+
throw forbidden("createdByRunId is not valid for this watchdog decision actor");
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
const decisionNow = input.now ?? new Date();
|
|
959
|
+
const effectiveSnoozedUntil = input.decision === "snooze"
|
|
960
|
+
? input.snoozedUntil ?? null
|
|
961
|
+
: input.decision === "continue"
|
|
962
|
+
? input.snoozedUntil && input.snoozedUntil > decisionNow
|
|
963
|
+
? input.snoozedUntil
|
|
964
|
+
: new Date(decisionNow.getTime() + ACTIVE_RUN_OUTPUT_CONTINUE_REARM_MS)
|
|
965
|
+
: null;
|
|
966
|
+
const [row] = await db
|
|
967
|
+
.insert(heartbeatRunWatchdogDecisions)
|
|
968
|
+
.values({
|
|
969
|
+
companyId: run.companyId,
|
|
970
|
+
runId: run.id,
|
|
971
|
+
evaluationIssueId: input.evaluationIssueId ?? null,
|
|
972
|
+
decision: input.decision,
|
|
973
|
+
snoozedUntil: effectiveSnoozedUntil,
|
|
974
|
+
reason: input.reason ?? null,
|
|
975
|
+
createdByAgentId: input.actor.type === "agent" ? input.actor.agentId ?? null : null,
|
|
976
|
+
createdByUserId: input.actor.type === "board" ? input.actor.userId ?? null : null,
|
|
977
|
+
createdByRunId,
|
|
978
|
+
})
|
|
979
|
+
.returning();
|
|
980
|
+
await logActivity(db, {
|
|
981
|
+
companyId: run.companyId,
|
|
982
|
+
actorType: input.actor.type === "agent" ? "agent" : "user",
|
|
983
|
+
actorId: input.actor.type === "agent"
|
|
984
|
+
? input.actor.agentId ?? "agent"
|
|
985
|
+
: input.actor.type === "board"
|
|
986
|
+
? input.actor.userId ?? "board"
|
|
987
|
+
: "unknown",
|
|
988
|
+
agentId: input.actor.type === "agent" ? input.actor.agentId ?? null : null,
|
|
989
|
+
runId: run.id,
|
|
990
|
+
action: input.decision === "snooze" ? "heartbeat.watchdog_snoozed" : "heartbeat.watchdog_decision_recorded",
|
|
991
|
+
entityType: "heartbeat_run",
|
|
992
|
+
entityId: run.id,
|
|
993
|
+
details: {
|
|
994
|
+
source: "recovery.record_watchdog_decision",
|
|
995
|
+
decision: input.decision,
|
|
996
|
+
evaluationIssueId: input.evaluationIssueId ?? null,
|
|
997
|
+
snoozedUntil: effectiveSnoozedUntil?.toISOString() ?? null,
|
|
998
|
+
reason: input.reason ?? null,
|
|
999
|
+
},
|
|
1000
|
+
});
|
|
1001
|
+
return row;
|
|
1002
|
+
}
|
|
1003
|
+
async function findOpenStrandedIssueRecoveryIssue(companyId, sourceIssueId) {
|
|
1004
|
+
return db
|
|
1005
|
+
.select()
|
|
1006
|
+
.from(issues)
|
|
1007
|
+
.where(and(eq(issues.companyId, companyId), eq(issues.originKind, STRANDED_ISSUE_RECOVERY_ORIGIN_KIND), eq(issues.originId, sourceIssueId), isNull(issues.hiddenAt), notInArray(issues.status, ["done", "cancelled"])))
|
|
1008
|
+
.orderBy(desc(issues.createdAt))
|
|
1009
|
+
.limit(1)
|
|
1010
|
+
.then((rows) => rows[0] ?? null);
|
|
1011
|
+
}
|
|
1012
|
+
async function resolveStrandedIssueRecoveryOwnerAgentId(issue) {
|
|
1013
|
+
const candidateIds = [];
|
|
1014
|
+
if (issue.assigneeAgentId) {
|
|
1015
|
+
const assignee = await getAgent(issue.assigneeAgentId);
|
|
1016
|
+
if (assignee?.reportsTo)
|
|
1017
|
+
candidateIds.push(assignee.reportsTo);
|
|
1018
|
+
}
|
|
1019
|
+
if (issue.createdByAgentId) {
|
|
1020
|
+
const creator = await getAgent(issue.createdByAgentId);
|
|
1021
|
+
if (creator?.reportsTo)
|
|
1022
|
+
candidateIds.push(creator.reportsTo);
|
|
1023
|
+
candidateIds.push(issue.createdByAgentId);
|
|
1024
|
+
}
|
|
1025
|
+
const roleCandidates = await db
|
|
1026
|
+
.select()
|
|
1027
|
+
.from(agents)
|
|
1028
|
+
.where(and(eq(agents.companyId, issue.companyId), inArray(agents.role, ["cto", "ceo"])))
|
|
1029
|
+
.orderBy(sql `case when ${agents.role} = 'cto' then 0 else 1 end`, asc(agents.createdAt));
|
|
1030
|
+
candidateIds.push(...roleCandidates.map((agent) => agent.id));
|
|
1031
|
+
if (issue.assigneeAgentId)
|
|
1032
|
+
candidateIds.push(issue.assigneeAgentId);
|
|
1033
|
+
const seen = new Set();
|
|
1034
|
+
for (const agentId of candidateIds) {
|
|
1035
|
+
if (seen.has(agentId))
|
|
1036
|
+
continue;
|
|
1037
|
+
seen.add(agentId);
|
|
1038
|
+
const candidate = await getAgent(agentId);
|
|
1039
|
+
if (!candidate || candidate.companyId !== issue.companyId)
|
|
1040
|
+
continue;
|
|
1041
|
+
const budgetBlock = await budgets.getInvocationBlock(issue.companyId, candidate.id, {
|
|
1042
|
+
issueId: issue.id,
|
|
1043
|
+
projectId: issue.projectId,
|
|
1044
|
+
});
|
|
1045
|
+
if (isAgentInvokable(candidate) && !budgetBlock)
|
|
1046
|
+
return candidate.id;
|
|
1047
|
+
}
|
|
1048
|
+
return null;
|
|
1049
|
+
}
|
|
1050
|
+
function buildStrandedIssueRecoveryDescription(input) {
|
|
1051
|
+
const sourceIssue = issueUiLink({ identifier: input.issue.identifier, id: input.issue.id }, input.prefix);
|
|
1052
|
+
const runLink = input.latestRun
|
|
1053
|
+
? `[\`${input.latestRun.id}\`](/${input.prefix}/agents/${input.latestRun.agentId}/runs/${input.latestRun.id})`
|
|
1054
|
+
: "none";
|
|
1055
|
+
if (input.recoveryCause === SUCCESSFUL_RUN_MISSING_STATE_REASON) {
|
|
1056
|
+
const sourceRunId = input.successfulRunHandoffEvidence?.sourceRunId;
|
|
1057
|
+
const sourceRunLink = sourceRunId && input.latestRun
|
|
1058
|
+
? `[\`${sourceRunId}\`](/${input.prefix}/agents/${input.latestRun.agentId}/runs/${sourceRunId})`
|
|
1059
|
+
: "unknown";
|
|
1060
|
+
const missingDisposition = input.successfulRunHandoffEvidence?.missingDisposition ?? "clear_next_step";
|
|
1061
|
+
return [
|
|
1062
|
+
"Evermore exhausted the bounded corrective handoff for a successful run that still has no valid issue disposition.",
|
|
1063
|
+
"",
|
|
1064
|
+
"This is not a runtime/adapter crash report. The source run succeeded; the remaining problem is the missing `done`, `in_review`, `blocked`, delegated follow-up, or explicit continuation path.",
|
|
1065
|
+
"",
|
|
1066
|
+
"## Safe Evidence",
|
|
1067
|
+
"",
|
|
1068
|
+
`- Source issue: ${sourceIssue}`,
|
|
1069
|
+
`- Source run: ${sourceRunLink}`,
|
|
1070
|
+
`- Corrective handoff run: ${runLink}`,
|
|
1071
|
+
`- Source assignee: ${agentUiLink(input.sourceAssignee ?? null, input.prefix)}`,
|
|
1072
|
+
`- Latest issue status: \`${input.issue.status}\``,
|
|
1073
|
+
`- Latest handoff run status: \`${input.latestRun?.status ?? "unknown"}\``,
|
|
1074
|
+
`- Normalized cause: \`${SUCCESSFUL_RUN_MISSING_STATE_REASON}\``,
|
|
1075
|
+
`- Missing disposition: \`${missingDisposition}\``,
|
|
1076
|
+
"- Suggested manager action: choose and record a valid issue disposition without copying transcript content.",
|
|
1077
|
+
"",
|
|
1078
|
+
"## Required Action",
|
|
1079
|
+
"",
|
|
1080
|
+
"- Inspect the source issue and run metadata, not raw transcript excerpts.",
|
|
1081
|
+
"- Choose a valid issue disposition: `done`/`cancelled`, `in_review` with an owner, `blocked` with first-class blockers, delegated follow-up work, or an explicit continuation path.",
|
|
1082
|
+
"- When the source issue has a clear owner and disposition, mark this recovery issue done.",
|
|
1083
|
+
].join("\n");
|
|
1084
|
+
}
|
|
1085
|
+
const retryReason = readNonEmptyString(parseObject(input.latestRun?.contextSnapshot)?.retryReason) ?? "unknown";
|
|
1086
|
+
const failureSummary = summarizeRunFailureForIssueComment(input.latestRun);
|
|
1087
|
+
return [
|
|
1088
|
+
"Evermore exhausted automatic recovery for an assigned issue and created this explicit recovery task.",
|
|
1089
|
+
"",
|
|
1090
|
+
"## Source",
|
|
1091
|
+
"",
|
|
1092
|
+
`- Source issue: ${sourceIssue}`,
|
|
1093
|
+
`- Previous source status: \`${input.previousStatus}\``,
|
|
1094
|
+
`- Latest retry run: ${runLink}`,
|
|
1095
|
+
`- Latest retry status: \`${input.latestRun?.status ?? "unknown"}\``,
|
|
1096
|
+
`- Detected invariant: \`stranded_assigned_issue\``,
|
|
1097
|
+
`- Retry reason: \`${retryReason}\``,
|
|
1098
|
+
failureSummary ? `- Failure: ${failureSummary.trim()}` : "- Failure: none recorded",
|
|
1099
|
+
"",
|
|
1100
|
+
"## Ownership",
|
|
1101
|
+
"",
|
|
1102
|
+
"- Selected owner: the first invokable manager/creator/executive candidate with budget available.",
|
|
1103
|
+
"",
|
|
1104
|
+
"## Required Action",
|
|
1105
|
+
"",
|
|
1106
|
+
"- Inspect the latest run and source issue state.",
|
|
1107
|
+
"- Fix the runtime/adapter problem, reassign the source issue, or convert the source issue into a clear manual-review state.",
|
|
1108
|
+
"- When the source issue has a live execution path or has been intentionally resolved, mark this recovery issue done.",
|
|
1109
|
+
].join("\n");
|
|
1110
|
+
}
|
|
1111
|
+
async function ensureStrandedIssueRecoveryIssue(input) {
|
|
1112
|
+
if (isStrandedIssueRecoveryIssue(input.issue))
|
|
1113
|
+
return null;
|
|
1114
|
+
const existing = await findOpenStrandedIssueRecoveryIssue(input.issue.companyId, input.issue.id);
|
|
1115
|
+
if (existing)
|
|
1116
|
+
return existing;
|
|
1117
|
+
const ownerAgentId = await resolveStrandedIssueRecoveryOwnerAgentId(input.issue);
|
|
1118
|
+
if (!ownerAgentId)
|
|
1119
|
+
return null;
|
|
1120
|
+
const prefix = await getCompanyIssuePrefix(input.issue.companyId);
|
|
1121
|
+
const sourceAssignee = input.issue.assigneeAgentId ? await getAgent(input.issue.assigneeAgentId) : null;
|
|
1122
|
+
const recoveryCause = input.recoveryCause ?? "stranded_assigned_issue";
|
|
1123
|
+
let recovery;
|
|
1124
|
+
try {
|
|
1125
|
+
recovery = await issuesSvc.create(input.issue.companyId, {
|
|
1126
|
+
title: recoveryCause === SUCCESSFUL_RUN_MISSING_STATE_REASON
|
|
1127
|
+
? `Recover missing next step ${input.issue.identifier ?? input.issue.title}`
|
|
1128
|
+
: `Recover stalled issue ${input.issue.identifier ?? input.issue.title}`,
|
|
1129
|
+
description: buildStrandedIssueRecoveryDescription({
|
|
1130
|
+
issue: input.issue,
|
|
1131
|
+
latestRun: input.latestRun,
|
|
1132
|
+
previousStatus: input.previousStatus,
|
|
1133
|
+
prefix,
|
|
1134
|
+
recoveryCause,
|
|
1135
|
+
successfulRunHandoffEvidence: input.successfulRunHandoffEvidence,
|
|
1136
|
+
sourceAssignee,
|
|
1137
|
+
}),
|
|
1138
|
+
status: "todo",
|
|
1139
|
+
priority: input.issue.priority,
|
|
1140
|
+
parentId: input.issue.id,
|
|
1141
|
+
projectId: input.issue.projectId,
|
|
1142
|
+
goalId: input.issue.goalId,
|
|
1143
|
+
assigneeAgentId: ownerAgentId,
|
|
1144
|
+
assigneeAdapterOverrides: recoveryAssigneeAdapterOverrides(),
|
|
1145
|
+
originKind: STRANDED_ISSUE_RECOVERY_ORIGIN_KIND,
|
|
1146
|
+
originId: input.issue.id,
|
|
1147
|
+
originRunId: input.latestRun?.id ?? null,
|
|
1148
|
+
originFingerprint: [
|
|
1149
|
+
STRANDED_ISSUE_RECOVERY_ORIGIN_KIND,
|
|
1150
|
+
input.issue.companyId,
|
|
1151
|
+
input.issue.id,
|
|
1152
|
+
recoveryCause,
|
|
1153
|
+
input.latestRun?.id ?? "no-run",
|
|
1154
|
+
].join(":"),
|
|
1155
|
+
billingCode: input.issue.billingCode,
|
|
1156
|
+
inheritExecutionWorkspaceFromIssueId: input.issue.id,
|
|
1157
|
+
});
|
|
1158
|
+
}
|
|
1159
|
+
catch (error) {
|
|
1160
|
+
if (!isUniqueStrandedIssueRecoveryConflict(error))
|
|
1161
|
+
throw error;
|
|
1162
|
+
const raced = await findOpenStrandedIssueRecoveryIssue(input.issue.companyId, input.issue.id);
|
|
1163
|
+
if (!raced)
|
|
1164
|
+
throw error;
|
|
1165
|
+
return raced;
|
|
1166
|
+
}
|
|
1167
|
+
await deps.enqueueWakeup(ownerAgentId, {
|
|
1168
|
+
source: "assignment",
|
|
1169
|
+
triggerDetail: "system",
|
|
1170
|
+
reason: "issue_assigned",
|
|
1171
|
+
payload: withRecoveryModelProfileHint({
|
|
1172
|
+
issueId: recovery.id,
|
|
1173
|
+
sourceIssueId: input.issue.id,
|
|
1174
|
+
strandedRunId: input.latestRun?.id ?? null,
|
|
1175
|
+
recoveryCause,
|
|
1176
|
+
}),
|
|
1177
|
+
requestedByActorType: "system",
|
|
1178
|
+
requestedByActorId: null,
|
|
1179
|
+
contextSnapshot: withRecoveryModelProfileHint({
|
|
1180
|
+
issueId: recovery.id,
|
|
1181
|
+
taskId: recovery.id,
|
|
1182
|
+
wakeReason: "issue_assigned",
|
|
1183
|
+
source: STRANDED_ISSUE_RECOVERY_ORIGIN_KIND,
|
|
1184
|
+
sourceIssueId: input.issue.id,
|
|
1185
|
+
strandedRunId: input.latestRun?.id ?? null,
|
|
1186
|
+
recoveryCause,
|
|
1187
|
+
}),
|
|
1188
|
+
});
|
|
1189
|
+
return recovery;
|
|
1190
|
+
}
|
|
1191
|
+
function buildRecoveryIssueInPlaceEscalationComment(input) {
|
|
1192
|
+
const runLink = input.latestRun
|
|
1193
|
+
? runUiLink({ id: input.latestRun.id, agentId: input.latestRun.agentId }, input.prefix)
|
|
1194
|
+
: "none";
|
|
1195
|
+
const retryReason = readNonEmptyString(parseObject(input.latestRun?.contextSnapshot)?.retryReason) ?? "none";
|
|
1196
|
+
const failureSummary = summarizeRunFailureForIssueComment(input.latestRun);
|
|
1197
|
+
return [
|
|
1198
|
+
"Evermore stopped automatic stranded-work recovery for this recovery issue.",
|
|
1199
|
+
"",
|
|
1200
|
+
`- Recovery issue: ${issueUiLink({ identifier: input.issue.identifier, id: input.issue.id }, input.prefix)}`,
|
|
1201
|
+
`- Previous status: \`${input.previousStatus}\``,
|
|
1202
|
+
`- Latest run: ${runLink}`,
|
|
1203
|
+
`- Latest run status: \`${input.latestRun?.status ?? "unknown"}\``,
|
|
1204
|
+
`- Retry reason: \`${retryReason}\``,
|
|
1205
|
+
failureSummary ? `- Failure: ${failureSummary.trim()}` : "- Failure: none recorded",
|
|
1206
|
+
"- Guard: recovery issues do not create nested `stranded_issue_recovery` issues.",
|
|
1207
|
+
"",
|
|
1208
|
+
"Next action: the current recovery owner should inspect the failed run evidence, restore a live execution path or record the manual resolution, then move this recovery issue out of `blocked`.",
|
|
1209
|
+
].join("\n");
|
|
1210
|
+
}
|
|
1211
|
+
async function escalateStrandedRecoveryIssueInPlace(input) {
|
|
1212
|
+
const updated = await issuesSvc.update(input.issue.id, { status: "blocked" });
|
|
1213
|
+
if (!updated)
|
|
1214
|
+
return null;
|
|
1215
|
+
const prefix = await getCompanyIssuePrefix(input.issue.companyId);
|
|
1216
|
+
await issuesSvc.addComment(input.issue.id, buildRecoveryIssueInPlaceEscalationComment({
|
|
1217
|
+
issue: input.issue,
|
|
1218
|
+
previousStatus: input.previousStatus,
|
|
1219
|
+
latestRun: input.latestRun,
|
|
1220
|
+
prefix,
|
|
1221
|
+
}), {});
|
|
1222
|
+
await logActivity(db, {
|
|
1223
|
+
companyId: input.issue.companyId,
|
|
1224
|
+
actorType: "system",
|
|
1225
|
+
actorId: "system",
|
|
1226
|
+
agentId: null,
|
|
1227
|
+
runId: null,
|
|
1228
|
+
action: "issue.updated",
|
|
1229
|
+
entityType: "issue",
|
|
1230
|
+
entityId: input.issue.id,
|
|
1231
|
+
details: {
|
|
1232
|
+
identifier: input.issue.identifier,
|
|
1233
|
+
status: "blocked",
|
|
1234
|
+
previousStatus: input.previousStatus,
|
|
1235
|
+
source: "recovery.reconcile_stranded_recovery_issue",
|
|
1236
|
+
latestRunId: input.latestRun?.id ?? null,
|
|
1237
|
+
latestRunStatus: input.latestRun?.status ?? null,
|
|
1238
|
+
latestRunErrorCode: input.latestRun?.errorCode ?? null,
|
|
1239
|
+
originKind: input.issue.originKind,
|
|
1240
|
+
originId: input.issue.originId,
|
|
1241
|
+
},
|
|
1242
|
+
});
|
|
1243
|
+
return updated;
|
|
1244
|
+
}
|
|
1245
|
+
async function existingBlockerIssueIds(companyId, issueId) {
|
|
1246
|
+
return db
|
|
1247
|
+
.select({ blockerIssueId: issueRelations.issueId })
|
|
1248
|
+
.from(issueRelations)
|
|
1249
|
+
.where(and(eq(issueRelations.companyId, companyId), eq(issueRelations.relatedIssueId, issueId), eq(issueRelations.type, "blocks")))
|
|
1250
|
+
.then((rows) => rows.map((row) => row.blockerIssueId));
|
|
1251
|
+
}
|
|
1252
|
+
async function existingUnresolvedBlockerIssueIds(companyId, issueId) {
|
|
1253
|
+
return db
|
|
1254
|
+
.select({ blockerIssueId: issueRelations.issueId })
|
|
1255
|
+
.from(issueRelations)
|
|
1256
|
+
.innerJoin(issues, and(eq(issues.companyId, issueRelations.companyId), eq(issues.id, issueRelations.issueId)))
|
|
1257
|
+
.where(and(eq(issueRelations.companyId, companyId), eq(issueRelations.relatedIssueId, issueId), eq(issueRelations.type, "blocks"), notInArray(issues.status, ["done", "cancelled"])))
|
|
1258
|
+
.then((rows) => rows.map((row) => row.blockerIssueId));
|
|
1259
|
+
}
|
|
1260
|
+
async function escalateStrandedAssignedIssue(input) {
|
|
1261
|
+
if (isStrandedIssueRecoveryIssue(input.issue)) {
|
|
1262
|
+
return escalateStrandedRecoveryIssueInPlace({
|
|
1263
|
+
issue: input.issue,
|
|
1264
|
+
previousStatus: input.previousStatus,
|
|
1265
|
+
latestRun: input.latestRun,
|
|
1266
|
+
});
|
|
1267
|
+
}
|
|
1268
|
+
const recoveryIssue = await ensureStrandedIssueRecoveryIssue({
|
|
1269
|
+
issue: input.issue,
|
|
1270
|
+
previousStatus: input.previousStatus,
|
|
1271
|
+
latestRun: input.latestRun,
|
|
1272
|
+
recoveryCause: input.recoveryCause,
|
|
1273
|
+
successfulRunHandoffEvidence: input.successfulRunHandoffEvidence,
|
|
1274
|
+
});
|
|
1275
|
+
const blockerIds = await existingUnresolvedBlockerIssueIds(input.issue.companyId, input.issue.id);
|
|
1276
|
+
const nextBlockerIds = recoveryIssue
|
|
1277
|
+
? [...new Set([...blockerIds, recoveryIssue.id])]
|
|
1278
|
+
: blockerIds;
|
|
1279
|
+
const updated = await issuesSvc.update(input.issue.id, {
|
|
1280
|
+
status: "blocked",
|
|
1281
|
+
blockedByIssueIds: nextBlockerIds,
|
|
1282
|
+
});
|
|
1283
|
+
if (!updated)
|
|
1284
|
+
return null;
|
|
1285
|
+
const prefix = await getCompanyIssuePrefix(input.issue.companyId);
|
|
1286
|
+
const recoveryOwner = recoveryIssue?.assigneeAgentId ? await getAgent(recoveryIssue.assigneeAgentId) : null;
|
|
1287
|
+
const sourceAssignee = input.issue.assigneeAgentId ? await getAgent(input.issue.assigneeAgentId) : null;
|
|
1288
|
+
let notice = null;
|
|
1289
|
+
if (input.recoveryCause === SUCCESSFUL_RUN_MISSING_STATE_REASON && input.successfulRunHandoffEvidence) {
|
|
1290
|
+
notice = buildSuccessfulRunHandoffExhaustedNotice({
|
|
1291
|
+
issue: input.issue,
|
|
1292
|
+
sourceRun: input.successfulRunHandoffEvidence.sourceRunId
|
|
1293
|
+
? { id: input.successfulRunHandoffEvidence.sourceRunId, status: "succeeded" }
|
|
1294
|
+
: null,
|
|
1295
|
+
correctiveRun: input.latestRun ? { id: input.latestRun.id, status: input.latestRun.status } : null,
|
|
1296
|
+
sourceAssignee,
|
|
1297
|
+
recoveryIssue,
|
|
1298
|
+
recoveryOwner,
|
|
1299
|
+
latestIssueStatus: input.issue.status,
|
|
1300
|
+
latestHandoffRunStatus: input.latestRun?.status ?? "unknown",
|
|
1301
|
+
missingDisposition: input.successfulRunHandoffEvidence.missingDisposition,
|
|
1302
|
+
});
|
|
1303
|
+
}
|
|
1304
|
+
const recoveryLine = recoveryIssue
|
|
1305
|
+
? [
|
|
1306
|
+
"",
|
|
1307
|
+
`- Recovery issue: ${issueUiLink({ identifier: recoveryIssue.identifier, id: recoveryIssue.id }, prefix)}`,
|
|
1308
|
+
`- Recovery owner: ${agentUiLink(recoveryOwner, prefix)}`,
|
|
1309
|
+
"- Next action: the recovery owner should either restore a live execution path or record the manual resolution, then mark the recovery issue done.",
|
|
1310
|
+
].join("\n")
|
|
1311
|
+
: [
|
|
1312
|
+
"",
|
|
1313
|
+
"- Recovery issue: none created because Evermore could not find an invokable manager, creator, or executive owner with budget available.",
|
|
1314
|
+
"- Next action: a board operator should assign an invokable recovery owner, fix the agent/runtime state, or record an intentional manual resolution.",
|
|
1315
|
+
].join("\n");
|
|
1316
|
+
if (notice) {
|
|
1317
|
+
await issuesSvc.addComment(input.issue.id, notice.body, {}, {
|
|
1318
|
+
authorType: "system",
|
|
1319
|
+
presentation: notice.presentation,
|
|
1320
|
+
metadata: notice.metadata,
|
|
1321
|
+
});
|
|
1322
|
+
}
|
|
1323
|
+
else {
|
|
1324
|
+
await issuesSvc.addComment(input.issue.id, `${input.comment ?? ""}${recoveryLine}`, {});
|
|
1325
|
+
}
|
|
1326
|
+
await logActivity(db, {
|
|
1327
|
+
companyId: input.issue.companyId,
|
|
1328
|
+
actorType: "system",
|
|
1329
|
+
actorId: "system",
|
|
1330
|
+
agentId: null,
|
|
1331
|
+
runId: null,
|
|
1332
|
+
action: input.recoveryCause === SUCCESSFUL_RUN_MISSING_STATE_REASON
|
|
1333
|
+
? "issue.successful_run_handoff_escalated"
|
|
1334
|
+
: "issue.updated",
|
|
1335
|
+
entityType: "issue",
|
|
1336
|
+
entityId: input.issue.id,
|
|
1337
|
+
details: {
|
|
1338
|
+
identifier: input.issue.identifier,
|
|
1339
|
+
status: "blocked",
|
|
1340
|
+
previousStatus: input.previousStatus,
|
|
1341
|
+
source: input.recoveryCause === SUCCESSFUL_RUN_MISSING_STATE_REASON
|
|
1342
|
+
? "recovery.reconcile_successful_run_handoff_missing_state"
|
|
1343
|
+
: "recovery.reconcile_stranded_assigned_issue",
|
|
1344
|
+
recoveryCause: input.recoveryCause ?? "stranded_assigned_issue",
|
|
1345
|
+
latestRunId: input.latestRun?.id ?? null,
|
|
1346
|
+
latestRunStatus: input.latestRun?.status ?? null,
|
|
1347
|
+
latestRunErrorCode: input.latestRun?.errorCode ?? null,
|
|
1348
|
+
recoveryIssueId: recoveryIssue?.id ?? null,
|
|
1349
|
+
blockerIssueIds: nextBlockerIds,
|
|
1350
|
+
},
|
|
1351
|
+
});
|
|
1352
|
+
return updated;
|
|
1353
|
+
}
|
|
1354
|
+
async function reconcileStrandedAssignedIssues() {
|
|
1355
|
+
const candidates = await db
|
|
1356
|
+
.select()
|
|
1357
|
+
.from(issues)
|
|
1358
|
+
.where(and(isNull(issues.assigneeUserId), inArray(issues.status, ["todo", "in_progress"]), sql `${issues.assigneeAgentId} is not null`));
|
|
1359
|
+
const result = {
|
|
1360
|
+
assignmentDispatched: 0,
|
|
1361
|
+
dispatchRequeued: 0,
|
|
1362
|
+
continuationRequeued: 0,
|
|
1363
|
+
productiveContinuationObserved: 0,
|
|
1364
|
+
successfulContinuationObserved: 0,
|
|
1365
|
+
orphanBlockersAssigned: 0,
|
|
1366
|
+
successfulRunHandoffEscalated: 0,
|
|
1367
|
+
escalated: 0,
|
|
1368
|
+
skipped: 0,
|
|
1369
|
+
issueIds: [],
|
|
1370
|
+
};
|
|
1371
|
+
for (const issue of candidates) {
|
|
1372
|
+
const agentId = issue.assigneeAgentId;
|
|
1373
|
+
if (!agentId) {
|
|
1374
|
+
result.skipped += 1;
|
|
1375
|
+
continue;
|
|
1376
|
+
}
|
|
1377
|
+
const agent = await getAgent(agentId);
|
|
1378
|
+
if (!agent || agent.companyId !== issue.companyId || !isAgentInvokable(agent)) {
|
|
1379
|
+
result.skipped += 1;
|
|
1380
|
+
continue;
|
|
1381
|
+
}
|
|
1382
|
+
if (await hasActiveExecutionPath(issue.companyId, issue.id)) {
|
|
1383
|
+
result.skipped += 1;
|
|
1384
|
+
continue;
|
|
1385
|
+
}
|
|
1386
|
+
if (await isAutomaticRecoverySuppressedByPauseHold(db, issue.companyId, issue.id, treeControlSvc)) {
|
|
1387
|
+
result.skipped += 1;
|
|
1388
|
+
continue;
|
|
1389
|
+
}
|
|
1390
|
+
const latestRun = await getLatestIssueRun(issue.companyId, issue.id);
|
|
1391
|
+
if (isStrandedIssueRecoveryIssue(issue) && isUnsuccessfulTerminalIssueRun(latestRun)) {
|
|
1392
|
+
const updated = await escalateStrandedRecoveryIssueInPlace({
|
|
1393
|
+
issue,
|
|
1394
|
+
previousStatus: issue.status,
|
|
1395
|
+
latestRun,
|
|
1396
|
+
});
|
|
1397
|
+
if (updated) {
|
|
1398
|
+
result.escalated += 1;
|
|
1399
|
+
result.issueIds.push(issue.id);
|
|
1400
|
+
}
|
|
1401
|
+
else {
|
|
1402
|
+
result.skipped += 1;
|
|
1403
|
+
}
|
|
1404
|
+
continue;
|
|
1405
|
+
}
|
|
1406
|
+
if (issue.status === "todo") {
|
|
1407
|
+
if (!latestRun) {
|
|
1408
|
+
if (await hasQueuedIssueWake(issue.companyId, issue.id)) {
|
|
1409
|
+
result.skipped += 1;
|
|
1410
|
+
continue;
|
|
1411
|
+
}
|
|
1412
|
+
if (await isInvocationBudgetBlocked(issue, agentId)) {
|
|
1413
|
+
result.skipped += 1;
|
|
1414
|
+
continue;
|
|
1415
|
+
}
|
|
1416
|
+
const queued = await enqueueInitialAssignedTodoDispatch(issue, agentId);
|
|
1417
|
+
if (queued) {
|
|
1418
|
+
result.assignmentDispatched += 1;
|
|
1419
|
+
result.issueIds.push(issue.id);
|
|
1420
|
+
}
|
|
1421
|
+
else {
|
|
1422
|
+
result.skipped += 1;
|
|
1423
|
+
}
|
|
1424
|
+
continue;
|
|
1425
|
+
}
|
|
1426
|
+
if (latestRun.status === "succeeded") {
|
|
1427
|
+
result.skipped += 1;
|
|
1428
|
+
continue;
|
|
1429
|
+
}
|
|
1430
|
+
if (didAutomaticRecoveryFail(latestRun, "assignment_recovery")) {
|
|
1431
|
+
const failureSummary = summarizeRunFailureForIssueComment(latestRun);
|
|
1432
|
+
const updated = await escalateStrandedAssignedIssue({
|
|
1433
|
+
issue,
|
|
1434
|
+
previousStatus: "todo",
|
|
1435
|
+
latestRun,
|
|
1436
|
+
comment: "Evermore automatically retried dispatch for this assigned `todo` issue after a lost wake/run, " +
|
|
1437
|
+
`but it still has no live execution path.${failureSummary ?? ""} ` +
|
|
1438
|
+
"Moving it to `blocked` so it is visible for intervention.",
|
|
1439
|
+
});
|
|
1440
|
+
if (updated) {
|
|
1441
|
+
result.escalated += 1;
|
|
1442
|
+
result.issueIds.push(issue.id);
|
|
1443
|
+
}
|
|
1444
|
+
else {
|
|
1445
|
+
result.skipped += 1;
|
|
1446
|
+
}
|
|
1447
|
+
continue;
|
|
1448
|
+
}
|
|
1449
|
+
if (await isInvocationBudgetBlocked(issue, agentId)) {
|
|
1450
|
+
result.skipped += 1;
|
|
1451
|
+
continue;
|
|
1452
|
+
}
|
|
1453
|
+
const queued = await enqueueStrandedIssueRecovery({
|
|
1454
|
+
issueId: issue.id,
|
|
1455
|
+
agentId,
|
|
1456
|
+
reason: "issue_assignment_recovery",
|
|
1457
|
+
retryReason: "assignment_recovery",
|
|
1458
|
+
source: "issue.assignment_recovery",
|
|
1459
|
+
retryOfRunId: latestRun.id,
|
|
1460
|
+
});
|
|
1461
|
+
if (queued) {
|
|
1462
|
+
result.dispatchRequeued += 1;
|
|
1463
|
+
result.issueIds.push(issue.id);
|
|
1464
|
+
}
|
|
1465
|
+
else {
|
|
1466
|
+
result.skipped += 1;
|
|
1467
|
+
}
|
|
1468
|
+
continue;
|
|
1469
|
+
}
|
|
1470
|
+
if (!latestRun && !issue.checkoutRunId && !issue.executionRunId) {
|
|
1471
|
+
result.skipped += 1;
|
|
1472
|
+
continue;
|
|
1473
|
+
}
|
|
1474
|
+
const handoffEvidence = isExhaustedSuccessfulRunHandoff(latestRun);
|
|
1475
|
+
if (handoffEvidence) {
|
|
1476
|
+
if (!handoffEvidence.exhausted) {
|
|
1477
|
+
result.skipped += 1;
|
|
1478
|
+
continue;
|
|
1479
|
+
}
|
|
1480
|
+
const updated = await escalateStrandedAssignedIssue({
|
|
1481
|
+
issue,
|
|
1482
|
+
previousStatus: "in_progress",
|
|
1483
|
+
latestRun,
|
|
1484
|
+
recoveryCause: SUCCESSFUL_RUN_MISSING_STATE_REASON,
|
|
1485
|
+
successfulRunHandoffEvidence: handoffEvidence,
|
|
1486
|
+
});
|
|
1487
|
+
if (updated) {
|
|
1488
|
+
result.successfulRunHandoffEscalated += 1;
|
|
1489
|
+
result.issueIds.push(issue.id);
|
|
1490
|
+
}
|
|
1491
|
+
else {
|
|
1492
|
+
result.skipped += 1;
|
|
1493
|
+
}
|
|
1494
|
+
continue;
|
|
1495
|
+
}
|
|
1496
|
+
if (isSuccessfulInProgressContinuationRun(latestRun)) {
|
|
1497
|
+
const successfulRun = latestRun;
|
|
1498
|
+
if (!isProductiveContinuationRun(successfulRun)) {
|
|
1499
|
+
result.successfulContinuationObserved += 1;
|
|
1500
|
+
result.skipped += 1;
|
|
1501
|
+
continue;
|
|
1502
|
+
}
|
|
1503
|
+
if (isRepeatedProductiveContinuationRecovery(successfulRun)) {
|
|
1504
|
+
const updated = await escalateStrandedAssignedIssue({
|
|
1505
|
+
issue,
|
|
1506
|
+
previousStatus: "in_progress",
|
|
1507
|
+
latestRun: successfulRun,
|
|
1508
|
+
comment: "Evermore automatically retried continuation for this assigned `in_progress` issue and the retry " +
|
|
1509
|
+
"made progress, but it still has no live execution path. Moving it to `blocked` so it is visible for intervention.",
|
|
1510
|
+
});
|
|
1511
|
+
if (updated) {
|
|
1512
|
+
result.escalated += 1;
|
|
1513
|
+
result.issueIds.push(issue.id);
|
|
1514
|
+
}
|
|
1515
|
+
else {
|
|
1516
|
+
result.skipped += 1;
|
|
1517
|
+
}
|
|
1518
|
+
continue;
|
|
1519
|
+
}
|
|
1520
|
+
if (await isInvocationBudgetBlocked(issue, agentId)) {
|
|
1521
|
+
result.skipped += 1;
|
|
1522
|
+
continue;
|
|
1523
|
+
}
|
|
1524
|
+
const queued = await enqueueStrandedIssueRecovery({
|
|
1525
|
+
issueId: issue.id,
|
|
1526
|
+
agentId,
|
|
1527
|
+
reason: "issue_continuation_needed",
|
|
1528
|
+
retryReason: "issue_continuation_needed",
|
|
1529
|
+
source: "issue.productive_terminal_continuation_recovery",
|
|
1530
|
+
retryOfRunId: successfulRun.id,
|
|
1531
|
+
});
|
|
1532
|
+
if (queued) {
|
|
1533
|
+
result.continuationRequeued += 1;
|
|
1534
|
+
result.issueIds.push(issue.id);
|
|
1535
|
+
}
|
|
1536
|
+
else {
|
|
1537
|
+
result.skipped += 1;
|
|
1538
|
+
}
|
|
1539
|
+
continue;
|
|
1540
|
+
}
|
|
1541
|
+
if (didAutomaticRecoveryFail(latestRun, "issue_continuation_needed")) {
|
|
1542
|
+
const failureSummary = summarizeRunFailureForIssueComment(latestRun);
|
|
1543
|
+
const updated = await escalateStrandedAssignedIssue({
|
|
1544
|
+
issue,
|
|
1545
|
+
previousStatus: "in_progress",
|
|
1546
|
+
latestRun,
|
|
1547
|
+
comment: "Evermore automatically retried continuation for this assigned `in_progress` issue after its live " +
|
|
1548
|
+
`execution disappeared, but it still has no live execution path.${failureSummary ?? ""} ` +
|
|
1549
|
+
"Moving it to `blocked` so it is visible for intervention.",
|
|
1550
|
+
});
|
|
1551
|
+
if (updated) {
|
|
1552
|
+
result.escalated += 1;
|
|
1553
|
+
result.issueIds.push(issue.id);
|
|
1554
|
+
}
|
|
1555
|
+
else {
|
|
1556
|
+
result.skipped += 1;
|
|
1557
|
+
}
|
|
1558
|
+
continue;
|
|
1559
|
+
}
|
|
1560
|
+
if (await isInvocationBudgetBlocked(issue, agentId)) {
|
|
1561
|
+
result.skipped += 1;
|
|
1562
|
+
continue;
|
|
1563
|
+
}
|
|
1564
|
+
const queued = await enqueueStrandedIssueRecovery({
|
|
1565
|
+
issueId: issue.id,
|
|
1566
|
+
agentId,
|
|
1567
|
+
reason: "issue_continuation_needed",
|
|
1568
|
+
retryReason: "issue_continuation_needed",
|
|
1569
|
+
source: "issue.continuation_recovery",
|
|
1570
|
+
retryOfRunId: latestRun?.id ?? issue.checkoutRunId ?? null,
|
|
1571
|
+
});
|
|
1572
|
+
if (queued) {
|
|
1573
|
+
result.continuationRequeued += 1;
|
|
1574
|
+
result.issueIds.push(issue.id);
|
|
1575
|
+
}
|
|
1576
|
+
else {
|
|
1577
|
+
result.skipped += 1;
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
const orphanBlockerRecovery = await reconcileUnassignedBlockingIssues();
|
|
1581
|
+
result.orphanBlockersAssigned = orphanBlockerRecovery.assigned;
|
|
1582
|
+
result.skipped += orphanBlockerRecovery.skipped;
|
|
1583
|
+
result.issueIds.push(...orphanBlockerRecovery.issueIds);
|
|
1584
|
+
return result;
|
|
1585
|
+
}
|
|
1586
|
+
async function collectIssueGraphLivenessFindings() {
|
|
1587
|
+
const [issueRows, relationRows, agentRows, activeRunRows, activeIssueRunRows, wakeRows, interactionRows, approvalRows, recoveryIssueRows,] = await Promise.all([
|
|
1588
|
+
db
|
|
1589
|
+
.select({
|
|
1590
|
+
id: issues.id,
|
|
1591
|
+
companyId: issues.companyId,
|
|
1592
|
+
identifier: issues.identifier,
|
|
1593
|
+
title: issues.title,
|
|
1594
|
+
status: issues.status,
|
|
1595
|
+
projectId: issues.projectId,
|
|
1596
|
+
goalId: issues.goalId,
|
|
1597
|
+
parentId: issues.parentId,
|
|
1598
|
+
assigneeAgentId: issues.assigneeAgentId,
|
|
1599
|
+
assigneeUserId: issues.assigneeUserId,
|
|
1600
|
+
createdByAgentId: issues.createdByAgentId,
|
|
1601
|
+
createdByUserId: issues.createdByUserId,
|
|
1602
|
+
executionPolicy: issues.executionPolicy,
|
|
1603
|
+
executionState: issues.executionState,
|
|
1604
|
+
monitorNextCheckAt: issues.monitorNextCheckAt,
|
|
1605
|
+
monitorAttemptCount: issues.monitorAttemptCount,
|
|
1606
|
+
})
|
|
1607
|
+
.from(issues)
|
|
1608
|
+
.where(and(isNull(issues.hiddenAt), notInArray(issues.originKind, [RECOVERY_ORIGIN_KINDS.issueGraphLivenessEscalation]))),
|
|
1609
|
+
db
|
|
1610
|
+
.select({
|
|
1611
|
+
companyId: issueRelations.companyId,
|
|
1612
|
+
blockerIssueId: issueRelations.issueId,
|
|
1613
|
+
blockedIssueId: issueRelations.relatedIssueId,
|
|
1614
|
+
})
|
|
1615
|
+
.from(issueRelations)
|
|
1616
|
+
.where(eq(issueRelations.type, "blocks")),
|
|
1617
|
+
db
|
|
1618
|
+
.select({
|
|
1619
|
+
id: agents.id,
|
|
1620
|
+
companyId: agents.companyId,
|
|
1621
|
+
name: agents.name,
|
|
1622
|
+
role: agents.role,
|
|
1623
|
+
title: agents.title,
|
|
1624
|
+
status: agents.status,
|
|
1625
|
+
reportsTo: agents.reportsTo,
|
|
1626
|
+
})
|
|
1627
|
+
.from(agents),
|
|
1628
|
+
db
|
|
1629
|
+
.select({
|
|
1630
|
+
companyId: heartbeatRuns.companyId,
|
|
1631
|
+
agentId: heartbeatRuns.agentId,
|
|
1632
|
+
status: heartbeatRuns.status,
|
|
1633
|
+
contextSnapshot: heartbeatRuns.contextSnapshot,
|
|
1634
|
+
})
|
|
1635
|
+
.from(heartbeatRuns)
|
|
1636
|
+
.where(inArray(heartbeatRuns.status, [...EXECUTION_PATH_HEARTBEAT_RUN_STATUSES])),
|
|
1637
|
+
db
|
|
1638
|
+
.select({
|
|
1639
|
+
companyId: issues.companyId,
|
|
1640
|
+
agentId: heartbeatRuns.agentId,
|
|
1641
|
+
status: heartbeatRuns.status,
|
|
1642
|
+
issueId: issues.id,
|
|
1643
|
+
})
|
|
1644
|
+
.from(issues)
|
|
1645
|
+
.innerJoin(heartbeatRuns, eq(issues.executionRunId, heartbeatRuns.id))
|
|
1646
|
+
.where(and(isNull(issues.hiddenAt), notInArray(issues.originKind, [RECOVERY_ORIGIN_KINDS.issueGraphLivenessEscalation]), inArray(heartbeatRuns.status, [...EXECUTION_PATH_HEARTBEAT_RUN_STATUSES]))),
|
|
1647
|
+
db
|
|
1648
|
+
.select({
|
|
1649
|
+
companyId: agentWakeupRequests.companyId,
|
|
1650
|
+
agentId: agentWakeupRequests.agentId,
|
|
1651
|
+
status: agentWakeupRequests.status,
|
|
1652
|
+
payload: agentWakeupRequests.payload,
|
|
1653
|
+
})
|
|
1654
|
+
.from(agentWakeupRequests)
|
|
1655
|
+
.where(inArray(agentWakeupRequests.status, ["queued", "deferred_issue_execution"])),
|
|
1656
|
+
db
|
|
1657
|
+
.select({
|
|
1658
|
+
companyId: issueThreadInteractions.companyId,
|
|
1659
|
+
issueId: issueThreadInteractions.issueId,
|
|
1660
|
+
status: issueThreadInteractions.status,
|
|
1661
|
+
})
|
|
1662
|
+
.from(issueThreadInteractions)
|
|
1663
|
+
.where(eq(issueThreadInteractions.status, "pending")),
|
|
1664
|
+
db
|
|
1665
|
+
.select({
|
|
1666
|
+
companyId: issueApprovals.companyId,
|
|
1667
|
+
issueId: issueApprovals.issueId,
|
|
1668
|
+
status: approvals.status,
|
|
1669
|
+
})
|
|
1670
|
+
.from(issueApprovals)
|
|
1671
|
+
.innerJoin(approvals, eq(issueApprovals.approvalId, approvals.id))
|
|
1672
|
+
.where(inArray(approvals.status, ["pending", "revision_requested"])),
|
|
1673
|
+
db
|
|
1674
|
+
.select({
|
|
1675
|
+
companyId: issues.companyId,
|
|
1676
|
+
id: issues.id,
|
|
1677
|
+
status: issues.status,
|
|
1678
|
+
originId: issues.originId,
|
|
1679
|
+
})
|
|
1680
|
+
.from(issues)
|
|
1681
|
+
.where(and(isNull(issues.hiddenAt), eq(issues.originKind, STRANDED_ISSUE_RECOVERY_ORIGIN_KIND), notInArray(issues.status, ["done", "cancelled"]))),
|
|
1682
|
+
]);
|
|
1683
|
+
const openRecoveryIssues = recoveryIssueRows.flatMap((row) => {
|
|
1684
|
+
const issueId = readNonEmptyString(row.originId);
|
|
1685
|
+
if (!issueId)
|
|
1686
|
+
return [];
|
|
1687
|
+
return [{
|
|
1688
|
+
companyId: row.companyId,
|
|
1689
|
+
issueId,
|
|
1690
|
+
status: row.status,
|
|
1691
|
+
}];
|
|
1692
|
+
});
|
|
1693
|
+
return classifyIssueGraphLiveness({
|
|
1694
|
+
issues: issueRows,
|
|
1695
|
+
relations: relationRows,
|
|
1696
|
+
agents: agentRows,
|
|
1697
|
+
activeRuns: activeRunRows.map((row) => ({
|
|
1698
|
+
companyId: row.companyId,
|
|
1699
|
+
agentId: row.agentId,
|
|
1700
|
+
status: row.status,
|
|
1701
|
+
issueId: issueIdFromRunContext(row.contextSnapshot),
|
|
1702
|
+
})).concat(activeIssueRunRows.map((row) => ({
|
|
1703
|
+
companyId: row.companyId,
|
|
1704
|
+
agentId: row.agentId,
|
|
1705
|
+
status: row.status,
|
|
1706
|
+
issueId: row.issueId,
|
|
1707
|
+
}))),
|
|
1708
|
+
queuedWakeRequests: wakeRows.map((row) => ({
|
|
1709
|
+
companyId: row.companyId,
|
|
1710
|
+
agentId: row.agentId,
|
|
1711
|
+
status: row.status,
|
|
1712
|
+
issueId: issueIdFromWakePayload(row.payload),
|
|
1713
|
+
})),
|
|
1714
|
+
pendingInteractions: interactionRows,
|
|
1715
|
+
pendingApprovals: approvalRows,
|
|
1716
|
+
openRecoveryIssues,
|
|
1717
|
+
now: new Date(),
|
|
1718
|
+
});
|
|
1719
|
+
}
|
|
1720
|
+
async function findOpenLivenessEscalation(companyId, incidentKey) {
|
|
1721
|
+
return db
|
|
1722
|
+
.select()
|
|
1723
|
+
.from(issues)
|
|
1724
|
+
.where(and(eq(issues.companyId, companyId), eq(issues.originKind, RECOVERY_ORIGIN_KINDS.issueGraphLivenessEscalation), eq(issues.originId, incidentKey), isNull(issues.hiddenAt), notInArray(issues.status, ["done", "cancelled"])))
|
|
1725
|
+
.limit(1)
|
|
1726
|
+
.then((rows) => rows[0] ?? null);
|
|
1727
|
+
}
|
|
1728
|
+
async function findOpenLivenessRecoveryIssueForLeaf(finding) {
|
|
1729
|
+
const byFingerprint = await db
|
|
1730
|
+
.select()
|
|
1731
|
+
.from(issues)
|
|
1732
|
+
.where(and(eq(issues.companyId, finding.companyId), eq(issues.originKind, RECOVERY_ORIGIN_KINDS.issueGraphLivenessEscalation), eq(issues.originFingerprint, livenessRecoveryLeafFingerprint(finding)), isNull(issues.hiddenAt), notInArray(issues.status, ["done", "cancelled"])))
|
|
1733
|
+
.limit(1)
|
|
1734
|
+
.then((rows) => rows[0] ?? null);
|
|
1735
|
+
if (byFingerprint)
|
|
1736
|
+
return byFingerprint;
|
|
1737
|
+
const leafIssueId = livenessRecoveryLeafIssueId(finding);
|
|
1738
|
+
const openRecoveries = await db
|
|
1739
|
+
.select()
|
|
1740
|
+
.from(issues)
|
|
1741
|
+
.where(and(eq(issues.companyId, finding.companyId), eq(issues.originKind, RECOVERY_ORIGIN_KINDS.issueGraphLivenessEscalation), isNull(issues.hiddenAt), notInArray(issues.status, ["done", "cancelled"])));
|
|
1742
|
+
return openRecoveries.find((row) => {
|
|
1743
|
+
const parsed = parseLivenessIncidentKey(row.originId);
|
|
1744
|
+
return parsed?.state === finding.state && parsed.leafIssueId === leafIssueId;
|
|
1745
|
+
}) ?? null;
|
|
1746
|
+
}
|
|
1747
|
+
async function removeRecoveryBlockerFromSource(recovery) {
|
|
1748
|
+
const parsed = parseLivenessIncidentKey(recovery.originId);
|
|
1749
|
+
if (!parsed)
|
|
1750
|
+
return false;
|
|
1751
|
+
const sourceIssue = await db
|
|
1752
|
+
.select()
|
|
1753
|
+
.from(issues)
|
|
1754
|
+
.where(and(eq(issues.companyId, recovery.companyId), eq(issues.id, parsed.issueId)))
|
|
1755
|
+
.then((rows) => rows[0] ?? null);
|
|
1756
|
+
if (!sourceIssue)
|
|
1757
|
+
return false;
|
|
1758
|
+
const blockerIds = await existingBlockerIssueIds(sourceIssue.companyId, sourceIssue.id);
|
|
1759
|
+
if (!blockerIds.includes(recovery.id))
|
|
1760
|
+
return false;
|
|
1761
|
+
await issuesSvc.update(sourceIssue.id, {
|
|
1762
|
+
blockedByIssueIds: blockerIds.filter((blockerId) => blockerId !== recovery.id),
|
|
1763
|
+
});
|
|
1764
|
+
return true;
|
|
1765
|
+
}
|
|
1766
|
+
async function hasActiveRunForIssueId(companyId, issueId) {
|
|
1767
|
+
const [contextRun, issueRun] = await Promise.all([
|
|
1768
|
+
db
|
|
1769
|
+
.select({ id: heartbeatRuns.id })
|
|
1770
|
+
.from(heartbeatRuns)
|
|
1771
|
+
.where(and(eq(heartbeatRuns.companyId, companyId), inArray(heartbeatRuns.status, [...EXECUTION_PATH_HEARTBEAT_RUN_STATUSES]), sql `(${heartbeatRuns.contextSnapshot}->>'issueId' = ${issueId}
|
|
1772
|
+
OR ${heartbeatRuns.contextSnapshot}->>'taskId' = ${issueId})`))
|
|
1773
|
+
.limit(1)
|
|
1774
|
+
.then((rows) => rows[0] ?? null),
|
|
1775
|
+
db
|
|
1776
|
+
.select({ id: heartbeatRuns.id })
|
|
1777
|
+
.from(issues)
|
|
1778
|
+
.innerJoin(heartbeatRuns, eq(issues.executionRunId, heartbeatRuns.id))
|
|
1779
|
+
.where(and(eq(issues.companyId, companyId), eq(issues.id, issueId), inArray(heartbeatRuns.status, [...EXECUTION_PATH_HEARTBEAT_RUN_STATUSES])))
|
|
1780
|
+
.limit(1)
|
|
1781
|
+
.then((rows) => rows[0] ?? null),
|
|
1782
|
+
]);
|
|
1783
|
+
return Boolean(contextRun || issueRun);
|
|
1784
|
+
}
|
|
1785
|
+
async function retireObsoleteLivenessRecoveryIssues(findings) {
|
|
1786
|
+
const currentIncidentKeys = new Set(findings.map((finding) => finding.incidentKey));
|
|
1787
|
+
const currentLeafKeys = new Set(findings.map((finding) => livenessRecoveryLeafKey(finding.companyId, finding.state, livenessRecoveryLeafIssueId(finding))));
|
|
1788
|
+
const openRecoveries = await db
|
|
1789
|
+
.select()
|
|
1790
|
+
.from(issues)
|
|
1791
|
+
.where(and(eq(issues.originKind, RECOVERY_ORIGIN_KINDS.issueGraphLivenessEscalation), isNull(issues.hiddenAt), notInArray(issues.status, ["done", "cancelled"])));
|
|
1792
|
+
const result = {
|
|
1793
|
+
retired: 0,
|
|
1794
|
+
activeSkipped: 0,
|
|
1795
|
+
blockerRelationsRemoved: 0,
|
|
1796
|
+
retiredIssueIds: [],
|
|
1797
|
+
};
|
|
1798
|
+
for (const recovery of openRecoveries) {
|
|
1799
|
+
if (recovery.originId && currentIncidentKeys.has(recovery.originId))
|
|
1800
|
+
continue;
|
|
1801
|
+
const parsed = parseLivenessIncidentKey(recovery.originId);
|
|
1802
|
+
if (!parsed)
|
|
1803
|
+
continue;
|
|
1804
|
+
if (currentLeafKeys.has(livenessRecoveryLeafKey(parsed.companyId, parsed.state, parsed.leafIssueId))) {
|
|
1805
|
+
continue;
|
|
1806
|
+
}
|
|
1807
|
+
if (await removeRecoveryBlockerFromSource(recovery)) {
|
|
1808
|
+
result.blockerRelationsRemoved += 1;
|
|
1809
|
+
}
|
|
1810
|
+
if (await hasActiveRunForIssueId(recovery.companyId, recovery.id)) {
|
|
1811
|
+
result.activeSkipped += 1;
|
|
1812
|
+
continue;
|
|
1813
|
+
}
|
|
1814
|
+
await issuesSvc.update(recovery.id, { status: "cancelled" });
|
|
1815
|
+
result.retired += 1;
|
|
1816
|
+
result.retiredIssueIds.push(recovery.id);
|
|
1817
|
+
}
|
|
1818
|
+
return result;
|
|
1819
|
+
}
|
|
1820
|
+
function normalizeIssueGraphLivenessAutoRecoveryLookbackHours(raw) {
|
|
1821
|
+
const numeric = Math.floor(asNumber(raw, DEFAULT_ISSUE_GRAPH_LIVENESS_AUTO_RECOVERY_LOOKBACK_HOURS));
|
|
1822
|
+
return Math.min(MAX_ISSUE_GRAPH_LIVENESS_AUTO_RECOVERY_LOOKBACK_HOURS, Math.max(MIN_ISSUE_GRAPH_LIVENESS_AUTO_RECOVERY_LOOKBACK_HOURS, numeric));
|
|
1823
|
+
}
|
|
1824
|
+
function livenessDependencyIssueKey(companyId, issueId) {
|
|
1825
|
+
return `${companyId}:${issueId}`;
|
|
1826
|
+
}
|
|
1827
|
+
async function loadLivenessDependencyUpdatedAtByIssue(findings) {
|
|
1828
|
+
const issueIds = [
|
|
1829
|
+
...new Set(findings.flatMap((finding) => finding.dependencyPath.map((entry) => entry.issueId))),
|
|
1830
|
+
];
|
|
1831
|
+
if (issueIds.length === 0)
|
|
1832
|
+
return new Map();
|
|
1833
|
+
const rows = await db
|
|
1834
|
+
.select({ id: issues.id, companyId: issues.companyId, updatedAt: issues.updatedAt })
|
|
1835
|
+
.from(issues)
|
|
1836
|
+
.where(inArray(issues.id, issueIds));
|
|
1837
|
+
return new Map(rows.map((row) => [
|
|
1838
|
+
livenessDependencyIssueKey(row.companyId, row.id),
|
|
1839
|
+
row.updatedAt,
|
|
1840
|
+
]));
|
|
1841
|
+
}
|
|
1842
|
+
function latestDependencyUpdatedAtForLivenessFinding(finding, updatedAtByIssueKey) {
|
|
1843
|
+
const dependencyIssueIds = [...new Set(finding.dependencyPath.map((entry) => entry.issueId))];
|
|
1844
|
+
if (dependencyIssueIds.length === 0)
|
|
1845
|
+
return null;
|
|
1846
|
+
const timestamps = dependencyIssueIds.map((issueId) => updatedAtByIssueKey.get(livenessDependencyIssueKey(finding.companyId, issueId)) ?? null);
|
|
1847
|
+
if (timestamps.some((timestamp) => !timestamp))
|
|
1848
|
+
return null;
|
|
1849
|
+
const [firstTimestamp, ...remainingTimestamps] = timestamps;
|
|
1850
|
+
return remainingTimestamps.reduce((latest, updatedAt) => updatedAt > latest ? updatedAt : latest, firstTimestamp);
|
|
1851
|
+
}
|
|
1852
|
+
function isLivenessFindingInsideAutoRecoveryLookback(finding, cutoff, updatedAtByIssueKey) {
|
|
1853
|
+
const latestUpdatedAt = latestDependencyUpdatedAtForLivenessFinding(finding, updatedAtByIssueKey);
|
|
1854
|
+
return Boolean(latestUpdatedAt && latestUpdatedAt >= cutoff);
|
|
1855
|
+
}
|
|
1856
|
+
async function buildIssueGraphLivenessAutoRecoveryPreview(opts) {
|
|
1857
|
+
const now = opts?.now ?? new Date();
|
|
1858
|
+
const lookbackHours = normalizeIssueGraphLivenessAutoRecoveryLookbackHours(opts?.lookbackHours);
|
|
1859
|
+
const cutoff = new Date(now.getTime() - lookbackHours * 60 * 60 * 1000);
|
|
1860
|
+
const findings = await collectIssueGraphLivenessFindings();
|
|
1861
|
+
const updatedAtByIssueKey = await loadLivenessDependencyUpdatedAtByIssue(findings);
|
|
1862
|
+
const issueIds = [...new Set(findings.map((finding) => finding.recoveryIssueId))];
|
|
1863
|
+
const recoveryRows = issueIds.length > 0
|
|
1864
|
+
? await db
|
|
1865
|
+
.select({ id: issues.id, identifier: issues.identifier, title: issues.title })
|
|
1866
|
+
.from(issues)
|
|
1867
|
+
.where(inArray(issues.id, issueIds))
|
|
1868
|
+
: [];
|
|
1869
|
+
const recoveryById = new Map(recoveryRows.map((row) => [row.id, row]));
|
|
1870
|
+
const items = [];
|
|
1871
|
+
let skippedOutsideLookback = 0;
|
|
1872
|
+
for (const finding of findings) {
|
|
1873
|
+
const latestDependencyUpdatedAt = latestDependencyUpdatedAtForLivenessFinding(finding, updatedAtByIssueKey);
|
|
1874
|
+
if (!latestDependencyUpdatedAt || latestDependencyUpdatedAt < cutoff) {
|
|
1875
|
+
skippedOutsideLookback += 1;
|
|
1876
|
+
continue;
|
|
1877
|
+
}
|
|
1878
|
+
const recoveryIssue = recoveryById.get(finding.recoveryIssueId);
|
|
1879
|
+
items.push({
|
|
1880
|
+
issueId: finding.issueId,
|
|
1881
|
+
identifier: finding.identifier,
|
|
1882
|
+
title: finding.dependencyPath[0]?.title ?? finding.identifier ?? finding.issueId,
|
|
1883
|
+
state: finding.state,
|
|
1884
|
+
severity: finding.severity,
|
|
1885
|
+
reason: finding.reason,
|
|
1886
|
+
recoveryIssueId: finding.recoveryIssueId,
|
|
1887
|
+
recoveryIdentifier: recoveryIssue?.identifier ?? null,
|
|
1888
|
+
recoveryTitle: recoveryIssue?.title ?? null,
|
|
1889
|
+
recommendedOwnerAgentId: finding.recommendedOwnerAgentId,
|
|
1890
|
+
incidentKey: finding.incidentKey,
|
|
1891
|
+
latestDependencyUpdatedAt: latestDependencyUpdatedAt.toISOString(),
|
|
1892
|
+
dependencyPath: finding.dependencyPath,
|
|
1893
|
+
});
|
|
1894
|
+
}
|
|
1895
|
+
return {
|
|
1896
|
+
lookbackHours,
|
|
1897
|
+
cutoff: cutoff.toISOString(),
|
|
1898
|
+
generatedAt: now.toISOString(),
|
|
1899
|
+
findings: findings.length,
|
|
1900
|
+
recoverableFindings: items.length,
|
|
1901
|
+
skippedOutsideLookback,
|
|
1902
|
+
items,
|
|
1903
|
+
};
|
|
1904
|
+
}
|
|
1905
|
+
async function resolveEscalationOwnerAgentId(finding, issue) {
|
|
1906
|
+
const detailedCandidates = finding.recommendedOwnerCandidates.length > 0
|
|
1907
|
+
? finding.recommendedOwnerCandidates
|
|
1908
|
+
: finding.recommendedOwnerCandidateAgentIds.map((agentId) => ({
|
|
1909
|
+
agentId,
|
|
1910
|
+
reason: "ordered_invokable_fallback",
|
|
1911
|
+
sourceIssueId: finding.recoveryIssueId,
|
|
1912
|
+
}));
|
|
1913
|
+
const seenCandidates = new Set();
|
|
1914
|
+
const candidates = detailedCandidates.filter((candidate) => {
|
|
1915
|
+
if (seenCandidates.has(candidate.agentId))
|
|
1916
|
+
return false;
|
|
1917
|
+
seenCandidates.add(candidate.agentId);
|
|
1918
|
+
return true;
|
|
1919
|
+
});
|
|
1920
|
+
const budgetBlockedCandidateAgentIds = [];
|
|
1921
|
+
for (const candidate of candidates) {
|
|
1922
|
+
const budgetBlock = await budgets.getInvocationBlock(issue.companyId, candidate.agentId, {
|
|
1923
|
+
issueId: issue.id,
|
|
1924
|
+
projectId: issue.projectId,
|
|
1925
|
+
});
|
|
1926
|
+
if (!budgetBlock) {
|
|
1927
|
+
return {
|
|
1928
|
+
agentId: candidate.agentId,
|
|
1929
|
+
reason: candidate.reason,
|
|
1930
|
+
sourceIssueId: candidate.sourceIssueId,
|
|
1931
|
+
candidateAgentIds: candidates.map((entry) => entry.agentId),
|
|
1932
|
+
candidateReasons: candidates.map((entry) => ({
|
|
1933
|
+
agentId: entry.agentId,
|
|
1934
|
+
reason: entry.reason,
|
|
1935
|
+
sourceIssueId: entry.sourceIssueId,
|
|
1936
|
+
})),
|
|
1937
|
+
budgetBlockedCandidateAgentIds,
|
|
1938
|
+
};
|
|
1939
|
+
}
|
|
1940
|
+
budgetBlockedCandidateAgentIds.push(candidate.agentId);
|
|
1941
|
+
}
|
|
1942
|
+
return null;
|
|
1943
|
+
}
|
|
1944
|
+
function shouldReuseRecoveryExecutionWorkspace(input) {
|
|
1945
|
+
if (input.finding.recoveryIssueId === input.finding.issueId)
|
|
1946
|
+
return false;
|
|
1947
|
+
return input.recoveryIssue.assigneeAgentId === input.ownerAgentId;
|
|
1948
|
+
}
|
|
1949
|
+
async function ensureIssueBlockedByEscalation(input) {
|
|
1950
|
+
const blockerIds = await existingBlockerIssueIds(input.issue.companyId, input.issue.id);
|
|
1951
|
+
const nextBlockerIds = [...new Set([...blockerIds, input.escalationIssueId])];
|
|
1952
|
+
const isAlreadyBlockedByEscalation = blockerIds.includes(input.escalationIssueId);
|
|
1953
|
+
const isAlreadyBlocked = input.issue.status === "blocked";
|
|
1954
|
+
if (isAlreadyBlockedByEscalation && isAlreadyBlocked) {
|
|
1955
|
+
return input.issue;
|
|
1956
|
+
}
|
|
1957
|
+
const update = {
|
|
1958
|
+
blockedByIssueIds: nextBlockerIds,
|
|
1959
|
+
};
|
|
1960
|
+
if (!isAlreadyBlocked) {
|
|
1961
|
+
update.status = "blocked";
|
|
1962
|
+
}
|
|
1963
|
+
const updated = await issuesSvc.update(input.issue.id, update);
|
|
1964
|
+
if (!updated)
|
|
1965
|
+
return null;
|
|
1966
|
+
await logActivity(db, {
|
|
1967
|
+
companyId: input.issue.companyId,
|
|
1968
|
+
actorType: "system",
|
|
1969
|
+
actorId: "system",
|
|
1970
|
+
agentId: null,
|
|
1971
|
+
runId: input.runId ?? null,
|
|
1972
|
+
action: "issue.blockers.updated",
|
|
1973
|
+
entityType: "issue",
|
|
1974
|
+
entityId: input.issue.id,
|
|
1975
|
+
details: {
|
|
1976
|
+
source: "recovery.reconcile_issue_graph_liveness",
|
|
1977
|
+
incidentKey: input.finding.incidentKey,
|
|
1978
|
+
findingState: input.finding.state,
|
|
1979
|
+
blockerIssueIds: nextBlockerIds,
|
|
1980
|
+
escalationIssueId: input.escalationIssueId,
|
|
1981
|
+
status: update.status ?? input.issue.status,
|
|
1982
|
+
previousStatus: input.issue.status,
|
|
1983
|
+
},
|
|
1984
|
+
});
|
|
1985
|
+
return updated;
|
|
1986
|
+
}
|
|
1987
|
+
async function createIssueGraphLivenessEscalation(input) {
|
|
1988
|
+
const issue = await db
|
|
1989
|
+
.select()
|
|
1990
|
+
.from(issues)
|
|
1991
|
+
.where(eq(issues.id, input.finding.issueId))
|
|
1992
|
+
.then((rows) => rows[0] ?? null);
|
|
1993
|
+
if (!issue || issue.companyId !== input.finding.companyId)
|
|
1994
|
+
return { kind: "skipped" };
|
|
1995
|
+
if (await isAutomaticRecoverySuppressedByPauseHold(db, issue.companyId, issue.id, treeControlSvc)) {
|
|
1996
|
+
return { kind: "skipped" };
|
|
1997
|
+
}
|
|
1998
|
+
const recoveryIssue = await db
|
|
1999
|
+
.select()
|
|
2000
|
+
.from(issues)
|
|
2001
|
+
.where(and(eq(issues.id, input.finding.recoveryIssueId), eq(issues.companyId, issue.companyId)))
|
|
2002
|
+
.then((rows) => rows[0] ?? null);
|
|
2003
|
+
if (!recoveryIssue)
|
|
2004
|
+
return { kind: "skipped" };
|
|
2005
|
+
const existing = await findOpenLivenessEscalation(issue.companyId, input.finding.incidentKey) ??
|
|
2006
|
+
await findOpenLivenessRecoveryIssueForLeaf(input.finding);
|
|
2007
|
+
if (existing) {
|
|
2008
|
+
await ensureIssueBlockedByEscalation({
|
|
2009
|
+
issue,
|
|
2010
|
+
escalationIssueId: existing.id,
|
|
2011
|
+
finding: input.finding,
|
|
2012
|
+
runId: input.runId ?? null,
|
|
2013
|
+
});
|
|
2014
|
+
return { kind: "existing", escalationIssueId: existing.id };
|
|
2015
|
+
}
|
|
2016
|
+
const ownerSelection = await resolveEscalationOwnerAgentId(input.finding, recoveryIssue);
|
|
2017
|
+
if (!ownerSelection)
|
|
2018
|
+
return { kind: "skipped" };
|
|
2019
|
+
const reuseRecoveryExecutionWorkspace = shouldReuseRecoveryExecutionWorkspace({
|
|
2020
|
+
finding: input.finding,
|
|
2021
|
+
recoveryIssue,
|
|
2022
|
+
ownerAgentId: ownerSelection.agentId,
|
|
2023
|
+
});
|
|
2024
|
+
let escalation;
|
|
2025
|
+
try {
|
|
2026
|
+
escalation = await issuesSvc.create(issue.companyId, {
|
|
2027
|
+
title: `Unblock liveness incident for ${recoveryIssue.identifier ?? recoveryIssue.title}`,
|
|
2028
|
+
description: buildLivenessEscalationDescription(input.finding),
|
|
2029
|
+
status: "todo",
|
|
2030
|
+
priority: "high",
|
|
2031
|
+
parentId: recoveryIssue.id,
|
|
2032
|
+
projectId: recoveryIssue.projectId,
|
|
2033
|
+
goalId: recoveryIssue.goalId,
|
|
2034
|
+
assigneeAgentId: ownerSelection.agentId,
|
|
2035
|
+
assigneeAdapterOverrides: recoveryAssigneeAdapterOverrides(),
|
|
2036
|
+
originKind: RECOVERY_ORIGIN_KINDS.issueGraphLivenessEscalation,
|
|
2037
|
+
originId: input.finding.incidentKey,
|
|
2038
|
+
originFingerprint: livenessRecoveryLeafFingerprint(input.finding),
|
|
2039
|
+
billingCode: recoveryIssue.billingCode,
|
|
2040
|
+
...(reuseRecoveryExecutionWorkspace
|
|
2041
|
+
? { inheritExecutionWorkspaceFromIssueId: recoveryIssue.id }
|
|
2042
|
+
: {
|
|
2043
|
+
executionWorkspaceId: null,
|
|
2044
|
+
executionWorkspacePreference: null,
|
|
2045
|
+
executionWorkspaceSettings: null,
|
|
2046
|
+
}),
|
|
2047
|
+
});
|
|
2048
|
+
}
|
|
2049
|
+
catch (error) {
|
|
2050
|
+
if (!isUniqueLivenessRecoveryConflict(error))
|
|
2051
|
+
throw error;
|
|
2052
|
+
const raced = await findOpenLivenessEscalation(issue.companyId, input.finding.incidentKey) ??
|
|
2053
|
+
await findOpenLivenessRecoveryIssueForLeaf(input.finding);
|
|
2054
|
+
if (!raced)
|
|
2055
|
+
throw error;
|
|
2056
|
+
await ensureIssueBlockedByEscalation({
|
|
2057
|
+
issue,
|
|
2058
|
+
escalationIssueId: raced.id,
|
|
2059
|
+
finding: input.finding,
|
|
2060
|
+
runId: input.runId ?? null,
|
|
2061
|
+
});
|
|
2062
|
+
return { kind: "existing", escalationIssueId: raced.id };
|
|
2063
|
+
}
|
|
2064
|
+
await ensureIssueBlockedByEscalation({
|
|
2065
|
+
issue,
|
|
2066
|
+
escalationIssueId: escalation.id,
|
|
2067
|
+
finding: input.finding,
|
|
2068
|
+
runId: input.runId ?? null,
|
|
2069
|
+
});
|
|
2070
|
+
await issuesSvc.addComment(issue.id, buildLivenessOriginalIssueComment(input.finding, escalation), { runId: input.runId ?? null });
|
|
2071
|
+
await logActivity(db, {
|
|
2072
|
+
companyId: issue.companyId,
|
|
2073
|
+
actorType: "system",
|
|
2074
|
+
actorId: "system",
|
|
2075
|
+
agentId: ownerSelection.agentId,
|
|
2076
|
+
runId: input.runId ?? null,
|
|
2077
|
+
action: "issue.harness_liveness_escalation_created",
|
|
2078
|
+
entityType: "issue",
|
|
2079
|
+
entityId: escalation.id,
|
|
2080
|
+
details: {
|
|
2081
|
+
source: "recovery.reconcile_issue_graph_liveness",
|
|
2082
|
+
incidentKey: input.finding.incidentKey,
|
|
2083
|
+
findingState: input.finding.state,
|
|
2084
|
+
sourceIssueId: issue.id,
|
|
2085
|
+
sourceIdentifier: issue.identifier,
|
|
2086
|
+
recoveryIssueId: recoveryIssue.id,
|
|
2087
|
+
recoveryIdentifier: recoveryIssue.identifier,
|
|
2088
|
+
escalationIssueId: escalation.id,
|
|
2089
|
+
escalationIdentifier: escalation.identifier,
|
|
2090
|
+
dependencyPath: input.finding.dependencyPath,
|
|
2091
|
+
ownerSelection: {
|
|
2092
|
+
selectedAgentId: ownerSelection.agentId,
|
|
2093
|
+
selectedReason: ownerSelection.reason,
|
|
2094
|
+
selectedSourceIssueId: ownerSelection.sourceIssueId,
|
|
2095
|
+
candidateAgentIds: ownerSelection.candidateAgentIds,
|
|
2096
|
+
candidateReasons: ownerSelection.candidateReasons,
|
|
2097
|
+
budgetBlockedCandidateAgentIds: ownerSelection.budgetBlockedCandidateAgentIds,
|
|
2098
|
+
},
|
|
2099
|
+
workspaceSelection: {
|
|
2100
|
+
reuseRecoveryExecutionWorkspace,
|
|
2101
|
+
inheritedExecutionWorkspaceFromIssueId: reuseRecoveryExecutionWorkspace ? recoveryIssue.id : null,
|
|
2102
|
+
projectWorkspaceSourceIssueId: recoveryIssue.id,
|
|
2103
|
+
},
|
|
2104
|
+
},
|
|
2105
|
+
});
|
|
2106
|
+
const wake = await deps.enqueueWakeup(ownerSelection.agentId, {
|
|
2107
|
+
source: "assignment",
|
|
2108
|
+
triggerDetail: "system",
|
|
2109
|
+
reason: "issue_assigned",
|
|
2110
|
+
payload: withRecoveryModelProfileHint({
|
|
2111
|
+
issueId: escalation.id,
|
|
2112
|
+
sourceIssueId: issue.id,
|
|
2113
|
+
recoveryIssueId: recoveryIssue.id,
|
|
2114
|
+
incidentKey: input.finding.incidentKey,
|
|
2115
|
+
}),
|
|
2116
|
+
requestedByActorType: "system",
|
|
2117
|
+
requestedByActorId: null,
|
|
2118
|
+
contextSnapshot: withRecoveryModelProfileHint({
|
|
2119
|
+
issueId: escalation.id,
|
|
2120
|
+
taskId: escalation.id,
|
|
2121
|
+
wakeReason: "issue_assigned",
|
|
2122
|
+
source: RECOVERY_ORIGIN_KINDS.issueGraphLivenessEscalation,
|
|
2123
|
+
sourceIssueId: issue.id,
|
|
2124
|
+
recoveryIssueId: recoveryIssue.id,
|
|
2125
|
+
incidentKey: input.finding.incidentKey,
|
|
2126
|
+
}),
|
|
2127
|
+
});
|
|
2128
|
+
logger.warn({
|
|
2129
|
+
incidentKey: input.finding.incidentKey,
|
|
2130
|
+
findingState: input.finding.state,
|
|
2131
|
+
sourceIssueId: issue.id,
|
|
2132
|
+
recoveryIssueId: recoveryIssue.id,
|
|
2133
|
+
escalationIssueId: escalation.id,
|
|
2134
|
+
ownerAgentId: ownerSelection.agentId,
|
|
2135
|
+
ownerSelectionReason: ownerSelection.reason,
|
|
2136
|
+
wakeupRunId: wake?.id ?? null,
|
|
2137
|
+
}, "created issue graph liveness escalation");
|
|
2138
|
+
return { kind: "created", escalationIssueId: escalation.id };
|
|
2139
|
+
}
|
|
2140
|
+
async function reconcileIssueGraphLiveness(opts) {
|
|
2141
|
+
const findings = await collectIssueGraphLivenessFindings();
|
|
2142
|
+
const experimentalSettings = await instanceSettings.getExperimental();
|
|
2143
|
+
const autoRecoveryEnabled = asBoolean(experimentalSettings.enableIssueGraphLivenessAutoRecovery, true) || opts?.force === true;
|
|
2144
|
+
const lookbackHours = normalizeIssueGraphLivenessAutoRecoveryLookbackHours(opts?.lookbackHours ?? experimentalSettings.issueGraphLivenessAutoRecoveryLookbackHours);
|
|
2145
|
+
const now = new Date();
|
|
2146
|
+
const cutoff = new Date(now.getTime() - lookbackHours * 60 * 60 * 1000);
|
|
2147
|
+
const obsoleteRecoveryCleanup = await retireObsoleteLivenessRecoveryIssues(findings);
|
|
2148
|
+
const updatedAtByIssueKey = await loadLivenessDependencyUpdatedAtByIssue(findings);
|
|
2149
|
+
const result = {
|
|
2150
|
+
findings: findings.length,
|
|
2151
|
+
autoRecoveryEnabled,
|
|
2152
|
+
lookbackHours,
|
|
2153
|
+
cutoff: cutoff.toISOString(),
|
|
2154
|
+
escalationsCreated: 0,
|
|
2155
|
+
existingEscalations: 0,
|
|
2156
|
+
skipped: 0,
|
|
2157
|
+
skippedAutoRecoveryDisabled: 0,
|
|
2158
|
+
skippedOutsideLookback: 0,
|
|
2159
|
+
obsoleteRecoveriesRetired: obsoleteRecoveryCleanup.retired,
|
|
2160
|
+
obsoleteRecoveriesActiveSkipped: obsoleteRecoveryCleanup.activeSkipped,
|
|
2161
|
+
obsoleteRecoveryBlockerRelationsRemoved: obsoleteRecoveryCleanup.blockerRelationsRemoved,
|
|
2162
|
+
issueIds: [],
|
|
2163
|
+
escalationIssueIds: [],
|
|
2164
|
+
retiredRecoveryIssueIds: obsoleteRecoveryCleanup.retiredIssueIds,
|
|
2165
|
+
};
|
|
2166
|
+
if (!autoRecoveryEnabled) {
|
|
2167
|
+
result.skippedAutoRecoveryDisabled = findings.length;
|
|
2168
|
+
return result;
|
|
2169
|
+
}
|
|
2170
|
+
for (const finding of findings) {
|
|
2171
|
+
if (!isLivenessFindingInsideAutoRecoveryLookback(finding, cutoff, updatedAtByIssueKey)) {
|
|
2172
|
+
result.skippedOutsideLookback += 1;
|
|
2173
|
+
result.skipped += 1;
|
|
2174
|
+
continue;
|
|
2175
|
+
}
|
|
2176
|
+
const escalation = await createIssueGraphLivenessEscalation({
|
|
2177
|
+
finding,
|
|
2178
|
+
runId: opts?.runId ?? null,
|
|
2179
|
+
});
|
|
2180
|
+
if (escalation.kind === "created") {
|
|
2181
|
+
result.escalationsCreated += 1;
|
|
2182
|
+
result.issueIds.push(finding.issueId);
|
|
2183
|
+
result.escalationIssueIds.push(escalation.escalationIssueId);
|
|
2184
|
+
}
|
|
2185
|
+
else if (escalation.kind === "existing") {
|
|
2186
|
+
result.existingEscalations += 1;
|
|
2187
|
+
result.issueIds.push(finding.issueId);
|
|
2188
|
+
result.escalationIssueIds.push(escalation.escalationIssueId);
|
|
2189
|
+
}
|
|
2190
|
+
else {
|
|
2191
|
+
result.skipped += 1;
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
return result;
|
|
2195
|
+
}
|
|
2196
|
+
function readRecoveryTimerIntervalMs(raw, fallback) {
|
|
2197
|
+
return Math.max(1, Math.floor(asNumber(raw, fallback)));
|
|
2198
|
+
}
|
|
2199
|
+
return {
|
|
2200
|
+
buildRunOutputSilence,
|
|
2201
|
+
escalateStrandedAssignedIssue,
|
|
2202
|
+
recordWatchdogDecision,
|
|
2203
|
+
scanSilentActiveRuns,
|
|
2204
|
+
reconcileStrandedAssignedIssues,
|
|
2205
|
+
buildIssueGraphLivenessAutoRecoveryPreview,
|
|
2206
|
+
reconcileIssueGraphLiveness,
|
|
2207
|
+
readRecoveryTimerIntervalMs,
|
|
2208
|
+
};
|
|
2209
|
+
}
|
|
2210
|
+
//# sourceMappingURL=service.js.map
|