@fidelios/server 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/codex-models.d.ts +4 -0
- package/dist/adapters/codex-models.d.ts.map +1 -0
- package/dist/adapters/codex-models.js +98 -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 +39 -0
- package/dist/adapters/http/execute.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/process/execute.d.ts +3 -0
- package/dist/adapters/process/execute.d.ts.map +1 -0
- package/dist/adapters/process/execute.js +63 -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 +14 -0
- package/dist/adapters/registry.d.ts.map +1 -0
- package/dist/adapters/registry.js +164 -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 +10 -0
- package/dist/adapters/utils.d.ts.map +1 -0
- package/dist/adapters/utils.js +14 -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 +25 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +265 -0
- package/dist/app.js.map +1 -0
- package/dist/attachment-types.d.ts +33 -0
- package/dist/attachment-types.d.ts.map +1 -0
- package/dist/attachment-types.js +67 -0
- package/dist/attachment-types.js.map +1 -0
- package/dist/auth/better-auth.d.ts +24 -0
- package/dist/auth/better-auth.d.ts.map +1 -0
- package/dist/auth/better-auth.js +108 -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 +45 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +171 -0
- package/dist/config.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 +70 -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 +33 -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 +642 -0
- package/dist/index.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 +118 -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 +144 -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 +59 -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 +37 -0
- package/dist/middleware/error-handler.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 +87 -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 +54 -0
- package/dist/onboarding-assets/ceo/HEARTBEAT.md +72 -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 +3 -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 +4 -0
- package/dist/redaction.d.ts.map +1 -0
- package/dist/redaction.js +63 -0
- package/dist/redaction.js.map +1 -0
- package/dist/routes/access.d.ts +61 -0
- package/dist/routes/access.d.ts.map +1 -0
- package/dist/routes/access.js +2265 -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 +78 -0
- package/dist/routes/activity.js.map +1 -0
- package/dist/routes/agents.d.ts +3 -0
- package/dist/routes/agents.d.ts.map +1 -0
- package/dist/routes/agents.js +1828 -0
- package/dist/routes/agents.js.map +1 -0
- package/dist/routes/approvals.d.ts +3 -0
- package/dist/routes/approvals.d.ts.map +1 -0
- package/dist/routes/approvals.js +275 -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/authz.d.ts +16 -0
- package/dist/routes/authz.d.ts.map +1 -0
- package/dist/routes/authz.js +47 -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 +303 -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 +228 -0
- package/dist/routes/company-skills.js.map +1 -0
- package/dist/routes/costs.d.ts +3 -0
- package/dist/routes/costs.d.ts.map +1 -0
- package/dist/routes/costs.js +268 -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/execution-workspaces.d.ts +3 -0
- package/dist/routes/execution-workspaces.d.ts.map +1 -0
- package/dist/routes/execution-workspaces.js +165 -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 +95 -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 +69 -0
- package/dist/routes/health.js.map +1 -0
- package/dist/routes/index.d.ts +18 -0
- package/dist/routes/index.d.ts.map +1 -0
- package/dist/routes/index.js +18 -0
- package/dist/routes/index.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 +71 -0
- package/dist/routes/instance-settings.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 +4 -0
- package/dist/routes/issues.d.ts.map +1 -0
- package/dist/routes/issues.js +1520 -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 +78 -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 +120 -0
- package/dist/routes/plugins.d.ts.map +1 -0
- package/dist/routes/plugins.js +1784 -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 +257 -0
- package/dist/routes/projects.js.map +1 -0
- package/dist/routes/routines.d.ts +3 -0
- package/dist/routes/routines.d.ts.map +1 -0
- package/dist/routes/routines.js +277 -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 +45 -0
- package/dist/routes/sidebar-badges.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 +113 -0
- package/dist/services/access.d.ts.map +1 -0
- package/dist/services/access.js +247 -0
- package/dist/services/access.js.map +1 -0
- package/dist/services/activity-log.d.ts +17 -0
- package/dist/services/activity-log.d.ts.map +1 -0
- package/dist/services/activity-log.js +74 -0
- package/dist/services/activity-log.js.map +1 -0
- package/dist/services/activity.d.ts +764 -0
- package/dist/services/activity.d.ts.map +1 -0
- package/dist/services/activity.js +105 -0
- package/dist/services/activity.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/agents.d.ts +1670 -0
- package/dist/services/agents.d.ts.map +1 -0
- package/dist/services/agents.js +566 -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 +234 -0
- package/dist/services/board-auth.d.ts.map +1 -0
- package/dist/services/board-auth.js +295 -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 +124 -0
- package/dist/services/companies.d.ts.map +1 -0
- package/dist/services/companies.js +256 -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-portability.d.ts +23 -0
- package/dist/services/company-portability.d.ts.map +1 -0
- package/dist/services/company-portability.js +3739 -0
- package/dist/services/company-portability.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 +2042 -0
- package/dist/services/company-skills.js.map +1 -0
- package/dist/services/costs.d.ts +114 -0
- package/dist/services/costs.d.ts.map +1 -0
- package/dist/services/costs.js +294 -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 +26 -0
- package/dist/services/dashboard.d.ts.map +1 -0
- package/dist/services/dashboard.js +98 -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 +164 -0
- package/dist/services/documents.d.ts.map +1 -0
- package/dist/services/documents.js +382 -0
- package/dist/services/documents.js.map +1 -0
- package/dist/services/execution-workspace-policy.d.ts +21 -0
- package/dist/services/execution-workspace-policy.d.ts.map +1 -0
- package/dist/services/execution-workspace-policy.js +177 -0
- package/dist/services/execution-workspace-policy.js.map +1 -0
- package/dist/services/execution-workspaces.d.ts +19 -0
- package/dist/services/execution-workspaces.d.ts.map +1 -0
- package/dist/services/execution-workspaces.js +87 -0
- package/dist/services/execution-workspaces.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/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 +2 -0
- package/dist/services/heartbeat-run-summary.d.ts.map +1 -0
- package/dist/services/heartbeat-run-summary.js +30 -0
- package/dist/services/heartbeat-run-summary.js.map +1 -0
- package/dist/services/heartbeat.d.ts +812 -0
- package/dist/services/heartbeat.d.ts.map +1 -0
- package/dist/services/heartbeat.js +3156 -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/index.d.ts +33 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +33 -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 +116 -0
- package/dist/services/instance-settings.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-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/issues.d.ts +560 -0
- package/dist/services/issues.d.ts.map +1 -0
- package/dist/services/issues.js +1478 -0
- package/dist/services/issues.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/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 +268 -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-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-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 +13 -0
- package/dist/services/plugin-host-services.d.ts.map +1 -0
- package/dist/services/plugin-host-services.js +969 -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 +441 -0
- package/dist/services/plugin-loader.d.ts.map +1 -0
- package/dist/services/plugin-loader.js +1192 -0
- package/dist/services/plugin-loader.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-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 +2542 -0
- package/dist/services/plugin-registry.d.ts.map +1 -0
- package/dist/services/plugin-registry.js +539 -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 +275 -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 +260 -0
- package/dist/services/plugin-worker-manager.d.ts.map +1 -0
- package/dist/services/plugin-worker-manager.js +835 -0
- package/dist/services/plugin-worker-manager.js.map +1 -0
- package/dist/services/projects.d.ts +87 -0
- package/dist/services/projects.d.ts.map +1 -0
- package/dist/services/projects.js +656 -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/routines.d.ts +135 -0
- package/dist/services/routines.d.ts.map +1 -0
- package/dist/services/routines.js +1105 -0
- package/dist/services/routines.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 +109 -0
- package/dist/services/run-log-store.js.map +1 -0
- package/dist/services/secrets.d.ts +511 -0
- package/dist/services/secrets.d.ts.map +1 -0
- package/dist/services/secrets.js +289 -0
- package/dist/services/secrets.js.map +1 -0
- package/dist/services/sidebar-badges.d.ts +9 -0
- package/dist/services/sidebar-badges.d.ts.map +1 -0
- package/dist/services/sidebar-badges.js +33 -0
- package/dist/services/sidebar-badges.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-runtime.d.ts +164 -0
- package/dist/services/workspace-runtime.d.ts.map +1 -0
- package/dist/services/workspace-runtime.js +1235 -0
- package/dist/services/workspace-runtime.js.map +1 -0
- package/dist/startup-banner.d.ts +31 -0
- package/dist/startup-banner.d.ts.map +1 -0
- package/dist/startup-banner.js +117 -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/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/worktree-config.d.ts +19 -0
- package/dist/worktree-config.d.ts.map +1 -0
- package/dist/worktree-config.js +365 -0
- package/dist/worktree-config.js.map +1 -0
- package/package.json +95 -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-BB1S19s0.js +1 -0
- package/ui-dist/assets/_baseUniq-BNk0p-bq.js +1 -0
- package/ui-dist/assets/apl-B4CMkyY2.js +1 -0
- package/ui-dist/assets/arc-Ds13x1NW.js +1 -0
- package/ui-dist/assets/architectureDiagram-VXUJARFQ-D8Br-_jt.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-CCYsAhQ5.js +122 -0
- package/ui-dist/assets/brainfuck-C4LP7Hcl.js +1 -0
- package/ui-dist/assets/c4Diagram-YG6GDRKO-CsrnTJYB.js +10 -0
- package/ui-dist/assets/channel-Df4ReTUQ.js +1 -0
- package/ui-dist/assets/chunk-4BX2VUAB-s-S3bm2a.js +1 -0
- package/ui-dist/assets/chunk-55IACEB6-AlPeSG3C.js +1 -0
- package/ui-dist/assets/chunk-B4BG7PRW-Dlv3zmp0.js +165 -0
- package/ui-dist/assets/chunk-DI55MBZ5-y3Hfc2F6.js +220 -0
- package/ui-dist/assets/chunk-FMBD7UC4-ZbmFZ8uD.js +15 -0
- package/ui-dist/assets/chunk-QN33PNHL-g7XrAaL5.js +1 -0
- package/ui-dist/assets/chunk-QZHKN3VN-DQz2X_ZR.js +1 -0
- package/ui-dist/assets/chunk-TZMSLE5B-NRbDhryd.js +1 -0
- package/ui-dist/assets/classDiagram-2ON5EDUG-CdLb01dH.js +1 -0
- package/ui-dist/assets/classDiagram-v2-WZHVMYZB-CdLb01dH.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-ycTwxHSX.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-CifA3UGC.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-BQaXIfA_.js +331 -0
- package/ui-dist/assets/d-pRatUO7H.js +1 -0
- package/ui-dist/assets/dagre-6UL2VRFP-Cy4_402x.js +4 -0
- package/ui-dist/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/ui-dist/assets/diagram-PSM6KHXK-CUA-Vqxe.js +24 -0
- package/ui-dist/assets/diagram-QEK2KX5R-D763Ackt.js +43 -0
- package/ui-dist/assets/diagram-S2PKOQOG-Cu1xEKHt.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-9RlN9oCi.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-Ddv1tq-H.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-DAw7UfVT.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-CLPkzwpF.js +65 -0
- package/ui-dist/assets/graph-B1ThnnK5.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-0DYmQxT3.js +2 -0
- package/ui-dist/assets/index-1BRIjwwa.js +1 -0
- package/ui-dist/assets/index-4pxn9bje.js +1 -0
- package/ui-dist/assets/index-B02pjBpR.js +7 -0
- package/ui-dist/assets/index-BGjMkZzC.js +1 -0
- package/ui-dist/assets/index-BHsjaYJ1.js +1 -0
- package/ui-dist/assets/index-BTwpjL-6.js +1 -0
- package/ui-dist/assets/index-BZ72uG4K.js +6 -0
- package/ui-dist/assets/index-BpC8VHcj.js +3 -0
- package/ui-dist/assets/index-BrvXvCkd.js +1 -0
- package/ui-dist/assets/index-CEBcI-2f.js +1 -0
- package/ui-dist/assets/index-CkmjCahV.js +1 -0
- package/ui-dist/assets/index-Cp84QmJD.css +1 -0
- package/ui-dist/assets/index-CpVjtxma.js +1 -0
- package/ui-dist/assets/index-D3AJPUjv.js +1 -0
- package/ui-dist/assets/index-DRkeP4vs.js +13 -0
- package/ui-dist/assets/index-DXeNhnre.js +1180 -0
- package/ui-dist/assets/index-DXvXmooU.js +1 -0
- package/ui-dist/assets/index-DZdwValG.js +1 -0
- package/ui-dist/assets/index-DbWj5-qO.js +1 -0
- package/ui-dist/assets/index-DbcKBTbp.js +1 -0
- package/ui-dist/assets/index-DdAQdjTR.js +1 -0
- package/ui-dist/assets/index-DlImcHKo.js +1 -0
- package/ui-dist/assets/index-X9LdJDbl.js +1 -0
- package/ui-dist/assets/infoDiagram-HS3SLOUP-CbJm6kuq.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-CeNVFpGu.js +139 -0
- package/ui-dist/assets/julia-DuME0IfC.js +1 -0
- package/ui-dist/assets/kanban-definition-3W4ZIXB7-DRqPoDRI.js +89 -0
- package/ui-dist/assets/katex-O9d3_IXG.js +261 -0
- package/ui-dist/assets/layout-CZTKj8OD.js +1 -0
- package/ui-dist/assets/linear-BDJjeIco.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-B5v7dPHY.js +256 -0
- package/ui-dist/assets/mindmap-definition-VGOIOE7T-zjw0AyzL.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-ojNQ8Ukr.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-B8K2F86x.js +7 -0
- package/ui-dist/assets/r-B6wPVr8A.js +1 -0
- package/ui-dist/assets/requirementDiagram-UZGBJVZJ-DA_Bjcpk.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-yQfMgroQ.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-CWQm0UQc.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-C9L_ELvE.js +1 -0
- package/ui-dist/assets/stateDiagram-v2-4FDKWEC3-D3i22gRM.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-Bfva-2zq.js +61 -0
- package/ui-dist/assets/toml-Bm5Em-hy.js +1 -0
- package/ui-dist/assets/treemap-GDKQZRPO-6wTQWQt4.js +162 -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-DNsmIw3v.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 +48 -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,3156 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { execFile as execFileCallback } from "node:child_process";
|
|
4
|
+
import { promisify } from "node:util";
|
|
5
|
+
import { and, asc, desc, eq, gt, inArray, sql } from "drizzle-orm";
|
|
6
|
+
import { agents, agentRuntimeState, agentTaskSessions, agentWakeupRequests, heartbeatRunEvents, heartbeatRuns, issues, projects, projectWorkspaces, } from "@fidelios/db";
|
|
7
|
+
import { conflict, notFound } from "../errors.js";
|
|
8
|
+
import { logger } from "../middleware/logger.js";
|
|
9
|
+
import { publishLiveEvent } from "./live-events.js";
|
|
10
|
+
import { getRunLogStore } from "./run-log-store.js";
|
|
11
|
+
import { getServerAdapter, runningProcesses } from "../adapters/index.js";
|
|
12
|
+
import { createLocalAgentJwt } from "../agent-auth-jwt.js";
|
|
13
|
+
import { parseObject, asBoolean, asNumber, appendWithCap, MAX_EXCERPT_BYTES } from "../adapters/utils.js";
|
|
14
|
+
import { costService } from "./costs.js";
|
|
15
|
+
import { companySkillService } from "./company-skills.js";
|
|
16
|
+
import { budgetService } from "./budgets.js";
|
|
17
|
+
import { secretService } from "./secrets.js";
|
|
18
|
+
import { resolveDefaultAgentWorkspaceDir, resolveManagedProjectWorkspaceDir } from "../home-paths.js";
|
|
19
|
+
import { summarizeHeartbeatRunResultJson } from "./heartbeat-run-summary.js";
|
|
20
|
+
import { buildWorkspaceReadyComment, cleanupExecutionWorkspaceArtifacts, ensureRuntimeServicesForRun, persistAdapterManagedRuntimeServices, realizeExecutionWorkspace, releaseRuntimeServicesForRun, sanitizeRuntimeServiceBaseEnv, } from "./workspace-runtime.js";
|
|
21
|
+
import { issueService } from "./issues.js";
|
|
22
|
+
import { executionWorkspaceService } from "./execution-workspaces.js";
|
|
23
|
+
import { workspaceOperationService } from "./workspace-operations.js";
|
|
24
|
+
import { buildExecutionWorkspaceAdapterConfig, gateProjectExecutionWorkspacePolicy, issueExecutionWorkspaceModeForPersistedWorkspace, parseIssueExecutionWorkspaceSettings, parseProjectExecutionWorkspacePolicy, resolveExecutionWorkspaceMode, } from "./execution-workspace-policy.js";
|
|
25
|
+
import { instanceSettingsService } from "./instance-settings.js";
|
|
26
|
+
import { redactCurrentUserText, redactCurrentUserValue } from "../log-redaction.js";
|
|
27
|
+
import { hasSessionCompactionThresholds, resolveSessionCompactionPolicy, } from "@fidelios/adapter-utils";
|
|
28
|
+
const MAX_LIVE_LOG_CHUNK_BYTES = 8 * 1024;
|
|
29
|
+
const HEARTBEAT_MAX_CONCURRENT_RUNS_DEFAULT = 1;
|
|
30
|
+
const HEARTBEAT_MAX_CONCURRENT_RUNS_MAX = 10;
|
|
31
|
+
const DEFERRED_WAKE_CONTEXT_KEY = "_fideliosWakeContext";
|
|
32
|
+
const DETACHED_PROCESS_ERROR_CODE = "process_detached";
|
|
33
|
+
const startLocksByAgent = new Map();
|
|
34
|
+
const REPO_ONLY_CWD_SENTINEL = "/__fidelios_repo_only__";
|
|
35
|
+
const MANAGED_WORKSPACE_GIT_CLONE_TIMEOUT_MS = 10 * 60 * 1000;
|
|
36
|
+
const execFile = promisify(execFileCallback);
|
|
37
|
+
const SESSIONED_LOCAL_ADAPTERS = new Set([
|
|
38
|
+
"claude_local",
|
|
39
|
+
"codex_local",
|
|
40
|
+
"cursor",
|
|
41
|
+
"gemini_local",
|
|
42
|
+
"opencode_local",
|
|
43
|
+
"pi_local",
|
|
44
|
+
]);
|
|
45
|
+
function deriveRepoNameFromRepoUrl(repoUrl) {
|
|
46
|
+
const trimmed = repoUrl?.trim() ?? "";
|
|
47
|
+
if (!trimmed)
|
|
48
|
+
return null;
|
|
49
|
+
try {
|
|
50
|
+
const parsed = new URL(trimmed);
|
|
51
|
+
const cleanedPath = parsed.pathname.replace(/\/+$/, "");
|
|
52
|
+
const repoName = cleanedPath.split("/").filter(Boolean).pop()?.replace(/\.git$/i, "") ?? "";
|
|
53
|
+
return repoName || null;
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async function ensureManagedProjectWorkspace(input) {
|
|
60
|
+
const cwd = resolveManagedProjectWorkspaceDir({
|
|
61
|
+
companyId: input.companyId,
|
|
62
|
+
projectId: input.projectId,
|
|
63
|
+
repoName: deriveRepoNameFromRepoUrl(input.repoUrl),
|
|
64
|
+
});
|
|
65
|
+
await fs.mkdir(path.dirname(cwd), { recursive: true });
|
|
66
|
+
const stats = await fs.stat(cwd).catch(() => null);
|
|
67
|
+
if (!input.repoUrl) {
|
|
68
|
+
if (!stats) {
|
|
69
|
+
await fs.mkdir(cwd, { recursive: true });
|
|
70
|
+
}
|
|
71
|
+
return { cwd, warning: null };
|
|
72
|
+
}
|
|
73
|
+
const gitDirExists = await fs
|
|
74
|
+
.stat(path.resolve(cwd, ".git"))
|
|
75
|
+
.then((entry) => entry.isDirectory())
|
|
76
|
+
.catch(() => false);
|
|
77
|
+
if (gitDirExists) {
|
|
78
|
+
return { cwd, warning: null };
|
|
79
|
+
}
|
|
80
|
+
if (stats) {
|
|
81
|
+
const entries = await fs.readdir(cwd).catch(() => []);
|
|
82
|
+
if (entries.length > 0) {
|
|
83
|
+
return {
|
|
84
|
+
cwd,
|
|
85
|
+
warning: `Managed workspace path "${cwd}" already exists but is not a git checkout. Using it as-is.`,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
await fs.rm(cwd, { recursive: true, force: true });
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
await execFile("git", ["clone", input.repoUrl, cwd], {
|
|
92
|
+
env: sanitizeRuntimeServiceBaseEnv(process.env),
|
|
93
|
+
timeout: MANAGED_WORKSPACE_GIT_CLONE_TIMEOUT_MS,
|
|
94
|
+
});
|
|
95
|
+
return { cwd, warning: null };
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
99
|
+
throw new Error(`Failed to prepare managed checkout for "${input.repoUrl}" at "${cwd}": ${reason}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const heartbeatRunListColumns = {
|
|
103
|
+
id: heartbeatRuns.id,
|
|
104
|
+
companyId: heartbeatRuns.companyId,
|
|
105
|
+
agentId: heartbeatRuns.agentId,
|
|
106
|
+
invocationSource: heartbeatRuns.invocationSource,
|
|
107
|
+
triggerDetail: heartbeatRuns.triggerDetail,
|
|
108
|
+
status: heartbeatRuns.status,
|
|
109
|
+
startedAt: heartbeatRuns.startedAt,
|
|
110
|
+
finishedAt: heartbeatRuns.finishedAt,
|
|
111
|
+
error: heartbeatRuns.error,
|
|
112
|
+
wakeupRequestId: heartbeatRuns.wakeupRequestId,
|
|
113
|
+
exitCode: heartbeatRuns.exitCode,
|
|
114
|
+
signal: heartbeatRuns.signal,
|
|
115
|
+
usageJson: heartbeatRuns.usageJson,
|
|
116
|
+
resultJson: heartbeatRuns.resultJson,
|
|
117
|
+
sessionIdBefore: heartbeatRuns.sessionIdBefore,
|
|
118
|
+
sessionIdAfter: heartbeatRuns.sessionIdAfter,
|
|
119
|
+
logStore: heartbeatRuns.logStore,
|
|
120
|
+
logRef: heartbeatRuns.logRef,
|
|
121
|
+
logBytes: heartbeatRuns.logBytes,
|
|
122
|
+
logSha256: heartbeatRuns.logSha256,
|
|
123
|
+
logCompressed: heartbeatRuns.logCompressed,
|
|
124
|
+
stdoutExcerpt: sql `NULL`.as("stdoutExcerpt"),
|
|
125
|
+
stderrExcerpt: sql `NULL`.as("stderrExcerpt"),
|
|
126
|
+
errorCode: heartbeatRuns.errorCode,
|
|
127
|
+
externalRunId: heartbeatRuns.externalRunId,
|
|
128
|
+
processPid: heartbeatRuns.processPid,
|
|
129
|
+
processStartedAt: heartbeatRuns.processStartedAt,
|
|
130
|
+
retryOfRunId: heartbeatRuns.retryOfRunId,
|
|
131
|
+
processLossRetryCount: heartbeatRuns.processLossRetryCount,
|
|
132
|
+
contextSnapshot: heartbeatRuns.contextSnapshot,
|
|
133
|
+
createdAt: heartbeatRuns.createdAt,
|
|
134
|
+
updatedAt: heartbeatRuns.updatedAt,
|
|
135
|
+
};
|
|
136
|
+
function appendExcerpt(prev, chunk) {
|
|
137
|
+
return appendWithCap(prev, chunk, MAX_EXCERPT_BYTES);
|
|
138
|
+
}
|
|
139
|
+
function normalizeMaxConcurrentRuns(value) {
|
|
140
|
+
const parsed = Math.floor(asNumber(value, HEARTBEAT_MAX_CONCURRENT_RUNS_DEFAULT));
|
|
141
|
+
if (!Number.isFinite(parsed))
|
|
142
|
+
return HEARTBEAT_MAX_CONCURRENT_RUNS_DEFAULT;
|
|
143
|
+
return Math.max(HEARTBEAT_MAX_CONCURRENT_RUNS_DEFAULT, Math.min(HEARTBEAT_MAX_CONCURRENT_RUNS_MAX, parsed));
|
|
144
|
+
}
|
|
145
|
+
async function withAgentStartLock(agentId, fn) {
|
|
146
|
+
const previous = startLocksByAgent.get(agentId) ?? Promise.resolve();
|
|
147
|
+
const run = previous.then(fn);
|
|
148
|
+
const marker = run.then(() => undefined, () => undefined);
|
|
149
|
+
startLocksByAgent.set(agentId, marker);
|
|
150
|
+
try {
|
|
151
|
+
return await run;
|
|
152
|
+
}
|
|
153
|
+
finally {
|
|
154
|
+
if (startLocksByAgent.get(agentId) === marker) {
|
|
155
|
+
startLocksByAgent.delete(agentId);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
export function prioritizeProjectWorkspaceCandidatesForRun(rows, preferredWorkspaceId) {
|
|
160
|
+
if (!preferredWorkspaceId)
|
|
161
|
+
return rows;
|
|
162
|
+
const preferredIndex = rows.findIndex((row) => row.id === preferredWorkspaceId);
|
|
163
|
+
if (preferredIndex <= 0)
|
|
164
|
+
return rows;
|
|
165
|
+
return [rows[preferredIndex], ...rows.slice(0, preferredIndex), ...rows.slice(preferredIndex + 1)];
|
|
166
|
+
}
|
|
167
|
+
function readNonEmptyString(value) {
|
|
168
|
+
return typeof value === "string" && value.trim().length > 0 ? value : null;
|
|
169
|
+
}
|
|
170
|
+
function normalizeLedgerBillingType(value) {
|
|
171
|
+
const raw = readNonEmptyString(value);
|
|
172
|
+
switch (raw) {
|
|
173
|
+
case "api":
|
|
174
|
+
case "metered_api":
|
|
175
|
+
return "metered_api";
|
|
176
|
+
case "subscription":
|
|
177
|
+
case "subscription_included":
|
|
178
|
+
return "subscription_included";
|
|
179
|
+
case "subscription_overage":
|
|
180
|
+
return "subscription_overage";
|
|
181
|
+
case "credits":
|
|
182
|
+
return "credits";
|
|
183
|
+
case "fixed":
|
|
184
|
+
return "fixed";
|
|
185
|
+
default:
|
|
186
|
+
return "unknown";
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
function resolveLedgerBiller(result) {
|
|
190
|
+
return readNonEmptyString(result.biller) ?? readNonEmptyString(result.provider) ?? "unknown";
|
|
191
|
+
}
|
|
192
|
+
function normalizeBilledCostCents(costUsd, billingType) {
|
|
193
|
+
if (billingType === "subscription_included")
|
|
194
|
+
return 0;
|
|
195
|
+
if (typeof costUsd !== "number" || !Number.isFinite(costUsd))
|
|
196
|
+
return 0;
|
|
197
|
+
return Math.max(0, Math.round(costUsd * 100));
|
|
198
|
+
}
|
|
199
|
+
async function resolveLedgerScopeForRun(db, companyId, run) {
|
|
200
|
+
const context = parseObject(run.contextSnapshot);
|
|
201
|
+
const contextIssueId = readNonEmptyString(context.issueId);
|
|
202
|
+
const contextProjectId = readNonEmptyString(context.projectId);
|
|
203
|
+
if (!contextIssueId) {
|
|
204
|
+
return {
|
|
205
|
+
issueId: null,
|
|
206
|
+
projectId: contextProjectId,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
const issue = await db
|
|
210
|
+
.select({
|
|
211
|
+
id: issues.id,
|
|
212
|
+
projectId: issues.projectId,
|
|
213
|
+
})
|
|
214
|
+
.from(issues)
|
|
215
|
+
.where(and(eq(issues.id, contextIssueId), eq(issues.companyId, companyId)))
|
|
216
|
+
.then((rows) => rows[0] ?? null);
|
|
217
|
+
return {
|
|
218
|
+
issueId: issue?.id ?? null,
|
|
219
|
+
projectId: issue?.projectId ?? contextProjectId,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
export function buildExplicitResumeSessionOverride(input) {
|
|
223
|
+
const desiredDisplayId = truncateDisplayId(input.resumeRunSessionIdAfter ?? input.resumeRunSessionIdBefore);
|
|
224
|
+
const taskSessionParams = normalizeSessionParams(input.sessionCodec.deserialize(input.taskSession?.sessionParamsJson ?? null));
|
|
225
|
+
const taskSessionDisplayId = truncateDisplayId(input.taskSession?.sessionDisplayId ??
|
|
226
|
+
(input.sessionCodec.getDisplayId ? input.sessionCodec.getDisplayId(taskSessionParams) : null) ??
|
|
227
|
+
readNonEmptyString(taskSessionParams?.sessionId));
|
|
228
|
+
const canReuseTaskSessionParams = input.taskSession != null &&
|
|
229
|
+
(input.taskSession.lastRunId === input.resumeFromRunId ||
|
|
230
|
+
(!!desiredDisplayId && taskSessionDisplayId === desiredDisplayId));
|
|
231
|
+
const sessionParams = canReuseTaskSessionParams
|
|
232
|
+
? taskSessionParams
|
|
233
|
+
: desiredDisplayId
|
|
234
|
+
? { sessionId: desiredDisplayId }
|
|
235
|
+
: null;
|
|
236
|
+
const sessionDisplayId = desiredDisplayId ?? (canReuseTaskSessionParams ? taskSessionDisplayId : null);
|
|
237
|
+
if (!sessionDisplayId && !sessionParams)
|
|
238
|
+
return null;
|
|
239
|
+
return {
|
|
240
|
+
sessionDisplayId,
|
|
241
|
+
sessionParams,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
function normalizeUsageTotals(usage) {
|
|
245
|
+
if (!usage)
|
|
246
|
+
return null;
|
|
247
|
+
return {
|
|
248
|
+
inputTokens: Math.max(0, Math.floor(asNumber(usage.inputTokens, 0))),
|
|
249
|
+
cachedInputTokens: Math.max(0, Math.floor(asNumber(usage.cachedInputTokens, 0))),
|
|
250
|
+
outputTokens: Math.max(0, Math.floor(asNumber(usage.outputTokens, 0))),
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
function readRawUsageTotals(usageJson) {
|
|
254
|
+
const parsed = parseObject(usageJson);
|
|
255
|
+
if (Object.keys(parsed).length === 0)
|
|
256
|
+
return null;
|
|
257
|
+
const inputTokens = Math.max(0, Math.floor(asNumber(parsed.rawInputTokens, asNumber(parsed.inputTokens, 0))));
|
|
258
|
+
const cachedInputTokens = Math.max(0, Math.floor(asNumber(parsed.rawCachedInputTokens, asNumber(parsed.cachedInputTokens, 0))));
|
|
259
|
+
const outputTokens = Math.max(0, Math.floor(asNumber(parsed.rawOutputTokens, asNumber(parsed.outputTokens, 0))));
|
|
260
|
+
if (inputTokens <= 0 && cachedInputTokens <= 0 && outputTokens <= 0) {
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
return {
|
|
264
|
+
inputTokens,
|
|
265
|
+
cachedInputTokens,
|
|
266
|
+
outputTokens,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
function deriveNormalizedUsageDelta(current, previous) {
|
|
270
|
+
if (!current)
|
|
271
|
+
return null;
|
|
272
|
+
if (!previous)
|
|
273
|
+
return { ...current };
|
|
274
|
+
const inputTokens = current.inputTokens >= previous.inputTokens
|
|
275
|
+
? current.inputTokens - previous.inputTokens
|
|
276
|
+
: current.inputTokens;
|
|
277
|
+
const cachedInputTokens = current.cachedInputTokens >= previous.cachedInputTokens
|
|
278
|
+
? current.cachedInputTokens - previous.cachedInputTokens
|
|
279
|
+
: current.cachedInputTokens;
|
|
280
|
+
const outputTokens = current.outputTokens >= previous.outputTokens
|
|
281
|
+
? current.outputTokens - previous.outputTokens
|
|
282
|
+
: current.outputTokens;
|
|
283
|
+
return {
|
|
284
|
+
inputTokens: Math.max(0, inputTokens),
|
|
285
|
+
cachedInputTokens: Math.max(0, cachedInputTokens),
|
|
286
|
+
outputTokens: Math.max(0, outputTokens),
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
function formatCount(value) {
|
|
290
|
+
if (typeof value !== "number" || !Number.isFinite(value))
|
|
291
|
+
return "0";
|
|
292
|
+
return value.toLocaleString("en-US");
|
|
293
|
+
}
|
|
294
|
+
export function parseSessionCompactionPolicy(agent) {
|
|
295
|
+
return resolveSessionCompactionPolicy(agent.adapterType, agent.runtimeConfig).policy;
|
|
296
|
+
}
|
|
297
|
+
export function resolveRuntimeSessionParamsForWorkspace(input) {
|
|
298
|
+
const { agentId, previousSessionParams, resolvedWorkspace } = input;
|
|
299
|
+
const previousSessionId = readNonEmptyString(previousSessionParams?.sessionId);
|
|
300
|
+
const previousCwd = readNonEmptyString(previousSessionParams?.cwd);
|
|
301
|
+
if (!previousSessionId || !previousCwd) {
|
|
302
|
+
return {
|
|
303
|
+
sessionParams: previousSessionParams,
|
|
304
|
+
warning: null,
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
if (resolvedWorkspace.source !== "project_primary") {
|
|
308
|
+
return {
|
|
309
|
+
sessionParams: previousSessionParams,
|
|
310
|
+
warning: null,
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
const projectCwd = readNonEmptyString(resolvedWorkspace.cwd);
|
|
314
|
+
if (!projectCwd) {
|
|
315
|
+
return {
|
|
316
|
+
sessionParams: previousSessionParams,
|
|
317
|
+
warning: null,
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
const fallbackAgentHomeCwd = resolveDefaultAgentWorkspaceDir(agentId);
|
|
321
|
+
if (path.resolve(previousCwd) !== path.resolve(fallbackAgentHomeCwd)) {
|
|
322
|
+
return {
|
|
323
|
+
sessionParams: previousSessionParams,
|
|
324
|
+
warning: null,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
if (path.resolve(projectCwd) === path.resolve(previousCwd)) {
|
|
328
|
+
return {
|
|
329
|
+
sessionParams: previousSessionParams,
|
|
330
|
+
warning: null,
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
const previousWorkspaceId = readNonEmptyString(previousSessionParams?.workspaceId);
|
|
334
|
+
if (previousWorkspaceId &&
|
|
335
|
+
resolvedWorkspace.workspaceId &&
|
|
336
|
+
previousWorkspaceId !== resolvedWorkspace.workspaceId) {
|
|
337
|
+
return {
|
|
338
|
+
sessionParams: previousSessionParams,
|
|
339
|
+
warning: null,
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
const migratedSessionParams = {
|
|
343
|
+
...(previousSessionParams ?? {}),
|
|
344
|
+
cwd: projectCwd,
|
|
345
|
+
};
|
|
346
|
+
if (resolvedWorkspace.workspaceId)
|
|
347
|
+
migratedSessionParams.workspaceId = resolvedWorkspace.workspaceId;
|
|
348
|
+
if (resolvedWorkspace.repoUrl)
|
|
349
|
+
migratedSessionParams.repoUrl = resolvedWorkspace.repoUrl;
|
|
350
|
+
if (resolvedWorkspace.repoRef)
|
|
351
|
+
migratedSessionParams.repoRef = resolvedWorkspace.repoRef;
|
|
352
|
+
return {
|
|
353
|
+
sessionParams: migratedSessionParams,
|
|
354
|
+
warning: `Project workspace "${projectCwd}" is now available. ` +
|
|
355
|
+
`Attempting to resume session "${previousSessionId}" that was previously saved in fallback workspace "${previousCwd}".`,
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
function parseIssueAssigneeAdapterOverrides(raw) {
|
|
359
|
+
const parsed = parseObject(raw);
|
|
360
|
+
const parsedAdapterConfig = parseObject(parsed.adapterConfig);
|
|
361
|
+
const adapterConfig = Object.keys(parsedAdapterConfig).length > 0 ? parsedAdapterConfig : null;
|
|
362
|
+
const useProjectWorkspace = typeof parsed.useProjectWorkspace === "boolean"
|
|
363
|
+
? parsed.useProjectWorkspace
|
|
364
|
+
: null;
|
|
365
|
+
if (!adapterConfig && useProjectWorkspace === null)
|
|
366
|
+
return null;
|
|
367
|
+
return {
|
|
368
|
+
adapterConfig,
|
|
369
|
+
useProjectWorkspace,
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
function deriveTaskKey(contextSnapshot, payload) {
|
|
373
|
+
return (readNonEmptyString(contextSnapshot?.taskKey) ??
|
|
374
|
+
readNonEmptyString(contextSnapshot?.taskId) ??
|
|
375
|
+
readNonEmptyString(contextSnapshot?.issueId) ??
|
|
376
|
+
readNonEmptyString(payload?.taskKey) ??
|
|
377
|
+
readNonEmptyString(payload?.taskId) ??
|
|
378
|
+
readNonEmptyString(payload?.issueId) ??
|
|
379
|
+
null);
|
|
380
|
+
}
|
|
381
|
+
export function shouldResetTaskSessionForWake(contextSnapshot) {
|
|
382
|
+
if (contextSnapshot?.forceFreshSession === true)
|
|
383
|
+
return true;
|
|
384
|
+
const wakeReason = readNonEmptyString(contextSnapshot?.wakeReason);
|
|
385
|
+
if (wakeReason === "issue_assigned")
|
|
386
|
+
return true;
|
|
387
|
+
return false;
|
|
388
|
+
}
|
|
389
|
+
export function formatRuntimeWorkspaceWarningLog(warning) {
|
|
390
|
+
return {
|
|
391
|
+
stream: "stdout",
|
|
392
|
+
chunk: `[fidelios] ${warning}\n`,
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
function describeSessionResetReason(contextSnapshot) {
|
|
396
|
+
if (contextSnapshot?.forceFreshSession === true)
|
|
397
|
+
return "forceFreshSession was requested";
|
|
398
|
+
const wakeReason = readNonEmptyString(contextSnapshot?.wakeReason);
|
|
399
|
+
if (wakeReason === "issue_assigned")
|
|
400
|
+
return "wake reason is issue_assigned";
|
|
401
|
+
return null;
|
|
402
|
+
}
|
|
403
|
+
function deriveCommentId(contextSnapshot, payload) {
|
|
404
|
+
return (readNonEmptyString(contextSnapshot?.wakeCommentId) ??
|
|
405
|
+
readNonEmptyString(contextSnapshot?.commentId) ??
|
|
406
|
+
readNonEmptyString(payload?.commentId) ??
|
|
407
|
+
null);
|
|
408
|
+
}
|
|
409
|
+
function enrichWakeContextSnapshot(input) {
|
|
410
|
+
const { contextSnapshot, reason, source, triggerDetail, payload } = input;
|
|
411
|
+
const issueIdFromPayload = readNonEmptyString(payload?.["issueId"]);
|
|
412
|
+
const commentIdFromPayload = readNonEmptyString(payload?.["commentId"]);
|
|
413
|
+
const taskKey = deriveTaskKey(contextSnapshot, payload);
|
|
414
|
+
const wakeCommentId = deriveCommentId(contextSnapshot, payload);
|
|
415
|
+
if (!readNonEmptyString(contextSnapshot["wakeReason"]) && reason) {
|
|
416
|
+
contextSnapshot.wakeReason = reason;
|
|
417
|
+
}
|
|
418
|
+
if (!readNonEmptyString(contextSnapshot["issueId"]) && issueIdFromPayload) {
|
|
419
|
+
contextSnapshot.issueId = issueIdFromPayload;
|
|
420
|
+
}
|
|
421
|
+
if (!readNonEmptyString(contextSnapshot["taskId"]) && issueIdFromPayload) {
|
|
422
|
+
contextSnapshot.taskId = issueIdFromPayload;
|
|
423
|
+
}
|
|
424
|
+
if (!readNonEmptyString(contextSnapshot["taskKey"]) && taskKey) {
|
|
425
|
+
contextSnapshot.taskKey = taskKey;
|
|
426
|
+
}
|
|
427
|
+
if (!readNonEmptyString(contextSnapshot["commentId"]) && commentIdFromPayload) {
|
|
428
|
+
contextSnapshot.commentId = commentIdFromPayload;
|
|
429
|
+
}
|
|
430
|
+
if (!readNonEmptyString(contextSnapshot["wakeCommentId"]) && wakeCommentId) {
|
|
431
|
+
contextSnapshot.wakeCommentId = wakeCommentId;
|
|
432
|
+
}
|
|
433
|
+
if (!readNonEmptyString(contextSnapshot["wakeSource"]) && source) {
|
|
434
|
+
contextSnapshot.wakeSource = source;
|
|
435
|
+
}
|
|
436
|
+
if (!readNonEmptyString(contextSnapshot["wakeTriggerDetail"]) && triggerDetail) {
|
|
437
|
+
contextSnapshot.wakeTriggerDetail = triggerDetail;
|
|
438
|
+
}
|
|
439
|
+
return {
|
|
440
|
+
contextSnapshot,
|
|
441
|
+
issueIdFromPayload,
|
|
442
|
+
commentIdFromPayload,
|
|
443
|
+
taskKey,
|
|
444
|
+
wakeCommentId,
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
function mergeCoalescedContextSnapshot(existingRaw, incoming) {
|
|
448
|
+
const existing = parseObject(existingRaw);
|
|
449
|
+
const merged = {
|
|
450
|
+
...existing,
|
|
451
|
+
...incoming,
|
|
452
|
+
};
|
|
453
|
+
const commentId = deriveCommentId(incoming, null);
|
|
454
|
+
if (commentId) {
|
|
455
|
+
merged.commentId = commentId;
|
|
456
|
+
merged.wakeCommentId = commentId;
|
|
457
|
+
}
|
|
458
|
+
return merged;
|
|
459
|
+
}
|
|
460
|
+
function runTaskKey(run) {
|
|
461
|
+
return deriveTaskKey(run.contextSnapshot, null);
|
|
462
|
+
}
|
|
463
|
+
function isSameTaskScope(left, right) {
|
|
464
|
+
return (left ?? null) === (right ?? null);
|
|
465
|
+
}
|
|
466
|
+
function isTrackedLocalChildProcessAdapter(adapterType) {
|
|
467
|
+
return SESSIONED_LOCAL_ADAPTERS.has(adapterType);
|
|
468
|
+
}
|
|
469
|
+
// A positive liveness check means some process currently owns the PID.
|
|
470
|
+
// On Linux, PIDs can be recycled, so this is a best-effort signal rather
|
|
471
|
+
// than proof that the original child is still alive.
|
|
472
|
+
function isProcessAlive(pid) {
|
|
473
|
+
if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0)
|
|
474
|
+
return false;
|
|
475
|
+
try {
|
|
476
|
+
process.kill(pid, 0);
|
|
477
|
+
return true;
|
|
478
|
+
}
|
|
479
|
+
catch (error) {
|
|
480
|
+
const code = error?.code;
|
|
481
|
+
if (code === "EPERM")
|
|
482
|
+
return true;
|
|
483
|
+
if (code === "ESRCH")
|
|
484
|
+
return false;
|
|
485
|
+
return false;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
function truncateDisplayId(value, max = 128) {
|
|
489
|
+
if (!value)
|
|
490
|
+
return null;
|
|
491
|
+
return value.length > max ? value.slice(0, max) : value;
|
|
492
|
+
}
|
|
493
|
+
function normalizeAgentNameKey(value) {
|
|
494
|
+
if (typeof value !== "string")
|
|
495
|
+
return null;
|
|
496
|
+
const normalized = value.trim().toLowerCase();
|
|
497
|
+
return normalized.length > 0 ? normalized : null;
|
|
498
|
+
}
|
|
499
|
+
const defaultSessionCodec = {
|
|
500
|
+
deserialize(raw) {
|
|
501
|
+
const asObj = parseObject(raw);
|
|
502
|
+
if (Object.keys(asObj).length > 0)
|
|
503
|
+
return asObj;
|
|
504
|
+
const sessionId = readNonEmptyString(raw?.sessionId);
|
|
505
|
+
if (sessionId)
|
|
506
|
+
return { sessionId };
|
|
507
|
+
return null;
|
|
508
|
+
},
|
|
509
|
+
serialize(params) {
|
|
510
|
+
if (!params || Object.keys(params).length === 0)
|
|
511
|
+
return null;
|
|
512
|
+
return params;
|
|
513
|
+
},
|
|
514
|
+
getDisplayId(params) {
|
|
515
|
+
return readNonEmptyString(params?.sessionId);
|
|
516
|
+
},
|
|
517
|
+
};
|
|
518
|
+
function getAdapterSessionCodec(adapterType) {
|
|
519
|
+
const adapter = getServerAdapter(adapterType);
|
|
520
|
+
return adapter.sessionCodec ?? defaultSessionCodec;
|
|
521
|
+
}
|
|
522
|
+
function normalizeSessionParams(params) {
|
|
523
|
+
if (!params)
|
|
524
|
+
return null;
|
|
525
|
+
return Object.keys(params).length > 0 ? params : null;
|
|
526
|
+
}
|
|
527
|
+
function resolveNextSessionState(input) {
|
|
528
|
+
const { codec, adapterResult, previousParams, previousDisplayId, previousLegacySessionId } = input;
|
|
529
|
+
if (adapterResult.clearSession) {
|
|
530
|
+
return {
|
|
531
|
+
params: null,
|
|
532
|
+
displayId: null,
|
|
533
|
+
legacySessionId: null,
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
const explicitParams = adapterResult.sessionParams;
|
|
537
|
+
const hasExplicitParams = adapterResult.sessionParams !== undefined;
|
|
538
|
+
const hasExplicitSessionId = adapterResult.sessionId !== undefined;
|
|
539
|
+
const explicitSessionId = readNonEmptyString(adapterResult.sessionId);
|
|
540
|
+
const hasExplicitDisplay = adapterResult.sessionDisplayId !== undefined;
|
|
541
|
+
const explicitDisplayId = readNonEmptyString(adapterResult.sessionDisplayId);
|
|
542
|
+
const shouldUsePrevious = !hasExplicitParams && !hasExplicitSessionId && !hasExplicitDisplay;
|
|
543
|
+
const candidateParams = hasExplicitParams
|
|
544
|
+
? explicitParams
|
|
545
|
+
: hasExplicitSessionId
|
|
546
|
+
? (explicitSessionId ? { sessionId: explicitSessionId } : null)
|
|
547
|
+
: previousParams;
|
|
548
|
+
const serialized = normalizeSessionParams(codec.serialize(normalizeSessionParams(candidateParams) ?? null));
|
|
549
|
+
const deserialized = normalizeSessionParams(codec.deserialize(serialized));
|
|
550
|
+
const displayId = truncateDisplayId(explicitDisplayId ??
|
|
551
|
+
(codec.getDisplayId ? codec.getDisplayId(deserialized) : null) ??
|
|
552
|
+
readNonEmptyString(deserialized?.sessionId) ??
|
|
553
|
+
(shouldUsePrevious ? previousDisplayId : null) ??
|
|
554
|
+
explicitSessionId ??
|
|
555
|
+
(shouldUsePrevious ? previousLegacySessionId : null));
|
|
556
|
+
const legacySessionId = explicitSessionId ??
|
|
557
|
+
readNonEmptyString(deserialized?.sessionId) ??
|
|
558
|
+
displayId ??
|
|
559
|
+
(shouldUsePrevious ? previousLegacySessionId : null);
|
|
560
|
+
return {
|
|
561
|
+
params: serialized,
|
|
562
|
+
displayId,
|
|
563
|
+
legacySessionId,
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
export function heartbeatService(db) {
|
|
567
|
+
const instanceSettings = instanceSettingsService(db);
|
|
568
|
+
const getCurrentUserRedactionOptions = async () => ({
|
|
569
|
+
enabled: (await instanceSettings.getGeneral()).censorUsernameInLogs,
|
|
570
|
+
});
|
|
571
|
+
const runLogStore = getRunLogStore();
|
|
572
|
+
const secretsSvc = secretService(db);
|
|
573
|
+
const companySkills = companySkillService(db);
|
|
574
|
+
const issuesSvc = issueService(db);
|
|
575
|
+
const executionWorkspacesSvc = executionWorkspaceService(db);
|
|
576
|
+
const workspaceOperationsSvc = workspaceOperationService(db);
|
|
577
|
+
const activeRunExecutions = new Set();
|
|
578
|
+
const budgetHooks = {
|
|
579
|
+
cancelWorkForScope: cancelBudgetScopeWork,
|
|
580
|
+
};
|
|
581
|
+
const budgets = budgetService(db, budgetHooks);
|
|
582
|
+
async function getAgent(agentId) {
|
|
583
|
+
return db
|
|
584
|
+
.select()
|
|
585
|
+
.from(agents)
|
|
586
|
+
.where(eq(agents.id, agentId))
|
|
587
|
+
.then((rows) => rows[0] ?? null);
|
|
588
|
+
}
|
|
589
|
+
async function getRun(runId) {
|
|
590
|
+
return db
|
|
591
|
+
.select()
|
|
592
|
+
.from(heartbeatRuns)
|
|
593
|
+
.where(eq(heartbeatRuns.id, runId))
|
|
594
|
+
.then((rows) => rows[0] ?? null);
|
|
595
|
+
}
|
|
596
|
+
async function getRuntimeState(agentId) {
|
|
597
|
+
return db
|
|
598
|
+
.select()
|
|
599
|
+
.from(agentRuntimeState)
|
|
600
|
+
.where(eq(agentRuntimeState.agentId, agentId))
|
|
601
|
+
.then((rows) => rows[0] ?? null);
|
|
602
|
+
}
|
|
603
|
+
async function getTaskSession(companyId, agentId, adapterType, taskKey) {
|
|
604
|
+
return db
|
|
605
|
+
.select()
|
|
606
|
+
.from(agentTaskSessions)
|
|
607
|
+
.where(and(eq(agentTaskSessions.companyId, companyId), eq(agentTaskSessions.agentId, agentId), eq(agentTaskSessions.adapterType, adapterType), eq(agentTaskSessions.taskKey, taskKey)))
|
|
608
|
+
.then((rows) => rows[0] ?? null);
|
|
609
|
+
}
|
|
610
|
+
async function getLatestRunForSession(agentId, sessionId, opts) {
|
|
611
|
+
const conditions = [
|
|
612
|
+
eq(heartbeatRuns.agentId, agentId),
|
|
613
|
+
eq(heartbeatRuns.sessionIdAfter, sessionId),
|
|
614
|
+
];
|
|
615
|
+
if (opts?.excludeRunId) {
|
|
616
|
+
conditions.push(sql `${heartbeatRuns.id} <> ${opts.excludeRunId}`);
|
|
617
|
+
}
|
|
618
|
+
return db
|
|
619
|
+
.select()
|
|
620
|
+
.from(heartbeatRuns)
|
|
621
|
+
.where(and(...conditions))
|
|
622
|
+
.orderBy(desc(heartbeatRuns.createdAt))
|
|
623
|
+
.limit(1)
|
|
624
|
+
.then((rows) => rows[0] ?? null);
|
|
625
|
+
}
|
|
626
|
+
async function getOldestRunForSession(agentId, sessionId) {
|
|
627
|
+
return db
|
|
628
|
+
.select({
|
|
629
|
+
id: heartbeatRuns.id,
|
|
630
|
+
createdAt: heartbeatRuns.createdAt,
|
|
631
|
+
})
|
|
632
|
+
.from(heartbeatRuns)
|
|
633
|
+
.where(and(eq(heartbeatRuns.agentId, agentId), eq(heartbeatRuns.sessionIdAfter, sessionId)))
|
|
634
|
+
.orderBy(asc(heartbeatRuns.createdAt), asc(heartbeatRuns.id))
|
|
635
|
+
.limit(1)
|
|
636
|
+
.then((rows) => rows[0] ?? null);
|
|
637
|
+
}
|
|
638
|
+
async function resolveNormalizedUsageForSession(input) {
|
|
639
|
+
const { agentId, runId, sessionId, rawUsage } = input;
|
|
640
|
+
if (!sessionId || !rawUsage) {
|
|
641
|
+
return {
|
|
642
|
+
normalizedUsage: rawUsage,
|
|
643
|
+
previousRawUsage: null,
|
|
644
|
+
derivedFromSessionTotals: false,
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
const previousRun = await getLatestRunForSession(agentId, sessionId, { excludeRunId: runId });
|
|
648
|
+
const previousRawUsage = readRawUsageTotals(previousRun?.usageJson);
|
|
649
|
+
return {
|
|
650
|
+
normalizedUsage: deriveNormalizedUsageDelta(rawUsage, previousRawUsage),
|
|
651
|
+
previousRawUsage,
|
|
652
|
+
derivedFromSessionTotals: previousRawUsage !== null,
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
async function evaluateSessionCompaction(input) {
|
|
656
|
+
const { agent, sessionId, issueId } = input;
|
|
657
|
+
if (!sessionId) {
|
|
658
|
+
return {
|
|
659
|
+
rotate: false,
|
|
660
|
+
reason: null,
|
|
661
|
+
handoffMarkdown: null,
|
|
662
|
+
previousRunId: null,
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
const policy = parseSessionCompactionPolicy(agent);
|
|
666
|
+
if (!policy.enabled || !hasSessionCompactionThresholds(policy)) {
|
|
667
|
+
return {
|
|
668
|
+
rotate: false,
|
|
669
|
+
reason: null,
|
|
670
|
+
handoffMarkdown: null,
|
|
671
|
+
previousRunId: null,
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
const fetchLimit = Math.max(policy.maxSessionRuns > 0 ? policy.maxSessionRuns + 1 : 0, 4);
|
|
675
|
+
const runs = await db
|
|
676
|
+
.select({
|
|
677
|
+
id: heartbeatRuns.id,
|
|
678
|
+
createdAt: heartbeatRuns.createdAt,
|
|
679
|
+
usageJson: heartbeatRuns.usageJson,
|
|
680
|
+
resultJson: heartbeatRuns.resultJson,
|
|
681
|
+
error: heartbeatRuns.error,
|
|
682
|
+
})
|
|
683
|
+
.from(heartbeatRuns)
|
|
684
|
+
.where(and(eq(heartbeatRuns.agentId, agent.id), eq(heartbeatRuns.sessionIdAfter, sessionId)))
|
|
685
|
+
.orderBy(desc(heartbeatRuns.createdAt))
|
|
686
|
+
.limit(fetchLimit);
|
|
687
|
+
if (runs.length === 0) {
|
|
688
|
+
return {
|
|
689
|
+
rotate: false,
|
|
690
|
+
reason: null,
|
|
691
|
+
handoffMarkdown: null,
|
|
692
|
+
previousRunId: null,
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
const latestRun = runs[0] ?? null;
|
|
696
|
+
const oldestRun = policy.maxSessionAgeHours > 0
|
|
697
|
+
? await getOldestRunForSession(agent.id, sessionId)
|
|
698
|
+
: runs[runs.length - 1] ?? latestRun;
|
|
699
|
+
const latestRawUsage = readRawUsageTotals(latestRun?.usageJson);
|
|
700
|
+
const sessionAgeHours = latestRun && oldestRun
|
|
701
|
+
? Math.max(0, (new Date(latestRun.createdAt).getTime() - new Date(oldestRun.createdAt).getTime()) / (1000 * 60 * 60))
|
|
702
|
+
: 0;
|
|
703
|
+
let reason = null;
|
|
704
|
+
if (policy.maxSessionRuns > 0 && runs.length > policy.maxSessionRuns) {
|
|
705
|
+
reason = `session exceeded ${policy.maxSessionRuns} runs`;
|
|
706
|
+
}
|
|
707
|
+
else if (policy.maxRawInputTokens > 0 &&
|
|
708
|
+
latestRawUsage &&
|
|
709
|
+
latestRawUsage.inputTokens >= policy.maxRawInputTokens) {
|
|
710
|
+
reason =
|
|
711
|
+
`session raw input reached ${formatCount(latestRawUsage.inputTokens)} tokens ` +
|
|
712
|
+
`(threshold ${formatCount(policy.maxRawInputTokens)})`;
|
|
713
|
+
}
|
|
714
|
+
else if (policy.maxSessionAgeHours > 0 && sessionAgeHours >= policy.maxSessionAgeHours) {
|
|
715
|
+
reason = `session age reached ${Math.floor(sessionAgeHours)} hours`;
|
|
716
|
+
}
|
|
717
|
+
if (!reason || !latestRun) {
|
|
718
|
+
return {
|
|
719
|
+
rotate: false,
|
|
720
|
+
reason: null,
|
|
721
|
+
handoffMarkdown: null,
|
|
722
|
+
previousRunId: latestRun?.id ?? null,
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
const latestSummary = summarizeHeartbeatRunResultJson(latestRun.resultJson);
|
|
726
|
+
const latestTextSummary = readNonEmptyString(latestSummary?.summary) ??
|
|
727
|
+
readNonEmptyString(latestSummary?.result) ??
|
|
728
|
+
readNonEmptyString(latestSummary?.message) ??
|
|
729
|
+
readNonEmptyString(latestRun.error);
|
|
730
|
+
const handoffMarkdown = [
|
|
731
|
+
"FideliOS session handoff:",
|
|
732
|
+
`- Previous session: ${sessionId}`,
|
|
733
|
+
issueId ? `- Issue: ${issueId}` : "",
|
|
734
|
+
`- Rotation reason: ${reason}`,
|
|
735
|
+
latestTextSummary ? `- Last run summary: ${latestTextSummary}` : "",
|
|
736
|
+
"Continue from the current task state. Rebuild only the minimum context you need.",
|
|
737
|
+
]
|
|
738
|
+
.filter(Boolean)
|
|
739
|
+
.join("\n");
|
|
740
|
+
return {
|
|
741
|
+
rotate: true,
|
|
742
|
+
reason,
|
|
743
|
+
handoffMarkdown,
|
|
744
|
+
previousRunId: latestRun.id,
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
async function resolveSessionBeforeForWakeup(agent, taskKey) {
|
|
748
|
+
if (taskKey) {
|
|
749
|
+
const codec = getAdapterSessionCodec(agent.adapterType);
|
|
750
|
+
const existingTaskSession = await getTaskSession(agent.companyId, agent.id, agent.adapterType, taskKey);
|
|
751
|
+
const parsedParams = normalizeSessionParams(codec.deserialize(existingTaskSession?.sessionParamsJson ?? null));
|
|
752
|
+
return truncateDisplayId(existingTaskSession?.sessionDisplayId ??
|
|
753
|
+
(codec.getDisplayId ? codec.getDisplayId(parsedParams) : null) ??
|
|
754
|
+
readNonEmptyString(parsedParams?.sessionId));
|
|
755
|
+
}
|
|
756
|
+
const runtimeForRun = await getRuntimeState(agent.id);
|
|
757
|
+
return runtimeForRun?.sessionId ?? null;
|
|
758
|
+
}
|
|
759
|
+
async function resolveExplicitResumeSessionOverride(agent, payload, taskKey) {
|
|
760
|
+
const resumeFromRunId = readNonEmptyString(payload?.resumeFromRunId);
|
|
761
|
+
if (!resumeFromRunId)
|
|
762
|
+
return null;
|
|
763
|
+
const resumeRun = await db
|
|
764
|
+
.select({
|
|
765
|
+
id: heartbeatRuns.id,
|
|
766
|
+
contextSnapshot: heartbeatRuns.contextSnapshot,
|
|
767
|
+
sessionIdBefore: heartbeatRuns.sessionIdBefore,
|
|
768
|
+
sessionIdAfter: heartbeatRuns.sessionIdAfter,
|
|
769
|
+
})
|
|
770
|
+
.from(heartbeatRuns)
|
|
771
|
+
.where(and(eq(heartbeatRuns.id, resumeFromRunId), eq(heartbeatRuns.companyId, agent.companyId), eq(heartbeatRuns.agentId, agent.id)))
|
|
772
|
+
.then((rows) => rows[0] ?? null);
|
|
773
|
+
if (!resumeRun)
|
|
774
|
+
return null;
|
|
775
|
+
const resumeContext = parseObject(resumeRun.contextSnapshot);
|
|
776
|
+
const resumeTaskKey = deriveTaskKey(resumeContext, null) ?? taskKey;
|
|
777
|
+
const resumeTaskSession = resumeTaskKey
|
|
778
|
+
? await getTaskSession(agent.companyId, agent.id, agent.adapterType, resumeTaskKey)
|
|
779
|
+
: null;
|
|
780
|
+
const sessionCodec = getAdapterSessionCodec(agent.adapterType);
|
|
781
|
+
const sessionOverride = buildExplicitResumeSessionOverride({
|
|
782
|
+
resumeFromRunId,
|
|
783
|
+
resumeRunSessionIdBefore: resumeRun.sessionIdBefore,
|
|
784
|
+
resumeRunSessionIdAfter: resumeRun.sessionIdAfter,
|
|
785
|
+
taskSession: resumeTaskSession,
|
|
786
|
+
sessionCodec,
|
|
787
|
+
});
|
|
788
|
+
if (!sessionOverride)
|
|
789
|
+
return null;
|
|
790
|
+
return {
|
|
791
|
+
resumeFromRunId,
|
|
792
|
+
taskKey: resumeTaskKey,
|
|
793
|
+
issueId: readNonEmptyString(resumeContext.issueId),
|
|
794
|
+
taskId: readNonEmptyString(resumeContext.taskId) ?? readNonEmptyString(resumeContext.issueId),
|
|
795
|
+
sessionDisplayId: sessionOverride.sessionDisplayId,
|
|
796
|
+
sessionParams: sessionOverride.sessionParams,
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
async function resolveWorkspaceForRun(agent, context, previousSessionParams, opts) {
|
|
800
|
+
const issueId = readNonEmptyString(context.issueId);
|
|
801
|
+
const contextProjectId = readNonEmptyString(context.projectId);
|
|
802
|
+
const contextProjectWorkspaceId = readNonEmptyString(context.projectWorkspaceId);
|
|
803
|
+
const issueProjectRef = issueId
|
|
804
|
+
? await db
|
|
805
|
+
.select({
|
|
806
|
+
projectId: issues.projectId,
|
|
807
|
+
projectWorkspaceId: issues.projectWorkspaceId,
|
|
808
|
+
})
|
|
809
|
+
.from(issues)
|
|
810
|
+
.where(and(eq(issues.id, issueId), eq(issues.companyId, agent.companyId)))
|
|
811
|
+
.then((rows) => rows[0] ?? null)
|
|
812
|
+
: null;
|
|
813
|
+
const issueProjectId = issueProjectRef?.projectId ?? null;
|
|
814
|
+
const preferredProjectWorkspaceId = issueProjectRef?.projectWorkspaceId ?? contextProjectWorkspaceId ?? null;
|
|
815
|
+
const resolvedProjectId = issueProjectId ?? contextProjectId;
|
|
816
|
+
const useProjectWorkspace = opts?.useProjectWorkspace !== false;
|
|
817
|
+
const workspaceProjectId = useProjectWorkspace ? resolvedProjectId : null;
|
|
818
|
+
const unorderedProjectWorkspaceRows = workspaceProjectId
|
|
819
|
+
? await db
|
|
820
|
+
.select()
|
|
821
|
+
.from(projectWorkspaces)
|
|
822
|
+
.where(and(eq(projectWorkspaces.companyId, agent.companyId), eq(projectWorkspaces.projectId, workspaceProjectId)))
|
|
823
|
+
.orderBy(asc(projectWorkspaces.createdAt), asc(projectWorkspaces.id))
|
|
824
|
+
: [];
|
|
825
|
+
const projectWorkspaceRows = prioritizeProjectWorkspaceCandidatesForRun(unorderedProjectWorkspaceRows, preferredProjectWorkspaceId);
|
|
826
|
+
const workspaceHints = projectWorkspaceRows.map((workspace) => ({
|
|
827
|
+
workspaceId: workspace.id,
|
|
828
|
+
cwd: readNonEmptyString(workspace.cwd),
|
|
829
|
+
repoUrl: readNonEmptyString(workspace.repoUrl),
|
|
830
|
+
repoRef: readNonEmptyString(workspace.repoRef),
|
|
831
|
+
}));
|
|
832
|
+
if (projectWorkspaceRows.length > 0) {
|
|
833
|
+
const preferredWorkspace = preferredProjectWorkspaceId
|
|
834
|
+
? projectWorkspaceRows.find((workspace) => workspace.id === preferredProjectWorkspaceId) ?? null
|
|
835
|
+
: null;
|
|
836
|
+
const missingProjectCwds = [];
|
|
837
|
+
let hasConfiguredProjectCwd = false;
|
|
838
|
+
let preferredWorkspaceWarning = null;
|
|
839
|
+
if (preferredProjectWorkspaceId && !preferredWorkspace) {
|
|
840
|
+
preferredWorkspaceWarning =
|
|
841
|
+
`Selected project workspace "${preferredProjectWorkspaceId}" is not available on this project.`;
|
|
842
|
+
}
|
|
843
|
+
for (const workspace of projectWorkspaceRows) {
|
|
844
|
+
let projectCwd = readNonEmptyString(workspace.cwd);
|
|
845
|
+
let managedWorkspaceWarning = null;
|
|
846
|
+
if (!projectCwd || projectCwd === REPO_ONLY_CWD_SENTINEL) {
|
|
847
|
+
try {
|
|
848
|
+
const managedWorkspace = await ensureManagedProjectWorkspace({
|
|
849
|
+
companyId: agent.companyId,
|
|
850
|
+
projectId: workspaceProjectId ?? resolvedProjectId ?? workspace.projectId,
|
|
851
|
+
repoUrl: readNonEmptyString(workspace.repoUrl),
|
|
852
|
+
});
|
|
853
|
+
projectCwd = managedWorkspace.cwd;
|
|
854
|
+
managedWorkspaceWarning = managedWorkspace.warning;
|
|
855
|
+
}
|
|
856
|
+
catch (error) {
|
|
857
|
+
if (preferredWorkspace?.id === workspace.id) {
|
|
858
|
+
preferredWorkspaceWarning = error instanceof Error ? error.message : String(error);
|
|
859
|
+
}
|
|
860
|
+
continue;
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
hasConfiguredProjectCwd = true;
|
|
864
|
+
const projectCwdExists = await fs
|
|
865
|
+
.stat(projectCwd)
|
|
866
|
+
.then((stats) => stats.isDirectory())
|
|
867
|
+
.catch(() => false);
|
|
868
|
+
if (projectCwdExists) {
|
|
869
|
+
return {
|
|
870
|
+
cwd: projectCwd,
|
|
871
|
+
source: "project_primary",
|
|
872
|
+
projectId: resolvedProjectId,
|
|
873
|
+
workspaceId: workspace.id,
|
|
874
|
+
repoUrl: workspace.repoUrl,
|
|
875
|
+
repoRef: workspace.repoRef,
|
|
876
|
+
workspaceHints,
|
|
877
|
+
warnings: [preferredWorkspaceWarning, managedWorkspaceWarning].filter((value) => Boolean(value)),
|
|
878
|
+
};
|
|
879
|
+
}
|
|
880
|
+
if (preferredWorkspace?.id === workspace.id) {
|
|
881
|
+
preferredWorkspaceWarning =
|
|
882
|
+
`Selected project workspace path "${projectCwd}" is not available yet.`;
|
|
883
|
+
}
|
|
884
|
+
missingProjectCwds.push(projectCwd);
|
|
885
|
+
}
|
|
886
|
+
const fallbackCwd = resolveDefaultAgentWorkspaceDir(agent.id);
|
|
887
|
+
await fs.mkdir(fallbackCwd, { recursive: true });
|
|
888
|
+
const warnings = [];
|
|
889
|
+
if (preferredWorkspaceWarning) {
|
|
890
|
+
warnings.push(preferredWorkspaceWarning);
|
|
891
|
+
}
|
|
892
|
+
if (missingProjectCwds.length > 0) {
|
|
893
|
+
const firstMissing = missingProjectCwds[0];
|
|
894
|
+
const extraMissingCount = Math.max(0, missingProjectCwds.length - 1);
|
|
895
|
+
warnings.push(extraMissingCount > 0
|
|
896
|
+
? `Project workspace path "${firstMissing}" and ${extraMissingCount} other configured path(s) are not available yet. Using fallback workspace "${fallbackCwd}" for this run.`
|
|
897
|
+
: `Project workspace path "${firstMissing}" is not available yet. Using fallback workspace "${fallbackCwd}" for this run.`);
|
|
898
|
+
}
|
|
899
|
+
else if (!hasConfiguredProjectCwd) {
|
|
900
|
+
warnings.push(`Project workspace has no local cwd configured. Using fallback workspace "${fallbackCwd}" for this run.`);
|
|
901
|
+
}
|
|
902
|
+
return {
|
|
903
|
+
cwd: fallbackCwd,
|
|
904
|
+
source: "project_primary",
|
|
905
|
+
projectId: resolvedProjectId,
|
|
906
|
+
workspaceId: projectWorkspaceRows[0]?.id ?? null,
|
|
907
|
+
repoUrl: projectWorkspaceRows[0]?.repoUrl ?? null,
|
|
908
|
+
repoRef: projectWorkspaceRows[0]?.repoRef ?? null,
|
|
909
|
+
workspaceHints,
|
|
910
|
+
warnings,
|
|
911
|
+
};
|
|
912
|
+
}
|
|
913
|
+
if (workspaceProjectId) {
|
|
914
|
+
const managedWorkspace = await ensureManagedProjectWorkspace({
|
|
915
|
+
companyId: agent.companyId,
|
|
916
|
+
projectId: workspaceProjectId,
|
|
917
|
+
repoUrl: null,
|
|
918
|
+
});
|
|
919
|
+
return {
|
|
920
|
+
cwd: managedWorkspace.cwd,
|
|
921
|
+
source: "project_primary",
|
|
922
|
+
projectId: resolvedProjectId,
|
|
923
|
+
workspaceId: null,
|
|
924
|
+
repoUrl: null,
|
|
925
|
+
repoRef: null,
|
|
926
|
+
workspaceHints,
|
|
927
|
+
warnings: managedWorkspace.warning ? [managedWorkspace.warning] : [],
|
|
928
|
+
};
|
|
929
|
+
}
|
|
930
|
+
const sessionCwd = readNonEmptyString(previousSessionParams?.cwd);
|
|
931
|
+
if (sessionCwd) {
|
|
932
|
+
const sessionCwdExists = await fs
|
|
933
|
+
.stat(sessionCwd)
|
|
934
|
+
.then((stats) => stats.isDirectory())
|
|
935
|
+
.catch(() => false);
|
|
936
|
+
if (sessionCwdExists) {
|
|
937
|
+
return {
|
|
938
|
+
cwd: sessionCwd,
|
|
939
|
+
source: "task_session",
|
|
940
|
+
projectId: resolvedProjectId,
|
|
941
|
+
workspaceId: readNonEmptyString(previousSessionParams?.workspaceId),
|
|
942
|
+
repoUrl: readNonEmptyString(previousSessionParams?.repoUrl),
|
|
943
|
+
repoRef: readNonEmptyString(previousSessionParams?.repoRef),
|
|
944
|
+
workspaceHints,
|
|
945
|
+
warnings: [],
|
|
946
|
+
};
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
const cwd = resolveDefaultAgentWorkspaceDir(agent.id);
|
|
950
|
+
await fs.mkdir(cwd, { recursive: true });
|
|
951
|
+
const warnings = [];
|
|
952
|
+
if (sessionCwd) {
|
|
953
|
+
warnings.push(`Saved session workspace "${sessionCwd}" is not available. Using fallback workspace "${cwd}" for this run.`);
|
|
954
|
+
}
|
|
955
|
+
else if (resolvedProjectId) {
|
|
956
|
+
warnings.push(`No project workspace directory is currently available for this issue. Using fallback workspace "${cwd}" for this run.`);
|
|
957
|
+
}
|
|
958
|
+
else {
|
|
959
|
+
warnings.push(`No project or prior session workspace was available. Using fallback workspace "${cwd}" for this run.`);
|
|
960
|
+
}
|
|
961
|
+
return {
|
|
962
|
+
cwd,
|
|
963
|
+
source: "agent_home",
|
|
964
|
+
projectId: resolvedProjectId,
|
|
965
|
+
workspaceId: null,
|
|
966
|
+
repoUrl: null,
|
|
967
|
+
repoRef: null,
|
|
968
|
+
workspaceHints,
|
|
969
|
+
warnings,
|
|
970
|
+
};
|
|
971
|
+
}
|
|
972
|
+
async function upsertTaskSession(input) {
|
|
973
|
+
const existing = await getTaskSession(input.companyId, input.agentId, input.adapterType, input.taskKey);
|
|
974
|
+
if (existing) {
|
|
975
|
+
return db
|
|
976
|
+
.update(agentTaskSessions)
|
|
977
|
+
.set({
|
|
978
|
+
sessionParamsJson: input.sessionParamsJson,
|
|
979
|
+
sessionDisplayId: input.sessionDisplayId,
|
|
980
|
+
lastRunId: input.lastRunId,
|
|
981
|
+
lastError: input.lastError,
|
|
982
|
+
updatedAt: new Date(),
|
|
983
|
+
})
|
|
984
|
+
.where(eq(agentTaskSessions.id, existing.id))
|
|
985
|
+
.returning()
|
|
986
|
+
.then((rows) => rows[0] ?? null);
|
|
987
|
+
}
|
|
988
|
+
return db
|
|
989
|
+
.insert(agentTaskSessions)
|
|
990
|
+
.values({
|
|
991
|
+
companyId: input.companyId,
|
|
992
|
+
agentId: input.agentId,
|
|
993
|
+
adapterType: input.adapterType,
|
|
994
|
+
taskKey: input.taskKey,
|
|
995
|
+
sessionParamsJson: input.sessionParamsJson,
|
|
996
|
+
sessionDisplayId: input.sessionDisplayId,
|
|
997
|
+
lastRunId: input.lastRunId,
|
|
998
|
+
lastError: input.lastError,
|
|
999
|
+
})
|
|
1000
|
+
.returning()
|
|
1001
|
+
.then((rows) => rows[0] ?? null);
|
|
1002
|
+
}
|
|
1003
|
+
async function clearTaskSessions(companyId, agentId, opts) {
|
|
1004
|
+
const conditions = [
|
|
1005
|
+
eq(agentTaskSessions.companyId, companyId),
|
|
1006
|
+
eq(agentTaskSessions.agentId, agentId),
|
|
1007
|
+
];
|
|
1008
|
+
if (opts?.taskKey) {
|
|
1009
|
+
conditions.push(eq(agentTaskSessions.taskKey, opts.taskKey));
|
|
1010
|
+
}
|
|
1011
|
+
if (opts?.adapterType) {
|
|
1012
|
+
conditions.push(eq(agentTaskSessions.adapterType, opts.adapterType));
|
|
1013
|
+
}
|
|
1014
|
+
return db
|
|
1015
|
+
.delete(agentTaskSessions)
|
|
1016
|
+
.where(and(...conditions))
|
|
1017
|
+
.returning()
|
|
1018
|
+
.then((rows) => rows.length);
|
|
1019
|
+
}
|
|
1020
|
+
async function ensureRuntimeState(agent) {
|
|
1021
|
+
const existing = await getRuntimeState(agent.id);
|
|
1022
|
+
if (existing)
|
|
1023
|
+
return existing;
|
|
1024
|
+
return db
|
|
1025
|
+
.insert(agentRuntimeState)
|
|
1026
|
+
.values({
|
|
1027
|
+
agentId: agent.id,
|
|
1028
|
+
companyId: agent.companyId,
|
|
1029
|
+
adapterType: agent.adapterType,
|
|
1030
|
+
stateJson: {},
|
|
1031
|
+
})
|
|
1032
|
+
.returning()
|
|
1033
|
+
.then((rows) => rows[0]);
|
|
1034
|
+
}
|
|
1035
|
+
async function setRunStatus(runId, status, patch) {
|
|
1036
|
+
const updated = await db
|
|
1037
|
+
.update(heartbeatRuns)
|
|
1038
|
+
.set({ status, ...patch, updatedAt: new Date() })
|
|
1039
|
+
.where(eq(heartbeatRuns.id, runId))
|
|
1040
|
+
.returning()
|
|
1041
|
+
.then((rows) => rows[0] ?? null);
|
|
1042
|
+
if (updated) {
|
|
1043
|
+
publishLiveEvent({
|
|
1044
|
+
companyId: updated.companyId,
|
|
1045
|
+
type: "heartbeat.run.status",
|
|
1046
|
+
payload: {
|
|
1047
|
+
runId: updated.id,
|
|
1048
|
+
agentId: updated.agentId,
|
|
1049
|
+
status: updated.status,
|
|
1050
|
+
invocationSource: updated.invocationSource,
|
|
1051
|
+
triggerDetail: updated.triggerDetail,
|
|
1052
|
+
error: updated.error ?? null,
|
|
1053
|
+
errorCode: updated.errorCode ?? null,
|
|
1054
|
+
startedAt: updated.startedAt ? new Date(updated.startedAt).toISOString() : null,
|
|
1055
|
+
finishedAt: updated.finishedAt ? new Date(updated.finishedAt).toISOString() : null,
|
|
1056
|
+
},
|
|
1057
|
+
});
|
|
1058
|
+
}
|
|
1059
|
+
return updated;
|
|
1060
|
+
}
|
|
1061
|
+
async function setWakeupStatus(wakeupRequestId, status, patch) {
|
|
1062
|
+
if (!wakeupRequestId)
|
|
1063
|
+
return;
|
|
1064
|
+
await db
|
|
1065
|
+
.update(agentWakeupRequests)
|
|
1066
|
+
.set({ status, ...patch, updatedAt: new Date() })
|
|
1067
|
+
.where(eq(agentWakeupRequests.id, wakeupRequestId));
|
|
1068
|
+
}
|
|
1069
|
+
async function appendRunEvent(run, seq, event) {
|
|
1070
|
+
const currentUserRedactionOptions = await getCurrentUserRedactionOptions();
|
|
1071
|
+
const sanitizedMessage = event.message
|
|
1072
|
+
? redactCurrentUserText(event.message, currentUserRedactionOptions)
|
|
1073
|
+
: event.message;
|
|
1074
|
+
const sanitizedPayload = event.payload
|
|
1075
|
+
? redactCurrentUserValue(event.payload, currentUserRedactionOptions)
|
|
1076
|
+
: event.payload;
|
|
1077
|
+
await db.insert(heartbeatRunEvents).values({
|
|
1078
|
+
companyId: run.companyId,
|
|
1079
|
+
runId: run.id,
|
|
1080
|
+
agentId: run.agentId,
|
|
1081
|
+
seq,
|
|
1082
|
+
eventType: event.eventType,
|
|
1083
|
+
stream: event.stream,
|
|
1084
|
+
level: event.level,
|
|
1085
|
+
color: event.color,
|
|
1086
|
+
message: sanitizedMessage,
|
|
1087
|
+
payload: sanitizedPayload,
|
|
1088
|
+
});
|
|
1089
|
+
publishLiveEvent({
|
|
1090
|
+
companyId: run.companyId,
|
|
1091
|
+
type: "heartbeat.run.event",
|
|
1092
|
+
payload: {
|
|
1093
|
+
runId: run.id,
|
|
1094
|
+
agentId: run.agentId,
|
|
1095
|
+
seq,
|
|
1096
|
+
eventType: event.eventType,
|
|
1097
|
+
stream: event.stream ?? null,
|
|
1098
|
+
level: event.level ?? null,
|
|
1099
|
+
color: event.color ?? null,
|
|
1100
|
+
message: sanitizedMessage ?? null,
|
|
1101
|
+
payload: sanitizedPayload ?? null,
|
|
1102
|
+
},
|
|
1103
|
+
});
|
|
1104
|
+
}
|
|
1105
|
+
async function nextRunEventSeq(runId) {
|
|
1106
|
+
const [row] = await db
|
|
1107
|
+
.select({ maxSeq: sql `max(${heartbeatRunEvents.seq})` })
|
|
1108
|
+
.from(heartbeatRunEvents)
|
|
1109
|
+
.where(eq(heartbeatRunEvents.runId, runId));
|
|
1110
|
+
return Number(row?.maxSeq ?? 0) + 1;
|
|
1111
|
+
}
|
|
1112
|
+
async function persistRunProcessMetadata(runId, meta) {
|
|
1113
|
+
const startedAt = new Date(meta.startedAt);
|
|
1114
|
+
return db
|
|
1115
|
+
.update(heartbeatRuns)
|
|
1116
|
+
.set({
|
|
1117
|
+
processPid: meta.pid,
|
|
1118
|
+
processStartedAt: Number.isNaN(startedAt.getTime()) ? new Date() : startedAt,
|
|
1119
|
+
updatedAt: new Date(),
|
|
1120
|
+
})
|
|
1121
|
+
.where(eq(heartbeatRuns.id, runId))
|
|
1122
|
+
.returning()
|
|
1123
|
+
.then((rows) => rows[0] ?? null);
|
|
1124
|
+
}
|
|
1125
|
+
async function clearDetachedRunWarning(runId) {
|
|
1126
|
+
const updated = await db
|
|
1127
|
+
.update(heartbeatRuns)
|
|
1128
|
+
.set({
|
|
1129
|
+
error: null,
|
|
1130
|
+
errorCode: null,
|
|
1131
|
+
updatedAt: new Date(),
|
|
1132
|
+
})
|
|
1133
|
+
.where(and(eq(heartbeatRuns.id, runId), eq(heartbeatRuns.status, "running"), eq(heartbeatRuns.errorCode, DETACHED_PROCESS_ERROR_CODE)))
|
|
1134
|
+
.returning()
|
|
1135
|
+
.then((rows) => rows[0] ?? null);
|
|
1136
|
+
if (!updated)
|
|
1137
|
+
return null;
|
|
1138
|
+
await appendRunEvent(updated, await nextRunEventSeq(updated.id), {
|
|
1139
|
+
eventType: "lifecycle",
|
|
1140
|
+
stream: "system",
|
|
1141
|
+
level: "info",
|
|
1142
|
+
message: "Detached child process reported activity; cleared detached warning",
|
|
1143
|
+
});
|
|
1144
|
+
return updated;
|
|
1145
|
+
}
|
|
1146
|
+
async function enqueueProcessLossRetry(run, agent, now) {
|
|
1147
|
+
const contextSnapshot = parseObject(run.contextSnapshot);
|
|
1148
|
+
const issueId = readNonEmptyString(contextSnapshot.issueId);
|
|
1149
|
+
const taskKey = deriveTaskKey(contextSnapshot, null);
|
|
1150
|
+
const sessionBefore = await resolveSessionBeforeForWakeup(agent, taskKey);
|
|
1151
|
+
const retryContextSnapshot = {
|
|
1152
|
+
...contextSnapshot,
|
|
1153
|
+
retryOfRunId: run.id,
|
|
1154
|
+
wakeReason: "process_lost_retry",
|
|
1155
|
+
retryReason: "process_lost",
|
|
1156
|
+
};
|
|
1157
|
+
const queued = await db.transaction(async (tx) => {
|
|
1158
|
+
const wakeupRequest = await tx
|
|
1159
|
+
.insert(agentWakeupRequests)
|
|
1160
|
+
.values({
|
|
1161
|
+
companyId: run.companyId,
|
|
1162
|
+
agentId: run.agentId,
|
|
1163
|
+
source: "automation",
|
|
1164
|
+
triggerDetail: "system",
|
|
1165
|
+
reason: "process_lost_retry",
|
|
1166
|
+
payload: {
|
|
1167
|
+
...(issueId ? { issueId } : {}),
|
|
1168
|
+
retryOfRunId: run.id,
|
|
1169
|
+
},
|
|
1170
|
+
status: "queued",
|
|
1171
|
+
requestedByActorType: "system",
|
|
1172
|
+
requestedByActorId: null,
|
|
1173
|
+
updatedAt: now,
|
|
1174
|
+
})
|
|
1175
|
+
.returning()
|
|
1176
|
+
.then((rows) => rows[0]);
|
|
1177
|
+
const retryRun = await tx
|
|
1178
|
+
.insert(heartbeatRuns)
|
|
1179
|
+
.values({
|
|
1180
|
+
companyId: run.companyId,
|
|
1181
|
+
agentId: run.agentId,
|
|
1182
|
+
invocationSource: "automation",
|
|
1183
|
+
triggerDetail: "system",
|
|
1184
|
+
status: "queued",
|
|
1185
|
+
wakeupRequestId: wakeupRequest.id,
|
|
1186
|
+
contextSnapshot: retryContextSnapshot,
|
|
1187
|
+
sessionIdBefore: sessionBefore,
|
|
1188
|
+
retryOfRunId: run.id,
|
|
1189
|
+
processLossRetryCount: (run.processLossRetryCount ?? 0) + 1,
|
|
1190
|
+
updatedAt: now,
|
|
1191
|
+
})
|
|
1192
|
+
.returning()
|
|
1193
|
+
.then((rows) => rows[0]);
|
|
1194
|
+
await tx
|
|
1195
|
+
.update(agentWakeupRequests)
|
|
1196
|
+
.set({
|
|
1197
|
+
runId: retryRun.id,
|
|
1198
|
+
updatedAt: now,
|
|
1199
|
+
})
|
|
1200
|
+
.where(eq(agentWakeupRequests.id, wakeupRequest.id));
|
|
1201
|
+
if (issueId) {
|
|
1202
|
+
await tx
|
|
1203
|
+
.update(issues)
|
|
1204
|
+
.set({
|
|
1205
|
+
executionRunId: retryRun.id,
|
|
1206
|
+
executionAgentNameKey: normalizeAgentNameKey(agent.name),
|
|
1207
|
+
executionLockedAt: now,
|
|
1208
|
+
updatedAt: now,
|
|
1209
|
+
})
|
|
1210
|
+
.where(and(eq(issues.id, issueId), eq(issues.companyId, run.companyId), eq(issues.executionRunId, run.id)));
|
|
1211
|
+
}
|
|
1212
|
+
return retryRun;
|
|
1213
|
+
});
|
|
1214
|
+
publishLiveEvent({
|
|
1215
|
+
companyId: queued.companyId,
|
|
1216
|
+
type: "heartbeat.run.queued",
|
|
1217
|
+
payload: {
|
|
1218
|
+
runId: queued.id,
|
|
1219
|
+
agentId: queued.agentId,
|
|
1220
|
+
invocationSource: queued.invocationSource,
|
|
1221
|
+
triggerDetail: queued.triggerDetail,
|
|
1222
|
+
wakeupRequestId: queued.wakeupRequestId,
|
|
1223
|
+
},
|
|
1224
|
+
});
|
|
1225
|
+
await appendRunEvent(queued, 1, {
|
|
1226
|
+
eventType: "lifecycle",
|
|
1227
|
+
stream: "system",
|
|
1228
|
+
level: "warn",
|
|
1229
|
+
message: "Queued automatic retry after orphaned child process was confirmed dead",
|
|
1230
|
+
payload: {
|
|
1231
|
+
retryOfRunId: run.id,
|
|
1232
|
+
},
|
|
1233
|
+
});
|
|
1234
|
+
return queued;
|
|
1235
|
+
}
|
|
1236
|
+
function parseHeartbeatPolicy(agent) {
|
|
1237
|
+
const runtimeConfig = parseObject(agent.runtimeConfig);
|
|
1238
|
+
const heartbeat = parseObject(runtimeConfig.heartbeat);
|
|
1239
|
+
return {
|
|
1240
|
+
enabled: asBoolean(heartbeat.enabled, true),
|
|
1241
|
+
intervalSec: Math.max(0, asNumber(heartbeat.intervalSec, 0)),
|
|
1242
|
+
wakeOnDemand: asBoolean(heartbeat.wakeOnDemand ?? heartbeat.wakeOnAssignment ?? heartbeat.wakeOnOnDemand ?? heartbeat.wakeOnAutomation, true),
|
|
1243
|
+
maxConcurrentRuns: normalizeMaxConcurrentRuns(heartbeat.maxConcurrentRuns),
|
|
1244
|
+
};
|
|
1245
|
+
}
|
|
1246
|
+
async function countRunningRunsForAgent(agentId) {
|
|
1247
|
+
const [{ count }] = await db
|
|
1248
|
+
.select({ count: sql `count(*)` })
|
|
1249
|
+
.from(heartbeatRuns)
|
|
1250
|
+
.where(and(eq(heartbeatRuns.agentId, agentId), eq(heartbeatRuns.status, "running")));
|
|
1251
|
+
return Number(count ?? 0);
|
|
1252
|
+
}
|
|
1253
|
+
async function claimQueuedRun(run) {
|
|
1254
|
+
if (run.status !== "queued")
|
|
1255
|
+
return run;
|
|
1256
|
+
const agent = await getAgent(run.agentId);
|
|
1257
|
+
if (!agent) {
|
|
1258
|
+
await cancelRunInternal(run.id, "Cancelled because the agent no longer exists");
|
|
1259
|
+
return null;
|
|
1260
|
+
}
|
|
1261
|
+
if (agent.status === "paused" || agent.status === "terminated" || agent.status === "pending_approval") {
|
|
1262
|
+
await cancelRunInternal(run.id, "Cancelled because the agent is not invokable");
|
|
1263
|
+
return null;
|
|
1264
|
+
}
|
|
1265
|
+
const context = parseObject(run.contextSnapshot);
|
|
1266
|
+
const budgetBlock = await budgets.getInvocationBlock(run.companyId, run.agentId, {
|
|
1267
|
+
issueId: readNonEmptyString(context.issueId),
|
|
1268
|
+
projectId: readNonEmptyString(context.projectId),
|
|
1269
|
+
});
|
|
1270
|
+
if (budgetBlock) {
|
|
1271
|
+
await cancelRunInternal(run.id, budgetBlock.reason);
|
|
1272
|
+
return null;
|
|
1273
|
+
}
|
|
1274
|
+
const claimedAt = new Date();
|
|
1275
|
+
const claimed = await db
|
|
1276
|
+
.update(heartbeatRuns)
|
|
1277
|
+
.set({
|
|
1278
|
+
status: "running",
|
|
1279
|
+
startedAt: run.startedAt ?? claimedAt,
|
|
1280
|
+
updatedAt: claimedAt,
|
|
1281
|
+
})
|
|
1282
|
+
.where(and(eq(heartbeatRuns.id, run.id), eq(heartbeatRuns.status, "queued")))
|
|
1283
|
+
.returning()
|
|
1284
|
+
.then((rows) => rows[0] ?? null);
|
|
1285
|
+
if (!claimed)
|
|
1286
|
+
return null;
|
|
1287
|
+
publishLiveEvent({
|
|
1288
|
+
companyId: claimed.companyId,
|
|
1289
|
+
type: "heartbeat.run.status",
|
|
1290
|
+
payload: {
|
|
1291
|
+
runId: claimed.id,
|
|
1292
|
+
agentId: claimed.agentId,
|
|
1293
|
+
status: claimed.status,
|
|
1294
|
+
invocationSource: claimed.invocationSource,
|
|
1295
|
+
triggerDetail: claimed.triggerDetail,
|
|
1296
|
+
error: claimed.error ?? null,
|
|
1297
|
+
errorCode: claimed.errorCode ?? null,
|
|
1298
|
+
startedAt: claimed.startedAt ? new Date(claimed.startedAt).toISOString() : null,
|
|
1299
|
+
finishedAt: claimed.finishedAt ? new Date(claimed.finishedAt).toISOString() : null,
|
|
1300
|
+
},
|
|
1301
|
+
});
|
|
1302
|
+
await setWakeupStatus(claimed.wakeupRequestId, "claimed", { claimedAt });
|
|
1303
|
+
return claimed;
|
|
1304
|
+
}
|
|
1305
|
+
async function finalizeAgentStatus(agentId, outcome) {
|
|
1306
|
+
const existing = await getAgent(agentId);
|
|
1307
|
+
if (!existing)
|
|
1308
|
+
return;
|
|
1309
|
+
if (existing.status === "paused" || existing.status === "terminated") {
|
|
1310
|
+
return;
|
|
1311
|
+
}
|
|
1312
|
+
const runningCount = await countRunningRunsForAgent(agentId);
|
|
1313
|
+
const nextStatus = runningCount > 0
|
|
1314
|
+
? "running"
|
|
1315
|
+
: outcome === "succeeded" || outcome === "cancelled"
|
|
1316
|
+
? "idle"
|
|
1317
|
+
: "error";
|
|
1318
|
+
const updated = await db
|
|
1319
|
+
.update(agents)
|
|
1320
|
+
.set({
|
|
1321
|
+
status: nextStatus,
|
|
1322
|
+
lastHeartbeatAt: new Date(),
|
|
1323
|
+
updatedAt: new Date(),
|
|
1324
|
+
})
|
|
1325
|
+
.where(eq(agents.id, agentId))
|
|
1326
|
+
.returning()
|
|
1327
|
+
.then((rows) => rows[0] ?? null);
|
|
1328
|
+
if (updated) {
|
|
1329
|
+
publishLiveEvent({
|
|
1330
|
+
companyId: updated.companyId,
|
|
1331
|
+
type: "agent.status",
|
|
1332
|
+
payload: {
|
|
1333
|
+
agentId: updated.id,
|
|
1334
|
+
status: updated.status,
|
|
1335
|
+
lastHeartbeatAt: updated.lastHeartbeatAt
|
|
1336
|
+
? new Date(updated.lastHeartbeatAt).toISOString()
|
|
1337
|
+
: null,
|
|
1338
|
+
outcome,
|
|
1339
|
+
},
|
|
1340
|
+
});
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
async function reapOrphanedRuns(opts) {
|
|
1344
|
+
const staleThresholdMs = opts?.staleThresholdMs ?? 0;
|
|
1345
|
+
const now = new Date();
|
|
1346
|
+
// Find all runs stuck in "running" state (queued runs are legitimately waiting; resumeQueuedRuns handles them)
|
|
1347
|
+
const activeRuns = await db
|
|
1348
|
+
.select({
|
|
1349
|
+
run: heartbeatRuns,
|
|
1350
|
+
adapterType: agents.adapterType,
|
|
1351
|
+
})
|
|
1352
|
+
.from(heartbeatRuns)
|
|
1353
|
+
.innerJoin(agents, eq(heartbeatRuns.agentId, agents.id))
|
|
1354
|
+
.where(eq(heartbeatRuns.status, "running"));
|
|
1355
|
+
const reaped = [];
|
|
1356
|
+
for (const { run, adapterType } of activeRuns) {
|
|
1357
|
+
if (runningProcesses.has(run.id) || activeRunExecutions.has(run.id))
|
|
1358
|
+
continue;
|
|
1359
|
+
// Apply staleness threshold to avoid false positives
|
|
1360
|
+
if (staleThresholdMs > 0) {
|
|
1361
|
+
const refTime = run.updatedAt ? new Date(run.updatedAt).getTime() : 0;
|
|
1362
|
+
if (now.getTime() - refTime < staleThresholdMs)
|
|
1363
|
+
continue;
|
|
1364
|
+
}
|
|
1365
|
+
const tracksLocalChild = isTrackedLocalChildProcessAdapter(adapterType);
|
|
1366
|
+
if (tracksLocalChild && run.processPid && isProcessAlive(run.processPid)) {
|
|
1367
|
+
if (run.errorCode !== DETACHED_PROCESS_ERROR_CODE) {
|
|
1368
|
+
const detachedMessage = `Lost in-memory process handle, but child pid ${run.processPid} is still alive`;
|
|
1369
|
+
const detachedRun = await setRunStatus(run.id, "running", {
|
|
1370
|
+
error: detachedMessage,
|
|
1371
|
+
errorCode: DETACHED_PROCESS_ERROR_CODE,
|
|
1372
|
+
});
|
|
1373
|
+
if (detachedRun) {
|
|
1374
|
+
await appendRunEvent(detachedRun, await nextRunEventSeq(detachedRun.id), {
|
|
1375
|
+
eventType: "lifecycle",
|
|
1376
|
+
stream: "system",
|
|
1377
|
+
level: "warn",
|
|
1378
|
+
message: detachedMessage,
|
|
1379
|
+
payload: {
|
|
1380
|
+
processPid: run.processPid,
|
|
1381
|
+
},
|
|
1382
|
+
});
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
continue;
|
|
1386
|
+
}
|
|
1387
|
+
const shouldRetry = tracksLocalChild && !!run.processPid && (run.processLossRetryCount ?? 0) < 1;
|
|
1388
|
+
const baseMessage = run.processPid
|
|
1389
|
+
? `Process lost -- child pid ${run.processPid} is no longer running`
|
|
1390
|
+
: "Process lost -- server may have restarted";
|
|
1391
|
+
let finalizedRun = await setRunStatus(run.id, "failed", {
|
|
1392
|
+
error: shouldRetry ? `${baseMessage}; retrying once` : baseMessage,
|
|
1393
|
+
errorCode: "process_lost",
|
|
1394
|
+
finishedAt: now,
|
|
1395
|
+
});
|
|
1396
|
+
await setWakeupStatus(run.wakeupRequestId, "failed", {
|
|
1397
|
+
finishedAt: now,
|
|
1398
|
+
error: shouldRetry ? `${baseMessage}; retrying once` : baseMessage,
|
|
1399
|
+
});
|
|
1400
|
+
if (!finalizedRun)
|
|
1401
|
+
finalizedRun = await getRun(run.id);
|
|
1402
|
+
if (!finalizedRun)
|
|
1403
|
+
continue;
|
|
1404
|
+
let retriedRun = null;
|
|
1405
|
+
if (shouldRetry) {
|
|
1406
|
+
const agent = await getAgent(run.agentId);
|
|
1407
|
+
if (agent) {
|
|
1408
|
+
retriedRun = await enqueueProcessLossRetry(finalizedRun, agent, now);
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
else {
|
|
1412
|
+
await releaseIssueExecutionAndPromote(finalizedRun);
|
|
1413
|
+
}
|
|
1414
|
+
await appendRunEvent(finalizedRun, await nextRunEventSeq(finalizedRun.id), {
|
|
1415
|
+
eventType: "lifecycle",
|
|
1416
|
+
stream: "system",
|
|
1417
|
+
level: "error",
|
|
1418
|
+
message: shouldRetry
|
|
1419
|
+
? `${baseMessage}; queued retry ${retriedRun?.id ?? ""}`.trim()
|
|
1420
|
+
: baseMessage,
|
|
1421
|
+
payload: {
|
|
1422
|
+
...(run.processPid ? { processPid: run.processPid } : {}),
|
|
1423
|
+
...(retriedRun ? { retryRunId: retriedRun.id } : {}),
|
|
1424
|
+
},
|
|
1425
|
+
});
|
|
1426
|
+
await finalizeAgentStatus(run.agentId, "failed");
|
|
1427
|
+
await startNextQueuedRunForAgent(run.agentId);
|
|
1428
|
+
runningProcesses.delete(run.id);
|
|
1429
|
+
reaped.push(run.id);
|
|
1430
|
+
}
|
|
1431
|
+
if (reaped.length > 0) {
|
|
1432
|
+
logger.warn({ reapedCount: reaped.length, runIds: reaped }, "reaped orphaned heartbeat runs");
|
|
1433
|
+
}
|
|
1434
|
+
return { reaped: reaped.length, runIds: reaped };
|
|
1435
|
+
}
|
|
1436
|
+
async function resumeQueuedRuns() {
|
|
1437
|
+
const queuedRuns = await db
|
|
1438
|
+
.select({ agentId: heartbeatRuns.agentId })
|
|
1439
|
+
.from(heartbeatRuns)
|
|
1440
|
+
.where(eq(heartbeatRuns.status, "queued"));
|
|
1441
|
+
const agentIds = [...new Set(queuedRuns.map((r) => r.agentId))];
|
|
1442
|
+
for (const agentId of agentIds) {
|
|
1443
|
+
await startNextQueuedRunForAgent(agentId);
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
async function updateRuntimeState(agent, run, result, session, normalizedUsage) {
|
|
1447
|
+
await ensureRuntimeState(agent);
|
|
1448
|
+
const usage = normalizedUsage ?? normalizeUsageTotals(result.usage);
|
|
1449
|
+
const inputTokens = usage?.inputTokens ?? 0;
|
|
1450
|
+
const outputTokens = usage?.outputTokens ?? 0;
|
|
1451
|
+
const cachedInputTokens = usage?.cachedInputTokens ?? 0;
|
|
1452
|
+
const billingType = normalizeLedgerBillingType(result.billingType);
|
|
1453
|
+
const additionalCostCents = normalizeBilledCostCents(result.costUsd, billingType);
|
|
1454
|
+
const hasTokenUsage = inputTokens > 0 || outputTokens > 0 || cachedInputTokens > 0;
|
|
1455
|
+
const provider = result.provider ?? "unknown";
|
|
1456
|
+
const biller = resolveLedgerBiller(result);
|
|
1457
|
+
const ledgerScope = await resolveLedgerScopeForRun(db, agent.companyId, run);
|
|
1458
|
+
await db
|
|
1459
|
+
.update(agentRuntimeState)
|
|
1460
|
+
.set({
|
|
1461
|
+
adapterType: agent.adapterType,
|
|
1462
|
+
sessionId: session.legacySessionId,
|
|
1463
|
+
lastRunId: run.id,
|
|
1464
|
+
lastRunStatus: run.status,
|
|
1465
|
+
lastError: result.errorMessage ?? null,
|
|
1466
|
+
totalInputTokens: sql `${agentRuntimeState.totalInputTokens} + ${inputTokens}`,
|
|
1467
|
+
totalOutputTokens: sql `${agentRuntimeState.totalOutputTokens} + ${outputTokens}`,
|
|
1468
|
+
totalCachedInputTokens: sql `${agentRuntimeState.totalCachedInputTokens} + ${cachedInputTokens}`,
|
|
1469
|
+
totalCostCents: sql `${agentRuntimeState.totalCostCents} + ${additionalCostCents}`,
|
|
1470
|
+
updatedAt: new Date(),
|
|
1471
|
+
})
|
|
1472
|
+
.where(eq(agentRuntimeState.agentId, agent.id));
|
|
1473
|
+
if (additionalCostCents > 0 || hasTokenUsage) {
|
|
1474
|
+
const costs = costService(db, budgetHooks);
|
|
1475
|
+
await costs.createEvent(agent.companyId, {
|
|
1476
|
+
heartbeatRunId: run.id,
|
|
1477
|
+
agentId: agent.id,
|
|
1478
|
+
issueId: ledgerScope.issueId,
|
|
1479
|
+
projectId: ledgerScope.projectId,
|
|
1480
|
+
provider,
|
|
1481
|
+
biller,
|
|
1482
|
+
billingType,
|
|
1483
|
+
model: result.model ?? "unknown",
|
|
1484
|
+
inputTokens,
|
|
1485
|
+
cachedInputTokens,
|
|
1486
|
+
outputTokens,
|
|
1487
|
+
costCents: additionalCostCents,
|
|
1488
|
+
occurredAt: new Date(),
|
|
1489
|
+
});
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
async function startNextQueuedRunForAgent(agentId) {
|
|
1493
|
+
return withAgentStartLock(agentId, async () => {
|
|
1494
|
+
const agent = await getAgent(agentId);
|
|
1495
|
+
if (!agent)
|
|
1496
|
+
return [];
|
|
1497
|
+
if (agent.status === "paused" || agent.status === "terminated" || agent.status === "pending_approval") {
|
|
1498
|
+
return [];
|
|
1499
|
+
}
|
|
1500
|
+
const policy = parseHeartbeatPolicy(agent);
|
|
1501
|
+
const runningCount = await countRunningRunsForAgent(agentId);
|
|
1502
|
+
const availableSlots = Math.max(0, policy.maxConcurrentRuns - runningCount);
|
|
1503
|
+
if (availableSlots <= 0)
|
|
1504
|
+
return [];
|
|
1505
|
+
const queuedRuns = await db
|
|
1506
|
+
.select()
|
|
1507
|
+
.from(heartbeatRuns)
|
|
1508
|
+
.where(and(eq(heartbeatRuns.agentId, agentId), eq(heartbeatRuns.status, "queued")))
|
|
1509
|
+
.orderBy(asc(heartbeatRuns.createdAt))
|
|
1510
|
+
.limit(availableSlots);
|
|
1511
|
+
if (queuedRuns.length === 0)
|
|
1512
|
+
return [];
|
|
1513
|
+
const claimedRuns = [];
|
|
1514
|
+
for (const queuedRun of queuedRuns) {
|
|
1515
|
+
const claimed = await claimQueuedRun(queuedRun);
|
|
1516
|
+
if (claimed)
|
|
1517
|
+
claimedRuns.push(claimed);
|
|
1518
|
+
}
|
|
1519
|
+
if (claimedRuns.length === 0)
|
|
1520
|
+
return [];
|
|
1521
|
+
for (const claimedRun of claimedRuns) {
|
|
1522
|
+
void executeRun(claimedRun.id).catch((err) => {
|
|
1523
|
+
logger.error({ err, runId: claimedRun.id }, "queued heartbeat execution failed");
|
|
1524
|
+
});
|
|
1525
|
+
}
|
|
1526
|
+
return claimedRuns;
|
|
1527
|
+
});
|
|
1528
|
+
}
|
|
1529
|
+
async function executeRun(runId) {
|
|
1530
|
+
let run = await getRun(runId);
|
|
1531
|
+
if (!run)
|
|
1532
|
+
return;
|
|
1533
|
+
if (run.status !== "queued" && run.status !== "running")
|
|
1534
|
+
return;
|
|
1535
|
+
if (run.status === "queued") {
|
|
1536
|
+
const claimed = await claimQueuedRun(run);
|
|
1537
|
+
if (!claimed) {
|
|
1538
|
+
// Another worker has already claimed or finalized this run.
|
|
1539
|
+
return;
|
|
1540
|
+
}
|
|
1541
|
+
run = claimed;
|
|
1542
|
+
}
|
|
1543
|
+
activeRunExecutions.add(run.id);
|
|
1544
|
+
try {
|
|
1545
|
+
const agent = await getAgent(run.agentId);
|
|
1546
|
+
if (!agent) {
|
|
1547
|
+
await setRunStatus(runId, "failed", {
|
|
1548
|
+
error: "Agent not found",
|
|
1549
|
+
errorCode: "agent_not_found",
|
|
1550
|
+
finishedAt: new Date(),
|
|
1551
|
+
});
|
|
1552
|
+
await setWakeupStatus(run.wakeupRequestId, "failed", {
|
|
1553
|
+
finishedAt: new Date(),
|
|
1554
|
+
error: "Agent not found",
|
|
1555
|
+
});
|
|
1556
|
+
const failedRun = await getRun(runId);
|
|
1557
|
+
if (failedRun)
|
|
1558
|
+
await releaseIssueExecutionAndPromote(failedRun);
|
|
1559
|
+
return;
|
|
1560
|
+
}
|
|
1561
|
+
const runtime = await ensureRuntimeState(agent);
|
|
1562
|
+
const context = parseObject(run.contextSnapshot);
|
|
1563
|
+
const taskKey = deriveTaskKey(context, null);
|
|
1564
|
+
const sessionCodec = getAdapterSessionCodec(agent.adapterType);
|
|
1565
|
+
const issueId = readNonEmptyString(context.issueId);
|
|
1566
|
+
const issueContext = issueId
|
|
1567
|
+
? await db
|
|
1568
|
+
.select({
|
|
1569
|
+
id: issues.id,
|
|
1570
|
+
identifier: issues.identifier,
|
|
1571
|
+
title: issues.title,
|
|
1572
|
+
projectId: issues.projectId,
|
|
1573
|
+
projectWorkspaceId: issues.projectWorkspaceId,
|
|
1574
|
+
executionWorkspaceId: issues.executionWorkspaceId,
|
|
1575
|
+
executionWorkspacePreference: issues.executionWorkspacePreference,
|
|
1576
|
+
assigneeAgentId: issues.assigneeAgentId,
|
|
1577
|
+
assigneeAdapterOverrides: issues.assigneeAdapterOverrides,
|
|
1578
|
+
executionWorkspaceSettings: issues.executionWorkspaceSettings,
|
|
1579
|
+
})
|
|
1580
|
+
.from(issues)
|
|
1581
|
+
.where(and(eq(issues.id, issueId), eq(issues.companyId, agent.companyId)))
|
|
1582
|
+
.then((rows) => rows[0] ?? null)
|
|
1583
|
+
: null;
|
|
1584
|
+
const issueAssigneeOverrides = issueContext && issueContext.assigneeAgentId === agent.id
|
|
1585
|
+
? parseIssueAssigneeAdapterOverrides(issueContext.assigneeAdapterOverrides)
|
|
1586
|
+
: null;
|
|
1587
|
+
const isolatedWorkspacesEnabled = (await instanceSettings.getExperimental()).enableIsolatedWorkspaces;
|
|
1588
|
+
const issueExecutionWorkspaceSettings = isolatedWorkspacesEnabled
|
|
1589
|
+
? parseIssueExecutionWorkspaceSettings(issueContext?.executionWorkspaceSettings)
|
|
1590
|
+
: null;
|
|
1591
|
+
const contextProjectId = readNonEmptyString(context.projectId);
|
|
1592
|
+
const executionProjectId = issueContext?.projectId ?? contextProjectId;
|
|
1593
|
+
const projectExecutionWorkspacePolicy = executionProjectId
|
|
1594
|
+
? await db
|
|
1595
|
+
.select({ executionWorkspacePolicy: projects.executionWorkspacePolicy })
|
|
1596
|
+
.from(projects)
|
|
1597
|
+
.where(and(eq(projects.id, executionProjectId), eq(projects.companyId, agent.companyId)))
|
|
1598
|
+
.then((rows) => gateProjectExecutionWorkspacePolicy(parseProjectExecutionWorkspacePolicy(rows[0]?.executionWorkspacePolicy), isolatedWorkspacesEnabled))
|
|
1599
|
+
: null;
|
|
1600
|
+
const taskSession = taskKey
|
|
1601
|
+
? await getTaskSession(agent.companyId, agent.id, agent.adapterType, taskKey)
|
|
1602
|
+
: null;
|
|
1603
|
+
const resetTaskSession = shouldResetTaskSessionForWake(context);
|
|
1604
|
+
const sessionResetReason = describeSessionResetReason(context);
|
|
1605
|
+
const taskSessionForRun = resetTaskSession ? null : taskSession;
|
|
1606
|
+
const explicitResumeSessionParams = normalizeSessionParams(sessionCodec.deserialize(parseObject(context.resumeSessionParams)));
|
|
1607
|
+
const explicitResumeSessionDisplayId = truncateDisplayId(readNonEmptyString(context.resumeSessionDisplayId) ??
|
|
1608
|
+
(sessionCodec.getDisplayId ? sessionCodec.getDisplayId(explicitResumeSessionParams) : null) ??
|
|
1609
|
+
readNonEmptyString(explicitResumeSessionParams?.sessionId));
|
|
1610
|
+
const previousSessionParams = explicitResumeSessionParams ??
|
|
1611
|
+
(explicitResumeSessionDisplayId ? { sessionId: explicitResumeSessionDisplayId } : null) ??
|
|
1612
|
+
normalizeSessionParams(sessionCodec.deserialize(taskSessionForRun?.sessionParamsJson ?? null));
|
|
1613
|
+
const config = parseObject(agent.adapterConfig);
|
|
1614
|
+
const executionWorkspaceMode = resolveExecutionWorkspaceMode({
|
|
1615
|
+
projectPolicy: projectExecutionWorkspacePolicy,
|
|
1616
|
+
issueSettings: issueExecutionWorkspaceSettings,
|
|
1617
|
+
legacyUseProjectWorkspace: issueAssigneeOverrides?.useProjectWorkspace ?? null,
|
|
1618
|
+
});
|
|
1619
|
+
const resolvedWorkspace = await resolveWorkspaceForRun(agent, context, previousSessionParams, { useProjectWorkspace: executionWorkspaceMode !== "agent_default" });
|
|
1620
|
+
const workspaceManagedConfig = buildExecutionWorkspaceAdapterConfig({
|
|
1621
|
+
agentConfig: config,
|
|
1622
|
+
projectPolicy: projectExecutionWorkspacePolicy,
|
|
1623
|
+
issueSettings: issueExecutionWorkspaceSettings,
|
|
1624
|
+
mode: executionWorkspaceMode,
|
|
1625
|
+
legacyUseProjectWorkspace: issueAssigneeOverrides?.useProjectWorkspace ?? null,
|
|
1626
|
+
});
|
|
1627
|
+
const mergedConfig = issueAssigneeOverrides?.adapterConfig
|
|
1628
|
+
? { ...workspaceManagedConfig, ...issueAssigneeOverrides.adapterConfig }
|
|
1629
|
+
: workspaceManagedConfig;
|
|
1630
|
+
const { config: resolvedConfig, secretKeys } = await secretsSvc.resolveAdapterConfigForRuntime(agent.companyId, mergedConfig);
|
|
1631
|
+
const runtimeSkillEntries = await companySkills.listRuntimeSkillEntries(agent.companyId);
|
|
1632
|
+
const runtimeConfig = {
|
|
1633
|
+
...resolvedConfig,
|
|
1634
|
+
fideliosRuntimeSkills: runtimeSkillEntries,
|
|
1635
|
+
};
|
|
1636
|
+
const issueRef = issueContext
|
|
1637
|
+
? {
|
|
1638
|
+
id: issueContext.id,
|
|
1639
|
+
identifier: issueContext.identifier,
|
|
1640
|
+
title: issueContext.title,
|
|
1641
|
+
projectId: issueContext.projectId,
|
|
1642
|
+
projectWorkspaceId: issueContext.projectWorkspaceId,
|
|
1643
|
+
executionWorkspaceId: issueContext.executionWorkspaceId,
|
|
1644
|
+
executionWorkspacePreference: issueContext.executionWorkspacePreference,
|
|
1645
|
+
}
|
|
1646
|
+
: null;
|
|
1647
|
+
const existingExecutionWorkspace = issueRef?.executionWorkspaceId ? await executionWorkspacesSvc.getById(issueRef.executionWorkspaceId) : null;
|
|
1648
|
+
const workspaceOperationRecorder = workspaceOperationsSvc.createRecorder({
|
|
1649
|
+
companyId: agent.companyId,
|
|
1650
|
+
heartbeatRunId: run.id,
|
|
1651
|
+
executionWorkspaceId: existingExecutionWorkspace?.id ?? null,
|
|
1652
|
+
});
|
|
1653
|
+
const executionWorkspace = await realizeExecutionWorkspace({
|
|
1654
|
+
base: {
|
|
1655
|
+
baseCwd: resolvedWorkspace.cwd,
|
|
1656
|
+
source: resolvedWorkspace.source,
|
|
1657
|
+
projectId: resolvedWorkspace.projectId,
|
|
1658
|
+
workspaceId: resolvedWorkspace.workspaceId,
|
|
1659
|
+
repoUrl: resolvedWorkspace.repoUrl,
|
|
1660
|
+
repoRef: resolvedWorkspace.repoRef,
|
|
1661
|
+
},
|
|
1662
|
+
config: runtimeConfig,
|
|
1663
|
+
issue: issueRef,
|
|
1664
|
+
agent: {
|
|
1665
|
+
id: agent.id,
|
|
1666
|
+
name: agent.name,
|
|
1667
|
+
companyId: agent.companyId,
|
|
1668
|
+
},
|
|
1669
|
+
recorder: workspaceOperationRecorder,
|
|
1670
|
+
});
|
|
1671
|
+
const resolvedProjectId = executionWorkspace.projectId ?? issueRef?.projectId ?? executionProjectId ?? null;
|
|
1672
|
+
const resolvedProjectWorkspaceId = issueRef?.projectWorkspaceId ?? resolvedWorkspace.workspaceId ?? null;
|
|
1673
|
+
const shouldReuseExisting = issueRef?.executionWorkspacePreference === "reuse_existing" &&
|
|
1674
|
+
existingExecutionWorkspace &&
|
|
1675
|
+
existingExecutionWorkspace.status !== "archived";
|
|
1676
|
+
let persistedExecutionWorkspace = null;
|
|
1677
|
+
try {
|
|
1678
|
+
persistedExecutionWorkspace = shouldReuseExisting && existingExecutionWorkspace
|
|
1679
|
+
? await executionWorkspacesSvc.update(existingExecutionWorkspace.id, {
|
|
1680
|
+
cwd: executionWorkspace.cwd,
|
|
1681
|
+
repoUrl: executionWorkspace.repoUrl,
|
|
1682
|
+
baseRef: executionWorkspace.repoRef,
|
|
1683
|
+
branchName: executionWorkspace.branchName,
|
|
1684
|
+
providerType: executionWorkspace.strategy === "git_worktree" ? "git_worktree" : "local_fs",
|
|
1685
|
+
providerRef: executionWorkspace.worktreePath,
|
|
1686
|
+
status: "active",
|
|
1687
|
+
lastUsedAt: new Date(),
|
|
1688
|
+
metadata: {
|
|
1689
|
+
...(existingExecutionWorkspace.metadata ?? {}),
|
|
1690
|
+
source: executionWorkspace.source,
|
|
1691
|
+
createdByRuntime: executionWorkspace.created,
|
|
1692
|
+
},
|
|
1693
|
+
})
|
|
1694
|
+
: resolvedProjectId
|
|
1695
|
+
? await executionWorkspacesSvc.create({
|
|
1696
|
+
companyId: agent.companyId,
|
|
1697
|
+
projectId: resolvedProjectId,
|
|
1698
|
+
projectWorkspaceId: resolvedProjectWorkspaceId,
|
|
1699
|
+
sourceIssueId: issueRef?.id ?? null,
|
|
1700
|
+
mode: executionWorkspaceMode === "isolated_workspace"
|
|
1701
|
+
? "isolated_workspace"
|
|
1702
|
+
: executionWorkspaceMode === "operator_branch"
|
|
1703
|
+
? "operator_branch"
|
|
1704
|
+
: executionWorkspaceMode === "agent_default"
|
|
1705
|
+
? "adapter_managed"
|
|
1706
|
+
: "shared_workspace",
|
|
1707
|
+
strategyType: executionWorkspace.strategy === "git_worktree" ? "git_worktree" : "project_primary",
|
|
1708
|
+
name: executionWorkspace.branchName ?? issueRef?.identifier ?? `workspace-${agent.id.slice(0, 8)}`,
|
|
1709
|
+
status: "active",
|
|
1710
|
+
cwd: executionWorkspace.cwd,
|
|
1711
|
+
repoUrl: executionWorkspace.repoUrl,
|
|
1712
|
+
baseRef: executionWorkspace.repoRef,
|
|
1713
|
+
branchName: executionWorkspace.branchName,
|
|
1714
|
+
providerType: executionWorkspace.strategy === "git_worktree" ? "git_worktree" : "local_fs",
|
|
1715
|
+
providerRef: executionWorkspace.worktreePath,
|
|
1716
|
+
lastUsedAt: new Date(),
|
|
1717
|
+
openedAt: new Date(),
|
|
1718
|
+
metadata: {
|
|
1719
|
+
source: executionWorkspace.source,
|
|
1720
|
+
createdByRuntime: executionWorkspace.created,
|
|
1721
|
+
},
|
|
1722
|
+
})
|
|
1723
|
+
: null;
|
|
1724
|
+
}
|
|
1725
|
+
catch (error) {
|
|
1726
|
+
if (executionWorkspace.created) {
|
|
1727
|
+
try {
|
|
1728
|
+
await cleanupExecutionWorkspaceArtifacts({
|
|
1729
|
+
workspace: {
|
|
1730
|
+
id: existingExecutionWorkspace?.id ?? `transient-${run.id}`,
|
|
1731
|
+
cwd: executionWorkspace.cwd,
|
|
1732
|
+
providerType: executionWorkspace.strategy === "git_worktree" ? "git_worktree" : "local_fs",
|
|
1733
|
+
providerRef: executionWorkspace.worktreePath,
|
|
1734
|
+
branchName: executionWorkspace.branchName,
|
|
1735
|
+
repoUrl: executionWorkspace.repoUrl,
|
|
1736
|
+
baseRef: executionWorkspace.repoRef,
|
|
1737
|
+
projectId: resolvedProjectId,
|
|
1738
|
+
projectWorkspaceId: resolvedProjectWorkspaceId,
|
|
1739
|
+
sourceIssueId: issueRef?.id ?? null,
|
|
1740
|
+
metadata: {
|
|
1741
|
+
createdByRuntime: true,
|
|
1742
|
+
source: executionWorkspace.source,
|
|
1743
|
+
},
|
|
1744
|
+
},
|
|
1745
|
+
projectWorkspace: {
|
|
1746
|
+
cwd: resolvedWorkspace.cwd,
|
|
1747
|
+
cleanupCommand: null,
|
|
1748
|
+
},
|
|
1749
|
+
teardownCommand: projectExecutionWorkspacePolicy?.workspaceStrategy?.teardownCommand ?? null,
|
|
1750
|
+
recorder: workspaceOperationRecorder,
|
|
1751
|
+
});
|
|
1752
|
+
}
|
|
1753
|
+
catch (cleanupError) {
|
|
1754
|
+
logger.warn({
|
|
1755
|
+
runId: run.id,
|
|
1756
|
+
issueId,
|
|
1757
|
+
executionWorkspaceCwd: executionWorkspace.cwd,
|
|
1758
|
+
cleanupError: cleanupError instanceof Error ? cleanupError.message : String(cleanupError),
|
|
1759
|
+
}, "Failed to cleanup realized execution workspace after persistence failure");
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
throw error;
|
|
1763
|
+
}
|
|
1764
|
+
await workspaceOperationRecorder.attachExecutionWorkspaceId(persistedExecutionWorkspace?.id ?? null);
|
|
1765
|
+
if (existingExecutionWorkspace &&
|
|
1766
|
+
persistedExecutionWorkspace &&
|
|
1767
|
+
existingExecutionWorkspace.id !== persistedExecutionWorkspace.id &&
|
|
1768
|
+
existingExecutionWorkspace.status === "active") {
|
|
1769
|
+
await executionWorkspacesSvc.update(existingExecutionWorkspace.id, {
|
|
1770
|
+
status: "idle",
|
|
1771
|
+
cleanupReason: null,
|
|
1772
|
+
});
|
|
1773
|
+
}
|
|
1774
|
+
if (issueId && persistedExecutionWorkspace) {
|
|
1775
|
+
const nextIssueWorkspaceMode = issueExecutionWorkspaceModeForPersistedWorkspace(persistedExecutionWorkspace.mode);
|
|
1776
|
+
const shouldSwitchIssueToExistingWorkspace = issueRef?.executionWorkspacePreference === "reuse_existing" ||
|
|
1777
|
+
executionWorkspaceMode === "isolated_workspace" ||
|
|
1778
|
+
executionWorkspaceMode === "operator_branch";
|
|
1779
|
+
const nextIssuePatch = {};
|
|
1780
|
+
if (issueRef?.executionWorkspaceId !== persistedExecutionWorkspace.id) {
|
|
1781
|
+
nextIssuePatch.executionWorkspaceId = persistedExecutionWorkspace.id;
|
|
1782
|
+
}
|
|
1783
|
+
if (resolvedProjectWorkspaceId && issueRef?.projectWorkspaceId !== resolvedProjectWorkspaceId) {
|
|
1784
|
+
nextIssuePatch.projectWorkspaceId = resolvedProjectWorkspaceId;
|
|
1785
|
+
}
|
|
1786
|
+
if (shouldSwitchIssueToExistingWorkspace) {
|
|
1787
|
+
nextIssuePatch.executionWorkspacePreference = "reuse_existing";
|
|
1788
|
+
nextIssuePatch.executionWorkspaceSettings = {
|
|
1789
|
+
...(issueExecutionWorkspaceSettings ?? {}),
|
|
1790
|
+
mode: nextIssueWorkspaceMode,
|
|
1791
|
+
};
|
|
1792
|
+
}
|
|
1793
|
+
if (Object.keys(nextIssuePatch).length > 0) {
|
|
1794
|
+
await issuesSvc.update(issueId, nextIssuePatch);
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
if (persistedExecutionWorkspace) {
|
|
1798
|
+
context.executionWorkspaceId = persistedExecutionWorkspace.id;
|
|
1799
|
+
await db
|
|
1800
|
+
.update(heartbeatRuns)
|
|
1801
|
+
.set({
|
|
1802
|
+
contextSnapshot: context,
|
|
1803
|
+
updatedAt: new Date(),
|
|
1804
|
+
})
|
|
1805
|
+
.where(eq(heartbeatRuns.id, run.id));
|
|
1806
|
+
}
|
|
1807
|
+
const runtimeSessionResolution = resolveRuntimeSessionParamsForWorkspace({
|
|
1808
|
+
agentId: agent.id,
|
|
1809
|
+
previousSessionParams,
|
|
1810
|
+
resolvedWorkspace: {
|
|
1811
|
+
...resolvedWorkspace,
|
|
1812
|
+
cwd: executionWorkspace.cwd,
|
|
1813
|
+
},
|
|
1814
|
+
});
|
|
1815
|
+
const runtimeSessionParams = runtimeSessionResolution.sessionParams;
|
|
1816
|
+
const runtimeWorkspaceWarnings = [
|
|
1817
|
+
...resolvedWorkspace.warnings,
|
|
1818
|
+
...executionWorkspace.warnings,
|
|
1819
|
+
...(runtimeSessionResolution.warning ? [runtimeSessionResolution.warning] : []),
|
|
1820
|
+
...(resetTaskSession && sessionResetReason
|
|
1821
|
+
? [
|
|
1822
|
+
taskKey
|
|
1823
|
+
? `Skipping saved session resume for task "${taskKey}" because ${sessionResetReason}.`
|
|
1824
|
+
: `Skipping saved session resume because ${sessionResetReason}.`,
|
|
1825
|
+
]
|
|
1826
|
+
: []),
|
|
1827
|
+
];
|
|
1828
|
+
context.fideliosWorkspace = {
|
|
1829
|
+
cwd: executionWorkspace.cwd,
|
|
1830
|
+
source: executionWorkspace.source,
|
|
1831
|
+
mode: executionWorkspaceMode,
|
|
1832
|
+
strategy: executionWorkspace.strategy,
|
|
1833
|
+
projectId: executionWorkspace.projectId,
|
|
1834
|
+
workspaceId: executionWorkspace.workspaceId,
|
|
1835
|
+
repoUrl: executionWorkspace.repoUrl,
|
|
1836
|
+
repoRef: executionWorkspace.repoRef,
|
|
1837
|
+
branchName: executionWorkspace.branchName,
|
|
1838
|
+
worktreePath: executionWorkspace.worktreePath,
|
|
1839
|
+
agentHome: await (async () => {
|
|
1840
|
+
const home = resolveDefaultAgentWorkspaceDir(agent.id);
|
|
1841
|
+
await fs.mkdir(home, { recursive: true });
|
|
1842
|
+
return home;
|
|
1843
|
+
})(),
|
|
1844
|
+
};
|
|
1845
|
+
context.fideliosWorkspaces = resolvedWorkspace.workspaceHints;
|
|
1846
|
+
const runtimeServiceIntents = (() => {
|
|
1847
|
+
const runtimeConfig = parseObject(resolvedConfig.workspaceRuntime);
|
|
1848
|
+
return Array.isArray(runtimeConfig.services)
|
|
1849
|
+
? runtimeConfig.services.filter((value) => typeof value === "object" && value !== null)
|
|
1850
|
+
: [];
|
|
1851
|
+
})();
|
|
1852
|
+
if (runtimeServiceIntents.length > 0) {
|
|
1853
|
+
context.fideliosRuntimeServiceIntents = runtimeServiceIntents;
|
|
1854
|
+
}
|
|
1855
|
+
else {
|
|
1856
|
+
delete context.fideliosRuntimeServiceIntents;
|
|
1857
|
+
}
|
|
1858
|
+
if (executionWorkspace.projectId && !readNonEmptyString(context.projectId)) {
|
|
1859
|
+
context.projectId = executionWorkspace.projectId;
|
|
1860
|
+
}
|
|
1861
|
+
const runtimeSessionFallback = taskKey || resetTaskSession ? null : runtime.sessionId;
|
|
1862
|
+
let previousSessionDisplayId = truncateDisplayId(explicitResumeSessionDisplayId ??
|
|
1863
|
+
taskSessionForRun?.sessionDisplayId ??
|
|
1864
|
+
(sessionCodec.getDisplayId ? sessionCodec.getDisplayId(runtimeSessionParams) : null) ??
|
|
1865
|
+
readNonEmptyString(runtimeSessionParams?.sessionId) ??
|
|
1866
|
+
runtimeSessionFallback);
|
|
1867
|
+
let runtimeSessionIdForAdapter = readNonEmptyString(runtimeSessionParams?.sessionId) ?? runtimeSessionFallback;
|
|
1868
|
+
let runtimeSessionParamsForAdapter = runtimeSessionParams;
|
|
1869
|
+
const sessionCompaction = await evaluateSessionCompaction({
|
|
1870
|
+
agent,
|
|
1871
|
+
sessionId: previousSessionDisplayId ?? runtimeSessionIdForAdapter,
|
|
1872
|
+
issueId,
|
|
1873
|
+
});
|
|
1874
|
+
if (sessionCompaction.rotate) {
|
|
1875
|
+
context.fideliosSessionHandoffMarkdown = sessionCompaction.handoffMarkdown;
|
|
1876
|
+
context.fideliosSessionRotationReason = sessionCompaction.reason;
|
|
1877
|
+
context.fideliosPreviousSessionId = previousSessionDisplayId ?? runtimeSessionIdForAdapter;
|
|
1878
|
+
runtimeSessionIdForAdapter = null;
|
|
1879
|
+
runtimeSessionParamsForAdapter = null;
|
|
1880
|
+
previousSessionDisplayId = null;
|
|
1881
|
+
if (sessionCompaction.reason) {
|
|
1882
|
+
runtimeWorkspaceWarnings.push(`Starting a fresh session because ${sessionCompaction.reason}.`);
|
|
1883
|
+
}
|
|
1884
|
+
}
|
|
1885
|
+
else {
|
|
1886
|
+
delete context.fideliosSessionHandoffMarkdown;
|
|
1887
|
+
delete context.fideliosSessionRotationReason;
|
|
1888
|
+
delete context.fideliosPreviousSessionId;
|
|
1889
|
+
}
|
|
1890
|
+
const runtimeForAdapter = {
|
|
1891
|
+
sessionId: runtimeSessionIdForAdapter,
|
|
1892
|
+
sessionParams: runtimeSessionParamsForAdapter,
|
|
1893
|
+
sessionDisplayId: previousSessionDisplayId,
|
|
1894
|
+
taskKey,
|
|
1895
|
+
};
|
|
1896
|
+
let seq = 1;
|
|
1897
|
+
let handle = null;
|
|
1898
|
+
let stdoutExcerpt = "";
|
|
1899
|
+
let stderrExcerpt = "";
|
|
1900
|
+
try {
|
|
1901
|
+
const startedAt = run.startedAt ?? new Date();
|
|
1902
|
+
const runningWithSession = await db
|
|
1903
|
+
.update(heartbeatRuns)
|
|
1904
|
+
.set({
|
|
1905
|
+
startedAt,
|
|
1906
|
+
sessionIdBefore: runtimeForAdapter.sessionDisplayId ?? runtimeForAdapter.sessionId,
|
|
1907
|
+
contextSnapshot: context,
|
|
1908
|
+
updatedAt: new Date(),
|
|
1909
|
+
})
|
|
1910
|
+
.where(eq(heartbeatRuns.id, run.id))
|
|
1911
|
+
.returning()
|
|
1912
|
+
.then((rows) => rows[0] ?? null);
|
|
1913
|
+
if (runningWithSession)
|
|
1914
|
+
run = runningWithSession;
|
|
1915
|
+
const runningAgent = await db
|
|
1916
|
+
.update(agents)
|
|
1917
|
+
.set({ status: "running", updatedAt: new Date() })
|
|
1918
|
+
.where(eq(agents.id, agent.id))
|
|
1919
|
+
.returning()
|
|
1920
|
+
.then((rows) => rows[0] ?? null);
|
|
1921
|
+
if (runningAgent) {
|
|
1922
|
+
publishLiveEvent({
|
|
1923
|
+
companyId: runningAgent.companyId,
|
|
1924
|
+
type: "agent.status",
|
|
1925
|
+
payload: {
|
|
1926
|
+
agentId: runningAgent.id,
|
|
1927
|
+
status: runningAgent.status,
|
|
1928
|
+
outcome: "running",
|
|
1929
|
+
},
|
|
1930
|
+
});
|
|
1931
|
+
}
|
|
1932
|
+
const currentRun = run;
|
|
1933
|
+
await appendRunEvent(currentRun, seq++, {
|
|
1934
|
+
eventType: "lifecycle",
|
|
1935
|
+
stream: "system",
|
|
1936
|
+
level: "info",
|
|
1937
|
+
message: "run started",
|
|
1938
|
+
});
|
|
1939
|
+
handle = await runLogStore.begin({
|
|
1940
|
+
companyId: run.companyId,
|
|
1941
|
+
agentId: run.agentId,
|
|
1942
|
+
runId,
|
|
1943
|
+
});
|
|
1944
|
+
await db
|
|
1945
|
+
.update(heartbeatRuns)
|
|
1946
|
+
.set({
|
|
1947
|
+
logStore: handle.store,
|
|
1948
|
+
logRef: handle.logRef,
|
|
1949
|
+
updatedAt: new Date(),
|
|
1950
|
+
})
|
|
1951
|
+
.where(eq(heartbeatRuns.id, runId));
|
|
1952
|
+
const currentUserRedactionOptions = await getCurrentUserRedactionOptions();
|
|
1953
|
+
const onLog = async (stream, chunk) => {
|
|
1954
|
+
const sanitizedChunk = redactCurrentUserText(chunk, currentUserRedactionOptions);
|
|
1955
|
+
if (stream === "stdout")
|
|
1956
|
+
stdoutExcerpt = appendExcerpt(stdoutExcerpt, sanitizedChunk);
|
|
1957
|
+
if (stream === "stderr")
|
|
1958
|
+
stderrExcerpt = appendExcerpt(stderrExcerpt, sanitizedChunk);
|
|
1959
|
+
const ts = new Date().toISOString();
|
|
1960
|
+
if (handle) {
|
|
1961
|
+
await runLogStore.append(handle, {
|
|
1962
|
+
stream,
|
|
1963
|
+
chunk: sanitizedChunk,
|
|
1964
|
+
ts,
|
|
1965
|
+
});
|
|
1966
|
+
}
|
|
1967
|
+
const payloadChunk = sanitizedChunk.length > MAX_LIVE_LOG_CHUNK_BYTES
|
|
1968
|
+
? sanitizedChunk.slice(sanitizedChunk.length - MAX_LIVE_LOG_CHUNK_BYTES)
|
|
1969
|
+
: sanitizedChunk;
|
|
1970
|
+
publishLiveEvent({
|
|
1971
|
+
companyId: run.companyId,
|
|
1972
|
+
type: "heartbeat.run.log",
|
|
1973
|
+
payload: {
|
|
1974
|
+
runId: run.id,
|
|
1975
|
+
agentId: run.agentId,
|
|
1976
|
+
ts,
|
|
1977
|
+
stream,
|
|
1978
|
+
chunk: payloadChunk,
|
|
1979
|
+
truncated: payloadChunk.length !== sanitizedChunk.length,
|
|
1980
|
+
},
|
|
1981
|
+
});
|
|
1982
|
+
};
|
|
1983
|
+
for (const warning of runtimeWorkspaceWarnings) {
|
|
1984
|
+
const logEntry = formatRuntimeWorkspaceWarningLog(warning);
|
|
1985
|
+
await onLog(logEntry.stream, logEntry.chunk);
|
|
1986
|
+
}
|
|
1987
|
+
const adapterEnv = Object.fromEntries(Object.entries(parseObject(resolvedConfig.env)).filter((entry) => typeof entry[0] === "string" && typeof entry[1] === "string"));
|
|
1988
|
+
const runtimeServices = await ensureRuntimeServicesForRun({
|
|
1989
|
+
db,
|
|
1990
|
+
runId: run.id,
|
|
1991
|
+
agent: {
|
|
1992
|
+
id: agent.id,
|
|
1993
|
+
name: agent.name,
|
|
1994
|
+
companyId: agent.companyId,
|
|
1995
|
+
},
|
|
1996
|
+
issue: issueRef,
|
|
1997
|
+
workspace: executionWorkspace,
|
|
1998
|
+
executionWorkspaceId: persistedExecutionWorkspace?.id ?? issueRef?.executionWorkspaceId ?? null,
|
|
1999
|
+
config: resolvedConfig,
|
|
2000
|
+
adapterEnv,
|
|
2001
|
+
onLog,
|
|
2002
|
+
});
|
|
2003
|
+
if (runtimeServices.length > 0) {
|
|
2004
|
+
context.fideliosRuntimeServices = runtimeServices;
|
|
2005
|
+
context.fideliosRuntimePrimaryUrl =
|
|
2006
|
+
runtimeServices.find((service) => readNonEmptyString(service.url))?.url ?? null;
|
|
2007
|
+
await db
|
|
2008
|
+
.update(heartbeatRuns)
|
|
2009
|
+
.set({
|
|
2010
|
+
contextSnapshot: context,
|
|
2011
|
+
updatedAt: new Date(),
|
|
2012
|
+
})
|
|
2013
|
+
.where(eq(heartbeatRuns.id, run.id));
|
|
2014
|
+
}
|
|
2015
|
+
if (issueId && (executionWorkspace.created || runtimeServices.some((service) => !service.reused))) {
|
|
2016
|
+
try {
|
|
2017
|
+
await issuesSvc.addComment(issueId, buildWorkspaceReadyComment({
|
|
2018
|
+
workspace: executionWorkspace,
|
|
2019
|
+
runtimeServices,
|
|
2020
|
+
}), { agentId: agent.id });
|
|
2021
|
+
}
|
|
2022
|
+
catch (err) {
|
|
2023
|
+
await onLog("stderr", `[fidelios] Failed to post workspace-ready comment: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
const onAdapterMeta = async (meta) => {
|
|
2027
|
+
if (meta.env && secretKeys.size > 0) {
|
|
2028
|
+
for (const key of secretKeys) {
|
|
2029
|
+
if (key in meta.env)
|
|
2030
|
+
meta.env[key] = "***REDACTED***";
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
2033
|
+
await appendRunEvent(currentRun, seq++, {
|
|
2034
|
+
eventType: "adapter.invoke",
|
|
2035
|
+
stream: "system",
|
|
2036
|
+
level: "info",
|
|
2037
|
+
message: "adapter invocation",
|
|
2038
|
+
payload: meta,
|
|
2039
|
+
});
|
|
2040
|
+
};
|
|
2041
|
+
const adapter = getServerAdapter(agent.adapterType);
|
|
2042
|
+
const authToken = adapter.supportsLocalAgentJwt
|
|
2043
|
+
? createLocalAgentJwt(agent.id, agent.companyId, agent.adapterType, run.id)
|
|
2044
|
+
: null;
|
|
2045
|
+
if (adapter.supportsLocalAgentJwt && !authToken) {
|
|
2046
|
+
logger.warn({
|
|
2047
|
+
companyId: agent.companyId,
|
|
2048
|
+
agentId: agent.id,
|
|
2049
|
+
runId: run.id,
|
|
2050
|
+
adapterType: agent.adapterType,
|
|
2051
|
+
}, "local agent jwt secret missing or invalid; running without injected FIDELIOS_API_KEY");
|
|
2052
|
+
}
|
|
2053
|
+
const adapterResult = await adapter.execute({
|
|
2054
|
+
runId: run.id,
|
|
2055
|
+
agent,
|
|
2056
|
+
runtime: runtimeForAdapter,
|
|
2057
|
+
config: runtimeConfig,
|
|
2058
|
+
context,
|
|
2059
|
+
onLog,
|
|
2060
|
+
onMeta: onAdapterMeta,
|
|
2061
|
+
onSpawn: async (meta) => {
|
|
2062
|
+
await persistRunProcessMetadata(run.id, meta);
|
|
2063
|
+
},
|
|
2064
|
+
authToken: authToken ?? undefined,
|
|
2065
|
+
});
|
|
2066
|
+
const adapterManagedRuntimeServices = adapterResult.runtimeServices
|
|
2067
|
+
? await persistAdapterManagedRuntimeServices({
|
|
2068
|
+
db,
|
|
2069
|
+
adapterType: agent.adapterType,
|
|
2070
|
+
runId: run.id,
|
|
2071
|
+
agent: {
|
|
2072
|
+
id: agent.id,
|
|
2073
|
+
name: agent.name,
|
|
2074
|
+
companyId: agent.companyId,
|
|
2075
|
+
},
|
|
2076
|
+
issue: issueRef,
|
|
2077
|
+
workspace: executionWorkspace,
|
|
2078
|
+
reports: adapterResult.runtimeServices,
|
|
2079
|
+
})
|
|
2080
|
+
: [];
|
|
2081
|
+
if (adapterManagedRuntimeServices.length > 0) {
|
|
2082
|
+
const combinedRuntimeServices = [
|
|
2083
|
+
...runtimeServices,
|
|
2084
|
+
...adapterManagedRuntimeServices,
|
|
2085
|
+
];
|
|
2086
|
+
context.fideliosRuntimeServices = combinedRuntimeServices;
|
|
2087
|
+
context.fideliosRuntimePrimaryUrl =
|
|
2088
|
+
combinedRuntimeServices.find((service) => readNonEmptyString(service.url))?.url ?? null;
|
|
2089
|
+
await db
|
|
2090
|
+
.update(heartbeatRuns)
|
|
2091
|
+
.set({
|
|
2092
|
+
contextSnapshot: context,
|
|
2093
|
+
updatedAt: new Date(),
|
|
2094
|
+
})
|
|
2095
|
+
.where(eq(heartbeatRuns.id, run.id));
|
|
2096
|
+
if (issueId) {
|
|
2097
|
+
try {
|
|
2098
|
+
await issuesSvc.addComment(issueId, buildWorkspaceReadyComment({
|
|
2099
|
+
workspace: executionWorkspace,
|
|
2100
|
+
runtimeServices: adapterManagedRuntimeServices,
|
|
2101
|
+
}), { agentId: agent.id });
|
|
2102
|
+
}
|
|
2103
|
+
catch (err) {
|
|
2104
|
+
await onLog("stderr", `[fidelios] Failed to post adapter-managed runtime comment: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2108
|
+
const nextSessionState = resolveNextSessionState({
|
|
2109
|
+
codec: sessionCodec,
|
|
2110
|
+
adapterResult,
|
|
2111
|
+
previousParams: previousSessionParams,
|
|
2112
|
+
previousDisplayId: runtimeForAdapter.sessionDisplayId,
|
|
2113
|
+
previousLegacySessionId: runtimeForAdapter.sessionId,
|
|
2114
|
+
});
|
|
2115
|
+
const rawUsage = normalizeUsageTotals(adapterResult.usage);
|
|
2116
|
+
const sessionUsageResolution = await resolveNormalizedUsageForSession({
|
|
2117
|
+
agentId: agent.id,
|
|
2118
|
+
runId: run.id,
|
|
2119
|
+
sessionId: nextSessionState.displayId ?? nextSessionState.legacySessionId,
|
|
2120
|
+
rawUsage,
|
|
2121
|
+
});
|
|
2122
|
+
const normalizedUsage = sessionUsageResolution.normalizedUsage;
|
|
2123
|
+
let outcome;
|
|
2124
|
+
const latestRun = await getRun(run.id);
|
|
2125
|
+
if (latestRun?.status === "cancelled") {
|
|
2126
|
+
outcome = "cancelled";
|
|
2127
|
+
}
|
|
2128
|
+
else if (adapterResult.timedOut) {
|
|
2129
|
+
outcome = "timed_out";
|
|
2130
|
+
}
|
|
2131
|
+
else if ((adapterResult.exitCode ?? 0) === 0 && !adapterResult.errorMessage) {
|
|
2132
|
+
outcome = "succeeded";
|
|
2133
|
+
}
|
|
2134
|
+
else {
|
|
2135
|
+
outcome = "failed";
|
|
2136
|
+
}
|
|
2137
|
+
let logSummary = null;
|
|
2138
|
+
if (handle) {
|
|
2139
|
+
logSummary = await runLogStore.finalize(handle);
|
|
2140
|
+
}
|
|
2141
|
+
const status = outcome === "succeeded"
|
|
2142
|
+
? "succeeded"
|
|
2143
|
+
: outcome === "cancelled"
|
|
2144
|
+
? "cancelled"
|
|
2145
|
+
: outcome === "timed_out"
|
|
2146
|
+
? "timed_out"
|
|
2147
|
+
: "failed";
|
|
2148
|
+
const usageJson = normalizedUsage || adapterResult.costUsd != null
|
|
2149
|
+
? {
|
|
2150
|
+
...(normalizedUsage ?? {}),
|
|
2151
|
+
...(rawUsage ? {
|
|
2152
|
+
rawInputTokens: rawUsage.inputTokens,
|
|
2153
|
+
rawCachedInputTokens: rawUsage.cachedInputTokens,
|
|
2154
|
+
rawOutputTokens: rawUsage.outputTokens,
|
|
2155
|
+
} : {}),
|
|
2156
|
+
...(sessionUsageResolution.derivedFromSessionTotals ? { usageSource: "session_delta" } : {}),
|
|
2157
|
+
...((nextSessionState.displayId ?? nextSessionState.legacySessionId)
|
|
2158
|
+
? { persistedSessionId: nextSessionState.displayId ?? nextSessionState.legacySessionId }
|
|
2159
|
+
: {}),
|
|
2160
|
+
sessionReused: runtimeForAdapter.sessionId != null || runtimeForAdapter.sessionDisplayId != null,
|
|
2161
|
+
taskSessionReused: taskSessionForRun != null,
|
|
2162
|
+
freshSession: runtimeForAdapter.sessionId == null && runtimeForAdapter.sessionDisplayId == null,
|
|
2163
|
+
sessionRotated: sessionCompaction.rotate,
|
|
2164
|
+
sessionRotationReason: sessionCompaction.reason,
|
|
2165
|
+
provider: readNonEmptyString(adapterResult.provider) ?? "unknown",
|
|
2166
|
+
biller: resolveLedgerBiller(adapterResult),
|
|
2167
|
+
model: readNonEmptyString(adapterResult.model) ?? "unknown",
|
|
2168
|
+
...(adapterResult.costUsd != null ? { costUsd: adapterResult.costUsd } : {}),
|
|
2169
|
+
billingType: normalizeLedgerBillingType(adapterResult.billingType),
|
|
2170
|
+
}
|
|
2171
|
+
: null;
|
|
2172
|
+
await setRunStatus(run.id, status, {
|
|
2173
|
+
finishedAt: new Date(),
|
|
2174
|
+
error: outcome === "succeeded"
|
|
2175
|
+
? null
|
|
2176
|
+
: redactCurrentUserText(adapterResult.errorMessage ?? (outcome === "timed_out" ? "Timed out" : "Adapter failed"), currentUserRedactionOptions),
|
|
2177
|
+
errorCode: outcome === "timed_out"
|
|
2178
|
+
? "timeout"
|
|
2179
|
+
: outcome === "cancelled"
|
|
2180
|
+
? "cancelled"
|
|
2181
|
+
: outcome === "failed"
|
|
2182
|
+
? (adapterResult.errorCode ?? "adapter_failed")
|
|
2183
|
+
: null,
|
|
2184
|
+
exitCode: adapterResult.exitCode,
|
|
2185
|
+
signal: adapterResult.signal,
|
|
2186
|
+
usageJson,
|
|
2187
|
+
resultJson: adapterResult.resultJson ?? null,
|
|
2188
|
+
sessionIdAfter: nextSessionState.displayId ?? nextSessionState.legacySessionId,
|
|
2189
|
+
stdoutExcerpt,
|
|
2190
|
+
stderrExcerpt,
|
|
2191
|
+
logBytes: logSummary?.bytes,
|
|
2192
|
+
logSha256: logSummary?.sha256,
|
|
2193
|
+
logCompressed: logSummary?.compressed ?? false,
|
|
2194
|
+
});
|
|
2195
|
+
await setWakeupStatus(run.wakeupRequestId, outcome === "succeeded" ? "completed" : status, {
|
|
2196
|
+
finishedAt: new Date(),
|
|
2197
|
+
error: adapterResult.errorMessage ?? null,
|
|
2198
|
+
});
|
|
2199
|
+
const finalizedRun = await getRun(run.id);
|
|
2200
|
+
if (finalizedRun) {
|
|
2201
|
+
await appendRunEvent(finalizedRun, seq++, {
|
|
2202
|
+
eventType: "lifecycle",
|
|
2203
|
+
stream: "system",
|
|
2204
|
+
level: outcome === "succeeded" ? "info" : "error",
|
|
2205
|
+
message: `run ${outcome}`,
|
|
2206
|
+
payload: {
|
|
2207
|
+
status,
|
|
2208
|
+
exitCode: adapterResult.exitCode,
|
|
2209
|
+
},
|
|
2210
|
+
});
|
|
2211
|
+
await releaseIssueExecutionAndPromote(finalizedRun);
|
|
2212
|
+
}
|
|
2213
|
+
if (finalizedRun) {
|
|
2214
|
+
await updateRuntimeState(agent, finalizedRun, adapterResult, {
|
|
2215
|
+
legacySessionId: nextSessionState.legacySessionId,
|
|
2216
|
+
}, normalizedUsage);
|
|
2217
|
+
if (taskKey) {
|
|
2218
|
+
if (adapterResult.clearSession || (!nextSessionState.params && !nextSessionState.displayId)) {
|
|
2219
|
+
await clearTaskSessions(agent.companyId, agent.id, {
|
|
2220
|
+
taskKey,
|
|
2221
|
+
adapterType: agent.adapterType,
|
|
2222
|
+
});
|
|
2223
|
+
}
|
|
2224
|
+
else {
|
|
2225
|
+
await upsertTaskSession({
|
|
2226
|
+
companyId: agent.companyId,
|
|
2227
|
+
agentId: agent.id,
|
|
2228
|
+
adapterType: agent.adapterType,
|
|
2229
|
+
taskKey,
|
|
2230
|
+
sessionParamsJson: nextSessionState.params,
|
|
2231
|
+
sessionDisplayId: nextSessionState.displayId,
|
|
2232
|
+
lastRunId: finalizedRun.id,
|
|
2233
|
+
lastError: outcome === "succeeded" ? null : (adapterResult.errorMessage ?? "run_failed"),
|
|
2234
|
+
});
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
2238
|
+
await finalizeAgentStatus(agent.id, outcome);
|
|
2239
|
+
}
|
|
2240
|
+
catch (err) {
|
|
2241
|
+
const message = redactCurrentUserText(err instanceof Error ? err.message : "Unknown adapter failure", await getCurrentUserRedactionOptions());
|
|
2242
|
+
logger.error({ err, runId }, "heartbeat execution failed");
|
|
2243
|
+
let logSummary = null;
|
|
2244
|
+
if (handle) {
|
|
2245
|
+
try {
|
|
2246
|
+
logSummary = await runLogStore.finalize(handle);
|
|
2247
|
+
}
|
|
2248
|
+
catch (finalizeErr) {
|
|
2249
|
+
logger.warn({ err: finalizeErr, runId }, "failed to finalize run log after error");
|
|
2250
|
+
}
|
|
2251
|
+
}
|
|
2252
|
+
const failedRun = await setRunStatus(run.id, "failed", {
|
|
2253
|
+
error: message,
|
|
2254
|
+
errorCode: "adapter_failed",
|
|
2255
|
+
finishedAt: new Date(),
|
|
2256
|
+
stdoutExcerpt,
|
|
2257
|
+
stderrExcerpt,
|
|
2258
|
+
logBytes: logSummary?.bytes,
|
|
2259
|
+
logSha256: logSummary?.sha256,
|
|
2260
|
+
logCompressed: logSummary?.compressed ?? false,
|
|
2261
|
+
});
|
|
2262
|
+
await setWakeupStatus(run.wakeupRequestId, "failed", {
|
|
2263
|
+
finishedAt: new Date(),
|
|
2264
|
+
error: message,
|
|
2265
|
+
});
|
|
2266
|
+
if (failedRun) {
|
|
2267
|
+
await appendRunEvent(failedRun, seq++, {
|
|
2268
|
+
eventType: "error",
|
|
2269
|
+
stream: "system",
|
|
2270
|
+
level: "error",
|
|
2271
|
+
message,
|
|
2272
|
+
});
|
|
2273
|
+
await releaseIssueExecutionAndPromote(failedRun);
|
|
2274
|
+
await updateRuntimeState(agent, failedRun, {
|
|
2275
|
+
exitCode: null,
|
|
2276
|
+
signal: null,
|
|
2277
|
+
timedOut: false,
|
|
2278
|
+
errorMessage: message,
|
|
2279
|
+
}, {
|
|
2280
|
+
legacySessionId: runtimeForAdapter.sessionId,
|
|
2281
|
+
});
|
|
2282
|
+
if (taskKey && (previousSessionParams || previousSessionDisplayId || taskSession)) {
|
|
2283
|
+
await upsertTaskSession({
|
|
2284
|
+
companyId: agent.companyId,
|
|
2285
|
+
agentId: agent.id,
|
|
2286
|
+
adapterType: agent.adapterType,
|
|
2287
|
+
taskKey,
|
|
2288
|
+
sessionParamsJson: previousSessionParams,
|
|
2289
|
+
sessionDisplayId: previousSessionDisplayId,
|
|
2290
|
+
lastRunId: failedRun.id,
|
|
2291
|
+
lastError: message,
|
|
2292
|
+
});
|
|
2293
|
+
}
|
|
2294
|
+
}
|
|
2295
|
+
await finalizeAgentStatus(agent.id, "failed");
|
|
2296
|
+
}
|
|
2297
|
+
}
|
|
2298
|
+
catch (outerErr) {
|
|
2299
|
+
// Setup code before adapter.execute threw (e.g. ensureRuntimeState, resolveWorkspaceForRun).
|
|
2300
|
+
// The inner catch did not fire, so we must record the failure here.
|
|
2301
|
+
const message = outerErr instanceof Error ? outerErr.message : "Unknown setup failure";
|
|
2302
|
+
logger.error({ err: outerErr, runId }, "heartbeat execution setup failed");
|
|
2303
|
+
await setRunStatus(runId, "failed", {
|
|
2304
|
+
error: message,
|
|
2305
|
+
errorCode: "adapter_failed",
|
|
2306
|
+
finishedAt: new Date(),
|
|
2307
|
+
}).catch(() => undefined);
|
|
2308
|
+
await setWakeupStatus(run.wakeupRequestId, "failed", {
|
|
2309
|
+
finishedAt: new Date(),
|
|
2310
|
+
error: message,
|
|
2311
|
+
}).catch(() => undefined);
|
|
2312
|
+
const failedRun = await getRun(runId).catch(() => null);
|
|
2313
|
+
if (failedRun) {
|
|
2314
|
+
// Emit a run-log event so the failure is visible in the run timeline,
|
|
2315
|
+
// consistent with what the inner catch block does for adapter failures.
|
|
2316
|
+
await appendRunEvent(failedRun, 1, {
|
|
2317
|
+
eventType: "error",
|
|
2318
|
+
stream: "system",
|
|
2319
|
+
level: "error",
|
|
2320
|
+
message,
|
|
2321
|
+
}).catch(() => undefined);
|
|
2322
|
+
await releaseIssueExecutionAndPromote(failedRun).catch(() => undefined);
|
|
2323
|
+
}
|
|
2324
|
+
// Ensure the agent is not left stuck in "running" if the inner catch handler's
|
|
2325
|
+
// DB calls threw (e.g. a transient DB error in finalizeAgentStatus).
|
|
2326
|
+
await finalizeAgentStatus(run.agentId, "failed").catch(() => undefined);
|
|
2327
|
+
}
|
|
2328
|
+
finally {
|
|
2329
|
+
await releaseRuntimeServicesForRun(run.id).catch(() => undefined);
|
|
2330
|
+
activeRunExecutions.delete(run.id);
|
|
2331
|
+
await startNextQueuedRunForAgent(run.agentId);
|
|
2332
|
+
}
|
|
2333
|
+
}
|
|
2334
|
+
async function releaseIssueExecutionAndPromote(run) {
|
|
2335
|
+
const promotedRun = await db.transaction(async (tx) => {
|
|
2336
|
+
await tx.execute(sql `select id from issues where company_id = ${run.companyId} and execution_run_id = ${run.id} for update`);
|
|
2337
|
+
const issue = await tx
|
|
2338
|
+
.select({
|
|
2339
|
+
id: issues.id,
|
|
2340
|
+
companyId: issues.companyId,
|
|
2341
|
+
})
|
|
2342
|
+
.from(issues)
|
|
2343
|
+
.where(and(eq(issues.companyId, run.companyId), eq(issues.executionRunId, run.id)))
|
|
2344
|
+
.then((rows) => rows[0] ?? null);
|
|
2345
|
+
if (!issue)
|
|
2346
|
+
return;
|
|
2347
|
+
await tx
|
|
2348
|
+
.update(issues)
|
|
2349
|
+
.set({
|
|
2350
|
+
executionRunId: null,
|
|
2351
|
+
executionAgentNameKey: null,
|
|
2352
|
+
executionLockedAt: null,
|
|
2353
|
+
updatedAt: new Date(),
|
|
2354
|
+
})
|
|
2355
|
+
.where(eq(issues.id, issue.id));
|
|
2356
|
+
while (true) {
|
|
2357
|
+
const deferred = await tx
|
|
2358
|
+
.select()
|
|
2359
|
+
.from(agentWakeupRequests)
|
|
2360
|
+
.where(and(eq(agentWakeupRequests.companyId, issue.companyId), eq(agentWakeupRequests.status, "deferred_issue_execution"), sql `${agentWakeupRequests.payload} ->> 'issueId' = ${issue.id}`))
|
|
2361
|
+
.orderBy(asc(agentWakeupRequests.requestedAt))
|
|
2362
|
+
.limit(1)
|
|
2363
|
+
.then((rows) => rows[0] ?? null);
|
|
2364
|
+
if (!deferred)
|
|
2365
|
+
return null;
|
|
2366
|
+
const deferredAgent = await tx
|
|
2367
|
+
.select()
|
|
2368
|
+
.from(agents)
|
|
2369
|
+
.where(eq(agents.id, deferred.agentId))
|
|
2370
|
+
.then((rows) => rows[0] ?? null);
|
|
2371
|
+
if (!deferredAgent ||
|
|
2372
|
+
deferredAgent.companyId !== issue.companyId ||
|
|
2373
|
+
deferredAgent.status === "paused" ||
|
|
2374
|
+
deferredAgent.status === "terminated" ||
|
|
2375
|
+
deferredAgent.status === "pending_approval") {
|
|
2376
|
+
await tx
|
|
2377
|
+
.update(agentWakeupRequests)
|
|
2378
|
+
.set({
|
|
2379
|
+
status: "failed",
|
|
2380
|
+
finishedAt: new Date(),
|
|
2381
|
+
error: "Deferred wake could not be promoted: agent is not invokable",
|
|
2382
|
+
updatedAt: new Date(),
|
|
2383
|
+
})
|
|
2384
|
+
.where(eq(agentWakeupRequests.id, deferred.id));
|
|
2385
|
+
continue;
|
|
2386
|
+
}
|
|
2387
|
+
const deferredPayload = parseObject(deferred.payload);
|
|
2388
|
+
const deferredContextSeed = parseObject(deferredPayload[DEFERRED_WAKE_CONTEXT_KEY]);
|
|
2389
|
+
const promotedContextSeed = { ...deferredContextSeed };
|
|
2390
|
+
const promotedReason = readNonEmptyString(deferred.reason) ?? "issue_execution_promoted";
|
|
2391
|
+
const promotedSource = readNonEmptyString(deferred.source) ?? "automation";
|
|
2392
|
+
const promotedTriggerDetail = readNonEmptyString(deferred.triggerDetail) ?? null;
|
|
2393
|
+
const promotedPayload = deferredPayload;
|
|
2394
|
+
delete promotedPayload[DEFERRED_WAKE_CONTEXT_KEY];
|
|
2395
|
+
const { contextSnapshot: promotedContextSnapshot, taskKey: promotedTaskKey, } = enrichWakeContextSnapshot({
|
|
2396
|
+
contextSnapshot: promotedContextSeed,
|
|
2397
|
+
reason: promotedReason,
|
|
2398
|
+
source: promotedSource,
|
|
2399
|
+
triggerDetail: promotedTriggerDetail,
|
|
2400
|
+
payload: promotedPayload,
|
|
2401
|
+
});
|
|
2402
|
+
const sessionBefore = readNonEmptyString(promotedContextSnapshot.resumeSessionDisplayId) ??
|
|
2403
|
+
await resolveSessionBeforeForWakeup(deferredAgent, promotedTaskKey);
|
|
2404
|
+
const now = new Date();
|
|
2405
|
+
const newRun = await tx
|
|
2406
|
+
.insert(heartbeatRuns)
|
|
2407
|
+
.values({
|
|
2408
|
+
companyId: deferredAgent.companyId,
|
|
2409
|
+
agentId: deferredAgent.id,
|
|
2410
|
+
invocationSource: promotedSource,
|
|
2411
|
+
triggerDetail: promotedTriggerDetail,
|
|
2412
|
+
status: "queued",
|
|
2413
|
+
wakeupRequestId: deferred.id,
|
|
2414
|
+
contextSnapshot: promotedContextSnapshot,
|
|
2415
|
+
sessionIdBefore: sessionBefore,
|
|
2416
|
+
})
|
|
2417
|
+
.returning()
|
|
2418
|
+
.then((rows) => rows[0]);
|
|
2419
|
+
await tx
|
|
2420
|
+
.update(agentWakeupRequests)
|
|
2421
|
+
.set({
|
|
2422
|
+
status: "queued",
|
|
2423
|
+
reason: "issue_execution_promoted",
|
|
2424
|
+
runId: newRun.id,
|
|
2425
|
+
claimedAt: null,
|
|
2426
|
+
finishedAt: null,
|
|
2427
|
+
error: null,
|
|
2428
|
+
updatedAt: now,
|
|
2429
|
+
})
|
|
2430
|
+
.where(eq(agentWakeupRequests.id, deferred.id));
|
|
2431
|
+
await tx
|
|
2432
|
+
.update(issues)
|
|
2433
|
+
.set({
|
|
2434
|
+
executionRunId: newRun.id,
|
|
2435
|
+
executionAgentNameKey: normalizeAgentNameKey(deferredAgent.name),
|
|
2436
|
+
executionLockedAt: now,
|
|
2437
|
+
updatedAt: now,
|
|
2438
|
+
})
|
|
2439
|
+
.where(eq(issues.id, issue.id));
|
|
2440
|
+
return newRun;
|
|
2441
|
+
}
|
|
2442
|
+
});
|
|
2443
|
+
if (!promotedRun)
|
|
2444
|
+
return;
|
|
2445
|
+
publishLiveEvent({
|
|
2446
|
+
companyId: promotedRun.companyId,
|
|
2447
|
+
type: "heartbeat.run.queued",
|
|
2448
|
+
payload: {
|
|
2449
|
+
runId: promotedRun.id,
|
|
2450
|
+
agentId: promotedRun.agentId,
|
|
2451
|
+
invocationSource: promotedRun.invocationSource,
|
|
2452
|
+
triggerDetail: promotedRun.triggerDetail,
|
|
2453
|
+
wakeupRequestId: promotedRun.wakeupRequestId,
|
|
2454
|
+
},
|
|
2455
|
+
});
|
|
2456
|
+
await startNextQueuedRunForAgent(promotedRun.agentId);
|
|
2457
|
+
}
|
|
2458
|
+
async function enqueueWakeup(agentId, opts = {}) {
|
|
2459
|
+
const source = opts.source ?? "on_demand";
|
|
2460
|
+
const triggerDetail = opts.triggerDetail ?? null;
|
|
2461
|
+
const contextSnapshot = { ...(opts.contextSnapshot ?? {}) };
|
|
2462
|
+
const reason = opts.reason ?? null;
|
|
2463
|
+
const payload = opts.payload ?? null;
|
|
2464
|
+
const { contextSnapshot: enrichedContextSnapshot, issueIdFromPayload, taskKey, wakeCommentId, } = enrichWakeContextSnapshot({
|
|
2465
|
+
contextSnapshot,
|
|
2466
|
+
reason,
|
|
2467
|
+
source,
|
|
2468
|
+
triggerDetail,
|
|
2469
|
+
payload,
|
|
2470
|
+
});
|
|
2471
|
+
let issueId = readNonEmptyString(enrichedContextSnapshot.issueId) ?? issueIdFromPayload;
|
|
2472
|
+
const agent = await getAgent(agentId);
|
|
2473
|
+
if (!agent)
|
|
2474
|
+
throw notFound("Agent not found");
|
|
2475
|
+
const explicitResumeSession = await resolveExplicitResumeSessionOverride(agent, payload, taskKey);
|
|
2476
|
+
if (explicitResumeSession) {
|
|
2477
|
+
enrichedContextSnapshot.resumeFromRunId = explicitResumeSession.resumeFromRunId;
|
|
2478
|
+
enrichedContextSnapshot.resumeSessionDisplayId = explicitResumeSession.sessionDisplayId;
|
|
2479
|
+
enrichedContextSnapshot.resumeSessionParams = explicitResumeSession.sessionParams;
|
|
2480
|
+
if (!readNonEmptyString(enrichedContextSnapshot.issueId) && explicitResumeSession.issueId) {
|
|
2481
|
+
enrichedContextSnapshot.issueId = explicitResumeSession.issueId;
|
|
2482
|
+
}
|
|
2483
|
+
if (!readNonEmptyString(enrichedContextSnapshot.taskId) && explicitResumeSession.taskId) {
|
|
2484
|
+
enrichedContextSnapshot.taskId = explicitResumeSession.taskId;
|
|
2485
|
+
}
|
|
2486
|
+
if (!readNonEmptyString(enrichedContextSnapshot.taskKey) && explicitResumeSession.taskKey) {
|
|
2487
|
+
enrichedContextSnapshot.taskKey = explicitResumeSession.taskKey;
|
|
2488
|
+
}
|
|
2489
|
+
issueId = readNonEmptyString(enrichedContextSnapshot.issueId) ?? issueId;
|
|
2490
|
+
}
|
|
2491
|
+
const effectiveTaskKey = readNonEmptyString(enrichedContextSnapshot.taskKey) ?? taskKey;
|
|
2492
|
+
const sessionBefore = explicitResumeSession?.sessionDisplayId ??
|
|
2493
|
+
await resolveSessionBeforeForWakeup(agent, effectiveTaskKey);
|
|
2494
|
+
const writeSkippedRequest = async (skipReason) => {
|
|
2495
|
+
await db.insert(agentWakeupRequests).values({
|
|
2496
|
+
companyId: agent.companyId,
|
|
2497
|
+
agentId,
|
|
2498
|
+
source,
|
|
2499
|
+
triggerDetail,
|
|
2500
|
+
reason: skipReason,
|
|
2501
|
+
payload,
|
|
2502
|
+
status: "skipped",
|
|
2503
|
+
requestedByActorType: opts.requestedByActorType ?? null,
|
|
2504
|
+
requestedByActorId: opts.requestedByActorId ?? null,
|
|
2505
|
+
idempotencyKey: opts.idempotencyKey ?? null,
|
|
2506
|
+
finishedAt: new Date(),
|
|
2507
|
+
});
|
|
2508
|
+
};
|
|
2509
|
+
let projectId = readNonEmptyString(enrichedContextSnapshot.projectId);
|
|
2510
|
+
if (!projectId && issueId) {
|
|
2511
|
+
projectId = await db
|
|
2512
|
+
.select({ projectId: issues.projectId })
|
|
2513
|
+
.from(issues)
|
|
2514
|
+
.where(and(eq(issues.id, issueId), eq(issues.companyId, agent.companyId)))
|
|
2515
|
+
.then((rows) => rows[0]?.projectId ?? null);
|
|
2516
|
+
}
|
|
2517
|
+
const budgetBlock = await budgets.getInvocationBlock(agent.companyId, agentId, {
|
|
2518
|
+
issueId,
|
|
2519
|
+
projectId,
|
|
2520
|
+
});
|
|
2521
|
+
if (budgetBlock) {
|
|
2522
|
+
await writeSkippedRequest("budget.blocked");
|
|
2523
|
+
throw conflict(budgetBlock.reason, {
|
|
2524
|
+
scopeType: budgetBlock.scopeType,
|
|
2525
|
+
scopeId: budgetBlock.scopeId,
|
|
2526
|
+
});
|
|
2527
|
+
}
|
|
2528
|
+
if (agent.status === "paused" ||
|
|
2529
|
+
agent.status === "terminated" ||
|
|
2530
|
+
agent.status === "pending_approval") {
|
|
2531
|
+
throw conflict("Agent is not invokable in its current state", { status: agent.status });
|
|
2532
|
+
}
|
|
2533
|
+
const policy = parseHeartbeatPolicy(agent);
|
|
2534
|
+
if (source === "timer" && !policy.enabled) {
|
|
2535
|
+
await writeSkippedRequest("heartbeat.disabled");
|
|
2536
|
+
return null;
|
|
2537
|
+
}
|
|
2538
|
+
if (source !== "timer" && !policy.wakeOnDemand) {
|
|
2539
|
+
await writeSkippedRequest("heartbeat.wakeOnDemand.disabled");
|
|
2540
|
+
return null;
|
|
2541
|
+
}
|
|
2542
|
+
const bypassIssueExecutionLock = reason === "issue_comment_mentioned" ||
|
|
2543
|
+
readNonEmptyString(enrichedContextSnapshot.wakeReason) === "issue_comment_mentioned";
|
|
2544
|
+
if (issueId && !bypassIssueExecutionLock) {
|
|
2545
|
+
const agentNameKey = normalizeAgentNameKey(agent.name);
|
|
2546
|
+
const outcome = await db.transaction(async (tx) => {
|
|
2547
|
+
await tx.execute(sql `select id from issues where id = ${issueId} and company_id = ${agent.companyId} for update`);
|
|
2548
|
+
const issue = await tx
|
|
2549
|
+
.select({
|
|
2550
|
+
id: issues.id,
|
|
2551
|
+
companyId: issues.companyId,
|
|
2552
|
+
executionRunId: issues.executionRunId,
|
|
2553
|
+
executionAgentNameKey: issues.executionAgentNameKey,
|
|
2554
|
+
})
|
|
2555
|
+
.from(issues)
|
|
2556
|
+
.where(and(eq(issues.id, issueId), eq(issues.companyId, agent.companyId)))
|
|
2557
|
+
.then((rows) => rows[0] ?? null);
|
|
2558
|
+
if (!issue) {
|
|
2559
|
+
await tx.insert(agentWakeupRequests).values({
|
|
2560
|
+
companyId: agent.companyId,
|
|
2561
|
+
agentId,
|
|
2562
|
+
source,
|
|
2563
|
+
triggerDetail,
|
|
2564
|
+
reason: "issue_execution_issue_not_found",
|
|
2565
|
+
payload,
|
|
2566
|
+
status: "skipped",
|
|
2567
|
+
requestedByActorType: opts.requestedByActorType ?? null,
|
|
2568
|
+
requestedByActorId: opts.requestedByActorId ?? null,
|
|
2569
|
+
idempotencyKey: opts.idempotencyKey ?? null,
|
|
2570
|
+
finishedAt: new Date(),
|
|
2571
|
+
});
|
|
2572
|
+
return { kind: "skipped" };
|
|
2573
|
+
}
|
|
2574
|
+
let activeExecutionRun = issue.executionRunId
|
|
2575
|
+
? await tx
|
|
2576
|
+
.select()
|
|
2577
|
+
.from(heartbeatRuns)
|
|
2578
|
+
.where(eq(heartbeatRuns.id, issue.executionRunId))
|
|
2579
|
+
.then((rows) => rows[0] ?? null)
|
|
2580
|
+
: null;
|
|
2581
|
+
if (activeExecutionRun && activeExecutionRun.status !== "queued" && activeExecutionRun.status !== "running") {
|
|
2582
|
+
activeExecutionRun = null;
|
|
2583
|
+
}
|
|
2584
|
+
if (!activeExecutionRun && issue.executionRunId) {
|
|
2585
|
+
await tx
|
|
2586
|
+
.update(issues)
|
|
2587
|
+
.set({
|
|
2588
|
+
executionRunId: null,
|
|
2589
|
+
executionAgentNameKey: null,
|
|
2590
|
+
executionLockedAt: null,
|
|
2591
|
+
updatedAt: new Date(),
|
|
2592
|
+
})
|
|
2593
|
+
.where(eq(issues.id, issue.id));
|
|
2594
|
+
}
|
|
2595
|
+
if (!activeExecutionRun) {
|
|
2596
|
+
const legacyRun = await tx
|
|
2597
|
+
.select()
|
|
2598
|
+
.from(heartbeatRuns)
|
|
2599
|
+
.where(and(eq(heartbeatRuns.companyId, issue.companyId), inArray(heartbeatRuns.status, ["queued", "running"]), sql `${heartbeatRuns.contextSnapshot} ->> 'issueId' = ${issue.id}`))
|
|
2600
|
+
.orderBy(sql `case when ${heartbeatRuns.status} = 'running' then 0 else 1 end`, asc(heartbeatRuns.createdAt))
|
|
2601
|
+
.limit(1)
|
|
2602
|
+
.then((rows) => rows[0] ?? null);
|
|
2603
|
+
if (legacyRun) {
|
|
2604
|
+
activeExecutionRun = legacyRun;
|
|
2605
|
+
const legacyAgent = await tx
|
|
2606
|
+
.select({ name: agents.name })
|
|
2607
|
+
.from(agents)
|
|
2608
|
+
.where(eq(agents.id, legacyRun.agentId))
|
|
2609
|
+
.then((rows) => rows[0] ?? null);
|
|
2610
|
+
await tx
|
|
2611
|
+
.update(issues)
|
|
2612
|
+
.set({
|
|
2613
|
+
executionRunId: legacyRun.id,
|
|
2614
|
+
executionAgentNameKey: normalizeAgentNameKey(legacyAgent?.name),
|
|
2615
|
+
executionLockedAt: new Date(),
|
|
2616
|
+
updatedAt: new Date(),
|
|
2617
|
+
})
|
|
2618
|
+
.where(eq(issues.id, issue.id));
|
|
2619
|
+
}
|
|
2620
|
+
}
|
|
2621
|
+
if (activeExecutionRun) {
|
|
2622
|
+
const executionAgent = await tx
|
|
2623
|
+
.select({ name: agents.name })
|
|
2624
|
+
.from(agents)
|
|
2625
|
+
.where(eq(agents.id, activeExecutionRun.agentId))
|
|
2626
|
+
.then((rows) => rows[0] ?? null);
|
|
2627
|
+
const executionAgentNameKey = normalizeAgentNameKey(issue.executionAgentNameKey) ??
|
|
2628
|
+
normalizeAgentNameKey(executionAgent?.name);
|
|
2629
|
+
const isSameExecutionAgent = Boolean(executionAgentNameKey) && executionAgentNameKey === agentNameKey;
|
|
2630
|
+
const shouldQueueFollowupForCommentWake = Boolean(wakeCommentId) &&
|
|
2631
|
+
activeExecutionRun.status === "running" &&
|
|
2632
|
+
isSameExecutionAgent;
|
|
2633
|
+
if (isSameExecutionAgent && !shouldQueueFollowupForCommentWake) {
|
|
2634
|
+
const mergedContextSnapshot = mergeCoalescedContextSnapshot(activeExecutionRun.contextSnapshot, enrichedContextSnapshot);
|
|
2635
|
+
const mergedRun = await tx
|
|
2636
|
+
.update(heartbeatRuns)
|
|
2637
|
+
.set({
|
|
2638
|
+
contextSnapshot: mergedContextSnapshot,
|
|
2639
|
+
updatedAt: new Date(),
|
|
2640
|
+
})
|
|
2641
|
+
.where(eq(heartbeatRuns.id, activeExecutionRun.id))
|
|
2642
|
+
.returning()
|
|
2643
|
+
.then((rows) => rows[0] ?? activeExecutionRun);
|
|
2644
|
+
await tx.insert(agentWakeupRequests).values({
|
|
2645
|
+
companyId: agent.companyId,
|
|
2646
|
+
agentId,
|
|
2647
|
+
source,
|
|
2648
|
+
triggerDetail,
|
|
2649
|
+
reason: "issue_execution_same_name",
|
|
2650
|
+
payload,
|
|
2651
|
+
status: "coalesced",
|
|
2652
|
+
coalescedCount: 1,
|
|
2653
|
+
requestedByActorType: opts.requestedByActorType ?? null,
|
|
2654
|
+
requestedByActorId: opts.requestedByActorId ?? null,
|
|
2655
|
+
idempotencyKey: opts.idempotencyKey ?? null,
|
|
2656
|
+
runId: mergedRun.id,
|
|
2657
|
+
finishedAt: new Date(),
|
|
2658
|
+
});
|
|
2659
|
+
return { kind: "coalesced", run: mergedRun };
|
|
2660
|
+
}
|
|
2661
|
+
const deferredPayload = {
|
|
2662
|
+
...(payload ?? {}),
|
|
2663
|
+
issueId,
|
|
2664
|
+
[DEFERRED_WAKE_CONTEXT_KEY]: enrichedContextSnapshot,
|
|
2665
|
+
};
|
|
2666
|
+
const existingDeferred = await tx
|
|
2667
|
+
.select()
|
|
2668
|
+
.from(agentWakeupRequests)
|
|
2669
|
+
.where(and(eq(agentWakeupRequests.companyId, agent.companyId), eq(agentWakeupRequests.agentId, agentId), eq(agentWakeupRequests.status, "deferred_issue_execution"), sql `${agentWakeupRequests.payload} ->> 'issueId' = ${issue.id}`))
|
|
2670
|
+
.orderBy(asc(agentWakeupRequests.requestedAt))
|
|
2671
|
+
.limit(1)
|
|
2672
|
+
.then((rows) => rows[0] ?? null);
|
|
2673
|
+
if (existingDeferred) {
|
|
2674
|
+
const existingDeferredPayload = parseObject(existingDeferred.payload);
|
|
2675
|
+
const existingDeferredContext = parseObject(existingDeferredPayload[DEFERRED_WAKE_CONTEXT_KEY]);
|
|
2676
|
+
const mergedDeferredContext = mergeCoalescedContextSnapshot(existingDeferredContext, enrichedContextSnapshot);
|
|
2677
|
+
const mergedDeferredPayload = {
|
|
2678
|
+
...existingDeferredPayload,
|
|
2679
|
+
...(payload ?? {}),
|
|
2680
|
+
issueId,
|
|
2681
|
+
[DEFERRED_WAKE_CONTEXT_KEY]: mergedDeferredContext,
|
|
2682
|
+
};
|
|
2683
|
+
await tx
|
|
2684
|
+
.update(agentWakeupRequests)
|
|
2685
|
+
.set({
|
|
2686
|
+
payload: mergedDeferredPayload,
|
|
2687
|
+
coalescedCount: (existingDeferred.coalescedCount ?? 0) + 1,
|
|
2688
|
+
updatedAt: new Date(),
|
|
2689
|
+
})
|
|
2690
|
+
.where(eq(agentWakeupRequests.id, existingDeferred.id));
|
|
2691
|
+
return { kind: "deferred" };
|
|
2692
|
+
}
|
|
2693
|
+
await tx.insert(agentWakeupRequests).values({
|
|
2694
|
+
companyId: agent.companyId,
|
|
2695
|
+
agentId,
|
|
2696
|
+
source,
|
|
2697
|
+
triggerDetail,
|
|
2698
|
+
reason: "issue_execution_deferred",
|
|
2699
|
+
payload: deferredPayload,
|
|
2700
|
+
status: "deferred_issue_execution",
|
|
2701
|
+
requestedByActorType: opts.requestedByActorType ?? null,
|
|
2702
|
+
requestedByActorId: opts.requestedByActorId ?? null,
|
|
2703
|
+
idempotencyKey: opts.idempotencyKey ?? null,
|
|
2704
|
+
});
|
|
2705
|
+
return { kind: "deferred" };
|
|
2706
|
+
}
|
|
2707
|
+
const wakeupRequest = await tx
|
|
2708
|
+
.insert(agentWakeupRequests)
|
|
2709
|
+
.values({
|
|
2710
|
+
companyId: agent.companyId,
|
|
2711
|
+
agentId,
|
|
2712
|
+
source,
|
|
2713
|
+
triggerDetail,
|
|
2714
|
+
reason,
|
|
2715
|
+
payload,
|
|
2716
|
+
status: "queued",
|
|
2717
|
+
requestedByActorType: opts.requestedByActorType ?? null,
|
|
2718
|
+
requestedByActorId: opts.requestedByActorId ?? null,
|
|
2719
|
+
idempotencyKey: opts.idempotencyKey ?? null,
|
|
2720
|
+
})
|
|
2721
|
+
.returning()
|
|
2722
|
+
.then((rows) => rows[0]);
|
|
2723
|
+
const newRun = await tx
|
|
2724
|
+
.insert(heartbeatRuns)
|
|
2725
|
+
.values({
|
|
2726
|
+
companyId: agent.companyId,
|
|
2727
|
+
agentId,
|
|
2728
|
+
invocationSource: source,
|
|
2729
|
+
triggerDetail,
|
|
2730
|
+
status: "queued",
|
|
2731
|
+
wakeupRequestId: wakeupRequest.id,
|
|
2732
|
+
contextSnapshot: enrichedContextSnapshot,
|
|
2733
|
+
sessionIdBefore: sessionBefore,
|
|
2734
|
+
})
|
|
2735
|
+
.returning()
|
|
2736
|
+
.then((rows) => rows[0]);
|
|
2737
|
+
await tx
|
|
2738
|
+
.update(agentWakeupRequests)
|
|
2739
|
+
.set({
|
|
2740
|
+
runId: newRun.id,
|
|
2741
|
+
updatedAt: new Date(),
|
|
2742
|
+
})
|
|
2743
|
+
.where(eq(agentWakeupRequests.id, wakeupRequest.id));
|
|
2744
|
+
await tx
|
|
2745
|
+
.update(issues)
|
|
2746
|
+
.set({
|
|
2747
|
+
executionRunId: newRun.id,
|
|
2748
|
+
executionAgentNameKey: agentNameKey,
|
|
2749
|
+
executionLockedAt: new Date(),
|
|
2750
|
+
updatedAt: new Date(),
|
|
2751
|
+
})
|
|
2752
|
+
.where(eq(issues.id, issue.id));
|
|
2753
|
+
return { kind: "queued", run: newRun };
|
|
2754
|
+
});
|
|
2755
|
+
if (outcome.kind === "deferred" || outcome.kind === "skipped")
|
|
2756
|
+
return null;
|
|
2757
|
+
if (outcome.kind === "coalesced")
|
|
2758
|
+
return outcome.run;
|
|
2759
|
+
const newRun = outcome.run;
|
|
2760
|
+
publishLiveEvent({
|
|
2761
|
+
companyId: newRun.companyId,
|
|
2762
|
+
type: "heartbeat.run.queued",
|
|
2763
|
+
payload: {
|
|
2764
|
+
runId: newRun.id,
|
|
2765
|
+
agentId: newRun.agentId,
|
|
2766
|
+
invocationSource: newRun.invocationSource,
|
|
2767
|
+
triggerDetail: newRun.triggerDetail,
|
|
2768
|
+
wakeupRequestId: newRun.wakeupRequestId,
|
|
2769
|
+
},
|
|
2770
|
+
});
|
|
2771
|
+
await startNextQueuedRunForAgent(agent.id);
|
|
2772
|
+
return newRun;
|
|
2773
|
+
}
|
|
2774
|
+
const activeRuns = await db
|
|
2775
|
+
.select()
|
|
2776
|
+
.from(heartbeatRuns)
|
|
2777
|
+
.where(and(eq(heartbeatRuns.agentId, agentId), inArray(heartbeatRuns.status, ["queued", "running"])))
|
|
2778
|
+
.orderBy(desc(heartbeatRuns.createdAt));
|
|
2779
|
+
const sameScopeQueuedRun = activeRuns.find((candidate) => candidate.status === "queued" && isSameTaskScope(runTaskKey(candidate), taskKey));
|
|
2780
|
+
const sameScopeRunningRun = activeRuns.find((candidate) => candidate.status === "running" && isSameTaskScope(runTaskKey(candidate), taskKey));
|
|
2781
|
+
const shouldQueueFollowupForCommentWake = Boolean(wakeCommentId) && Boolean(sameScopeRunningRun) && !sameScopeQueuedRun;
|
|
2782
|
+
const coalescedTargetRun = sameScopeQueuedRun ??
|
|
2783
|
+
(shouldQueueFollowupForCommentWake ? null : sameScopeRunningRun ?? null);
|
|
2784
|
+
if (coalescedTargetRun) {
|
|
2785
|
+
const mergedContextSnapshot = mergeCoalescedContextSnapshot(coalescedTargetRun.contextSnapshot, contextSnapshot);
|
|
2786
|
+
const mergedRun = await db
|
|
2787
|
+
.update(heartbeatRuns)
|
|
2788
|
+
.set({
|
|
2789
|
+
contextSnapshot: mergedContextSnapshot,
|
|
2790
|
+
updatedAt: new Date(),
|
|
2791
|
+
})
|
|
2792
|
+
.where(eq(heartbeatRuns.id, coalescedTargetRun.id))
|
|
2793
|
+
.returning()
|
|
2794
|
+
.then((rows) => rows[0] ?? coalescedTargetRun);
|
|
2795
|
+
await db.insert(agentWakeupRequests).values({
|
|
2796
|
+
companyId: agent.companyId,
|
|
2797
|
+
agentId,
|
|
2798
|
+
source,
|
|
2799
|
+
triggerDetail,
|
|
2800
|
+
reason,
|
|
2801
|
+
payload,
|
|
2802
|
+
status: "coalesced",
|
|
2803
|
+
coalescedCount: 1,
|
|
2804
|
+
requestedByActorType: opts.requestedByActorType ?? null,
|
|
2805
|
+
requestedByActorId: opts.requestedByActorId ?? null,
|
|
2806
|
+
idempotencyKey: opts.idempotencyKey ?? null,
|
|
2807
|
+
runId: mergedRun.id,
|
|
2808
|
+
finishedAt: new Date(),
|
|
2809
|
+
});
|
|
2810
|
+
return mergedRun;
|
|
2811
|
+
}
|
|
2812
|
+
const wakeupRequest = await db
|
|
2813
|
+
.insert(agentWakeupRequests)
|
|
2814
|
+
.values({
|
|
2815
|
+
companyId: agent.companyId,
|
|
2816
|
+
agentId,
|
|
2817
|
+
source,
|
|
2818
|
+
triggerDetail,
|
|
2819
|
+
reason,
|
|
2820
|
+
payload,
|
|
2821
|
+
status: "queued",
|
|
2822
|
+
requestedByActorType: opts.requestedByActorType ?? null,
|
|
2823
|
+
requestedByActorId: opts.requestedByActorId ?? null,
|
|
2824
|
+
idempotencyKey: opts.idempotencyKey ?? null,
|
|
2825
|
+
})
|
|
2826
|
+
.returning()
|
|
2827
|
+
.then((rows) => rows[0]);
|
|
2828
|
+
const newRun = await db
|
|
2829
|
+
.insert(heartbeatRuns)
|
|
2830
|
+
.values({
|
|
2831
|
+
companyId: agent.companyId,
|
|
2832
|
+
agentId,
|
|
2833
|
+
invocationSource: source,
|
|
2834
|
+
triggerDetail,
|
|
2835
|
+
status: "queued",
|
|
2836
|
+
wakeupRequestId: wakeupRequest.id,
|
|
2837
|
+
contextSnapshot: enrichedContextSnapshot,
|
|
2838
|
+
sessionIdBefore: sessionBefore,
|
|
2839
|
+
})
|
|
2840
|
+
.returning()
|
|
2841
|
+
.then((rows) => rows[0]);
|
|
2842
|
+
await db
|
|
2843
|
+
.update(agentWakeupRequests)
|
|
2844
|
+
.set({
|
|
2845
|
+
runId: newRun.id,
|
|
2846
|
+
updatedAt: new Date(),
|
|
2847
|
+
})
|
|
2848
|
+
.where(eq(agentWakeupRequests.id, wakeupRequest.id));
|
|
2849
|
+
publishLiveEvent({
|
|
2850
|
+
companyId: newRun.companyId,
|
|
2851
|
+
type: "heartbeat.run.queued",
|
|
2852
|
+
payload: {
|
|
2853
|
+
runId: newRun.id,
|
|
2854
|
+
agentId: newRun.agentId,
|
|
2855
|
+
invocationSource: newRun.invocationSource,
|
|
2856
|
+
triggerDetail: newRun.triggerDetail,
|
|
2857
|
+
wakeupRequestId: newRun.wakeupRequestId,
|
|
2858
|
+
},
|
|
2859
|
+
});
|
|
2860
|
+
await startNextQueuedRunForAgent(agent.id);
|
|
2861
|
+
return newRun;
|
|
2862
|
+
}
|
|
2863
|
+
async function listProjectScopedRunIds(companyId, projectId) {
|
|
2864
|
+
const runIssueId = sql `${heartbeatRuns.contextSnapshot} ->> 'issueId'`;
|
|
2865
|
+
const effectiveProjectId = sql `coalesce(${heartbeatRuns.contextSnapshot} ->> 'projectId', ${issues.projectId}::text)`;
|
|
2866
|
+
const rows = await db
|
|
2867
|
+
.selectDistinctOn([heartbeatRuns.id], { id: heartbeatRuns.id })
|
|
2868
|
+
.from(heartbeatRuns)
|
|
2869
|
+
.leftJoin(issues, and(eq(issues.companyId, companyId), sql `${issues.id}::text = ${runIssueId}`))
|
|
2870
|
+
.where(and(eq(heartbeatRuns.companyId, companyId), inArray(heartbeatRuns.status, ["queued", "running"]), sql `${effectiveProjectId} = ${projectId}`));
|
|
2871
|
+
return rows.map((row) => row.id);
|
|
2872
|
+
}
|
|
2873
|
+
async function listProjectScopedWakeupIds(companyId, projectId) {
|
|
2874
|
+
const wakeIssueId = sql `${agentWakeupRequests.payload} ->> 'issueId'`;
|
|
2875
|
+
const effectiveProjectId = sql `coalesce(${agentWakeupRequests.payload} ->> 'projectId', ${issues.projectId}::text)`;
|
|
2876
|
+
const rows = await db
|
|
2877
|
+
.selectDistinctOn([agentWakeupRequests.id], { id: agentWakeupRequests.id })
|
|
2878
|
+
.from(agentWakeupRequests)
|
|
2879
|
+
.leftJoin(issues, and(eq(issues.companyId, companyId), sql `${issues.id}::text = ${wakeIssueId}`))
|
|
2880
|
+
.where(and(eq(agentWakeupRequests.companyId, companyId), inArray(agentWakeupRequests.status, ["queued", "deferred_issue_execution"]), sql `${agentWakeupRequests.runId} is null`, sql `${effectiveProjectId} = ${projectId}`));
|
|
2881
|
+
return rows.map((row) => row.id);
|
|
2882
|
+
}
|
|
2883
|
+
async function cancelPendingWakeupsForBudgetScope(scope) {
|
|
2884
|
+
const now = new Date();
|
|
2885
|
+
let wakeupIds = [];
|
|
2886
|
+
if (scope.scopeType === "company") {
|
|
2887
|
+
wakeupIds = await db
|
|
2888
|
+
.select({ id: agentWakeupRequests.id })
|
|
2889
|
+
.from(agentWakeupRequests)
|
|
2890
|
+
.where(and(eq(agentWakeupRequests.companyId, scope.companyId), inArray(agentWakeupRequests.status, ["queued", "deferred_issue_execution"]), sql `${agentWakeupRequests.runId} is null`))
|
|
2891
|
+
.then((rows) => rows.map((row) => row.id));
|
|
2892
|
+
}
|
|
2893
|
+
else if (scope.scopeType === "agent") {
|
|
2894
|
+
wakeupIds = await db
|
|
2895
|
+
.select({ id: agentWakeupRequests.id })
|
|
2896
|
+
.from(agentWakeupRequests)
|
|
2897
|
+
.where(and(eq(agentWakeupRequests.companyId, scope.companyId), eq(agentWakeupRequests.agentId, scope.scopeId), inArray(agentWakeupRequests.status, ["queued", "deferred_issue_execution"]), sql `${agentWakeupRequests.runId} is null`))
|
|
2898
|
+
.then((rows) => rows.map((row) => row.id));
|
|
2899
|
+
}
|
|
2900
|
+
else {
|
|
2901
|
+
wakeupIds = await listProjectScopedWakeupIds(scope.companyId, scope.scopeId);
|
|
2902
|
+
}
|
|
2903
|
+
if (wakeupIds.length === 0)
|
|
2904
|
+
return 0;
|
|
2905
|
+
await db
|
|
2906
|
+
.update(agentWakeupRequests)
|
|
2907
|
+
.set({
|
|
2908
|
+
status: "cancelled",
|
|
2909
|
+
finishedAt: now,
|
|
2910
|
+
error: "Cancelled due to budget pause",
|
|
2911
|
+
updatedAt: now,
|
|
2912
|
+
})
|
|
2913
|
+
.where(inArray(agentWakeupRequests.id, wakeupIds));
|
|
2914
|
+
return wakeupIds.length;
|
|
2915
|
+
}
|
|
2916
|
+
async function cancelRunInternal(runId, reason = "Cancelled by control plane") {
|
|
2917
|
+
const run = await getRun(runId);
|
|
2918
|
+
if (!run)
|
|
2919
|
+
throw notFound("Heartbeat run not found");
|
|
2920
|
+
if (run.status !== "running" && run.status !== "queued")
|
|
2921
|
+
return run;
|
|
2922
|
+
const running = runningProcesses.get(run.id);
|
|
2923
|
+
if (running) {
|
|
2924
|
+
running.child.kill("SIGTERM");
|
|
2925
|
+
const graceMs = Math.max(1, running.graceSec) * 1000;
|
|
2926
|
+
setTimeout(() => {
|
|
2927
|
+
if (!running.child.killed) {
|
|
2928
|
+
running.child.kill("SIGKILL");
|
|
2929
|
+
}
|
|
2930
|
+
}, graceMs);
|
|
2931
|
+
}
|
|
2932
|
+
const cancelled = await setRunStatus(run.id, "cancelled", {
|
|
2933
|
+
finishedAt: new Date(),
|
|
2934
|
+
error: reason,
|
|
2935
|
+
errorCode: "cancelled",
|
|
2936
|
+
});
|
|
2937
|
+
await setWakeupStatus(run.wakeupRequestId, "cancelled", {
|
|
2938
|
+
finishedAt: new Date(),
|
|
2939
|
+
error: reason,
|
|
2940
|
+
});
|
|
2941
|
+
if (cancelled) {
|
|
2942
|
+
await appendRunEvent(cancelled, 1, {
|
|
2943
|
+
eventType: "lifecycle",
|
|
2944
|
+
stream: "system",
|
|
2945
|
+
level: "warn",
|
|
2946
|
+
message: "run cancelled",
|
|
2947
|
+
});
|
|
2948
|
+
await releaseIssueExecutionAndPromote(cancelled);
|
|
2949
|
+
}
|
|
2950
|
+
runningProcesses.delete(run.id);
|
|
2951
|
+
await finalizeAgentStatus(run.agentId, "cancelled");
|
|
2952
|
+
await startNextQueuedRunForAgent(run.agentId);
|
|
2953
|
+
return cancelled;
|
|
2954
|
+
}
|
|
2955
|
+
async function cancelActiveForAgentInternal(agentId, reason = "Cancelled due to agent pause") {
|
|
2956
|
+
const runs = await db
|
|
2957
|
+
.select()
|
|
2958
|
+
.from(heartbeatRuns)
|
|
2959
|
+
.where(and(eq(heartbeatRuns.agentId, agentId), inArray(heartbeatRuns.status, ["queued", "running"])));
|
|
2960
|
+
for (const run of runs) {
|
|
2961
|
+
await setRunStatus(run.id, "cancelled", {
|
|
2962
|
+
finishedAt: new Date(),
|
|
2963
|
+
error: reason,
|
|
2964
|
+
errorCode: "cancelled",
|
|
2965
|
+
});
|
|
2966
|
+
await setWakeupStatus(run.wakeupRequestId, "cancelled", {
|
|
2967
|
+
finishedAt: new Date(),
|
|
2968
|
+
error: reason,
|
|
2969
|
+
});
|
|
2970
|
+
const running = runningProcesses.get(run.id);
|
|
2971
|
+
if (running) {
|
|
2972
|
+
running.child.kill("SIGTERM");
|
|
2973
|
+
runningProcesses.delete(run.id);
|
|
2974
|
+
}
|
|
2975
|
+
await releaseIssueExecutionAndPromote(run);
|
|
2976
|
+
}
|
|
2977
|
+
return runs.length;
|
|
2978
|
+
}
|
|
2979
|
+
async function cancelBudgetScopeWork(scope) {
|
|
2980
|
+
if (scope.scopeType === "agent") {
|
|
2981
|
+
await cancelActiveForAgentInternal(scope.scopeId, "Cancelled due to budget pause");
|
|
2982
|
+
await cancelPendingWakeupsForBudgetScope(scope);
|
|
2983
|
+
return;
|
|
2984
|
+
}
|
|
2985
|
+
const runIds = scope.scopeType === "company"
|
|
2986
|
+
? await db
|
|
2987
|
+
.select({ id: heartbeatRuns.id })
|
|
2988
|
+
.from(heartbeatRuns)
|
|
2989
|
+
.where(and(eq(heartbeatRuns.companyId, scope.companyId), inArray(heartbeatRuns.status, ["queued", "running"])))
|
|
2990
|
+
.then((rows) => rows.map((row) => row.id))
|
|
2991
|
+
: await listProjectScopedRunIds(scope.companyId, scope.scopeId);
|
|
2992
|
+
for (const runId of runIds) {
|
|
2993
|
+
await cancelRunInternal(runId, "Cancelled due to budget pause");
|
|
2994
|
+
}
|
|
2995
|
+
await cancelPendingWakeupsForBudgetScope(scope);
|
|
2996
|
+
}
|
|
2997
|
+
return {
|
|
2998
|
+
list: async (companyId, agentId, limit) => {
|
|
2999
|
+
const query = db
|
|
3000
|
+
.select(heartbeatRunListColumns)
|
|
3001
|
+
.from(heartbeatRuns)
|
|
3002
|
+
.where(agentId
|
|
3003
|
+
? and(eq(heartbeatRuns.companyId, companyId), eq(heartbeatRuns.agentId, agentId))
|
|
3004
|
+
: eq(heartbeatRuns.companyId, companyId))
|
|
3005
|
+
.orderBy(desc(heartbeatRuns.createdAt));
|
|
3006
|
+
const rows = limit ? await query.limit(limit) : await query;
|
|
3007
|
+
return rows.map((row) => ({
|
|
3008
|
+
...row,
|
|
3009
|
+
resultJson: summarizeHeartbeatRunResultJson(row.resultJson),
|
|
3010
|
+
}));
|
|
3011
|
+
},
|
|
3012
|
+
getRun,
|
|
3013
|
+
getRuntimeState: async (agentId) => {
|
|
3014
|
+
const state = await getRuntimeState(agentId);
|
|
3015
|
+
const agent = await getAgent(agentId);
|
|
3016
|
+
if (!agent)
|
|
3017
|
+
return null;
|
|
3018
|
+
const ensured = state ?? (await ensureRuntimeState(agent));
|
|
3019
|
+
const latestTaskSession = await db
|
|
3020
|
+
.select()
|
|
3021
|
+
.from(agentTaskSessions)
|
|
3022
|
+
.where(and(eq(agentTaskSessions.companyId, agent.companyId), eq(agentTaskSessions.agentId, agent.id)))
|
|
3023
|
+
.orderBy(desc(agentTaskSessions.updatedAt))
|
|
3024
|
+
.limit(1)
|
|
3025
|
+
.then((rows) => rows[0] ?? null);
|
|
3026
|
+
return {
|
|
3027
|
+
...ensured,
|
|
3028
|
+
sessionDisplayId: latestTaskSession?.sessionDisplayId ?? ensured.sessionId,
|
|
3029
|
+
sessionParamsJson: latestTaskSession?.sessionParamsJson ?? null,
|
|
3030
|
+
};
|
|
3031
|
+
},
|
|
3032
|
+
listTaskSessions: async (agentId) => {
|
|
3033
|
+
const agent = await getAgent(agentId);
|
|
3034
|
+
if (!agent)
|
|
3035
|
+
throw notFound("Agent not found");
|
|
3036
|
+
return db
|
|
3037
|
+
.select()
|
|
3038
|
+
.from(agentTaskSessions)
|
|
3039
|
+
.where(and(eq(agentTaskSessions.companyId, agent.companyId), eq(agentTaskSessions.agentId, agentId)))
|
|
3040
|
+
.orderBy(desc(agentTaskSessions.updatedAt), desc(agentTaskSessions.createdAt));
|
|
3041
|
+
},
|
|
3042
|
+
resetRuntimeSession: async (agentId, opts) => {
|
|
3043
|
+
const agent = await getAgent(agentId);
|
|
3044
|
+
if (!agent)
|
|
3045
|
+
throw notFound("Agent not found");
|
|
3046
|
+
await ensureRuntimeState(agent);
|
|
3047
|
+
const taskKey = readNonEmptyString(opts?.taskKey);
|
|
3048
|
+
const clearedTaskSessions = await clearTaskSessions(agent.companyId, agent.id, taskKey ? { taskKey, adapterType: agent.adapterType } : undefined);
|
|
3049
|
+
const runtimePatch = {
|
|
3050
|
+
sessionId: null,
|
|
3051
|
+
lastError: null,
|
|
3052
|
+
updatedAt: new Date(),
|
|
3053
|
+
};
|
|
3054
|
+
if (!taskKey) {
|
|
3055
|
+
runtimePatch.stateJson = {};
|
|
3056
|
+
}
|
|
3057
|
+
const updated = await db
|
|
3058
|
+
.update(agentRuntimeState)
|
|
3059
|
+
.set(runtimePatch)
|
|
3060
|
+
.where(eq(agentRuntimeState.agentId, agentId))
|
|
3061
|
+
.returning()
|
|
3062
|
+
.then((rows) => rows[0] ?? null);
|
|
3063
|
+
if (!updated)
|
|
3064
|
+
return null;
|
|
3065
|
+
return {
|
|
3066
|
+
...updated,
|
|
3067
|
+
sessionDisplayId: null,
|
|
3068
|
+
sessionParamsJson: null,
|
|
3069
|
+
clearedTaskSessions,
|
|
3070
|
+
};
|
|
3071
|
+
},
|
|
3072
|
+
listEvents: (runId, afterSeq = 0, limit = 200) => db
|
|
3073
|
+
.select()
|
|
3074
|
+
.from(heartbeatRunEvents)
|
|
3075
|
+
.where(and(eq(heartbeatRunEvents.runId, runId), gt(heartbeatRunEvents.seq, afterSeq)))
|
|
3076
|
+
.orderBy(asc(heartbeatRunEvents.seq))
|
|
3077
|
+
.limit(Math.max(1, Math.min(limit, 1000))),
|
|
3078
|
+
readLog: async (runId, opts) => {
|
|
3079
|
+
const run = await getRun(runId);
|
|
3080
|
+
if (!run)
|
|
3081
|
+
throw notFound("Heartbeat run not found");
|
|
3082
|
+
if (!run.logStore || !run.logRef)
|
|
3083
|
+
throw notFound("Run log not found");
|
|
3084
|
+
const result = await runLogStore.read({
|
|
3085
|
+
store: run.logStore,
|
|
3086
|
+
logRef: run.logRef,
|
|
3087
|
+
}, opts);
|
|
3088
|
+
return {
|
|
3089
|
+
runId,
|
|
3090
|
+
store: run.logStore,
|
|
3091
|
+
logRef: run.logRef,
|
|
3092
|
+
...result,
|
|
3093
|
+
content: redactCurrentUserText(result.content, await getCurrentUserRedactionOptions()),
|
|
3094
|
+
};
|
|
3095
|
+
},
|
|
3096
|
+
invoke: async (agentId, source = "on_demand", contextSnapshot = {}, triggerDetail = "manual", actor) => enqueueWakeup(agentId, {
|
|
3097
|
+
source,
|
|
3098
|
+
triggerDetail,
|
|
3099
|
+
contextSnapshot,
|
|
3100
|
+
requestedByActorType: actor?.actorType,
|
|
3101
|
+
requestedByActorId: actor?.actorId ?? null,
|
|
3102
|
+
}),
|
|
3103
|
+
wakeup: enqueueWakeup,
|
|
3104
|
+
reportRunActivity: clearDetachedRunWarning,
|
|
3105
|
+
reapOrphanedRuns,
|
|
3106
|
+
resumeQueuedRuns,
|
|
3107
|
+
tickTimers: async (now = new Date()) => {
|
|
3108
|
+
const allAgents = await db.select().from(agents);
|
|
3109
|
+
let checked = 0;
|
|
3110
|
+
let enqueued = 0;
|
|
3111
|
+
let skipped = 0;
|
|
3112
|
+
for (const agent of allAgents) {
|
|
3113
|
+
if (agent.status === "paused" || agent.status === "terminated" || agent.status === "pending_approval")
|
|
3114
|
+
continue;
|
|
3115
|
+
const policy = parseHeartbeatPolicy(agent);
|
|
3116
|
+
if (!policy.enabled || policy.intervalSec <= 0)
|
|
3117
|
+
continue;
|
|
3118
|
+
checked += 1;
|
|
3119
|
+
const baseline = new Date(agent.lastHeartbeatAt ?? agent.createdAt).getTime();
|
|
3120
|
+
const elapsedMs = now.getTime() - baseline;
|
|
3121
|
+
if (elapsedMs < policy.intervalSec * 1000)
|
|
3122
|
+
continue;
|
|
3123
|
+
const run = await enqueueWakeup(agent.id, {
|
|
3124
|
+
source: "timer",
|
|
3125
|
+
triggerDetail: "system",
|
|
3126
|
+
reason: "heartbeat_timer",
|
|
3127
|
+
requestedByActorType: "system",
|
|
3128
|
+
requestedByActorId: "heartbeat_scheduler",
|
|
3129
|
+
contextSnapshot: {
|
|
3130
|
+
source: "scheduler",
|
|
3131
|
+
reason: "interval_elapsed",
|
|
3132
|
+
now: now.toISOString(),
|
|
3133
|
+
},
|
|
3134
|
+
});
|
|
3135
|
+
if (run)
|
|
3136
|
+
enqueued += 1;
|
|
3137
|
+
else
|
|
3138
|
+
skipped += 1;
|
|
3139
|
+
}
|
|
3140
|
+
return { checked, enqueued, skipped };
|
|
3141
|
+
},
|
|
3142
|
+
cancelRun: (runId) => cancelRunInternal(runId),
|
|
3143
|
+
cancelActiveForAgent: (agentId) => cancelActiveForAgentInternal(agentId),
|
|
3144
|
+
cancelBudgetScopeWork,
|
|
3145
|
+
getActiveRunForAgent: async (agentId) => {
|
|
3146
|
+
const [run] = await db
|
|
3147
|
+
.select()
|
|
3148
|
+
.from(heartbeatRuns)
|
|
3149
|
+
.where(and(eq(heartbeatRuns.agentId, agentId), eq(heartbeatRuns.status, "running")))
|
|
3150
|
+
.orderBy(desc(heartbeatRuns.startedAt))
|
|
3151
|
+
.limit(1);
|
|
3152
|
+
return run ?? null;
|
|
3153
|
+
},
|
|
3154
|
+
};
|
|
3155
|
+
}
|
|
3156
|
+
//# sourceMappingURL=heartbeat.js.map
|