@evermore.work/server 2026.509.0-canary.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/builtin-adapter-types.d.ts +5 -0
- package/dist/adapters/builtin-adapter-types.d.ts.map +1 -0
- package/dist/adapters/builtin-adapter-types.js +17 -0
- package/dist/adapters/builtin-adapter-types.js.map +1 -0
- package/dist/adapters/codex-models.d.ts +5 -0
- package/dist/adapters/codex-models.d.ts.map +1 -0
- package/dist/adapters/codex-models.js +105 -0
- package/dist/adapters/codex-models.js.map +1 -0
- package/dist/adapters/cursor-models.d.ts +13 -0
- package/dist/adapters/cursor-models.d.ts.map +1 -0
- package/dist/adapters/cursor-models.js +148 -0
- package/dist/adapters/cursor-models.js.map +1 -0
- package/dist/adapters/http/execute.d.ts +3 -0
- package/dist/adapters/http/execute.d.ts.map +1 -0
- package/dist/adapters/http/execute.js +51 -0
- package/dist/adapters/http/execute.js.map +1 -0
- package/dist/adapters/http/execute.test.d.ts +2 -0
- package/dist/adapters/http/execute.test.d.ts.map +1 -0
- package/dist/adapters/http/execute.test.js +40 -0
- package/dist/adapters/http/execute.test.js.map +1 -0
- package/dist/adapters/http/index.d.ts +3 -0
- package/dist/adapters/http/index.d.ts.map +1 -0
- package/dist/adapters/http/index.js +20 -0
- package/dist/adapters/http/index.js.map +1 -0
- package/dist/adapters/http/test.d.ts +3 -0
- package/dist/adapters/http/test.d.ts.map +1 -0
- package/dist/adapters/http/test.js +106 -0
- package/dist/adapters/http/test.js.map +1 -0
- package/dist/adapters/index.d.ts +4 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +3 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/plugin-loader.d.ts +28 -0
- package/dist/adapters/plugin-loader.d.ts.map +1 -0
- package/dist/adapters/plugin-loader.js +196 -0
- package/dist/adapters/plugin-loader.js.map +1 -0
- package/dist/adapters/process/execute.d.ts +3 -0
- package/dist/adapters/process/execute.d.ts.map +1 -0
- package/dist/adapters/process/execute.js +70 -0
- package/dist/adapters/process/execute.js.map +1 -0
- package/dist/adapters/process/index.d.ts +3 -0
- package/dist/adapters/process/index.d.ts.map +1 -0
- package/dist/adapters/process/index.js +23 -0
- package/dist/adapters/process/index.js.map +1 -0
- package/dist/adapters/process/test.d.ts +3 -0
- package/dist/adapters/process/test.d.ts.map +1 -0
- package/dist/adapters/process/test.js +77 -0
- package/dist/adapters/process/test.js.map +1 -0
- package/dist/adapters/registry.d.ts +69 -0
- package/dist/adapters/registry.d.ts.map +1 -0
- package/dist/adapters/registry.js +566 -0
- package/dist/adapters/registry.js.map +1 -0
- package/dist/adapters/types.d.ts +2 -0
- package/dist/adapters/types.d.ts.map +1 -0
- package/dist/adapters/types.js +2 -0
- package/dist/adapters/types.js.map +1 -0
- package/dist/adapters/utils.d.ts +43 -0
- package/dist/adapters/utils.d.ts.map +1 -0
- package/dist/adapters/utils.js +52 -0
- package/dist/adapters/utils.js.map +1 -0
- package/dist/agent-auth-jwt.d.ts +14 -0
- package/dist/agent-auth-jwt.d.ts.map +1 -0
- package/dist/agent-auth-jwt.js +117 -0
- package/dist/agent-auth-jwt.js.map +1 -0
- package/dist/app.d.ts +43 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +373 -0
- package/dist/app.js.map +1 -0
- package/dist/attachment-types.d.ts +23 -0
- package/dist/attachment-types.d.ts.map +1 -0
- package/dist/attachment-types.js +91 -0
- package/dist/attachment-types.js.map +1 -0
- package/dist/auth/better-auth.d.ts +33 -0
- package/dist/auth/better-auth.d.ts.map +1 -0
- package/dist/auth/better-auth.js +133 -0
- package/dist/auth/better-auth.js.map +1 -0
- package/dist/board-claim.d.ts +23 -0
- package/dist/board-claim.d.ts.map +1 -0
- package/dist/board-claim.js +115 -0
- package/dist/board-claim.js.map +1 -0
- package/dist/config-file.d.ts +3 -0
- package/dist/config-file.d.ts.map +1 -0
- package/dist/config-file.js +16 -0
- package/dist/config-file.js.map +1 -0
- package/dist/config.d.ts +44 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +226 -0
- package/dist/config.js.map +1 -0
- package/dist/dev-runner-worktree.d.ts +15 -0
- package/dist/dev-runner-worktree.d.ts.map +1 -0
- package/dist/dev-runner-worktree.js +68 -0
- package/dist/dev-runner-worktree.js.map +1 -0
- package/dist/dev-server-status.d.ts +27 -0
- package/dist/dev-server-status.d.ts.map +1 -0
- package/dist/dev-server-status.js +74 -0
- package/dist/dev-server-status.js.map +1 -0
- package/dist/dev-watch-ignore.d.ts +2 -0
- package/dist/dev-watch-ignore.d.ts.map +1 -0
- package/dist/dev-watch-ignore.js +36 -0
- package/dist/dev-watch-ignore.js.map +1 -0
- package/dist/errors.d.ts +12 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +28 -0
- package/dist/errors.js.map +1 -0
- package/dist/home-paths.d.ts +17 -0
- package/dist/home-paths.d.ts.map +1 -0
- package/dist/home-paths.js +75 -0
- package/dist/home-paths.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +753 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/join-request-dedupe.d.ts +11 -0
- package/dist/lib/join-request-dedupe.d.ts.map +1 -0
- package/dist/lib/join-request-dedupe.js +49 -0
- package/dist/lib/join-request-dedupe.js.map +1 -0
- package/dist/log-redaction.d.ts +11 -0
- package/dist/log-redaction.d.ts.map +1 -0
- package/dist/log-redaction.js +122 -0
- package/dist/log-redaction.js.map +1 -0
- package/dist/middleware/auth.d.ts +12 -0
- package/dist/middleware/auth.d.ts.map +1 -0
- package/dist/middleware/auth.js +302 -0
- package/dist/middleware/auth.js.map +1 -0
- package/dist/middleware/board-mutation-guard.d.ts +3 -0
- package/dist/middleware/board-mutation-guard.d.ts.map +1 -0
- package/dist/middleware/board-mutation-guard.js +67 -0
- package/dist/middleware/board-mutation-guard.js.map +1 -0
- package/dist/middleware/error-handler.d.ts +17 -0
- package/dist/middleware/error-handler.d.ts.map +1 -0
- package/dist/middleware/error-handler.js +45 -0
- package/dist/middleware/error-handler.js.map +1 -0
- package/dist/middleware/http-log-policy.d.ts +2 -0
- package/dist/middleware/http-log-policy.d.ts.map +1 -0
- package/dist/middleware/http-log-policy.js +52 -0
- package/dist/middleware/http-log-policy.js.map +1 -0
- package/dist/middleware/index.d.ts +4 -0
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +4 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/middleware/logger.d.ts +4 -0
- package/dist/middleware/logger.d.ts.map +1 -0
- package/dist/middleware/logger.js +92 -0
- package/dist/middleware/logger.js.map +1 -0
- package/dist/middleware/private-hostname-guard.d.ts +11 -0
- package/dist/middleware/private-hostname-guard.d.ts.map +1 -0
- package/dist/middleware/private-hostname-guard.js +78 -0
- package/dist/middleware/private-hostname-guard.js.map +1 -0
- package/dist/middleware/validate.d.ts +4 -0
- package/dist/middleware/validate.d.ts.map +1 -0
- package/dist/middleware/validate.js +7 -0
- package/dist/middleware/validate.js.map +1 -0
- package/dist/onboarding-assets/ceo/AGENTS.md +59 -0
- package/dist/onboarding-assets/ceo/HEARTBEAT.md +85 -0
- package/dist/onboarding-assets/ceo/SOUL.md +33 -0
- package/dist/onboarding-assets/ceo/TOOLS.md +3 -0
- package/dist/onboarding-assets/default/AGENTS.md +17 -0
- package/dist/paths.d.ts +3 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +31 -0
- package/dist/paths.js.map +1 -0
- package/dist/realtime/live-events-ws.d.ts +28 -0
- package/dist/realtime/live-events-ws.d.ts.map +1 -0
- package/dist/realtime/live-events-ws.js +187 -0
- package/dist/realtime/live-events-ws.js.map +1 -0
- package/dist/redaction.d.ts +5 -0
- package/dist/redaction.d.ts.map +1 -0
- package/dist/redaction.js +98 -0
- package/dist/redaction.js.map +1 -0
- package/dist/routes/access.d.ts +82 -0
- package/dist/routes/access.d.ts.map +1 -0
- package/dist/routes/access.js +3411 -0
- package/dist/routes/access.js.map +1 -0
- package/dist/routes/activity.d.ts +3 -0
- package/dist/routes/activity.d.ts.map +1 -0
- package/dist/routes/activity.js +90 -0
- package/dist/routes/activity.js.map +1 -0
- package/dist/routes/adapters.d.ts +16 -0
- package/dist/routes/adapters.d.ts.map +1 -0
- package/dist/routes/adapters.js +527 -0
- package/dist/routes/adapters.js.map +1 -0
- package/dist/routes/agents.d.ts +6 -0
- package/dist/routes/agents.d.ts.map +1 -0
- package/dist/routes/agents.js +2753 -0
- package/dist/routes/agents.js.map +1 -0
- package/dist/routes/approvals.d.ts +6 -0
- package/dist/routes/approvals.d.ts.map +1 -0
- package/dist/routes/approvals.js +300 -0
- package/dist/routes/approvals.js.map +1 -0
- package/dist/routes/assets.d.ts +4 -0
- package/dist/routes/assets.d.ts.map +1 -0
- package/dist/routes/assets.js +309 -0
- package/dist/routes/assets.js.map +1 -0
- package/dist/routes/auth.d.ts +3 -0
- package/dist/routes/auth.d.ts.map +1 -0
- package/dist/routes/auth.js +82 -0
- package/dist/routes/auth.js.map +1 -0
- package/dist/routes/authz.d.ts +19 -0
- package/dist/routes/authz.d.ts.map +1 -0
- package/dist/routes/authz.js +75 -0
- package/dist/routes/authz.js.map +1 -0
- package/dist/routes/companies.d.ts +4 -0
- package/dist/routes/companies.d.ts.map +1 -0
- package/dist/routes/companies.js +359 -0
- package/dist/routes/companies.js.map +1 -0
- package/dist/routes/company-skills.d.ts +3 -0
- package/dist/routes/company-skills.d.ts.map +1 -0
- package/dist/routes/company-skills.js +258 -0
- package/dist/routes/company-skills.js.map +1 -0
- package/dist/routes/costs.d.ts +11 -0
- package/dist/routes/costs.d.ts.map +1 -0
- package/dist/routes/costs.js +285 -0
- package/dist/routes/costs.js.map +1 -0
- package/dist/routes/dashboard.d.ts +3 -0
- package/dist/routes/dashboard.d.ts.map +1 -0
- package/dist/routes/dashboard.js +15 -0
- package/dist/routes/dashboard.js.map +1 -0
- package/dist/routes/environment-selection.d.ts +13 -0
- package/dist/routes/environment-selection.d.ts.map +1 -0
- package/dist/routes/environment-selection.js +30 -0
- package/dist/routes/environment-selection.js.map +1 -0
- package/dist/routes/environments.d.ts +6 -0
- package/dist/routes/environments.d.ts.map +1 -0
- package/dist/routes/environments.js +401 -0
- package/dist/routes/environments.js.map +1 -0
- package/dist/routes/execution-workspaces.d.ts +3 -0
- package/dist/routes/execution-workspaces.d.ts.map +1 -0
- package/dist/routes/execution-workspaces.js +536 -0
- package/dist/routes/execution-workspaces.js.map +1 -0
- package/dist/routes/goals.d.ts +3 -0
- package/dist/routes/goals.d.ts.map +1 -0
- package/dist/routes/goals.js +101 -0
- package/dist/routes/goals.js.map +1 -0
- package/dist/routes/health.d.ts +9 -0
- package/dist/routes/health.d.ts.map +1 -0
- package/dist/routes/health.js +114 -0
- package/dist/routes/health.js.map +1 -0
- package/dist/routes/inbox-dismissals.d.ts +3 -0
- package/dist/routes/inbox-dismissals.d.ts.map +1 -0
- package/dist/routes/inbox-dismissals.js +58 -0
- package/dist/routes/inbox-dismissals.js.map +1 -0
- package/dist/routes/index.d.ts +22 -0
- package/dist/routes/index.d.ts.map +1 -0
- package/dist/routes/index.js +22 -0
- package/dist/routes/index.js.map +1 -0
- package/dist/routes/instance-database-backups.d.ts +15 -0
- package/dist/routes/instance-database-backups.d.ts.map +1 -0
- package/dist/routes/instance-database-backups.js +12 -0
- package/dist/routes/instance-database-backups.js.map +1 -0
- package/dist/routes/instance-settings.d.ts +3 -0
- package/dist/routes/instance-settings.d.ts.map +1 -0
- package/dist/routes/instance-settings.js +110 -0
- package/dist/routes/instance-settings.js.map +1 -0
- package/dist/routes/issue-tree-control.d.ts +3 -0
- package/dist/routes/issue-tree-control.d.ts.map +1 -0
- package/dist/routes/issue-tree-control.js +363 -0
- package/dist/routes/issue-tree-control.js.map +1 -0
- package/dist/routes/issues-checkout-wakeup.d.ts +9 -0
- package/dist/routes/issues-checkout-wakeup.d.ts.map +1 -0
- package/dist/routes/issues-checkout-wakeup.js +12 -0
- package/dist/routes/issues-checkout-wakeup.js.map +1 -0
- package/dist/routes/issues.d.ts +23 -0
- package/dist/routes/issues.d.ts.map +1 -0
- package/dist/routes/issues.js +3886 -0
- package/dist/routes/issues.js.map +1 -0
- package/dist/routes/llms.d.ts +3 -0
- package/dist/routes/llms.d.ts.map +1 -0
- package/dist/routes/llms.js +80 -0
- package/dist/routes/llms.js.map +1 -0
- package/dist/routes/org-chart-svg.d.ts +25 -0
- package/dist/routes/org-chart-svg.d.ts.map +1 -0
- package/dist/routes/org-chart-svg.js +656 -0
- package/dist/routes/org-chart-svg.js.map +1 -0
- package/dist/routes/plugin-ui-static.d.ts +69 -0
- package/dist/routes/plugin-ui-static.d.ts.map +1 -0
- package/dist/routes/plugin-ui-static.js +411 -0
- package/dist/routes/plugin-ui-static.js.map +1 -0
- package/dist/routes/plugins.d.ts +121 -0
- package/dist/routes/plugins.d.ts.map +1 -0
- package/dist/routes/plugins.js +2192 -0
- package/dist/routes/plugins.js.map +1 -0
- package/dist/routes/projects.d.ts +3 -0
- package/dist/routes/projects.d.ts.map +1 -0
- package/dist/routes/projects.js +566 -0
- package/dist/routes/projects.js.map +1 -0
- package/dist/routes/routines.d.ts +6 -0
- package/dist/routes/routines.d.ts.map +1 -0
- package/dist/routes/routines.js +417 -0
- package/dist/routes/routines.js.map +1 -0
- package/dist/routes/secrets.d.ts +3 -0
- package/dist/routes/secrets.d.ts.map +1 -0
- package/dist/routes/secrets.js +128 -0
- package/dist/routes/secrets.js.map +1 -0
- package/dist/routes/sidebar-badges.d.ts +3 -0
- package/dist/routes/sidebar-badges.d.ts.map +1 -0
- package/dist/routes/sidebar-badges.js +68 -0
- package/dist/routes/sidebar-badges.js.map +1 -0
- package/dist/routes/sidebar-preferences.d.ts +3 -0
- package/dist/routes/sidebar-preferences.d.ts.map +1 -0
- package/dist/routes/sidebar-preferences.js +63 -0
- package/dist/routes/sidebar-preferences.js.map +1 -0
- package/dist/routes/user-profiles.d.ts +3 -0
- package/dist/routes/user-profiles.d.ts.map +1 -0
- package/dist/routes/user-profiles.js +337 -0
- package/dist/routes/user-profiles.js.map +1 -0
- package/dist/routes/workspace-command-authz.d.ts +14 -0
- package/dist/routes/workspace-command-authz.d.ts.map +1 -0
- package/dist/routes/workspace-command-authz.js +83 -0
- package/dist/routes/workspace-command-authz.js.map +1 -0
- package/dist/routes/workspace-runtime-service-authz.d.ts +12 -0
- package/dist/routes/workspace-runtime-service-authz.d.ts.map +1 -0
- package/dist/routes/workspace-runtime-service-authz.js +96 -0
- package/dist/routes/workspace-runtime-service-authz.js.map +1 -0
- package/dist/runtime-api.d.ts +19 -0
- package/dist/runtime-api.d.ts.map +1 -0
- package/dist/runtime-api.js +137 -0
- package/dist/runtime-api.js.map +1 -0
- package/dist/secrets/external-stub-providers.d.ts +5 -0
- package/dist/secrets/external-stub-providers.d.ts.map +1 -0
- package/dist/secrets/external-stub-providers.js +21 -0
- package/dist/secrets/external-stub-providers.js.map +1 -0
- package/dist/secrets/local-encrypted-provider.d.ts +3 -0
- package/dist/secrets/local-encrypted-provider.d.ts.map +1 -0
- package/dist/secrets/local-encrypted-provider.js +116 -0
- package/dist/secrets/local-encrypted-provider.js.map +1 -0
- package/dist/secrets/provider-registry.d.ts +5 -0
- package/dist/secrets/provider-registry.d.ts.map +1 -0
- package/dist/secrets/provider-registry.js +20 -0
- package/dist/secrets/provider-registry.js.map +1 -0
- package/dist/secrets/types.d.ts +21 -0
- package/dist/secrets/types.d.ts.map +1 -0
- package/dist/secrets/types.js +2 -0
- package/dist/secrets/types.js.map +1 -0
- package/dist/services/access.d.ts +171 -0
- package/dist/services/access.d.ts.map +1 -0
- package/dist/services/access.js +522 -0
- package/dist/services/access.js.map +1 -0
- package/dist/services/activity-log.d.ts +19 -0
- package/dist/services/activity-log.d.ts.map +1 -0
- package/dist/services/activity-log.js +99 -0
- package/dist/services/activity-log.js.map +1 -0
- package/dist/services/activity.d.ts +462 -0
- package/dist/services/activity.d.ts.map +1 -0
- package/dist/services/activity.js +443 -0
- package/dist/services/activity.js.map +1 -0
- package/dist/services/adapter-plugin-store.d.ts +36 -0
- package/dist/services/adapter-plugin-store.d.ts.map +1 -0
- package/dist/services/adapter-plugin-store.js +154 -0
- package/dist/services/adapter-plugin-store.js.map +1 -0
- package/dist/services/agent-instructions.d.ts +91 -0
- package/dist/services/agent-instructions.d.ts.map +1 -0
- package/dist/services/agent-instructions.js +580 -0
- package/dist/services/agent-instructions.js.map +1 -0
- package/dist/services/agent-permissions.d.ts +6 -0
- package/dist/services/agent-permissions.d.ts.map +1 -0
- package/dist/services/agent-permissions.js +18 -0
- package/dist/services/agent-permissions.js.map +1 -0
- package/dist/services/agent-start-lock.d.ts +2 -0
- package/dist/services/agent-start-lock.d.ts.map +1 -0
- package/dist/services/agent-start-lock.js +43 -0
- package/dist/services/agent-start-lock.js.map +1 -0
- package/dist/services/agents.d.ts +2253 -0
- package/dist/services/agents.d.ts.map +1 -0
- package/dist/services/agents.js +609 -0
- package/dist/services/agents.js.map +1 -0
- package/dist/services/approvals.d.ts +546 -0
- package/dist/services/approvals.d.ts.map +1 -0
- package/dist/services/approvals.js +212 -0
- package/dist/services/approvals.js.map +1 -0
- package/dist/services/assets.d.ts +33 -0
- package/dist/services/assets.d.ts.map +1 -0
- package/dist/services/assets.js +17 -0
- package/dist/services/assets.js.map +1 -0
- package/dist/services/board-auth.d.ts +239 -0
- package/dist/services/board-auth.d.ts.map +1 -0
- package/dist/services/board-auth.js +300 -0
- package/dist/services/board-auth.js.map +1 -0
- package/dist/services/budgets.d.ts +38 -0
- package/dist/services/budgets.d.ts.map +1 -0
- package/dist/services/budgets.js +784 -0
- package/dist/services/budgets.js.map +1 -0
- package/dist/services/companies.d.ts +154 -0
- package/dist/services/companies.d.ts.map +1 -0
- package/dist/services/companies.js +267 -0
- package/dist/services/companies.js.map +1 -0
- package/dist/services/company-export-readme.d.ts +17 -0
- package/dist/services/company-export-readme.d.ts.map +1 -0
- package/dist/services/company-export-readme.js +148 -0
- package/dist/services/company-export-readme.js.map +1 -0
- package/dist/services/company-member-roles.d.ts +9 -0
- package/dist/services/company-member-roles.d.ts.map +1 -0
- package/dist/services/company-member-roles.js +46 -0
- package/dist/services/company-member-roles.js.map +1 -0
- package/dist/services/company-portability.d.ts +24 -0
- package/dist/services/company-portability.d.ts.map +1 -0
- package/dist/services/company-portability.js +4076 -0
- package/dist/services/company-portability.js.map +1 -0
- package/dist/services/company-search-rate-limit.d.ts +22 -0
- package/dist/services/company-search-rate-limit.d.ts.map +1 -0
- package/dist/services/company-search-rate-limit.js +38 -0
- package/dist/services/company-search-rate-limit.js.map +1 -0
- package/dist/services/company-search.d.ts +8 -0
- package/dist/services/company-search.d.ts.map +1 -0
- package/dist/services/company-search.js +626 -0
- package/dist/services/company-search.js.map +1 -0
- package/dist/services/company-skills.d.ts +77 -0
- package/dist/services/company-skills.d.ts.map +1 -0
- package/dist/services/company-skills.js +2120 -0
- package/dist/services/company-skills.js.map +1 -0
- package/dist/services/costs.d.ts +127 -0
- package/dist/services/costs.d.ts.map +1 -0
- package/dist/services/costs.js +409 -0
- package/dist/services/costs.js.map +1 -0
- package/dist/services/cron.d.ts +80 -0
- package/dist/services/cron.d.ts.map +1 -0
- package/dist/services/cron.js +300 -0
- package/dist/services/cron.js.map +1 -0
- package/dist/services/dashboard.d.ts +34 -0
- package/dist/services/dashboard.d.ts.map +1 -0
- package/dist/services/dashboard.js +142 -0
- package/dist/services/dashboard.js.map +1 -0
- package/dist/services/default-agent-instructions.d.ts +9 -0
- package/dist/services/default-agent-instructions.d.ts.map +1 -0
- package/dist/services/default-agent-instructions.js +20 -0
- package/dist/services/default-agent-instructions.js.map +1 -0
- package/dist/services/documents.d.ts +199 -0
- package/dist/services/documents.d.ts.map +1 -0
- package/dist/services/documents.js +411 -0
- package/dist/services/documents.js.map +1 -0
- package/dist/services/environment-config.d.ts +43 -0
- package/dist/services/environment-config.d.ts.map +1 -0
- package/dist/services/environment-config.js +388 -0
- package/dist/services/environment-config.js.map +1 -0
- package/dist/services/environment-execution-target.d.ts +21 -0
- package/dist/services/environment-execution-target.d.ts.map +1 -0
- package/dist/services/environment-execution-target.js +119 -0
- package/dist/services/environment-execution-target.js.map +1 -0
- package/dist/services/environment-probe.d.ts +9 -0
- package/dist/services/environment-probe.d.ts.map +1 -0
- package/dist/services/environment-probe.js +106 -0
- package/dist/services/environment-probe.js.map +1 -0
- package/dist/services/environment-run-orchestrator.d.ts +124 -0
- package/dist/services/environment-run-orchestrator.d.ts.map +1 -0
- package/dist/services/environment-run-orchestrator.js +392 -0
- package/dist/services/environment-run-orchestrator.js.map +1 -0
- package/dist/services/environment-runtime.d.ts +90 -0
- package/dist/services/environment-runtime.d.ts.map +1 -0
- package/dist/services/environment-runtime.js +934 -0
- package/dist/services/environment-runtime.js.map +1 -0
- package/dist/services/environments.d.ts +36 -0
- package/dist/services/environments.d.ts.map +1 -0
- package/dist/services/environments.js +260 -0
- package/dist/services/environments.js.map +1 -0
- package/dist/services/execution-workspace-policy.d.ts +30 -0
- package/dist/services/execution-workspace-policy.d.ts.map +1 -0
- package/dist/services/execution-workspace-policy.js +195 -0
- package/dist/services/execution-workspace-policy.js.map +1 -0
- package/dist/services/execution-workspaces.d.ts +30 -0
- package/dist/services/execution-workspaces.d.ts.map +1 -0
- package/dist/services/execution-workspaces.js +635 -0
- package/dist/services/execution-workspaces.js.map +1 -0
- package/dist/services/feedback-redaction.d.ts +23 -0
- package/dist/services/feedback-redaction.d.ts.map +1 -0
- package/dist/services/feedback-redaction.js +150 -0
- package/dist/services/feedback-redaction.js.map +1 -0
- package/dist/services/feedback-share-client.d.ts +9 -0
- package/dist/services/feedback-share-client.d.ts.map +1 -0
- package/dist/services/feedback-share-client.js +46 -0
- package/dist/services/feedback-share-client.js.map +1 -0
- package/dist/services/feedback.d.ts +93 -0
- package/dist/services/feedback.d.ts.map +1 -0
- package/dist/services/feedback.js +1717 -0
- package/dist/services/feedback.js.map +1 -0
- package/dist/services/finance.d.ts +93 -0
- package/dist/services/finance.d.ts.map +1 -0
- package/dist/services/finance.js +120 -0
- package/dist/services/finance.js.map +1 -0
- package/dist/services/github-fetch.d.ts +4 -0
- package/dist/services/github-fetch.d.ts.map +1 -0
- package/dist/services/github-fetch.js +23 -0
- package/dist/services/github-fetch.js.map +1 -0
- package/dist/services/goals.d.ts +433 -0
- package/dist/services/goals.d.ts.map +1 -0
- package/dist/services/goals.js +54 -0
- package/dist/services/goals.js.map +1 -0
- package/dist/services/heartbeat-run-summary.d.ts +7 -0
- package/dist/services/heartbeat-run-summary.d.ts.map +1 -0
- package/dist/services/heartbeat-run-summary.js +84 -0
- package/dist/services/heartbeat-run-summary.js.map +1 -0
- package/dist/services/heartbeat-stop-metadata.d.ts +28 -0
- package/dist/services/heartbeat-stop-metadata.d.ts.map +1 -0
- package/dist/services/heartbeat-stop-metadata.js +86 -0
- package/dist/services/heartbeat-stop-metadata.js.map +1 -0
- package/dist/services/heartbeat-stop-metadata.test.d.ts +2 -0
- package/dist/services/heartbeat-stop-metadata.test.d.ts.map +1 -0
- package/dist/services/heartbeat-stop-metadata.test.js +93 -0
- package/dist/services/heartbeat-stop-metadata.test.js.map +1 -0
- package/dist/services/heartbeat.d.ts +1484 -0
- package/dist/services/heartbeat.d.ts.map +1 -0
- package/dist/services/heartbeat.js +7557 -0
- package/dist/services/heartbeat.js.map +1 -0
- package/dist/services/hire-hook.d.ts +14 -0
- package/dist/services/hire-hook.d.ts.map +1 -0
- package/dist/services/hire-hook.js +85 -0
- package/dist/services/hire-hook.js.map +1 -0
- package/dist/services/inbox-dismissals.d.ts +22 -0
- package/dist/services/inbox-dismissals.d.ts.map +1 -0
- package/dist/services/inbox-dismissals.js +33 -0
- package/dist/services/inbox-dismissals.js.map +1 -0
- package/dist/services/index.d.ts +44 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +44 -0
- package/dist/services/index.js.map +1 -0
- package/dist/services/instance-settings.d.ts +11 -0
- package/dist/services/instance-settings.d.ts.map +1 -0
- package/dist/services/instance-settings.js +138 -0
- package/dist/services/instance-settings.js.map +1 -0
- package/dist/services/invite-grants.d.ts +15 -0
- package/dist/services/invite-grants.d.ts.map +1 -0
- package/dist/services/invite-grants.js +50 -0
- package/dist/services/invite-grants.js.map +1 -0
- package/dist/services/issue-approvals.d.ts +56 -0
- package/dist/services/issue-approvals.d.ts.map +1 -0
- package/dist/services/issue-approvals.js +153 -0
- package/dist/services/issue-approvals.js.map +1 -0
- package/dist/services/issue-assignment-wakeup.d.ts +29 -0
- package/dist/services/issue-assignment-wakeup.d.ts.map +1 -0
- package/dist/services/issue-assignment-wakeup.js +22 -0
- package/dist/services/issue-assignment-wakeup.js.map +1 -0
- package/dist/services/issue-continuation-summary.d.ts +66 -0
- package/dist/services/issue-continuation-summary.d.ts.map +1 -0
- package/dist/services/issue-continuation-summary.js +212 -0
- package/dist/services/issue-continuation-summary.js.map +1 -0
- package/dist/services/issue-execution-policy.d.ts +93 -0
- package/dist/services/issue-execution-policy.d.ts.map +1 -0
- package/dist/services/issue-execution-policy.js +838 -0
- package/dist/services/issue-execution-policy.js.map +1 -0
- package/dist/services/issue-goal-fallback.d.ts +18 -0
- package/dist/services/issue-goal-fallback.d.ts.map +1 -0
- package/dist/services/issue-goal-fallback.js +33 -0
- package/dist/services/issue-goal-fallback.js.map +1 -0
- package/dist/services/issue-liveness.d.ts +3 -0
- package/dist/services/issue-liveness.d.ts.map +1 -0
- package/dist/services/issue-liveness.js +2 -0
- package/dist/services/issue-liveness.js.map +1 -0
- package/dist/services/issue-references.d.ts +21 -0
- package/dist/services/issue-references.d.ts.map +1 -0
- package/dist/services/issue-references.js +318 -0
- package/dist/services/issue-references.js.map +1 -0
- package/dist/services/issue-thread-interactions.d.ts +76 -0
- package/dist/services/issue-thread-interactions.d.ts.map +1 -0
- package/dist/services/issue-thread-interactions.js +923 -0
- package/dist/services/issue-thread-interactions.js.map +1 -0
- package/dist/services/issue-thread-interactions.test.d.ts +2 -0
- package/dist/services/issue-thread-interactions.test.d.ts.map +1 -0
- package/dist/services/issue-thread-interactions.test.js +195 -0
- package/dist/services/issue-thread-interactions.test.js.map +1 -0
- package/dist/services/issue-tree-control.d.ts +89 -0
- package/dist/services/issue-tree-control.d.ts.map +1 -0
- package/dist/services/issue-tree-control.js +933 -0
- package/dist/services/issue-tree-control.js.map +1 -0
- package/dist/services/issues.d.ts +710 -0
- package/dist/services/issues.d.ts.map +1 -0
- package/dist/services/issues.js +3199 -0
- package/dist/services/issues.js.map +1 -0
- package/dist/services/json-schema-secret-refs.d.ts +5 -0
- package/dist/services/json-schema-secret-refs.d.ts.map +1 -0
- package/dist/services/json-schema-secret-refs.js +67 -0
- package/dist/services/json-schema-secret-refs.js.map +1 -0
- package/dist/services/live-events.d.ts +17 -0
- package/dist/services/live-events.d.ts.map +1 -0
- package/dist/services/live-events.js +33 -0
- package/dist/services/live-events.js.map +1 -0
- package/dist/services/local-service-supervisor.d.ts +56 -0
- package/dist/services/local-service-supervisor.d.ts.map +1 -0
- package/dist/services/local-service-supervisor.js +284 -0
- package/dist/services/local-service-supervisor.js.map +1 -0
- package/dist/services/plugin-capability-validator.d.ts +108 -0
- package/dist/services/plugin-capability-validator.d.ts.map +1 -0
- package/dist/services/plugin-capability-validator.js +313 -0
- package/dist/services/plugin-capability-validator.js.map +1 -0
- package/dist/services/plugin-config-validator.d.ts +26 -0
- package/dist/services/plugin-config-validator.d.ts.map +1 -0
- package/dist/services/plugin-config-validator.js +41 -0
- package/dist/services/plugin-config-validator.js.map +1 -0
- package/dist/services/plugin-database.d.ts +49 -0
- package/dist/services/plugin-database.d.ts.map +1 -0
- package/dist/services/plugin-database.js +440 -0
- package/dist/services/plugin-database.js.map +1 -0
- package/dist/services/plugin-dev-watcher.d.ts +30 -0
- package/dist/services/plugin-dev-watcher.d.ts.map +1 -0
- package/dist/services/plugin-dev-watcher.js +241 -0
- package/dist/services/plugin-dev-watcher.js.map +1 -0
- package/dist/services/plugin-environment-driver.d.ts +124 -0
- package/dist/services/plugin-environment-driver.d.ts.map +1 -0
- package/dist/services/plugin-environment-driver.js +224 -0
- package/dist/services/plugin-environment-driver.js.map +1 -0
- package/dist/services/plugin-event-bus.d.ts +149 -0
- package/dist/services/plugin-event-bus.d.ts.map +1 -0
- package/dist/services/plugin-event-bus.js +258 -0
- package/dist/services/plugin-event-bus.js.map +1 -0
- package/dist/services/plugin-host-service-cleanup.d.ts +14 -0
- package/dist/services/plugin-host-service-cleanup.d.ts.map +1 -0
- package/dist/services/plugin-host-service-cleanup.js +37 -0
- package/dist/services/plugin-host-service-cleanup.js.map +1 -0
- package/dist/services/plugin-host-services.d.ts +17 -0
- package/dist/services/plugin-host-services.d.ts.map +1 -0
- package/dist/services/plugin-host-services.js +1861 -0
- package/dist/services/plugin-host-services.js.map +1 -0
- package/dist/services/plugin-job-coordinator.d.ts +81 -0
- package/dist/services/plugin-job-coordinator.d.ts.map +1 -0
- package/dist/services/plugin-job-coordinator.js +172 -0
- package/dist/services/plugin-job-coordinator.js.map +1 -0
- package/dist/services/plugin-job-scheduler.d.ts +163 -0
- package/dist/services/plugin-job-scheduler.d.ts.map +1 -0
- package/dist/services/plugin-job-scheduler.js +454 -0
- package/dist/services/plugin-job-scheduler.js.map +1 -0
- package/dist/services/plugin-job-store.d.ts +208 -0
- package/dist/services/plugin-job-store.d.ts.map +1 -0
- package/dist/services/plugin-job-store.js +350 -0
- package/dist/services/plugin-job-store.js.map +1 -0
- package/dist/services/plugin-lifecycle.d.ts +203 -0
- package/dist/services/plugin-lifecycle.d.ts.map +1 -0
- package/dist/services/plugin-lifecycle.js +476 -0
- package/dist/services/plugin-lifecycle.js.map +1 -0
- package/dist/services/plugin-loader.d.ts +445 -0
- package/dist/services/plugin-loader.d.ts.map +1 -0
- package/dist/services/plugin-loader.js +1273 -0
- package/dist/services/plugin-loader.js.map +1 -0
- package/dist/services/plugin-local-folders.d.ts +48 -0
- package/dist/services/plugin-local-folders.d.ts.map +1 -0
- package/dist/services/plugin-local-folders.js +461 -0
- package/dist/services/plugin-local-folders.js.map +1 -0
- package/dist/services/plugin-log-retention.d.ts +20 -0
- package/dist/services/plugin-log-retention.d.ts.map +1 -0
- package/dist/services/plugin-log-retention.js +63 -0
- package/dist/services/plugin-log-retention.js.map +1 -0
- package/dist/services/plugin-managed-agents.d.ts +15 -0
- package/dist/services/plugin-managed-agents.d.ts.map +1 -0
- package/dist/services/plugin-managed-agents.js +414 -0
- package/dist/services/plugin-managed-agents.js.map +1 -0
- package/dist/services/plugin-managed-routines.d.ts +41 -0
- package/dist/services/plugin-managed-routines.d.ts.map +1 -0
- package/dist/services/plugin-managed-routines.js +416 -0
- package/dist/services/plugin-managed-routines.js.map +1 -0
- package/dist/services/plugin-manifest-validator.d.ts +79 -0
- package/dist/services/plugin-manifest-validator.d.ts.map +1 -0
- package/dist/services/plugin-manifest-validator.js +84 -0
- package/dist/services/plugin-manifest-validator.js.map +1 -0
- package/dist/services/plugin-registry.d.ts +2550 -0
- package/dist/services/plugin-registry.d.ts.map +1 -0
- package/dist/services/plugin-registry.js +581 -0
- package/dist/services/plugin-registry.js.map +1 -0
- package/dist/services/plugin-runtime-sandbox.d.ts +40 -0
- package/dist/services/plugin-runtime-sandbox.d.ts.map +1 -0
- package/dist/services/plugin-runtime-sandbox.js +154 -0
- package/dist/services/plugin-runtime-sandbox.js.map +1 -0
- package/dist/services/plugin-secrets-handler.d.ts +81 -0
- package/dist/services/plugin-secrets-handler.d.ts.map +1 -0
- package/dist/services/plugin-secrets-handler.js +231 -0
- package/dist/services/plugin-secrets-handler.js.map +1 -0
- package/dist/services/plugin-state-store.d.ts +92 -0
- package/dist/services/plugin-state-store.d.ts.map +1 -0
- package/dist/services/plugin-state-store.js +190 -0
- package/dist/services/plugin-state-store.js.map +1 -0
- package/dist/services/plugin-stream-bus.d.ts +29 -0
- package/dist/services/plugin-stream-bus.d.ts.map +1 -0
- package/dist/services/plugin-stream-bus.js +48 -0
- package/dist/services/plugin-stream-bus.js.map +1 -0
- package/dist/services/plugin-tool-dispatcher.d.ts +180 -0
- package/dist/services/plugin-tool-dispatcher.d.ts.map +1 -0
- package/dist/services/plugin-tool-dispatcher.js +224 -0
- package/dist/services/plugin-tool-dispatcher.js.map +1 -0
- package/dist/services/plugin-tool-registry.d.ts +192 -0
- package/dist/services/plugin-tool-registry.d.ts.map +1 -0
- package/dist/services/plugin-tool-registry.js +224 -0
- package/dist/services/plugin-tool-registry.js.map +1 -0
- package/dist/services/plugin-worker-manager.d.ts +262 -0
- package/dist/services/plugin-worker-manager.d.ts.map +1 -0
- package/dist/services/plugin-worker-manager.js +836 -0
- package/dist/services/plugin-worker-manager.js.map +1 -0
- package/dist/services/productivity-review.d.ts +83 -0
- package/dist/services/productivity-review.d.ts.map +1 -0
- package/dist/services/productivity-review.js +652 -0
- package/dist/services/productivity-review.js.map +1 -0
- package/dist/services/project-workspace-runtime-config.d.ts +4 -0
- package/dist/services/project-workspace-runtime-config.d.ts.map +1 -0
- package/dist/services/project-workspace-runtime-config.js +54 -0
- package/dist/services/project-workspace-runtime-config.js.map +1 -0
- package/dist/services/projects.d.ts +99 -0
- package/dist/services/projects.d.ts.map +1 -0
- package/dist/services/projects.js +879 -0
- package/dist/services/projects.js.map +1 -0
- package/dist/services/quota-windows.d.ts +9 -0
- package/dist/services/quota-windows.d.ts.map +1 -0
- package/dist/services/quota-windows.js +56 -0
- package/dist/services/quota-windows.js.map +1 -0
- package/dist/services/recovery/index.d.ts +10 -0
- package/dist/services/recovery/index.d.ts.map +1 -0
- package/dist/services/recovery/index.js +6 -0
- package/dist/services/recovery/index.js.map +1 -0
- package/dist/services/recovery/issue-graph-liveness.d.ts +85 -0
- package/dist/services/recovery/issue-graph-liveness.d.ts.map +1 -0
- package/dist/services/recovery/issue-graph-liveness.js +343 -0
- package/dist/services/recovery/issue-graph-liveness.js.map +1 -0
- package/dist/services/recovery/model-profile-hint.d.ts +8 -0
- package/dist/services/recovery/model-profile-hint.d.ts.map +1 -0
- package/dist/services/recovery/model-profile-hint.js +11 -0
- package/dist/services/recovery/model-profile-hint.js.map +1 -0
- package/dist/services/recovery/origins.d.ts +36 -0
- package/dist/services/recovery/origins.d.ts.map +1 -0
- package/dist/services/recovery/origins.js +45 -0
- package/dist/services/recovery/origins.js.map +1 -0
- package/dist/services/recovery/pause-hold-guard.d.ts +6 -0
- package/dist/services/recovery/pause-hold-guard.d.ts.map +1 -0
- package/dist/services/recovery/pause-hold-guard.js +6 -0
- package/dist/services/recovery/pause-hold-guard.js.map +1 -0
- package/dist/services/recovery/run-liveness-continuations.d.ts +50 -0
- package/dist/services/recovery/run-liveness-continuations.d.ts.map +1 -0
- package/dist/services/recovery/run-liveness-continuations.js +117 -0
- package/dist/services/recovery/run-liveness-continuations.js.map +1 -0
- package/dist/services/recovery/service.d.ts +195 -0
- package/dist/services/recovery/service.d.ts.map +1 -0
- package/dist/services/recovery/service.js +2210 -0
- package/dist/services/recovery/service.js.map +1 -0
- package/dist/services/recovery/successful-run-handoff.d.ts +87 -0
- package/dist/services/recovery/successful-run-handoff.d.ts.map +1 -0
- package/dist/services/recovery/successful-run-handoff.js +297 -0
- package/dist/services/recovery/successful-run-handoff.js.map +1 -0
- package/dist/services/recovery/successful-run-handoff.test.d.ts +2 -0
- package/dist/services/recovery/successful-run-handoff.test.d.ts.map +1 -0
- package/dist/services/recovery/successful-run-handoff.test.js +267 -0
- package/dist/services/recovery/successful-run-handoff.test.js.map +1 -0
- package/dist/services/routines.d.ts +166 -0
- package/dist/services/routines.d.ts.map +1 -0
- package/dist/services/routines.js +1937 -0
- package/dist/services/routines.js.map +1 -0
- package/dist/services/run-continuations.d.ts +3 -0
- package/dist/services/run-continuations.d.ts.map +1 -0
- package/dist/services/run-continuations.js +2 -0
- package/dist/services/run-continuations.js.map +1 -0
- package/dist/services/run-liveness.d.ts +46 -0
- package/dist/services/run-liveness.d.ts.map +1 -0
- package/dist/services/run-liveness.js +275 -0
- package/dist/services/run-liveness.js.map +1 -0
- package/dist/services/run-log-store.d.ts +34 -0
- package/dist/services/run-log-store.d.ts.map +1 -0
- package/dist/services/run-log-store.js +111 -0
- package/dist/services/run-log-store.js.map +1 -0
- package/dist/services/sandbox-provider-runtime.d.ts +132 -0
- package/dist/services/sandbox-provider-runtime.d.ts.map +1 -0
- package/dist/services/sandbox-provider-runtime.js +216 -0
- package/dist/services/sandbox-provider-runtime.js.map +1 -0
- package/dist/services/secrets.d.ts +515 -0
- package/dist/services/secrets.d.ts.map +1 -0
- package/dist/services/secrets.js +290 -0
- package/dist/services/secrets.js.map +1 -0
- package/dist/services/sidebar-badges.d.ts +14 -0
- package/dist/services/sidebar-badges.d.ts.map +1 -0
- package/dist/services/sidebar-badges.js +48 -0
- package/dist/services/sidebar-badges.js.map +1 -0
- package/dist/services/sidebar-preferences.d.ts +9 -0
- package/dist/services/sidebar-preferences.d.ts.map +1 -0
- package/dist/services/sidebar-preferences.js +82 -0
- package/dist/services/sidebar-preferences.js.map +1 -0
- package/dist/services/work-products.d.ts +14 -0
- package/dist/services/work-products.d.ts.map +1 -0
- package/dist/services/work-products.js +100 -0
- package/dist/services/work-products.js.map +1 -0
- package/dist/services/workspace-operation-log-store.d.ts +33 -0
- package/dist/services/workspace-operation-log-store.d.ts.map +1 -0
- package/dist/services/workspace-operation-log-store.js +110 -0
- package/dist/services/workspace-operation-log-store.js.map +1 -0
- package/dist/services/workspace-operations.d.ts +44 -0
- package/dist/services/workspace-operations.d.ts.map +1 -0
- package/dist/services/workspace-operations.js +211 -0
- package/dist/services/workspace-operations.js.map +1 -0
- package/dist/services/workspace-realization.d.ts +33 -0
- package/dist/services/workspace-realization.d.ts.map +1 -0
- package/dist/services/workspace-realization.js +221 -0
- package/dist/services/workspace-realization.js.map +1 -0
- package/dist/services/workspace-runtime-read-model.d.ts +92 -0
- package/dist/services/workspace-runtime-read-model.d.ts.map +1 -0
- package/dist/services/workspace-runtime-read-model.js +67 -0
- package/dist/services/workspace-runtime-read-model.js.map +1 -0
- package/dist/services/workspace-runtime.d.ts +238 -0
- package/dist/services/workspace-runtime.d.ts.map +1 -0
- package/dist/services/workspace-runtime.js +2388 -0
- package/dist/services/workspace-runtime.js.map +1 -0
- package/dist/startup-banner.d.ts +32 -0
- package/dist/startup-banner.d.ts.map +1 -0
- package/dist/startup-banner.js +118 -0
- package/dist/startup-banner.js.map +1 -0
- package/dist/storage/index.d.ts +6 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +29 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/local-disk-provider.d.ts +3 -0
- package/dist/storage/local-disk-provider.d.ts.map +1 -0
- package/dist/storage/local-disk-provider.js +79 -0
- package/dist/storage/local-disk-provider.js.map +1 -0
- package/dist/storage/provider-registry.d.ts +4 -0
- package/dist/storage/provider-registry.d.ts.map +1 -0
- package/dist/storage/provider-registry.js +15 -0
- package/dist/storage/provider-registry.js.map +1 -0
- package/dist/storage/s3-provider.d.ts +11 -0
- package/dist/storage/s3-provider.d.ts.map +1 -0
- package/dist/storage/s3-provider.js +123 -0
- package/dist/storage/s3-provider.js.map +1 -0
- package/dist/storage/service.d.ts +3 -0
- package/dist/storage/service.d.ts.map +1 -0
- package/dist/storage/service.js +120 -0
- package/dist/storage/service.js.map +1 -0
- package/dist/storage/types.d.ts +55 -0
- package/dist/storage/types.d.ts.map +1 -0
- package/dist/storage/types.js +2 -0
- package/dist/storage/types.js.map +1 -0
- package/dist/telemetry.d.ts +6 -0
- package/dist/telemetry.d.ts.map +1 -0
- package/dist/telemetry.js +20 -0
- package/dist/telemetry.js.map +1 -0
- package/dist/ui-branding.d.ts +13 -0
- package/dist/ui-branding.d.ts.map +1 -0
- package/dist/ui-branding.js +187 -0
- package/dist/ui-branding.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +5 -0
- package/dist/version.js.map +1 -0
- package/dist/vite-html-renderer.d.ts +18 -0
- package/dist/vite-html-renderer.d.ts.map +1 -0
- package/dist/vite-html-renderer.js +61 -0
- package/dist/vite-html-renderer.js.map +1 -0
- package/dist/worktree-config.d.ts +19 -0
- package/dist/worktree-config.d.ts.map +1 -0
- package/dist/worktree-config.js +368 -0
- package/dist/worktree-config.js.map +1 -0
- package/package.json +90 -0
- package/skills/diagnose-why-work-stopped/SKILL.md +161 -0
- package/skills/evermore/SKILL.md +366 -0
- package/skills/evermore/references/api-reference.md +899 -0
- package/skills/evermore/references/company-skills.md +193 -0
- package/skills/evermore/references/issue-workspaces.md +80 -0
- package/skills/evermore/references/routines.md +187 -0
- package/skills/evermore/references/workflows.md +141 -0
- package/skills/evermore-converting-plans-to-tasks/SKILL.md +42 -0
- package/skills/evermore-create-agent/SKILL.md +163 -0
- package/skills/evermore-create-agent/references/agent-instruction-templates.md +123 -0
- package/skills/evermore-create-agent/references/agents/coder.md +64 -0
- package/skills/evermore-create-agent/references/agents/qa.md +88 -0
- package/skills/evermore-create-agent/references/agents/securityengineer.md +135 -0
- package/skills/evermore-create-agent/references/agents/uxdesigner.md +115 -0
- package/skills/evermore-create-agent/references/api-reference.md +110 -0
- package/skills/evermore-create-agent/references/baseline-role-guide.md +168 -0
- package/skills/evermore-create-agent/references/draft-review-checklist.md +95 -0
- package/skills/evermore-create-plugin/SKILL.md +101 -0
- package/skills/evermore-dev/SKILL.md +267 -0
- package/skills/para-memory-files/SKILL.md +104 -0
- package/skills/para-memory-files/references/schemas.md +35 -0
- package/skills/terminal-bench-loop/SKILL.md +236 -0
- package/ui-dist/android-chrome-192x192.png +0 -0
- package/ui-dist/android-chrome-512x512.png +0 -0
- package/ui-dist/apple-touch-icon.png +0 -0
- package/ui-dist/assets/_basePickBy-Ds9oHp1_.js +1 -0
- package/ui-dist/assets/_baseUniq-CHYwQyJ_.js +1 -0
- package/ui-dist/assets/apl-B4CMkyY2.js +1 -0
- package/ui-dist/assets/arc-CiUKtBzk.js +1 -0
- package/ui-dist/assets/architectureDiagram-VXUJARFQ-CAW2b6pT.js +36 -0
- package/ui-dist/assets/asciiarmor-Df11BRmG.js +1 -0
- package/ui-dist/assets/asn1-EdZsLKOL.js +1 -0
- package/ui-dist/assets/asterisk-B-8jnY81.js +1 -0
- package/ui-dist/assets/blockDiagram-VD42YOAC-1Rk6YCcn.js +122 -0
- package/ui-dist/assets/brainfuck-C4LP7Hcl.js +1 -0
- package/ui-dist/assets/c4Diagram-YG6GDRKO-DF5RJtyZ.js +10 -0
- package/ui-dist/assets/channel-D7SqxhNi.js +1 -0
- package/ui-dist/assets/chunk-4BX2VUAB-BSZDJAxk.js +1 -0
- package/ui-dist/assets/chunk-55IACEB6-DgVOW-V3.js +1 -0
- package/ui-dist/assets/chunk-B4BG7PRW-C1sGAq6t.js +165 -0
- package/ui-dist/assets/chunk-DI55MBZ5-DZyfq3VK.js +220 -0
- package/ui-dist/assets/chunk-FMBD7UC4-D6K9nYXi.js +15 -0
- package/ui-dist/assets/chunk-QN33PNHL-BJ0Ni2l9.js +1 -0
- package/ui-dist/assets/chunk-QZHKN3VN-Cwjr0vxG.js +1 -0
- package/ui-dist/assets/chunk-TZMSLE5B-C2RGCkyV.js +1 -0
- package/ui-dist/assets/classDiagram-2ON5EDUG-Cx1PlXXb.js +1 -0
- package/ui-dist/assets/classDiagram-v2-WZHVMYZB-Cx1PlXXb.js +1 -0
- package/ui-dist/assets/clike-B9uivgTg.js +1 -0
- package/ui-dist/assets/clojure-BMjYHr_A.js +1 -0
- package/ui-dist/assets/clone-5ZE-SRxk.js +1 -0
- package/ui-dist/assets/cmake-BQqOBYOt.js +1 -0
- package/ui-dist/assets/cobol-CWcv1MsR.js +1 -0
- package/ui-dist/assets/coffeescript-S37ZYGWr.js +1 -0
- package/ui-dist/assets/commonlisp-DBKNyK5s.js +1 -0
- package/ui-dist/assets/cose-bilkent-S5V4N54A-CW0Mh3DC.js +1 -0
- package/ui-dist/assets/crystal-SjHAIU92.js +1 -0
- package/ui-dist/assets/css-BnMrqG3P.js +1 -0
- package/ui-dist/assets/cypher-C_CwsFkJ.js +1 -0
- package/ui-dist/assets/cytoscape.esm-jbPEKk2Y.js +321 -0
- package/ui-dist/assets/d-pRatUO7H.js +1 -0
- package/ui-dist/assets/dagre-6UL2VRFP-BXDbyGyw.js +4 -0
- package/ui-dist/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/ui-dist/assets/diagram-PSM6KHXK-BHW-b3P9.js +24 -0
- package/ui-dist/assets/diagram-QEK2KX5R-vjGsaMmX.js +43 -0
- package/ui-dist/assets/diagram-S2PKOQOG-BHVhRqBj.js +24 -0
- package/ui-dist/assets/diff-DbItnlRl.js +1 -0
- package/ui-dist/assets/dockerfile-BKs6k2Af.js +1 -0
- package/ui-dist/assets/dtd-DF_7sFjM.js +1 -0
- package/ui-dist/assets/dylan-DwRh75JA.js +1 -0
- package/ui-dist/assets/ebnf-CDyGwa7X.js +1 -0
- package/ui-dist/assets/ecl-Cabwm37j.js +1 -0
- package/ui-dist/assets/eiffel-CnydiIhH.js +1 -0
- package/ui-dist/assets/elm-vLlmbW-K.js +1 -0
- package/ui-dist/assets/erDiagram-Q2GNP2WA-DwsdEDB2.js +60 -0
- package/ui-dist/assets/erlang-BNw1qcRV.js +1 -0
- package/ui-dist/assets/factor-kuTfRLto.js +1 -0
- package/ui-dist/assets/fcl-Kvtd6kyn.js +1 -0
- package/ui-dist/assets/flowDiagram-NV44I4VS-Bv7drkZu.js +162 -0
- package/ui-dist/assets/forth-Ffai-XNe.js +1 -0
- package/ui-dist/assets/fortran-DYz_wnZ1.js +1 -0
- package/ui-dist/assets/ganttDiagram-JELNMOA3-DyYAGyjV.js +267 -0
- package/ui-dist/assets/gas-Bneqetm1.js +1 -0
- package/ui-dist/assets/gherkin-heZmZLOM.js +1 -0
- package/ui-dist/assets/gitGraphDiagram-V2S2FVAM-DmKJJ2X0.js +65 -0
- package/ui-dist/assets/graph-BI12oqz9.js +1 -0
- package/ui-dist/assets/groovy-D9Dt4D0W.js +1 -0
- package/ui-dist/assets/haskell-Cw1EW3IL.js +1 -0
- package/ui-dist/assets/haxe-H-WmDvRZ.js +1 -0
- package/ui-dist/assets/http-DBlCnlav.js +1 -0
- package/ui-dist/assets/idl-BEugSyMb.js +1 -0
- package/ui-dist/assets/index-B1YmgKZD.js +1 -0
- package/ui-dist/assets/index-B5EG1mbW.js +1 -0
- package/ui-dist/assets/index-B63DPkk7.js +2 -0
- package/ui-dist/assets/index-BAalUM2u.js +1 -0
- package/ui-dist/assets/index-BCyQqUEQ.js +1 -0
- package/ui-dist/assets/index-BSRsyahM.js +1 -0
- package/ui-dist/assets/index-B_Iu4zUd.js +1 -0
- package/ui-dist/assets/index-C-BdZUIH.js +1 -0
- package/ui-dist/assets/index-CGgUnSQj.js +6 -0
- package/ui-dist/assets/index-COTIEysQ.js +1 -0
- package/ui-dist/assets/index-ChSqseHR.js +1 -0
- package/ui-dist/assets/index-Ckf1hADU.js +534 -0
- package/ui-dist/assets/index-D4M1TSCO.js +3 -0
- package/ui-dist/assets/index-DCTk2CN-.js +7 -0
- package/ui-dist/assets/index-DI-wyUUr.js +1 -0
- package/ui-dist/assets/index-DNtLqZ-D.js +1 -0
- package/ui-dist/assets/index-DSRR_614.css +1 -0
- package/ui-dist/assets/index-DarmgkJv.js +1 -0
- package/ui-dist/assets/index-DdcxF71a.js +1 -0
- package/ui-dist/assets/index-DfwWaMga.js +1 -0
- package/ui-dist/assets/index-SzUviW57.js +13 -0
- package/ui-dist/assets/index-VjZhELb6.js +1 -0
- package/ui-dist/assets/index-mEcmy8wG.js +1 -0
- package/ui-dist/assets/index-q7ldlDv6.js +1 -0
- package/ui-dist/assets/infoDiagram-HS3SLOUP-CqAxMRbC.js +2 -0
- package/ui-dist/assets/init-Gi6I4Gst.js +1 -0
- package/ui-dist/assets/javascript-iXu5QeM3.js +1 -0
- package/ui-dist/assets/journeyDiagram-XKPGCS4Q-DUJiudtY.js +139 -0
- package/ui-dist/assets/julia-DuME0IfC.js +1 -0
- package/ui-dist/assets/kanban-definition-3W4ZIXB7-CiRgJ4X2.js +89 -0
- package/ui-dist/assets/katex-B95LWT_Q.js +261 -0
- package/ui-dist/assets/layout-DJ8V2pqt.js +1 -0
- package/ui-dist/assets/linear-BwnNUuyZ.js +1 -0
- package/ui-dist/assets/livescript-BwQOo05w.js +1 -0
- package/ui-dist/assets/lua-BgMRiT3U.js +1 -0
- package/ui-dist/assets/mathematica-DTrFuWx2.js +1 -0
- package/ui-dist/assets/mbox-CNhZ1qSd.js +1 -0
- package/ui-dist/assets/mermaid.core-DLCiCWj4.js +250 -0
- package/ui-dist/assets/mindmap-definition-VGOIOE7T-epIiGA01.js +68 -0
- package/ui-dist/assets/mirc-CjQqDB4T.js +1 -0
- package/ui-dist/assets/mllike-CXdrOF99.js +1 -0
- package/ui-dist/assets/modelica-Dc1JOy9r.js +1 -0
- package/ui-dist/assets/mscgen-BA5vi2Kp.js +1 -0
- package/ui-dist/assets/mumps-BT43cFF4.js +1 -0
- package/ui-dist/assets/nginx-DdIZxoE0.js +1 -0
- package/ui-dist/assets/nsis-LdVXkNf5.js +1 -0
- package/ui-dist/assets/ntriples-BfvgReVJ.js +1 -0
- package/ui-dist/assets/octave-Ck1zUtKM.js +1 -0
- package/ui-dist/assets/ordinal-Cboi1Yqb.js +1 -0
- package/ui-dist/assets/oz-BzwKVEFT.js +1 -0
- package/ui-dist/assets/pascal--L3eBynH.js +1 -0
- package/ui-dist/assets/perl-CdXCOZ3F.js +1 -0
- package/ui-dist/assets/pieDiagram-ADFJNKIX-CHx4pJ9Y.js +30 -0
- package/ui-dist/assets/pig-CevX1Tat.js +1 -0
- package/ui-dist/assets/powershell-CFHJl5sT.js +1 -0
- package/ui-dist/assets/properties-C78fOPTZ.js +1 -0
- package/ui-dist/assets/protobuf-ChK-085T.js +1 -0
- package/ui-dist/assets/pug-DeIclll2.js +1 -0
- package/ui-dist/assets/puppet-DMA9R1ak.js +1 -0
- package/ui-dist/assets/python-BuPzkPfP.js +1 -0
- package/ui-dist/assets/q-pXgVlZs6.js +1 -0
- package/ui-dist/assets/quadrantDiagram-AYHSOK5B-D0tXmGxE.js +7 -0
- package/ui-dist/assets/r-B6wPVr8A.js +1 -0
- package/ui-dist/assets/requirementDiagram-UZGBJVZJ-BgFc9Xc4.js +64 -0
- package/ui-dist/assets/rpm-CTu-6PCP.js +1 -0
- package/ui-dist/assets/ruby-B2Rjki9n.js +1 -0
- package/ui-dist/assets/sankeyDiagram-TZEHDZUN-DqntX5wV.js +10 -0
- package/ui-dist/assets/sas-B4kiWyti.js +1 -0
- package/ui-dist/assets/scheme-C41bIUwD.js +1 -0
- package/ui-dist/assets/sequenceDiagram-WL72ISMW-BfIJeGAT.js +145 -0
- package/ui-dist/assets/shell-CjFT_Tl9.js +1 -0
- package/ui-dist/assets/sieve-C3Gn_uJK.js +1 -0
- package/ui-dist/assets/simple-mode-GW_nhZxv.js +1 -0
- package/ui-dist/assets/smalltalk-CnHTOXQT.js +1 -0
- package/ui-dist/assets/solr-DehyRSwq.js +1 -0
- package/ui-dist/assets/sparql-DkYu6x3z.js +1 -0
- package/ui-dist/assets/spreadsheet-BCZA_wO0.js +1 -0
- package/ui-dist/assets/sql-D0XecflT.js +1 -0
- package/ui-dist/assets/stateDiagram-FKZM4ZOC-CBlsmcoW.js +1 -0
- package/ui-dist/assets/stateDiagram-v2-4FDKWEC3-DJtH2tCS.js +1 -0
- package/ui-dist/assets/stex-C3f8Ysf7.js +1 -0
- package/ui-dist/assets/stylus-B533Al4x.js +1 -0
- package/ui-dist/assets/swift-BzpIVaGY.js +1 -0
- package/ui-dist/assets/tcl-DVfN8rqt.js +1 -0
- package/ui-dist/assets/textile-CnDTJFAw.js +1 -0
- package/ui-dist/assets/tiddlywiki-DO-Gjzrf.js +1 -0
- package/ui-dist/assets/tiki-DGYXhP31.js +1 -0
- package/ui-dist/assets/timeline-definition-IT6M3QCI-C2XyT4Lo.js +61 -0
- package/ui-dist/assets/toml-Bm5Em-hy.js +1 -0
- package/ui-dist/assets/treemap-GDKQZRPO-DvTrxs0e.js +154 -0
- package/ui-dist/assets/troff-wAsdV37c.js +1 -0
- package/ui-dist/assets/ttcn-CfJYG6tj.js +1 -0
- package/ui-dist/assets/ttcn-cfg-B9xdYoR4.js +1 -0
- package/ui-dist/assets/turtle-B1tBg_DP.js +1 -0
- package/ui-dist/assets/vb-CmGdzxic.js +1 -0
- package/ui-dist/assets/vbscript-BuJXcnF6.js +1 -0
- package/ui-dist/assets/velocity-D8B20fx6.js +1 -0
- package/ui-dist/assets/verilog-C6RDOZhf.js +1 -0
- package/ui-dist/assets/vhdl-lSbBsy5d.js +1 -0
- package/ui-dist/assets/webidl-ZXfAyPTL.js +1 -0
- package/ui-dist/assets/xquery-DzFWVndE.js +1 -0
- package/ui-dist/assets/xychartDiagram-PRI3JC2R-pTizTbG0.js +7 -0
- package/ui-dist/assets/yacas-BJ4BC0dw.js +1 -0
- package/ui-dist/assets/z80-Hz9HOZM7.js +1 -0
- package/ui-dist/brands/opencode-logo-dark-square.svg +18 -0
- package/ui-dist/brands/opencode-logo-light-square.svg +18 -0
- package/ui-dist/favicon-16x16.png +0 -0
- package/ui-dist/favicon-32x32.png +0 -0
- package/ui-dist/favicon.ico +0 -0
- package/ui-dist/favicon.svg +9 -0
- package/ui-dist/index.html +49 -0
- package/ui-dist/site.webmanifest +30 -0
- package/ui-dist/sw.js +42 -0
- package/ui-dist/worktree-favicon-16x16.png +0 -0
- package/ui-dist/worktree-favicon-32x32.png +0 -0
- package/ui-dist/worktree-favicon.ico +0 -0
- package/ui-dist/worktree-favicon.svg +9 -0
|
@@ -0,0 +1,2753 @@
|
|
|
1
|
+
import { Router } from "express";
|
|
2
|
+
import { generateKeyPairSync, randomUUID } from "node:crypto";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { agents as agentsTable, companies, heartbeatRuns, issues as issuesTable } from "@evermore.work/db";
|
|
5
|
+
import { and, desc, eq, inArray, not, sql } from "drizzle-orm";
|
|
6
|
+
import { agentSkillSyncSchema, agentMineInboxQuerySchema, AGENT_DEFAULT_MAX_CONCURRENT_RUNS, createAgentKeySchema, createAgentHireSchema, createAgentSchema, deriveAgentUrlKey, isUuidLike, normalizeIssueIdentifier, resetAgentSessionSchema, testAdapterEnvironmentSchema, upsertAgentInstructionsFileSchema, updateAgentInstructionsBundleSchema, updateAgentPermissionsSchema, updateAgentInstructionsPathSchema, wakeAgentSchema, updateAgentSchema, supportedEnvironmentDriversForAdapter, } from "@evermore.work/shared";
|
|
7
|
+
import { readEvermoreSkillSyncPreference, writeEvermoreSkillSyncPreference, } from "@evermore.work/adapter-utils/server-utils";
|
|
8
|
+
import { trackAgentCreated } from "@evermore.work/shared/telemetry";
|
|
9
|
+
import { validate } from "../middleware/validate.js";
|
|
10
|
+
import { agentService, agentInstructionsService, accessService, approvalService, companySkillService, budgetService, heartbeatService, ISSUE_LIST_DEFAULT_LIMIT, issueApprovalService, issueService, logActivity, syncInstructionsBundleConfigFromFilePath, workspaceOperationService, } from "../services/index.js";
|
|
11
|
+
import { conflict, forbidden, notFound, unprocessable } from "../errors.js";
|
|
12
|
+
import { assertBoard, assertCompanyAccess, assertInstanceAdmin, getActorInfo } from "./authz.js";
|
|
13
|
+
import { assertNoAgentHostWorkspaceCommandMutation, collectAgentAdapterWorkspaceCommandPaths, } from "./workspace-command-authz.js";
|
|
14
|
+
import { environmentService } from "../services/environments.js";
|
|
15
|
+
import { resolveEnvironmentExecutionTarget } from "../services/environment-execution-target.js";
|
|
16
|
+
import { environmentRuntimeService } from "../services/environment-runtime.js";
|
|
17
|
+
import { secretService } from "../services/secrets.js";
|
|
18
|
+
import { detectAdapterModel, findActiveServerAdapter, findServerAdapter, listAdapterModels, listAdapterModelProfiles, refreshAdapterModels, requireServerAdapter, } from "../adapters/index.js";
|
|
19
|
+
import { redactEventPayload } from "../redaction.js";
|
|
20
|
+
import { redactCurrentUserValue } from "../log-redaction.js";
|
|
21
|
+
import { renderOrgChartSvg, renderOrgChartPng, ORG_CHART_STYLES } from "./org-chart-svg.js";
|
|
22
|
+
import { instanceSettingsService } from "../services/instance-settings.js";
|
|
23
|
+
import { runClaudeLogin } from "@evermore.work/adapter-claude-local/server";
|
|
24
|
+
import { DEFAULT_ACPX_LOCAL_AGENT, DEFAULT_ACPX_LOCAL_MODE, DEFAULT_ACPX_LOCAL_NON_INTERACTIVE_PERMISSIONS, DEFAULT_ACPX_LOCAL_PERMISSION_MODE, } from "@evermore.work/adapter-acpx-local";
|
|
25
|
+
import { DEFAULT_CODEX_LOCAL_BYPASS_APPROVALS_AND_SANDBOX, DEFAULT_CODEX_LOCAL_MODEL, } from "@evermore.work/adapter-codex-local";
|
|
26
|
+
import { DEFAULT_CURSOR_LOCAL_MODEL } from "@evermore.work/adapter-cursor-local";
|
|
27
|
+
import { DEFAULT_GEMINI_LOCAL_MODEL } from "@evermore.work/adapter-gemini-local";
|
|
28
|
+
import { DEFAULT_OPENCODE_LOCAL_MODEL } from "@evermore.work/adapter-opencode-local";
|
|
29
|
+
import { requireOpenCodeModelId } from "@evermore.work/adapter-opencode-local/server";
|
|
30
|
+
import { loadDefaultAgentInstructionsBundle, resolveDefaultAgentInstructionsBundleRole, } from "../services/default-agent-instructions.js";
|
|
31
|
+
import { getTelemetryClient } from "../telemetry.js";
|
|
32
|
+
import { assertEnvironmentSelectionForCompany } from "./environment-selection.js";
|
|
33
|
+
import { recoveryService } from "../services/recovery/service.js";
|
|
34
|
+
const RUN_LOG_DEFAULT_LIMIT_BYTES = 256_000;
|
|
35
|
+
const RUN_LOG_MAX_LIMIT_BYTES = 1024 * 1024;
|
|
36
|
+
function readRunLogLimitBytes(value) {
|
|
37
|
+
const parsed = Number(value ?? RUN_LOG_DEFAULT_LIMIT_BYTES);
|
|
38
|
+
if (!Number.isFinite(parsed))
|
|
39
|
+
return RUN_LOG_DEFAULT_LIMIT_BYTES;
|
|
40
|
+
return Math.max(1, Math.min(RUN_LOG_MAX_LIMIT_BYTES, Math.trunc(parsed)));
|
|
41
|
+
}
|
|
42
|
+
function readLiveRunsQueryInt(value, max, fallback = 0) {
|
|
43
|
+
const parsed = Number(value);
|
|
44
|
+
if (!Number.isFinite(parsed))
|
|
45
|
+
return fallback;
|
|
46
|
+
if (parsed <= 0)
|
|
47
|
+
return fallback;
|
|
48
|
+
return Math.min(max, Math.trunc(parsed));
|
|
49
|
+
}
|
|
50
|
+
export function agentRoutes(db, options = {}) {
|
|
51
|
+
// Legacy hardcoded maps — used as fallback when adapter module does not
|
|
52
|
+
// declare capability flags explicitly.
|
|
53
|
+
const DEFAULT_INSTRUCTIONS_PATH_KEYS = {
|
|
54
|
+
acpx_local: "instructionsFilePath",
|
|
55
|
+
claude_local: "instructionsFilePath",
|
|
56
|
+
codex_local: "instructionsFilePath",
|
|
57
|
+
droid_local: "instructionsFilePath",
|
|
58
|
+
gemini_local: "instructionsFilePath",
|
|
59
|
+
hermes_local: "instructionsFilePath",
|
|
60
|
+
opencode_local: "instructionsFilePath",
|
|
61
|
+
cursor: "instructionsFilePath",
|
|
62
|
+
pi_local: "instructionsFilePath",
|
|
63
|
+
};
|
|
64
|
+
const DEFAULT_MANAGED_INSTRUCTIONS_ADAPTER_TYPES = new Set(Object.keys(DEFAULT_INSTRUCTIONS_PATH_KEYS));
|
|
65
|
+
/** Check if an adapter supports the managed instructions bundle. */
|
|
66
|
+
function adapterSupportsInstructionsBundle(adapterType) {
|
|
67
|
+
const adapter = findActiveServerAdapter(adapterType);
|
|
68
|
+
if (adapter?.supportsInstructionsBundle !== undefined)
|
|
69
|
+
return adapter.supportsInstructionsBundle;
|
|
70
|
+
return DEFAULT_MANAGED_INSTRUCTIONS_ADAPTER_TYPES.has(adapterType);
|
|
71
|
+
}
|
|
72
|
+
/** Resolve the adapter config key for the instructions file path. */
|
|
73
|
+
function resolveInstructionsPathKey(adapterType) {
|
|
74
|
+
const adapter = findActiveServerAdapter(adapterType);
|
|
75
|
+
if (adapter?.instructionsPathKey)
|
|
76
|
+
return adapter.instructionsPathKey;
|
|
77
|
+
if (adapter?.supportsInstructionsBundle === true)
|
|
78
|
+
return "instructionsFilePath";
|
|
79
|
+
if (adapter?.supportsInstructionsBundle === false)
|
|
80
|
+
return null;
|
|
81
|
+
return DEFAULT_INSTRUCTIONS_PATH_KEYS[adapterType] ?? null;
|
|
82
|
+
}
|
|
83
|
+
const KNOWN_INSTRUCTIONS_PATH_KEYS = new Set(["instructionsFilePath", "agentsMdPath"]);
|
|
84
|
+
const KNOWN_INSTRUCTIONS_BUNDLE_KEYS = [
|
|
85
|
+
"instructionsBundleMode",
|
|
86
|
+
"instructionsRootPath",
|
|
87
|
+
"instructionsEntryFile",
|
|
88
|
+
"instructionsFilePath",
|
|
89
|
+
"agentsMdPath",
|
|
90
|
+
];
|
|
91
|
+
const router = Router();
|
|
92
|
+
const svc = agentService(db);
|
|
93
|
+
const access = accessService(db);
|
|
94
|
+
const approvalsSvc = approvalService(db);
|
|
95
|
+
const budgets = budgetService(db);
|
|
96
|
+
const environmentsSvc = environmentService(db);
|
|
97
|
+
const environmentRuntime = environmentRuntimeService(db, {
|
|
98
|
+
pluginWorkerManager: options.pluginWorkerManager,
|
|
99
|
+
});
|
|
100
|
+
const heartbeat = heartbeatService(db, {
|
|
101
|
+
pluginWorkerManager: options.pluginWorkerManager,
|
|
102
|
+
});
|
|
103
|
+
const recovery = recoveryService(db, { enqueueWakeup: heartbeat.wakeup });
|
|
104
|
+
const issueApprovalsSvc = issueApprovalService(db);
|
|
105
|
+
const secretsSvc = secretService(db);
|
|
106
|
+
const instructions = agentInstructionsService();
|
|
107
|
+
const companySkills = companySkillService(db);
|
|
108
|
+
const workspaceOperations = workspaceOperationService(db);
|
|
109
|
+
const instanceSettings = instanceSettingsService(db);
|
|
110
|
+
const strictSecretsMode = process.env.EVERMORE_SECRETS_STRICT_MODE === "true";
|
|
111
|
+
async function assertAgentEnvironmentSelection(companyId, adapterType, environmentId) {
|
|
112
|
+
if (environmentId === undefined || environmentId === null)
|
|
113
|
+
return;
|
|
114
|
+
await assertEnvironmentSelectionForCompany(environmentService(db), companyId, environmentId, {
|
|
115
|
+
allowedDrivers: allowedEnvironmentDriversForAgent(adapterType),
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Resolve the execution target the adapter should run its test probes against.
|
|
120
|
+
*
|
|
121
|
+
* - No environmentId / local environment → returns a local target so the
|
|
122
|
+
* adapter probes the Evermore host (legacy behavior).
|
|
123
|
+
* - SSH environment → builds an SSH execution target from the environment
|
|
124
|
+
* config so the adapter probes the remote box. No lease is required:
|
|
125
|
+
* the SSH spec is fully derived from the saved environment config.
|
|
126
|
+
* - Sandbox / plugin environments → acquires an ad-hoc lease, realizes the
|
|
127
|
+
* workspace, and resolves a sandbox execution target wired to the runtime
|
|
128
|
+
* so the adapter probe runs inside the sandbox the same way a heartbeat
|
|
129
|
+
* would. The returned `release` callback rolls the lease back when the
|
|
130
|
+
* route is done.
|
|
131
|
+
*
|
|
132
|
+
* The caller MUST always invoke `release()` (typically in a `finally` block).
|
|
133
|
+
*/
|
|
134
|
+
async function resolveAdapterTestExecutionContext(input) {
|
|
135
|
+
const noopRelease = async () => { };
|
|
136
|
+
if (!input.environmentId) {
|
|
137
|
+
return {
|
|
138
|
+
executionTarget: null,
|
|
139
|
+
environmentName: null,
|
|
140
|
+
fallbackChecks: [],
|
|
141
|
+
release: noopRelease,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
const environment = await environmentsSvc.getById(input.environmentId);
|
|
145
|
+
if (!environment || environment.companyId !== input.companyId) {
|
|
146
|
+
return {
|
|
147
|
+
executionTarget: null,
|
|
148
|
+
environmentName: null,
|
|
149
|
+
fallbackChecks: [
|
|
150
|
+
{
|
|
151
|
+
code: "environment_not_found",
|
|
152
|
+
level: "warn",
|
|
153
|
+
message: "Selected environment was not found. The test did not run.",
|
|
154
|
+
},
|
|
155
|
+
],
|
|
156
|
+
release: noopRelease,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
if (environment.driver === "local") {
|
|
160
|
+
return {
|
|
161
|
+
executionTarget: null,
|
|
162
|
+
environmentName: environment.name,
|
|
163
|
+
fallbackChecks: [],
|
|
164
|
+
release: noopRelease,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
if (environment.driver === "ssh") {
|
|
168
|
+
try {
|
|
169
|
+
const target = await resolveEnvironmentExecutionTarget({
|
|
170
|
+
db,
|
|
171
|
+
companyId: input.companyId,
|
|
172
|
+
adapterType: input.adapterType,
|
|
173
|
+
environment: {
|
|
174
|
+
id: environment.id,
|
|
175
|
+
driver: environment.driver,
|
|
176
|
+
config: environment.config ?? null,
|
|
177
|
+
},
|
|
178
|
+
leaseMetadata: null,
|
|
179
|
+
});
|
|
180
|
+
if (target) {
|
|
181
|
+
return {
|
|
182
|
+
executionTarget: target,
|
|
183
|
+
environmentName: environment.name,
|
|
184
|
+
fallbackChecks: [],
|
|
185
|
+
release: noopRelease,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
return {
|
|
189
|
+
executionTarget: null,
|
|
190
|
+
environmentName: environment.name,
|
|
191
|
+
fallbackChecks: [
|
|
192
|
+
{
|
|
193
|
+
code: "environment_target_unavailable",
|
|
194
|
+
level: "warn",
|
|
195
|
+
message: `Could not resolve an execution target for environment "${environment.name}". The test did not run.`,
|
|
196
|
+
},
|
|
197
|
+
],
|
|
198
|
+
release: noopRelease,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
catch (err) {
|
|
202
|
+
return {
|
|
203
|
+
executionTarget: null,
|
|
204
|
+
environmentName: environment.name,
|
|
205
|
+
fallbackChecks: [
|
|
206
|
+
{
|
|
207
|
+
code: "environment_target_failed",
|
|
208
|
+
level: "warn",
|
|
209
|
+
message: `Could not connect to environment "${environment.name}" to run the test.`,
|
|
210
|
+
detail: err instanceof Error ? err.message : String(err),
|
|
211
|
+
},
|
|
212
|
+
],
|
|
213
|
+
release: noopRelease,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
// sandbox / plugin / other remote drivers: spin up an ad-hoc lease, realize
|
|
218
|
+
// the workspace inside the box, and run the same probe SSH uses against
|
|
219
|
+
// a sandbox execution target wired to the environment runtime.
|
|
220
|
+
//
|
|
221
|
+
// We pass `heartbeatRunId: null` because there's no heartbeat run for an
|
|
222
|
+
// operator-initiated `Test` invocation — the leases table FKs heartbeat
|
|
223
|
+
// run id to heartbeat_runs.id, and we don't want to manufacture a fake
|
|
224
|
+
// run row. Cleanup goes through the driver's `releaseRunLease` directly
|
|
225
|
+
// (by lease record), since the batch helper queries by heartbeatRunId.
|
|
226
|
+
let leaseRecord;
|
|
227
|
+
try {
|
|
228
|
+
leaseRecord = await environmentRuntime.acquireRunLease({
|
|
229
|
+
companyId: input.companyId,
|
|
230
|
+
environment,
|
|
231
|
+
issueId: null,
|
|
232
|
+
heartbeatRunId: null,
|
|
233
|
+
persistedExecutionWorkspace: null,
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
catch (err) {
|
|
237
|
+
return {
|
|
238
|
+
executionTarget: null,
|
|
239
|
+
environmentName: environment.name,
|
|
240
|
+
fallbackChecks: [
|
|
241
|
+
{
|
|
242
|
+
code: "environment_lease_acquire_failed",
|
|
243
|
+
level: "error",
|
|
244
|
+
message: `Could not acquire a lease for environment "${environment.name}".`,
|
|
245
|
+
detail: err instanceof Error ? err.message : String(err),
|
|
246
|
+
hint: "Check the environment's provider credentials and quota.",
|
|
247
|
+
},
|
|
248
|
+
],
|
|
249
|
+
release: noopRelease,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
const driver = environmentRuntime.getDriver(environment.driver);
|
|
253
|
+
const releaseLease = async (status = "released") => {
|
|
254
|
+
try {
|
|
255
|
+
if (driver) {
|
|
256
|
+
await driver.releaseRunLease({
|
|
257
|
+
environment,
|
|
258
|
+
lease: leaseRecord.lease,
|
|
259
|
+
status,
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
await environmentsSvc.releaseLease(leaseRecord.lease.id, status);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
catch (err) {
|
|
267
|
+
// Cleanup failures must not mask the test result.
|
|
268
|
+
// eslint-disable-next-line no-console
|
|
269
|
+
console.warn(`[adapter-test] Failed to release lease ${leaseRecord.lease.id}: ${err instanceof Error ? err.message : String(err)}`);
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
let realizedCwd = null;
|
|
273
|
+
try {
|
|
274
|
+
const realized = await environmentRuntime.realizeWorkspace({
|
|
275
|
+
environment,
|
|
276
|
+
lease: leaseRecord.lease,
|
|
277
|
+
// No host workspace to copy for a Test invocation; sandbox/plugin
|
|
278
|
+
// realize implementations use the lease metadata's remoteCwd to
|
|
279
|
+
// create the working directory inside the box.
|
|
280
|
+
workspace: {},
|
|
281
|
+
});
|
|
282
|
+
realizedCwd =
|
|
283
|
+
typeof realized.cwd === "string" && realized.cwd.trim().length > 0
|
|
284
|
+
? realized.cwd.trim()
|
|
285
|
+
: null;
|
|
286
|
+
}
|
|
287
|
+
catch (err) {
|
|
288
|
+
await releaseLease("failed");
|
|
289
|
+
return {
|
|
290
|
+
executionTarget: null,
|
|
291
|
+
environmentName: environment.name,
|
|
292
|
+
fallbackChecks: [
|
|
293
|
+
{
|
|
294
|
+
code: "environment_workspace_realize_failed",
|
|
295
|
+
level: "error",
|
|
296
|
+
message: `Could not realize a workspace inside "${environment.name}".`,
|
|
297
|
+
detail: err instanceof Error ? err.message : String(err),
|
|
298
|
+
},
|
|
299
|
+
],
|
|
300
|
+
release: noopRelease,
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
let target;
|
|
304
|
+
try {
|
|
305
|
+
// Prefer the cwd the realize step returned; fall back to lease metadata.
|
|
306
|
+
const leaseMetadataForTarget = realizedCwd
|
|
307
|
+
? { ...(leaseRecord.lease.metadata ?? {}), remoteCwd: realizedCwd }
|
|
308
|
+
: leaseRecord.lease.metadata ?? null;
|
|
309
|
+
target = await resolveEnvironmentExecutionTarget({
|
|
310
|
+
db,
|
|
311
|
+
companyId: input.companyId,
|
|
312
|
+
adapterType: input.adapterType,
|
|
313
|
+
environment: {
|
|
314
|
+
id: environment.id,
|
|
315
|
+
driver: environment.driver,
|
|
316
|
+
config: environment.config ?? null,
|
|
317
|
+
},
|
|
318
|
+
leaseId: leaseRecord.lease.id,
|
|
319
|
+
leaseMetadata: leaseMetadataForTarget,
|
|
320
|
+
lease: leaseRecord.lease,
|
|
321
|
+
environmentRuntime,
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
catch (err) {
|
|
325
|
+
await releaseLease("failed");
|
|
326
|
+
return {
|
|
327
|
+
executionTarget: null,
|
|
328
|
+
environmentName: environment.name,
|
|
329
|
+
fallbackChecks: [
|
|
330
|
+
{
|
|
331
|
+
code: "environment_target_failed",
|
|
332
|
+
level: "error",
|
|
333
|
+
message: `Could not resolve a sandbox execution target for "${environment.name}".`,
|
|
334
|
+
detail: err instanceof Error ? err.message : String(err),
|
|
335
|
+
},
|
|
336
|
+
],
|
|
337
|
+
release: noopRelease,
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
if (!target) {
|
|
341
|
+
await releaseLease("failed");
|
|
342
|
+
return {
|
|
343
|
+
executionTarget: null,
|
|
344
|
+
environmentName: environment.name,
|
|
345
|
+
fallbackChecks: [
|
|
346
|
+
{
|
|
347
|
+
code: "environment_target_unsupported",
|
|
348
|
+
level: "warn",
|
|
349
|
+
message: `Adapter "${input.adapterType}" is not allowed in "${environment.name}" environments.`,
|
|
350
|
+
},
|
|
351
|
+
],
|
|
352
|
+
release: noopRelease,
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
return {
|
|
356
|
+
executionTarget: target,
|
|
357
|
+
environmentName: environment.name,
|
|
358
|
+
fallbackChecks: [],
|
|
359
|
+
release: releaseLease,
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
async function getCurrentUserRedactionOptions() {
|
|
363
|
+
return {
|
|
364
|
+
enabled: (await instanceSettings.getGeneral()).censorUsernameInLogs,
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
function canCreateAgents(agent) {
|
|
368
|
+
if (!agent.permissions || typeof agent.permissions !== "object")
|
|
369
|
+
return false;
|
|
370
|
+
return Boolean(agent.permissions.canCreateAgents);
|
|
371
|
+
}
|
|
372
|
+
async function buildAgentAccessState(agent) {
|
|
373
|
+
const membership = await access.getMembership(agent.companyId, "agent", agent.id);
|
|
374
|
+
const grants = membership
|
|
375
|
+
? await access.listPrincipalGrants(agent.companyId, "agent", agent.id)
|
|
376
|
+
: [];
|
|
377
|
+
const hasExplicitTaskAssignGrant = grants.some((grant) => grant.permissionKey === "tasks:assign");
|
|
378
|
+
if (agent.role === "ceo") {
|
|
379
|
+
return {
|
|
380
|
+
canAssignTasks: true,
|
|
381
|
+
taskAssignSource: "ceo_role",
|
|
382
|
+
membership,
|
|
383
|
+
grants,
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
if (canCreateAgents(agent)) {
|
|
387
|
+
return {
|
|
388
|
+
canAssignTasks: true,
|
|
389
|
+
taskAssignSource: "agent_creator",
|
|
390
|
+
membership,
|
|
391
|
+
grants,
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
if (hasExplicitTaskAssignGrant) {
|
|
395
|
+
return {
|
|
396
|
+
canAssignTasks: true,
|
|
397
|
+
taskAssignSource: "explicit_grant",
|
|
398
|
+
membership,
|
|
399
|
+
grants,
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
return {
|
|
403
|
+
canAssignTasks: false,
|
|
404
|
+
taskAssignSource: "none",
|
|
405
|
+
membership,
|
|
406
|
+
grants,
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
async function buildAgentDetail(agent, options) {
|
|
410
|
+
const [chainOfCommand, accessState] = await Promise.all([
|
|
411
|
+
svc.getChainOfCommand(agent.id),
|
|
412
|
+
buildAgentAccessState(agent),
|
|
413
|
+
]);
|
|
414
|
+
return {
|
|
415
|
+
...(options?.restricted ? redactForRestrictedAgentView(agent) : agent),
|
|
416
|
+
chainOfCommand,
|
|
417
|
+
access: accessState,
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
async function applyDefaultAgentTaskAssignGrant(companyId, agentId, grantedByUserId) {
|
|
421
|
+
await access.ensureMembership(companyId, "agent", agentId, "member", "active");
|
|
422
|
+
await access.setPrincipalPermission(companyId, "agent", agentId, "tasks:assign", true, grantedByUserId);
|
|
423
|
+
}
|
|
424
|
+
async function assertCanCreateAgentsForCompany(req, companyId) {
|
|
425
|
+
assertCompanyAccess(req, companyId);
|
|
426
|
+
if (req.actor.type === "board") {
|
|
427
|
+
if (req.actor.source === "local_implicit" || req.actor.isInstanceAdmin)
|
|
428
|
+
return null;
|
|
429
|
+
const allowed = await access.canUser(companyId, req.actor.userId, "agents:create");
|
|
430
|
+
if (!allowed) {
|
|
431
|
+
throw forbidden("Missing permission: agents:create");
|
|
432
|
+
}
|
|
433
|
+
return null;
|
|
434
|
+
}
|
|
435
|
+
if (!req.actor.agentId)
|
|
436
|
+
throw forbidden("Agent authentication required");
|
|
437
|
+
const actorAgent = await svc.getById(req.actor.agentId);
|
|
438
|
+
if (!actorAgent || actorAgent.companyId !== companyId) {
|
|
439
|
+
throw forbidden("Agent key cannot access another company");
|
|
440
|
+
}
|
|
441
|
+
const allowedByGrant = await access.hasPermission(companyId, "agent", actorAgent.id, "agents:create");
|
|
442
|
+
if (!allowedByGrant && !canCreateAgents(actorAgent)) {
|
|
443
|
+
throw forbidden("Missing permission: can create agents");
|
|
444
|
+
}
|
|
445
|
+
return actorAgent;
|
|
446
|
+
}
|
|
447
|
+
async function assertBoardCanManageAgentsForCompany(req, companyId) {
|
|
448
|
+
assertBoard(req);
|
|
449
|
+
assertCompanyAccess(req, companyId);
|
|
450
|
+
if (req.actor.source === "local_implicit" || req.actor.isInstanceAdmin)
|
|
451
|
+
return;
|
|
452
|
+
const allowed = await access.canUser(companyId, req.actor.userId, "agents:create");
|
|
453
|
+
if (!allowed) {
|
|
454
|
+
throw forbidden("Missing permission: agents:create");
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
async function assertCanReadConfigurations(req, companyId) {
|
|
458
|
+
return assertCanCreateAgentsForCompany(req, companyId);
|
|
459
|
+
}
|
|
460
|
+
async function getAccessibleAgent(req, res, id) {
|
|
461
|
+
const agent = await svc.getById(id);
|
|
462
|
+
if (!agent) {
|
|
463
|
+
res.status(404).json({ error: "Agent not found" });
|
|
464
|
+
return null;
|
|
465
|
+
}
|
|
466
|
+
assertCompanyAccess(req, agent.companyId);
|
|
467
|
+
if (req.actor.type === "board") {
|
|
468
|
+
await assertBoardCanManageAgentsForCompany(req, agent.companyId);
|
|
469
|
+
}
|
|
470
|
+
return agent;
|
|
471
|
+
}
|
|
472
|
+
async function actorCanReadConfigurationsForCompany(req, companyId) {
|
|
473
|
+
assertCompanyAccess(req, companyId);
|
|
474
|
+
if (req.actor.type === "board") {
|
|
475
|
+
if (req.actor.source === "local_implicit" || req.actor.isInstanceAdmin)
|
|
476
|
+
return true;
|
|
477
|
+
return access.canUser(companyId, req.actor.userId, "agents:create");
|
|
478
|
+
}
|
|
479
|
+
if (!req.actor.agentId)
|
|
480
|
+
return false;
|
|
481
|
+
const actorAgent = await svc.getById(req.actor.agentId);
|
|
482
|
+
if (!actorAgent || actorAgent.companyId !== companyId)
|
|
483
|
+
return false;
|
|
484
|
+
const allowedByGrant = await access.hasPermission(companyId, "agent", actorAgent.id, "agents:create");
|
|
485
|
+
return allowedByGrant || canCreateAgents(actorAgent);
|
|
486
|
+
}
|
|
487
|
+
async function buildSkippedWakeupResponse(agent, payload) {
|
|
488
|
+
const issueId = typeof payload?.issueId === "string" && payload.issueId.trim() ? payload.issueId : null;
|
|
489
|
+
if (!issueId) {
|
|
490
|
+
return {
|
|
491
|
+
status: "skipped",
|
|
492
|
+
reason: "wakeup_skipped",
|
|
493
|
+
message: "Wakeup was skipped.",
|
|
494
|
+
issueId: null,
|
|
495
|
+
executionRunId: null,
|
|
496
|
+
executionAgentId: null,
|
|
497
|
+
executionAgentName: null,
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
const issue = await db
|
|
501
|
+
.select({
|
|
502
|
+
id: issuesTable.id,
|
|
503
|
+
executionRunId: issuesTable.executionRunId,
|
|
504
|
+
})
|
|
505
|
+
.from(issuesTable)
|
|
506
|
+
.where(and(eq(issuesTable.id, issueId), eq(issuesTable.companyId, agent.companyId)))
|
|
507
|
+
.then((rows) => rows[0] ?? null);
|
|
508
|
+
if (!issue?.executionRunId) {
|
|
509
|
+
return {
|
|
510
|
+
status: "skipped",
|
|
511
|
+
reason: "wakeup_skipped",
|
|
512
|
+
message: "Wakeup was skipped.",
|
|
513
|
+
issueId,
|
|
514
|
+
executionRunId: null,
|
|
515
|
+
executionAgentId: null,
|
|
516
|
+
executionAgentName: null,
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
const executionRun = await heartbeat.getRun(issue.executionRunId);
|
|
520
|
+
if (!executionRun || (executionRun.status !== "queued" && executionRun.status !== "running")) {
|
|
521
|
+
return {
|
|
522
|
+
status: "skipped",
|
|
523
|
+
reason: "wakeup_skipped",
|
|
524
|
+
message: "Wakeup was skipped.",
|
|
525
|
+
issueId,
|
|
526
|
+
executionRunId: issue.executionRunId,
|
|
527
|
+
executionAgentId: null,
|
|
528
|
+
executionAgentName: null,
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
const executionAgent = await svc.getById(executionRun.agentId);
|
|
532
|
+
const executionAgentName = executionAgent?.name ?? null;
|
|
533
|
+
return {
|
|
534
|
+
status: "skipped",
|
|
535
|
+
reason: "issue_execution_deferred",
|
|
536
|
+
message: executionAgentName
|
|
537
|
+
? `Wakeup was deferred because this issue is already being executed by ${executionAgentName}.`
|
|
538
|
+
: "Wakeup was deferred because this issue already has an active execution run.",
|
|
539
|
+
issueId,
|
|
540
|
+
executionRunId: executionRun.id,
|
|
541
|
+
executionAgentId: executionRun.agentId,
|
|
542
|
+
executionAgentName,
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
async function assertCanUpdateAgent(req, targetAgent) {
|
|
546
|
+
assertCompanyAccess(req, targetAgent.companyId);
|
|
547
|
+
if (req.actor.type === "board") {
|
|
548
|
+
await assertBoardCanManageAgentsForCompany(req, targetAgent.companyId);
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
if (!req.actor.agentId)
|
|
552
|
+
throw forbidden("Agent authentication required");
|
|
553
|
+
const actorAgent = await svc.getById(req.actor.agentId);
|
|
554
|
+
if (!actorAgent || actorAgent.companyId !== targetAgent.companyId) {
|
|
555
|
+
throw forbidden("Agent key cannot access another company");
|
|
556
|
+
}
|
|
557
|
+
if (actorAgent.id === targetAgent.id)
|
|
558
|
+
return;
|
|
559
|
+
if (actorAgent.role === "ceo")
|
|
560
|
+
return;
|
|
561
|
+
const allowedByGrant = await access.hasPermission(targetAgent.companyId, "agent", actorAgent.id, "agents:create");
|
|
562
|
+
if (allowedByGrant || canCreateAgents(actorAgent))
|
|
563
|
+
return;
|
|
564
|
+
throw forbidden("Only CEO or agent creators can modify other agents");
|
|
565
|
+
}
|
|
566
|
+
async function assertCanReadAgent(req, targetAgent) {
|
|
567
|
+
assertCompanyAccess(req, targetAgent.companyId);
|
|
568
|
+
if (req.actor.type === "board") {
|
|
569
|
+
await assertCanReadConfigurations(req, targetAgent.companyId);
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
if (!req.actor.agentId)
|
|
573
|
+
throw forbidden("Agent authentication required");
|
|
574
|
+
const actorAgent = await svc.getById(req.actor.agentId);
|
|
575
|
+
if (!actorAgent || actorAgent.companyId !== targetAgent.companyId) {
|
|
576
|
+
throw forbidden("Agent key cannot access another company");
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
function assertKnownAdapterType(type) {
|
|
580
|
+
const adapterType = typeof type === "string" ? type.trim() : "";
|
|
581
|
+
if (!adapterType) {
|
|
582
|
+
throw unprocessable("Adapter type is required");
|
|
583
|
+
}
|
|
584
|
+
if (!findServerAdapter(adapterType)) {
|
|
585
|
+
throw unprocessable(`Unknown adapter type: ${adapterType}`);
|
|
586
|
+
}
|
|
587
|
+
return adapterType;
|
|
588
|
+
}
|
|
589
|
+
async function assertAgentDefaultEnvironmentSelection(companyId, environmentId, options) {
|
|
590
|
+
if (environmentId === undefined || environmentId === null)
|
|
591
|
+
return;
|
|
592
|
+
const environment = await environmentsSvc.getById(environmentId);
|
|
593
|
+
if (!environment || environment.companyId !== companyId) {
|
|
594
|
+
throw unprocessable("Selected environment must belong to the same company");
|
|
595
|
+
}
|
|
596
|
+
if (options?.allowedDrivers && !options.allowedDrivers.includes(environment.driver)) {
|
|
597
|
+
throw unprocessable(`Environment driver "${environment.driver}" is not allowed here`);
|
|
598
|
+
}
|
|
599
|
+
if (environment.driver === "sandbox" && options?.allowedSandboxProviders) {
|
|
600
|
+
const config = environment.config && typeof environment.config === "object"
|
|
601
|
+
? environment.config
|
|
602
|
+
: {};
|
|
603
|
+
const provider = typeof config.provider === "string" ? config.provider : "";
|
|
604
|
+
if (provider === "fake") {
|
|
605
|
+
throw unprocessable(`Selected sandbox provider "${provider}" is not supported for agent defaults yet`);
|
|
606
|
+
}
|
|
607
|
+
if (options.allowedSandboxProviders.length > 0 && !options.allowedSandboxProviders.includes(provider)) {
|
|
608
|
+
throw unprocessable(`Selected sandbox provider "${provider || "unknown"}" is not supported for agent defaults yet`);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
function hasOwn(value, key) {
|
|
613
|
+
return Object.hasOwn(value, key);
|
|
614
|
+
}
|
|
615
|
+
function allowedEnvironmentDriversForAgent(adapterType) {
|
|
616
|
+
return supportedEnvironmentDriversForAdapter(adapterType);
|
|
617
|
+
}
|
|
618
|
+
function allowedSandboxProvidersForAgent(adapterType) {
|
|
619
|
+
return supportedEnvironmentDriversForAdapter(adapterType).includes("sandbox") ? [] : [];
|
|
620
|
+
}
|
|
621
|
+
async function resolveCompanyIdForAgentReference(req) {
|
|
622
|
+
const companyIdQuery = req.query.companyId;
|
|
623
|
+
const requestedCompanyId = typeof companyIdQuery === "string" && companyIdQuery.trim().length > 0
|
|
624
|
+
? companyIdQuery.trim()
|
|
625
|
+
: null;
|
|
626
|
+
if (requestedCompanyId) {
|
|
627
|
+
assertCompanyAccess(req, requestedCompanyId);
|
|
628
|
+
return requestedCompanyId;
|
|
629
|
+
}
|
|
630
|
+
if (req.actor.type === "agent" && req.actor.companyId) {
|
|
631
|
+
return req.actor.companyId;
|
|
632
|
+
}
|
|
633
|
+
return null;
|
|
634
|
+
}
|
|
635
|
+
async function normalizeAgentReference(req, rawId) {
|
|
636
|
+
const raw = rawId.trim();
|
|
637
|
+
if (isUuidLike(raw))
|
|
638
|
+
return raw;
|
|
639
|
+
const companyId = await resolveCompanyIdForAgentReference(req);
|
|
640
|
+
if (!companyId) {
|
|
641
|
+
throw unprocessable("Agent shortname lookup requires companyId query parameter");
|
|
642
|
+
}
|
|
643
|
+
const resolved = await svc.resolveByReference(companyId, raw);
|
|
644
|
+
if (resolved.ambiguous) {
|
|
645
|
+
throw conflict("Agent shortname is ambiguous in this company. Use the agent ID.");
|
|
646
|
+
}
|
|
647
|
+
if (!resolved.agent) {
|
|
648
|
+
throw notFound("Agent not found");
|
|
649
|
+
}
|
|
650
|
+
return resolved.agent.id;
|
|
651
|
+
}
|
|
652
|
+
function parseSourceIssueIds(input) {
|
|
653
|
+
const values = [];
|
|
654
|
+
if (Array.isArray(input.sourceIssueIds))
|
|
655
|
+
values.push(...input.sourceIssueIds);
|
|
656
|
+
if (typeof input.sourceIssueId === "string" && input.sourceIssueId.length > 0) {
|
|
657
|
+
values.push(input.sourceIssueId);
|
|
658
|
+
}
|
|
659
|
+
return Array.from(new Set(values));
|
|
660
|
+
}
|
|
661
|
+
function asRecord(value) {
|
|
662
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
663
|
+
return null;
|
|
664
|
+
return value;
|
|
665
|
+
}
|
|
666
|
+
function asNonEmptyString(value) {
|
|
667
|
+
if (typeof value !== "string")
|
|
668
|
+
return null;
|
|
669
|
+
const trimmed = value.trim();
|
|
670
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
671
|
+
}
|
|
672
|
+
function preserveInstructionsBundleConfig(existingAdapterConfig, nextAdapterConfig) {
|
|
673
|
+
const nextKeys = new Set(Object.keys(nextAdapterConfig));
|
|
674
|
+
if (KNOWN_INSTRUCTIONS_BUNDLE_KEYS.some((key) => nextKeys.has(key))) {
|
|
675
|
+
return nextAdapterConfig;
|
|
676
|
+
}
|
|
677
|
+
const merged = { ...nextAdapterConfig };
|
|
678
|
+
for (const key of KNOWN_INSTRUCTIONS_BUNDLE_KEYS) {
|
|
679
|
+
if (merged[key] === undefined && existingAdapterConfig[key] !== undefined) {
|
|
680
|
+
merged[key] = existingAdapterConfig[key];
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
return merged;
|
|
684
|
+
}
|
|
685
|
+
function parseBooleanLike(value) {
|
|
686
|
+
if (typeof value === "boolean")
|
|
687
|
+
return value;
|
|
688
|
+
if (typeof value === "number") {
|
|
689
|
+
if (value === 1)
|
|
690
|
+
return true;
|
|
691
|
+
if (value === 0)
|
|
692
|
+
return false;
|
|
693
|
+
return null;
|
|
694
|
+
}
|
|
695
|
+
if (typeof value !== "string")
|
|
696
|
+
return null;
|
|
697
|
+
const normalized = value.trim().toLowerCase();
|
|
698
|
+
if (normalized === "true" || normalized === "1" || normalized === "yes" || normalized === "on") {
|
|
699
|
+
return true;
|
|
700
|
+
}
|
|
701
|
+
if (normalized === "false" || normalized === "0" || normalized === "no" || normalized === "off") {
|
|
702
|
+
return false;
|
|
703
|
+
}
|
|
704
|
+
return null;
|
|
705
|
+
}
|
|
706
|
+
function parseNumberLike(value) {
|
|
707
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
708
|
+
return value;
|
|
709
|
+
if (typeof value !== "string")
|
|
710
|
+
return null;
|
|
711
|
+
const parsed = Number(value.trim());
|
|
712
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
713
|
+
}
|
|
714
|
+
function parseSchedulerHeartbeatPolicy(runtimeConfig) {
|
|
715
|
+
const heartbeat = asRecord(asRecord(runtimeConfig)?.heartbeat) ?? {};
|
|
716
|
+
return {
|
|
717
|
+
enabled: parseBooleanLike(heartbeat.enabled) ?? false,
|
|
718
|
+
intervalSec: Math.max(0, parseNumberLike(heartbeat.intervalSec) ?? 0),
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
function normalizeNewAgentRuntimeConfig(runtimeConfig) {
|
|
722
|
+
const parsedRuntimeConfig = asRecord(runtimeConfig);
|
|
723
|
+
const normalizedRuntimeConfig = parsedRuntimeConfig ? { ...parsedRuntimeConfig } : {};
|
|
724
|
+
const parsedHeartbeat = asRecord(normalizedRuntimeConfig.heartbeat);
|
|
725
|
+
const heartbeat = parsedHeartbeat ? { ...parsedHeartbeat } : {};
|
|
726
|
+
if (parseBooleanLike(heartbeat.enabled) == null) {
|
|
727
|
+
heartbeat.enabled = false;
|
|
728
|
+
}
|
|
729
|
+
if (parseNumberLike(heartbeat.maxConcurrentRuns) == null) {
|
|
730
|
+
heartbeat.maxConcurrentRuns = AGENT_DEFAULT_MAX_CONCURRENT_RUNS;
|
|
731
|
+
}
|
|
732
|
+
normalizedRuntimeConfig.heartbeat = heartbeat;
|
|
733
|
+
return normalizedRuntimeConfig;
|
|
734
|
+
}
|
|
735
|
+
function listRuntimeModelProfileAdapterConfigs(runtimeConfig) {
|
|
736
|
+
const runtimeRecord = asRecord(runtimeConfig);
|
|
737
|
+
const modelProfiles = asRecord(runtimeRecord?.modelProfiles);
|
|
738
|
+
if (!modelProfiles)
|
|
739
|
+
return [];
|
|
740
|
+
const entries = [];
|
|
741
|
+
for (const [profileKey, rawProfile] of Object.entries(modelProfiles)) {
|
|
742
|
+
const profile = asRecord(rawProfile);
|
|
743
|
+
const adapterConfig = asRecord(profile?.adapterConfig);
|
|
744
|
+
if (!profile || !adapterConfig)
|
|
745
|
+
continue;
|
|
746
|
+
entries.push({
|
|
747
|
+
profileKey,
|
|
748
|
+
profile,
|
|
749
|
+
adapterConfig,
|
|
750
|
+
path: `runtimeConfig.modelProfiles.${profileKey}.adapterConfig`,
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
return entries;
|
|
754
|
+
}
|
|
755
|
+
function assertNoAgentRuntimeConfigAdapterConfigMutation(req, runtimeConfig) {
|
|
756
|
+
for (const entry of listRuntimeModelProfileAdapterConfigs(runtimeConfig)) {
|
|
757
|
+
assertNoAgentAdapterConfigMutation(req, entry.adapterConfig, entry.path);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
async function normalizeMediatedAdapterConfigForPersistence(input) {
|
|
761
|
+
const normalizedAdapterConfig = await secretsSvc.normalizeAdapterConfigForPersistence(input.companyId, input.adapterConfig, { strictMode: strictSecretsMode });
|
|
762
|
+
await assertAdapterConfigConstraints(input.adapterType, input.constraintAdapterConfig
|
|
763
|
+
? { ...input.constraintAdapterConfig, ...normalizedAdapterConfig }
|
|
764
|
+
: normalizedAdapterConfig);
|
|
765
|
+
return normalizedAdapterConfig;
|
|
766
|
+
}
|
|
767
|
+
async function normalizeRuntimeConfigAdapterConfigsForPersistence(companyId, adapterType, runtimeConfig, baseAdapterConfig) {
|
|
768
|
+
const entries = listRuntimeModelProfileAdapterConfigs(runtimeConfig);
|
|
769
|
+
if (entries.length === 0)
|
|
770
|
+
return runtimeConfig;
|
|
771
|
+
const adapterModelProfiles = await listAdapterModelProfiles(adapterType);
|
|
772
|
+
const normalizedRuntimeConfig = { ...runtimeConfig };
|
|
773
|
+
const modelProfiles = asRecord(runtimeConfig.modelProfiles) ?? {};
|
|
774
|
+
const normalizedModelProfiles = { ...modelProfiles };
|
|
775
|
+
normalizedRuntimeConfig.modelProfiles = normalizedModelProfiles;
|
|
776
|
+
for (const entry of entries) {
|
|
777
|
+
const adapterProfile = adapterModelProfiles.find((profile) => profile.key === entry.profileKey);
|
|
778
|
+
const adapterDefaultConfig = asRecord(adapterProfile?.adapterConfig) ?? {};
|
|
779
|
+
const normalizedAdapterConfig = await normalizeMediatedAdapterConfigForPersistence({
|
|
780
|
+
companyId,
|
|
781
|
+
adapterType,
|
|
782
|
+
adapterConfig: entry.adapterConfig,
|
|
783
|
+
constraintAdapterConfig: {
|
|
784
|
+
...baseAdapterConfig,
|
|
785
|
+
...adapterDefaultConfig,
|
|
786
|
+
},
|
|
787
|
+
});
|
|
788
|
+
normalizedModelProfiles[entry.profileKey] = {
|
|
789
|
+
...entry.profile,
|
|
790
|
+
adapterConfig: normalizedAdapterConfig,
|
|
791
|
+
};
|
|
792
|
+
}
|
|
793
|
+
return normalizedRuntimeConfig;
|
|
794
|
+
}
|
|
795
|
+
function generateEd25519PrivateKeyPem() {
|
|
796
|
+
const { privateKey } = generateKeyPairSync("ed25519");
|
|
797
|
+
return privateKey.export({ type: "pkcs8", format: "pem" }).toString();
|
|
798
|
+
}
|
|
799
|
+
function ensureGatewayDeviceKey(adapterType, adapterConfig) {
|
|
800
|
+
if (adapterType !== "openclaw_gateway")
|
|
801
|
+
return adapterConfig;
|
|
802
|
+
const disableDeviceAuth = parseBooleanLike(adapterConfig.disableDeviceAuth) === true;
|
|
803
|
+
if (disableDeviceAuth)
|
|
804
|
+
return adapterConfig;
|
|
805
|
+
if (asNonEmptyString(adapterConfig.devicePrivateKeyPem))
|
|
806
|
+
return adapterConfig;
|
|
807
|
+
return { ...adapterConfig, devicePrivateKeyPem: generateEd25519PrivateKeyPem() };
|
|
808
|
+
}
|
|
809
|
+
function applyCreateDefaultsByAdapterType(adapterType, adapterConfig) {
|
|
810
|
+
const next = { ...adapterConfig };
|
|
811
|
+
if (adapterType === "acpx_local") {
|
|
812
|
+
if (!asNonEmptyString(next.agent)) {
|
|
813
|
+
next.agent = DEFAULT_ACPX_LOCAL_AGENT;
|
|
814
|
+
}
|
|
815
|
+
if (!asNonEmptyString(next.mode)) {
|
|
816
|
+
next.mode = DEFAULT_ACPX_LOCAL_MODE;
|
|
817
|
+
}
|
|
818
|
+
if (!asNonEmptyString(next.permissionMode)) {
|
|
819
|
+
next.permissionMode = DEFAULT_ACPX_LOCAL_PERMISSION_MODE;
|
|
820
|
+
}
|
|
821
|
+
if (!asNonEmptyString(next.nonInteractivePermissions)) {
|
|
822
|
+
next.nonInteractivePermissions = DEFAULT_ACPX_LOCAL_NON_INTERACTIVE_PERMISSIONS;
|
|
823
|
+
}
|
|
824
|
+
return ensureGatewayDeviceKey(adapterType, next);
|
|
825
|
+
}
|
|
826
|
+
if (adapterType === "codex_local") {
|
|
827
|
+
if (!asNonEmptyString(next.model)) {
|
|
828
|
+
next.model = DEFAULT_CODEX_LOCAL_MODEL;
|
|
829
|
+
}
|
|
830
|
+
const hasBypassFlag = typeof next.dangerouslyBypassApprovalsAndSandbox === "boolean" ||
|
|
831
|
+
typeof next.dangerouslyBypassSandbox === "boolean";
|
|
832
|
+
if (!hasBypassFlag) {
|
|
833
|
+
next.dangerouslyBypassApprovalsAndSandbox = DEFAULT_CODEX_LOCAL_BYPASS_APPROVALS_AND_SANDBOX;
|
|
834
|
+
}
|
|
835
|
+
return ensureGatewayDeviceKey(adapterType, next);
|
|
836
|
+
}
|
|
837
|
+
if (adapterType === "gemini_local" && !asNonEmptyString(next.model)) {
|
|
838
|
+
next.model = DEFAULT_GEMINI_LOCAL_MODEL;
|
|
839
|
+
return ensureGatewayDeviceKey(adapterType, next);
|
|
840
|
+
}
|
|
841
|
+
if (adapterType === "opencode_local" && !asNonEmptyString(next.model)) {
|
|
842
|
+
next.model = DEFAULT_OPENCODE_LOCAL_MODEL;
|
|
843
|
+
return ensureGatewayDeviceKey(adapterType, next);
|
|
844
|
+
}
|
|
845
|
+
if (adapterType === "cursor" && !asNonEmptyString(next.model)) {
|
|
846
|
+
next.model = DEFAULT_CURSOR_LOCAL_MODEL;
|
|
847
|
+
}
|
|
848
|
+
return ensureGatewayDeviceKey(adapterType, next);
|
|
849
|
+
}
|
|
850
|
+
async function assertAdapterConfigConstraints(adapterType, adapterConfig) {
|
|
851
|
+
if (adapterType !== "opencode_local")
|
|
852
|
+
return;
|
|
853
|
+
try {
|
|
854
|
+
requireOpenCodeModelId(adapterConfig.model);
|
|
855
|
+
}
|
|
856
|
+
catch (err) {
|
|
857
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
858
|
+
throw unprocessable(`Invalid opencode_local adapterConfig: ${reason}`);
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
function resolveInstructionsFilePath(candidatePath, adapterConfig) {
|
|
862
|
+
const trimmed = candidatePath.trim();
|
|
863
|
+
if (path.isAbsolute(trimmed))
|
|
864
|
+
return trimmed;
|
|
865
|
+
const cwd = asNonEmptyString(adapterConfig.cwd);
|
|
866
|
+
if (!cwd) {
|
|
867
|
+
throw unprocessable("Relative instructions path requires adapterConfig.cwd to be set to an absolute path");
|
|
868
|
+
}
|
|
869
|
+
if (!path.isAbsolute(cwd)) {
|
|
870
|
+
throw unprocessable("adapterConfig.cwd must be an absolute path to resolve relative instructions path");
|
|
871
|
+
}
|
|
872
|
+
return path.resolve(cwd, trimmed);
|
|
873
|
+
}
|
|
874
|
+
async function materializeDefaultInstructionsBundleForNewAgent(agent, input) {
|
|
875
|
+
if (!adapterSupportsInstructionsBundle(agent.adapterType)) {
|
|
876
|
+
return agent;
|
|
877
|
+
}
|
|
878
|
+
const adapterConfig = asRecord(agent.adapterConfig) ?? {};
|
|
879
|
+
const hasExplicitInstructionsBundle = Boolean(asNonEmptyString(adapterConfig.instructionsBundleMode))
|
|
880
|
+
|| Boolean(asNonEmptyString(adapterConfig.instructionsRootPath))
|
|
881
|
+
|| Boolean(asNonEmptyString(adapterConfig.instructionsEntryFile))
|
|
882
|
+
|| Boolean(asNonEmptyString(adapterConfig.instructionsFilePath))
|
|
883
|
+
|| Boolean(asNonEmptyString(adapterConfig.agentsMdPath));
|
|
884
|
+
if (hasExplicitInstructionsBundle) {
|
|
885
|
+
const nextAdapterConfig = { ...adapterConfig };
|
|
886
|
+
const hadLegacyPrompt = Object.prototype.hasOwnProperty.call(nextAdapterConfig, "promptTemplate")
|
|
887
|
+
|| Object.prototype.hasOwnProperty.call(nextAdapterConfig, "bootstrapPromptTemplate");
|
|
888
|
+
delete nextAdapterConfig.promptTemplate;
|
|
889
|
+
delete nextAdapterConfig.bootstrapPromptTemplate;
|
|
890
|
+
if (!hadLegacyPrompt)
|
|
891
|
+
return agent;
|
|
892
|
+
const updated = await svc.update(agent.id, { adapterConfig: nextAdapterConfig });
|
|
893
|
+
return updated ?? { ...agent, adapterConfig: nextAdapterConfig };
|
|
894
|
+
}
|
|
895
|
+
const files = input?.files
|
|
896
|
+
?? await loadDefaultAgentInstructionsBundle(resolveDefaultAgentInstructionsBundleRole(agent.role));
|
|
897
|
+
const materialized = await instructions.materializeManagedBundle(agent, files, { entryFile: input?.entryFile ?? "AGENTS.md", replaceExisting: false });
|
|
898
|
+
const nextAdapterConfig = { ...materialized.adapterConfig };
|
|
899
|
+
delete nextAdapterConfig.promptTemplate;
|
|
900
|
+
delete nextAdapterConfig.bootstrapPromptTemplate;
|
|
901
|
+
const updated = await svc.update(agent.id, { adapterConfig: nextAdapterConfig });
|
|
902
|
+
return updated ?? { ...agent, adapterConfig: nextAdapterConfig };
|
|
903
|
+
}
|
|
904
|
+
function assertNoNewAgentLegacyPromptTemplate(adapterType, adapterConfig) {
|
|
905
|
+
if (!adapterSupportsInstructionsBundle(adapterType))
|
|
906
|
+
return;
|
|
907
|
+
if (Object.prototype.hasOwnProperty.call(adapterConfig, "promptTemplate")
|
|
908
|
+
|| Object.prototype.hasOwnProperty.call(adapterConfig, "bootstrapPromptTemplate")) {
|
|
909
|
+
throw unprocessable("New agents must use instructionsBundle/AGENTS.md instead of adapterConfig.promptTemplate or bootstrapPromptTemplate");
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
async function assertCanManageInstructionsPath(req, targetAgent) {
|
|
913
|
+
assertCompanyAccess(req, targetAgent.companyId);
|
|
914
|
+
if (req.actor.type !== "board") {
|
|
915
|
+
throw forbidden("Only board-authenticated callers can manage instructions path or bundle configuration");
|
|
916
|
+
}
|
|
917
|
+
await assertBoardCanManageAgentsForCompany(req, targetAgent.companyId);
|
|
918
|
+
}
|
|
919
|
+
function assertNoAgentInstructionsConfigMutation(req, adapterConfig, path = "adapterConfig") {
|
|
920
|
+
if (req.actor.type !== "agent" || !adapterConfig)
|
|
921
|
+
return;
|
|
922
|
+
const changedSensitiveKeys = KNOWN_INSTRUCTIONS_BUNDLE_KEYS
|
|
923
|
+
.filter((key) => adapterConfig[key] !== undefined)
|
|
924
|
+
.map((key) => `${path}.${key}`);
|
|
925
|
+
if (changedSensitiveKeys.length === 0)
|
|
926
|
+
return;
|
|
927
|
+
throw forbidden(`Agent-authenticated callers cannot modify instructions path or bundle configuration (${changedSensitiveKeys.join(", ")})`);
|
|
928
|
+
}
|
|
929
|
+
function adapterConfigTouchesInstructionsConfig(adapterConfig) {
|
|
930
|
+
return KNOWN_INSTRUCTIONS_BUNDLE_KEYS.some((key) => adapterConfig[key] !== undefined);
|
|
931
|
+
}
|
|
932
|
+
function assertNoAgentAdapterConfigMutation(req, adapterConfig, path = "adapterConfig") {
|
|
933
|
+
assertNoAgentInstructionsConfigMutation(req, adapterConfig, path);
|
|
934
|
+
assertNoAgentHostWorkspaceCommandMutation(req, collectAgentAdapterWorkspaceCommandPaths(adapterConfig, path));
|
|
935
|
+
}
|
|
936
|
+
function summarizeAgentUpdateDetails(patch) {
|
|
937
|
+
const changedTopLevelKeys = Object.keys(patch).sort();
|
|
938
|
+
const details = { changedTopLevelKeys };
|
|
939
|
+
const adapterConfigPatch = asRecord(patch.adapterConfig);
|
|
940
|
+
if (adapterConfigPatch) {
|
|
941
|
+
details.changedAdapterConfigKeys = Object.keys(adapterConfigPatch).sort();
|
|
942
|
+
}
|
|
943
|
+
const runtimeConfigPatch = asRecord(patch.runtimeConfig);
|
|
944
|
+
if (runtimeConfigPatch) {
|
|
945
|
+
details.changedRuntimeConfigKeys = Object.keys(runtimeConfigPatch).sort();
|
|
946
|
+
}
|
|
947
|
+
return details;
|
|
948
|
+
}
|
|
949
|
+
function buildUnsupportedSkillSnapshot(adapterType, desiredSkills = []) {
|
|
950
|
+
return {
|
|
951
|
+
adapterType,
|
|
952
|
+
supported: false,
|
|
953
|
+
mode: "unsupported",
|
|
954
|
+
desiredSkills,
|
|
955
|
+
entries: [],
|
|
956
|
+
warnings: ["This adapter does not implement skill sync yet."],
|
|
957
|
+
};
|
|
958
|
+
}
|
|
959
|
+
// Legacy hardcoded set — used as fallback when adapter module does not
|
|
960
|
+
// declare requiresMaterializedRuntimeSkills explicitly.
|
|
961
|
+
const LEGACY_MATERIALIZED_SKILLS_SET = new Set([
|
|
962
|
+
"cursor",
|
|
963
|
+
"gemini_local",
|
|
964
|
+
"opencode_local",
|
|
965
|
+
"pi_local",
|
|
966
|
+
]);
|
|
967
|
+
function shouldMaterializeRuntimeSkillsForAdapter(adapterType) {
|
|
968
|
+
const adapter = findActiveServerAdapter(adapterType);
|
|
969
|
+
if (adapter?.requiresMaterializedRuntimeSkills !== undefined) {
|
|
970
|
+
return adapter.requiresMaterializedRuntimeSkills;
|
|
971
|
+
}
|
|
972
|
+
return LEGACY_MATERIALIZED_SKILLS_SET.has(adapterType);
|
|
973
|
+
}
|
|
974
|
+
async function buildRuntimeSkillConfig(companyId, adapterType, config) {
|
|
975
|
+
const runtimeSkillEntries = await companySkills.listRuntimeSkillEntries(companyId, {
|
|
976
|
+
materializeMissing: shouldMaterializeRuntimeSkillsForAdapter(adapterType),
|
|
977
|
+
});
|
|
978
|
+
return {
|
|
979
|
+
...config,
|
|
980
|
+
evermoreRuntimeSkills: runtimeSkillEntries,
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
async function resolveDesiredSkillAssignment(companyId, adapterType, adapterConfig, requestedDesiredSkills) {
|
|
984
|
+
if (!requestedDesiredSkills) {
|
|
985
|
+
return {
|
|
986
|
+
adapterConfig,
|
|
987
|
+
desiredSkills: null,
|
|
988
|
+
runtimeSkillEntries: null,
|
|
989
|
+
};
|
|
990
|
+
}
|
|
991
|
+
const resolvedRequestedSkills = await companySkills.resolveRequestedSkillKeys(companyId, requestedDesiredSkills);
|
|
992
|
+
const runtimeSkillEntries = await companySkills.listRuntimeSkillEntries(companyId, {
|
|
993
|
+
materializeMissing: shouldMaterializeRuntimeSkillsForAdapter(adapterType),
|
|
994
|
+
});
|
|
995
|
+
const requiredSkills = runtimeSkillEntries
|
|
996
|
+
.filter((entry) => entry.required)
|
|
997
|
+
.map((entry) => entry.key);
|
|
998
|
+
const desiredSkills = Array.from(new Set([...requiredSkills, ...resolvedRequestedSkills]));
|
|
999
|
+
return {
|
|
1000
|
+
adapterConfig: writeEvermoreSkillSyncPreference(adapterConfig, desiredSkills),
|
|
1001
|
+
desiredSkills,
|
|
1002
|
+
runtimeSkillEntries,
|
|
1003
|
+
};
|
|
1004
|
+
}
|
|
1005
|
+
function redactForRestrictedAgentView(agent) {
|
|
1006
|
+
if (!agent)
|
|
1007
|
+
return null;
|
|
1008
|
+
return {
|
|
1009
|
+
...agent,
|
|
1010
|
+
adapterConfig: {},
|
|
1011
|
+
runtimeConfig: {},
|
|
1012
|
+
};
|
|
1013
|
+
}
|
|
1014
|
+
function redactAgentConfiguration(agent) {
|
|
1015
|
+
if (!agent)
|
|
1016
|
+
return null;
|
|
1017
|
+
return {
|
|
1018
|
+
id: agent.id,
|
|
1019
|
+
companyId: agent.companyId,
|
|
1020
|
+
name: agent.name,
|
|
1021
|
+
role: agent.role,
|
|
1022
|
+
title: agent.title,
|
|
1023
|
+
status: agent.status,
|
|
1024
|
+
reportsTo: agent.reportsTo,
|
|
1025
|
+
adapterType: agent.adapterType,
|
|
1026
|
+
adapterConfig: redactEventPayload(agent.adapterConfig),
|
|
1027
|
+
runtimeConfig: redactEventPayload(agent.runtimeConfig),
|
|
1028
|
+
permissions: agent.permissions,
|
|
1029
|
+
updatedAt: agent.updatedAt,
|
|
1030
|
+
};
|
|
1031
|
+
}
|
|
1032
|
+
function redactRevisionSnapshot(snapshot) {
|
|
1033
|
+
if (!snapshot || typeof snapshot !== "object" || Array.isArray(snapshot))
|
|
1034
|
+
return {};
|
|
1035
|
+
const record = snapshot;
|
|
1036
|
+
return {
|
|
1037
|
+
...record,
|
|
1038
|
+
adapterConfig: redactEventPayload(typeof record.adapterConfig === "object" && record.adapterConfig !== null
|
|
1039
|
+
? record.adapterConfig
|
|
1040
|
+
: {}),
|
|
1041
|
+
runtimeConfig: redactEventPayload(typeof record.runtimeConfig === "object" && record.runtimeConfig !== null
|
|
1042
|
+
? record.runtimeConfig
|
|
1043
|
+
: {}),
|
|
1044
|
+
metadata: typeof record.metadata === "object" && record.metadata !== null
|
|
1045
|
+
? redactEventPayload(record.metadata)
|
|
1046
|
+
: record.metadata ?? null,
|
|
1047
|
+
};
|
|
1048
|
+
}
|
|
1049
|
+
function redactConfigRevision(revision) {
|
|
1050
|
+
return {
|
|
1051
|
+
...revision,
|
|
1052
|
+
beforeConfig: redactRevisionSnapshot(revision.beforeConfig),
|
|
1053
|
+
afterConfig: redactRevisionSnapshot(revision.afterConfig),
|
|
1054
|
+
};
|
|
1055
|
+
}
|
|
1056
|
+
function toLeanOrgNode(node) {
|
|
1057
|
+
const reports = Array.isArray(node.reports)
|
|
1058
|
+
? node.reports.map((report) => toLeanOrgNode(report))
|
|
1059
|
+
: [];
|
|
1060
|
+
return {
|
|
1061
|
+
id: String(node.id),
|
|
1062
|
+
name: String(node.name),
|
|
1063
|
+
role: String(node.role),
|
|
1064
|
+
status: String(node.status),
|
|
1065
|
+
reports,
|
|
1066
|
+
};
|
|
1067
|
+
}
|
|
1068
|
+
router.param("id", async (req, _res, next, rawId) => {
|
|
1069
|
+
try {
|
|
1070
|
+
req.params.id = await normalizeAgentReference(req, String(rawId));
|
|
1071
|
+
next();
|
|
1072
|
+
}
|
|
1073
|
+
catch (err) {
|
|
1074
|
+
next(err);
|
|
1075
|
+
}
|
|
1076
|
+
});
|
|
1077
|
+
router.get("/companies/:companyId/adapters/:type/models", async (req, res) => {
|
|
1078
|
+
const companyId = req.params.companyId;
|
|
1079
|
+
assertCompanyAccess(req, companyId);
|
|
1080
|
+
const type = assertKnownAdapterType(req.params.type);
|
|
1081
|
+
const refresh = typeof req.query.refresh === "string"
|
|
1082
|
+
? ["1", "true", "yes"].includes(req.query.refresh.toLowerCase())
|
|
1083
|
+
: false;
|
|
1084
|
+
const environmentId = asNonEmptyString(req.query.environmentId);
|
|
1085
|
+
const environment = environmentId ? await environmentsSvc.getById(environmentId) : null;
|
|
1086
|
+
if (environmentId && (!environment || environment.companyId !== companyId)) {
|
|
1087
|
+
res.status(404).json({ error: "Environment not found" });
|
|
1088
|
+
return;
|
|
1089
|
+
}
|
|
1090
|
+
if (type === "opencode_local" && environment && environment.driver !== "local") {
|
|
1091
|
+
const adapter = requireServerAdapter(type);
|
|
1092
|
+
res.json(adapter.models ?? []);
|
|
1093
|
+
return;
|
|
1094
|
+
}
|
|
1095
|
+
const models = refresh
|
|
1096
|
+
? await refreshAdapterModels(type)
|
|
1097
|
+
: await listAdapterModels(type);
|
|
1098
|
+
res.json(models);
|
|
1099
|
+
});
|
|
1100
|
+
router.get("/companies/:companyId/adapters/:type/model-profiles", async (req, res) => {
|
|
1101
|
+
const companyId = req.params.companyId;
|
|
1102
|
+
assertCompanyAccess(req, companyId);
|
|
1103
|
+
const type = assertKnownAdapterType(req.params.type);
|
|
1104
|
+
const profiles = await listAdapterModelProfiles(type);
|
|
1105
|
+
res.json(profiles);
|
|
1106
|
+
});
|
|
1107
|
+
router.get("/companies/:companyId/adapters/:type/detect-model", async (req, res) => {
|
|
1108
|
+
const companyId = req.params.companyId;
|
|
1109
|
+
assertCompanyAccess(req, companyId);
|
|
1110
|
+
const type = assertKnownAdapterType(req.params.type);
|
|
1111
|
+
const detected = await detectAdapterModel(type);
|
|
1112
|
+
res.json(detected);
|
|
1113
|
+
});
|
|
1114
|
+
router.post("/companies/:companyId/adapters/:type/test-environment", validate(testAdapterEnvironmentSchema), async (req, res) => {
|
|
1115
|
+
const companyId = req.params.companyId;
|
|
1116
|
+
const type = assertKnownAdapterType(req.params.type);
|
|
1117
|
+
await assertCanReadConfigurations(req, companyId);
|
|
1118
|
+
const adapter = requireServerAdapter(type);
|
|
1119
|
+
const inputAdapterConfig = (req.body?.adapterConfig ?? {});
|
|
1120
|
+
const requestedEnvironmentId = typeof req.body?.environmentId === "string" && req.body.environmentId.trim().length > 0
|
|
1121
|
+
? req.body.environmentId
|
|
1122
|
+
: null;
|
|
1123
|
+
const normalizedAdapterConfig = await secretsSvc.normalizeAdapterConfigForPersistence(companyId, inputAdapterConfig, { strictMode: strictSecretsMode });
|
|
1124
|
+
const { config: runtimeAdapterConfig } = await secretsSvc.resolveAdapterConfigForRuntime(companyId, normalizedAdapterConfig);
|
|
1125
|
+
const { executionTarget, environmentName, fallbackChecks, release } = await resolveAdapterTestExecutionContext({
|
|
1126
|
+
companyId,
|
|
1127
|
+
adapterType: type,
|
|
1128
|
+
environmentId: requestedEnvironmentId,
|
|
1129
|
+
});
|
|
1130
|
+
let releaseStatus = "released";
|
|
1131
|
+
try {
|
|
1132
|
+
// If the caller explicitly selected an environment, never fall back to
|
|
1133
|
+
// probing the host when we couldn't resolve that environment's
|
|
1134
|
+
// execution target. Surface the diagnostic checks instead.
|
|
1135
|
+
if (requestedEnvironmentId && !executionTarget && fallbackChecks.length > 0) {
|
|
1136
|
+
const status = fallbackChecks.some((c) => c.level === "error")
|
|
1137
|
+
? "fail"
|
|
1138
|
+
: fallbackChecks.some((c) => c.level === "warn")
|
|
1139
|
+
? "warn"
|
|
1140
|
+
: "pass";
|
|
1141
|
+
if (status === "fail")
|
|
1142
|
+
releaseStatus = "failed";
|
|
1143
|
+
const synthesized = {
|
|
1144
|
+
adapterType: type,
|
|
1145
|
+
status,
|
|
1146
|
+
checks: fallbackChecks,
|
|
1147
|
+
testedAt: new Date().toISOString(),
|
|
1148
|
+
};
|
|
1149
|
+
res.json(synthesized);
|
|
1150
|
+
return;
|
|
1151
|
+
}
|
|
1152
|
+
const result = await adapter.testEnvironment({
|
|
1153
|
+
companyId,
|
|
1154
|
+
adapterType: type,
|
|
1155
|
+
config: runtimeAdapterConfig,
|
|
1156
|
+
executionTarget,
|
|
1157
|
+
environmentName,
|
|
1158
|
+
});
|
|
1159
|
+
if (result.status === "fail")
|
|
1160
|
+
releaseStatus = "failed";
|
|
1161
|
+
res.json(result);
|
|
1162
|
+
}
|
|
1163
|
+
catch (err) {
|
|
1164
|
+
releaseStatus = "failed";
|
|
1165
|
+
throw err;
|
|
1166
|
+
}
|
|
1167
|
+
finally {
|
|
1168
|
+
await release(releaseStatus);
|
|
1169
|
+
}
|
|
1170
|
+
});
|
|
1171
|
+
router.get("/agents/:id/skills", async (req, res) => {
|
|
1172
|
+
const id = req.params.id;
|
|
1173
|
+
const agent = await svc.getById(id);
|
|
1174
|
+
if (!agent) {
|
|
1175
|
+
res.status(404).json({ error: "Agent not found" });
|
|
1176
|
+
return;
|
|
1177
|
+
}
|
|
1178
|
+
await assertCanReadConfigurations(req, agent.companyId);
|
|
1179
|
+
const adapter = findActiveServerAdapter(agent.adapterType);
|
|
1180
|
+
if (!adapter?.listSkills) {
|
|
1181
|
+
const preference = readEvermoreSkillSyncPreference(agent.adapterConfig);
|
|
1182
|
+
const runtimeSkillEntries = await companySkills.listRuntimeSkillEntries(agent.companyId, {
|
|
1183
|
+
materializeMissing: false,
|
|
1184
|
+
});
|
|
1185
|
+
const requiredSkills = runtimeSkillEntries.filter((entry) => entry.required).map((entry) => entry.key);
|
|
1186
|
+
res.json(buildUnsupportedSkillSnapshot(agent.adapterType, Array.from(new Set([...requiredSkills, ...preference.desiredSkills]))));
|
|
1187
|
+
return;
|
|
1188
|
+
}
|
|
1189
|
+
const { config: runtimeConfig } = await secretsSvc.resolveAdapterConfigForRuntime(agent.companyId, agent.adapterConfig);
|
|
1190
|
+
const runtimeSkillConfig = await buildRuntimeSkillConfig(agent.companyId, agent.adapterType, runtimeConfig);
|
|
1191
|
+
const snapshot = await adapter.listSkills({
|
|
1192
|
+
agentId: agent.id,
|
|
1193
|
+
companyId: agent.companyId,
|
|
1194
|
+
adapterType: agent.adapterType,
|
|
1195
|
+
config: runtimeSkillConfig,
|
|
1196
|
+
});
|
|
1197
|
+
res.json(snapshot);
|
|
1198
|
+
});
|
|
1199
|
+
router.post("/agents/:id/skills/sync", validate(agentSkillSyncSchema), async (req, res) => {
|
|
1200
|
+
const id = req.params.id;
|
|
1201
|
+
const agent = await svc.getById(id);
|
|
1202
|
+
if (!agent) {
|
|
1203
|
+
res.status(404).json({ error: "Agent not found" });
|
|
1204
|
+
return;
|
|
1205
|
+
}
|
|
1206
|
+
await assertCanUpdateAgent(req, agent);
|
|
1207
|
+
const requestedSkills = Array.from(new Set(req.body.desiredSkills
|
|
1208
|
+
.map((value) => value.trim())
|
|
1209
|
+
.filter(Boolean)));
|
|
1210
|
+
const { adapterConfig: nextAdapterConfig, desiredSkills, runtimeSkillEntries, } = await resolveDesiredSkillAssignment(agent.companyId, agent.adapterType, agent.adapterConfig, requestedSkills);
|
|
1211
|
+
if (!desiredSkills || !runtimeSkillEntries) {
|
|
1212
|
+
throw unprocessable("Skill sync requires desiredSkills.");
|
|
1213
|
+
}
|
|
1214
|
+
const actor = getActorInfo(req);
|
|
1215
|
+
const updated = await svc.update(agent.id, {
|
|
1216
|
+
adapterConfig: nextAdapterConfig,
|
|
1217
|
+
}, {
|
|
1218
|
+
recordRevision: {
|
|
1219
|
+
createdByAgentId: actor.agentId,
|
|
1220
|
+
createdByUserId: actor.actorType === "user" ? actor.actorId : null,
|
|
1221
|
+
source: "skill-sync",
|
|
1222
|
+
},
|
|
1223
|
+
});
|
|
1224
|
+
if (!updated) {
|
|
1225
|
+
res.status(404).json({ error: "Agent not found" });
|
|
1226
|
+
return;
|
|
1227
|
+
}
|
|
1228
|
+
const adapter = findActiveServerAdapter(updated.adapterType);
|
|
1229
|
+
const { config: runtimeConfig } = await secretsSvc.resolveAdapterConfigForRuntime(updated.companyId, updated.adapterConfig);
|
|
1230
|
+
const runtimeSkillConfig = {
|
|
1231
|
+
...runtimeConfig,
|
|
1232
|
+
evermoreRuntimeSkills: runtimeSkillEntries,
|
|
1233
|
+
};
|
|
1234
|
+
const snapshot = adapter?.syncSkills
|
|
1235
|
+
? await adapter.syncSkills({
|
|
1236
|
+
agentId: updated.id,
|
|
1237
|
+
companyId: updated.companyId,
|
|
1238
|
+
adapterType: updated.adapterType,
|
|
1239
|
+
config: runtimeSkillConfig,
|
|
1240
|
+
}, desiredSkills)
|
|
1241
|
+
: adapter?.listSkills
|
|
1242
|
+
? await adapter.listSkills({
|
|
1243
|
+
agentId: updated.id,
|
|
1244
|
+
companyId: updated.companyId,
|
|
1245
|
+
adapterType: updated.adapterType,
|
|
1246
|
+
config: runtimeSkillConfig,
|
|
1247
|
+
})
|
|
1248
|
+
: buildUnsupportedSkillSnapshot(updated.adapterType, desiredSkills);
|
|
1249
|
+
await logActivity(db, {
|
|
1250
|
+
companyId: updated.companyId,
|
|
1251
|
+
actorType: actor.actorType,
|
|
1252
|
+
actorId: actor.actorId,
|
|
1253
|
+
action: "agent.skills_synced",
|
|
1254
|
+
entityType: "agent",
|
|
1255
|
+
entityId: updated.id,
|
|
1256
|
+
agentId: actor.agentId,
|
|
1257
|
+
runId: actor.runId,
|
|
1258
|
+
details: {
|
|
1259
|
+
adapterType: updated.adapterType,
|
|
1260
|
+
desiredSkills,
|
|
1261
|
+
mode: snapshot.mode,
|
|
1262
|
+
supported: snapshot.supported,
|
|
1263
|
+
entryCount: snapshot.entries.length,
|
|
1264
|
+
warningCount: snapshot.warnings.length,
|
|
1265
|
+
},
|
|
1266
|
+
});
|
|
1267
|
+
res.json(snapshot);
|
|
1268
|
+
});
|
|
1269
|
+
router.get("/companies/:companyId/agents", async (req, res) => {
|
|
1270
|
+
const companyId = req.params.companyId;
|
|
1271
|
+
assertCompanyAccess(req, companyId);
|
|
1272
|
+
const unsupportedQueryParams = Object.keys(req.query).sort();
|
|
1273
|
+
if (unsupportedQueryParams.length > 0) {
|
|
1274
|
+
res.status(400).json({
|
|
1275
|
+
error: `Unsupported query parameter${unsupportedQueryParams.length === 1 ? "" : "s"}: ${unsupportedQueryParams.join(", ")}`,
|
|
1276
|
+
});
|
|
1277
|
+
return;
|
|
1278
|
+
}
|
|
1279
|
+
const result = await svc.list(companyId);
|
|
1280
|
+
const canReadConfigs = await actorCanReadConfigurationsForCompany(req, companyId);
|
|
1281
|
+
if (canReadConfigs) {
|
|
1282
|
+
res.json(result);
|
|
1283
|
+
return;
|
|
1284
|
+
}
|
|
1285
|
+
res.json(result.map((agent) => redactForRestrictedAgentView(agent)));
|
|
1286
|
+
});
|
|
1287
|
+
router.get("/instance/scheduler-heartbeats", async (req, res) => {
|
|
1288
|
+
assertInstanceAdmin(req);
|
|
1289
|
+
const rows = await db
|
|
1290
|
+
.select({
|
|
1291
|
+
id: agentsTable.id,
|
|
1292
|
+
companyId: agentsTable.companyId,
|
|
1293
|
+
agentName: agentsTable.name,
|
|
1294
|
+
role: agentsTable.role,
|
|
1295
|
+
title: agentsTable.title,
|
|
1296
|
+
status: agentsTable.status,
|
|
1297
|
+
adapterType: agentsTable.adapterType,
|
|
1298
|
+
runtimeConfig: agentsTable.runtimeConfig,
|
|
1299
|
+
lastHeartbeatAt: agentsTable.lastHeartbeatAt,
|
|
1300
|
+
companyName: companies.name,
|
|
1301
|
+
companyIssuePrefix: companies.issuePrefix,
|
|
1302
|
+
})
|
|
1303
|
+
.from(agentsTable)
|
|
1304
|
+
.innerJoin(companies, eq(agentsTable.companyId, companies.id))
|
|
1305
|
+
.orderBy(companies.name, agentsTable.name);
|
|
1306
|
+
const items = rows
|
|
1307
|
+
.map((row) => {
|
|
1308
|
+
const policy = parseSchedulerHeartbeatPolicy(row.runtimeConfig);
|
|
1309
|
+
const statusEligible = row.status !== "paused" &&
|
|
1310
|
+
row.status !== "terminated" &&
|
|
1311
|
+
row.status !== "pending_approval";
|
|
1312
|
+
return {
|
|
1313
|
+
id: row.id,
|
|
1314
|
+
companyId: row.companyId,
|
|
1315
|
+
companyName: row.companyName,
|
|
1316
|
+
companyIssuePrefix: row.companyIssuePrefix,
|
|
1317
|
+
agentName: row.agentName,
|
|
1318
|
+
agentUrlKey: deriveAgentUrlKey(row.agentName, row.id),
|
|
1319
|
+
role: row.role,
|
|
1320
|
+
title: row.title,
|
|
1321
|
+
status: row.status,
|
|
1322
|
+
adapterType: row.adapterType,
|
|
1323
|
+
intervalSec: policy.intervalSec,
|
|
1324
|
+
heartbeatEnabled: policy.enabled,
|
|
1325
|
+
schedulerActive: statusEligible && policy.enabled && policy.intervalSec > 0,
|
|
1326
|
+
lastHeartbeatAt: row.lastHeartbeatAt,
|
|
1327
|
+
};
|
|
1328
|
+
})
|
|
1329
|
+
.filter((item) => item.status !== "paused" &&
|
|
1330
|
+
item.status !== "terminated" &&
|
|
1331
|
+
item.status !== "pending_approval")
|
|
1332
|
+
.sort((left, right) => {
|
|
1333
|
+
if (left.schedulerActive !== right.schedulerActive) {
|
|
1334
|
+
return left.schedulerActive ? -1 : 1;
|
|
1335
|
+
}
|
|
1336
|
+
const companyOrder = left.companyName.localeCompare(right.companyName);
|
|
1337
|
+
if (companyOrder !== 0)
|
|
1338
|
+
return companyOrder;
|
|
1339
|
+
return left.agentName.localeCompare(right.agentName);
|
|
1340
|
+
});
|
|
1341
|
+
res.json(items);
|
|
1342
|
+
});
|
|
1343
|
+
router.get("/companies/:companyId/org", async (req, res) => {
|
|
1344
|
+
const companyId = req.params.companyId;
|
|
1345
|
+
assertCompanyAccess(req, companyId);
|
|
1346
|
+
const tree = await svc.orgForCompany(companyId);
|
|
1347
|
+
const leanTree = tree.map((node) => toLeanOrgNode(node));
|
|
1348
|
+
res.json(leanTree);
|
|
1349
|
+
});
|
|
1350
|
+
router.get("/companies/:companyId/org.svg", async (req, res) => {
|
|
1351
|
+
const companyId = req.params.companyId;
|
|
1352
|
+
assertCompanyAccess(req, companyId);
|
|
1353
|
+
const style = (ORG_CHART_STYLES.includes(req.query.style) ? req.query.style : "warmth");
|
|
1354
|
+
const tree = await svc.orgForCompany(companyId);
|
|
1355
|
+
const leanTree = tree.map((node) => toLeanOrgNode(node));
|
|
1356
|
+
const svg = renderOrgChartSvg(leanTree, style);
|
|
1357
|
+
res.setHeader("Content-Type", "image/svg+xml");
|
|
1358
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
1359
|
+
res.send(svg);
|
|
1360
|
+
});
|
|
1361
|
+
router.get("/companies/:companyId/org.png", async (req, res) => {
|
|
1362
|
+
const companyId = req.params.companyId;
|
|
1363
|
+
assertCompanyAccess(req, companyId);
|
|
1364
|
+
const style = (ORG_CHART_STYLES.includes(req.query.style) ? req.query.style : "warmth");
|
|
1365
|
+
const tree = await svc.orgForCompany(companyId);
|
|
1366
|
+
const leanTree = tree.map((node) => toLeanOrgNode(node));
|
|
1367
|
+
const png = await renderOrgChartPng(leanTree, style);
|
|
1368
|
+
res.setHeader("Content-Type", "image/png");
|
|
1369
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
1370
|
+
res.send(png);
|
|
1371
|
+
});
|
|
1372
|
+
router.get("/companies/:companyId/agent-configurations", async (req, res) => {
|
|
1373
|
+
const companyId = req.params.companyId;
|
|
1374
|
+
await assertCanReadConfigurations(req, companyId);
|
|
1375
|
+
const rows = await svc.list(companyId);
|
|
1376
|
+
res.json(rows.map((row) => redactAgentConfiguration(row)));
|
|
1377
|
+
});
|
|
1378
|
+
router.get("/agents/me", async (req, res) => {
|
|
1379
|
+
if (req.actor.type !== "agent" || !req.actor.agentId) {
|
|
1380
|
+
res.status(401).json({ error: "Agent authentication required" });
|
|
1381
|
+
return;
|
|
1382
|
+
}
|
|
1383
|
+
const agent = await svc.getById(req.actor.agentId);
|
|
1384
|
+
if (!agent) {
|
|
1385
|
+
res.status(404).json({ error: "Agent not found" });
|
|
1386
|
+
return;
|
|
1387
|
+
}
|
|
1388
|
+
res.json(await buildAgentDetail(agent));
|
|
1389
|
+
});
|
|
1390
|
+
router.get("/agents/me/inbox-lite", async (req, res) => {
|
|
1391
|
+
if (req.actor.type !== "agent" || !req.actor.agentId || !req.actor.companyId) {
|
|
1392
|
+
res.status(401).json({ error: "Agent authentication required" });
|
|
1393
|
+
return;
|
|
1394
|
+
}
|
|
1395
|
+
const issuesSvc = issueService(db);
|
|
1396
|
+
const rows = await issuesSvc.list(req.actor.companyId, {
|
|
1397
|
+
assigneeAgentId: req.actor.agentId,
|
|
1398
|
+
status: "todo,in_progress,blocked",
|
|
1399
|
+
includeRoutineExecutions: true,
|
|
1400
|
+
limit: ISSUE_LIST_DEFAULT_LIMIT,
|
|
1401
|
+
});
|
|
1402
|
+
const dependencyReadiness = await issuesSvc.listDependencyReadiness(req.actor.companyId, rows.map((issue) => issue.id));
|
|
1403
|
+
res.json(rows.map((issue) => ({
|
|
1404
|
+
id: issue.id,
|
|
1405
|
+
identifier: issue.identifier,
|
|
1406
|
+
title: issue.title,
|
|
1407
|
+
status: issue.status,
|
|
1408
|
+
priority: issue.priority,
|
|
1409
|
+
projectId: issue.projectId,
|
|
1410
|
+
goalId: issue.goalId,
|
|
1411
|
+
parentId: issue.parentId,
|
|
1412
|
+
updatedAt: issue.updatedAt,
|
|
1413
|
+
activeRun: issue.activeRun,
|
|
1414
|
+
dependencyReady: dependencyReadiness.get(issue.id)?.isDependencyReady ?? true,
|
|
1415
|
+
unresolvedBlockerCount: dependencyReadiness.get(issue.id)?.unresolvedBlockerCount ?? 0,
|
|
1416
|
+
unresolvedBlockerIssueIds: dependencyReadiness.get(issue.id)?.unresolvedBlockerIssueIds ?? [],
|
|
1417
|
+
})));
|
|
1418
|
+
});
|
|
1419
|
+
router.get("/agents/me/inbox/mine", async (req, res) => {
|
|
1420
|
+
if (req.actor.type !== "agent" || !req.actor.agentId || !req.actor.companyId) {
|
|
1421
|
+
res.status(401).json({ error: "Agent authentication required" });
|
|
1422
|
+
return;
|
|
1423
|
+
}
|
|
1424
|
+
const query = agentMineInboxQuerySchema.parse(req.query);
|
|
1425
|
+
const issuesSvc = issueService(db);
|
|
1426
|
+
const rows = await issuesSvc.list(req.actor.companyId, {
|
|
1427
|
+
touchedByUserId: query.userId,
|
|
1428
|
+
inboxArchivedByUserId: query.userId,
|
|
1429
|
+
status: query.status,
|
|
1430
|
+
limit: ISSUE_LIST_DEFAULT_LIMIT,
|
|
1431
|
+
});
|
|
1432
|
+
res.json(rows);
|
|
1433
|
+
});
|
|
1434
|
+
router.get("/agents/:id", async (req, res) => {
|
|
1435
|
+
const id = req.params.id;
|
|
1436
|
+
const agent = await svc.getById(id);
|
|
1437
|
+
if (!agent) {
|
|
1438
|
+
res.status(404).json({ error: "Agent not found" });
|
|
1439
|
+
return;
|
|
1440
|
+
}
|
|
1441
|
+
assertCompanyAccess(req, agent.companyId);
|
|
1442
|
+
const isSelf = req.actor.type === "agent" && req.actor.agentId === id;
|
|
1443
|
+
const canReadSensitiveDetail = isSelf
|
|
1444
|
+
? true
|
|
1445
|
+
: await actorCanReadConfigurationsForCompany(req, agent.companyId);
|
|
1446
|
+
if (!canReadSensitiveDetail) {
|
|
1447
|
+
res.json(await buildAgentDetail(agent, { restricted: true }));
|
|
1448
|
+
return;
|
|
1449
|
+
}
|
|
1450
|
+
res.json(await buildAgentDetail(agent));
|
|
1451
|
+
});
|
|
1452
|
+
router.get("/agents/:id/configuration", async (req, res) => {
|
|
1453
|
+
const id = req.params.id;
|
|
1454
|
+
const agent = await svc.getById(id);
|
|
1455
|
+
if (!agent) {
|
|
1456
|
+
res.status(404).json({ error: "Agent not found" });
|
|
1457
|
+
return;
|
|
1458
|
+
}
|
|
1459
|
+
await assertCanReadConfigurations(req, agent.companyId);
|
|
1460
|
+
res.json(redactAgentConfiguration(agent));
|
|
1461
|
+
});
|
|
1462
|
+
router.get("/agents/:id/config-revisions", async (req, res) => {
|
|
1463
|
+
const id = req.params.id;
|
|
1464
|
+
const agent = await svc.getById(id);
|
|
1465
|
+
if (!agent) {
|
|
1466
|
+
res.status(404).json({ error: "Agent not found" });
|
|
1467
|
+
return;
|
|
1468
|
+
}
|
|
1469
|
+
await assertCanReadConfigurations(req, agent.companyId);
|
|
1470
|
+
const revisions = await svc.listConfigRevisions(id);
|
|
1471
|
+
res.json(revisions.map((revision) => redactConfigRevision(revision)));
|
|
1472
|
+
});
|
|
1473
|
+
router.get("/agents/:id/config-revisions/:revisionId", async (req, res) => {
|
|
1474
|
+
const id = req.params.id;
|
|
1475
|
+
const revisionId = req.params.revisionId;
|
|
1476
|
+
const agent = await svc.getById(id);
|
|
1477
|
+
if (!agent) {
|
|
1478
|
+
res.status(404).json({ error: "Agent not found" });
|
|
1479
|
+
return;
|
|
1480
|
+
}
|
|
1481
|
+
await assertCanReadConfigurations(req, agent.companyId);
|
|
1482
|
+
const revision = await svc.getConfigRevision(id, revisionId);
|
|
1483
|
+
if (!revision) {
|
|
1484
|
+
res.status(404).json({ error: "Revision not found" });
|
|
1485
|
+
return;
|
|
1486
|
+
}
|
|
1487
|
+
res.json(redactConfigRevision(revision));
|
|
1488
|
+
});
|
|
1489
|
+
router.post("/agents/:id/config-revisions/:revisionId/rollback", async (req, res) => {
|
|
1490
|
+
const id = req.params.id;
|
|
1491
|
+
const revisionId = req.params.revisionId;
|
|
1492
|
+
const existing = await svc.getById(id);
|
|
1493
|
+
if (!existing) {
|
|
1494
|
+
res.status(404).json({ error: "Agent not found" });
|
|
1495
|
+
return;
|
|
1496
|
+
}
|
|
1497
|
+
await assertCanUpdateAgent(req, existing);
|
|
1498
|
+
const actor = getActorInfo(req);
|
|
1499
|
+
const updated = await svc.rollbackConfigRevision(id, revisionId, {
|
|
1500
|
+
agentId: actor.agentId,
|
|
1501
|
+
userId: actor.actorType === "user" ? actor.actorId : null,
|
|
1502
|
+
});
|
|
1503
|
+
if (!updated) {
|
|
1504
|
+
res.status(404).json({ error: "Revision not found" });
|
|
1505
|
+
return;
|
|
1506
|
+
}
|
|
1507
|
+
await logActivity(db, {
|
|
1508
|
+
companyId: updated.companyId,
|
|
1509
|
+
actorType: actor.actorType,
|
|
1510
|
+
actorId: actor.actorId,
|
|
1511
|
+
agentId: actor.agentId,
|
|
1512
|
+
runId: actor.runId,
|
|
1513
|
+
action: "agent.config_rolled_back",
|
|
1514
|
+
entityType: "agent",
|
|
1515
|
+
entityId: updated.id,
|
|
1516
|
+
details: { revisionId },
|
|
1517
|
+
});
|
|
1518
|
+
res.json(updated);
|
|
1519
|
+
});
|
|
1520
|
+
router.get("/agents/:id/runtime-state", async (req, res) => {
|
|
1521
|
+
assertBoard(req);
|
|
1522
|
+
const id = req.params.id;
|
|
1523
|
+
const agent = await svc.getById(id);
|
|
1524
|
+
if (!agent) {
|
|
1525
|
+
res.status(404).json({ error: "Agent not found" });
|
|
1526
|
+
return;
|
|
1527
|
+
}
|
|
1528
|
+
await assertBoardCanManageAgentsForCompany(req, agent.companyId);
|
|
1529
|
+
assertCompanyAccess(req, agent.companyId);
|
|
1530
|
+
const state = await heartbeat.getRuntimeState(id);
|
|
1531
|
+
res.json(state);
|
|
1532
|
+
});
|
|
1533
|
+
router.get("/agents/:id/task-sessions", async (req, res) => {
|
|
1534
|
+
assertBoard(req);
|
|
1535
|
+
const id = req.params.id;
|
|
1536
|
+
const agent = await svc.getById(id);
|
|
1537
|
+
if (!agent) {
|
|
1538
|
+
res.status(404).json({ error: "Agent not found" });
|
|
1539
|
+
return;
|
|
1540
|
+
}
|
|
1541
|
+
await assertBoardCanManageAgentsForCompany(req, agent.companyId);
|
|
1542
|
+
assertCompanyAccess(req, agent.companyId);
|
|
1543
|
+
const sessions = await heartbeat.listTaskSessions(id);
|
|
1544
|
+
res.json(sessions.map((session) => ({
|
|
1545
|
+
...session,
|
|
1546
|
+
sessionParamsJson: redactEventPayload(session.sessionParamsJson ?? null),
|
|
1547
|
+
})));
|
|
1548
|
+
});
|
|
1549
|
+
router.post("/agents/:id/runtime-state/reset-session", validate(resetAgentSessionSchema), async (req, res) => {
|
|
1550
|
+
assertBoard(req);
|
|
1551
|
+
const id = req.params.id;
|
|
1552
|
+
const agent = await svc.getById(id);
|
|
1553
|
+
if (!agent) {
|
|
1554
|
+
res.status(404).json({ error: "Agent not found" });
|
|
1555
|
+
return;
|
|
1556
|
+
}
|
|
1557
|
+
await assertBoardCanManageAgentsForCompany(req, agent.companyId);
|
|
1558
|
+
assertCompanyAccess(req, agent.companyId);
|
|
1559
|
+
const taskKey = typeof req.body.taskKey === "string" && req.body.taskKey.trim().length > 0
|
|
1560
|
+
? req.body.taskKey.trim()
|
|
1561
|
+
: null;
|
|
1562
|
+
const state = await heartbeat.resetRuntimeSession(id, { taskKey });
|
|
1563
|
+
await logActivity(db, {
|
|
1564
|
+
companyId: agent.companyId,
|
|
1565
|
+
actorType: "user",
|
|
1566
|
+
actorId: req.actor.userId ?? "board",
|
|
1567
|
+
action: "agent.runtime_session_reset",
|
|
1568
|
+
entityType: "agent",
|
|
1569
|
+
entityId: id,
|
|
1570
|
+
details: { taskKey: taskKey ?? null },
|
|
1571
|
+
});
|
|
1572
|
+
res.json(state);
|
|
1573
|
+
});
|
|
1574
|
+
router.post("/companies/:companyId/agent-hires", validate(createAgentHireSchema), async (req, res) => {
|
|
1575
|
+
const companyId = req.params.companyId;
|
|
1576
|
+
await assertCanCreateAgentsForCompany(req, companyId);
|
|
1577
|
+
const sourceIssueIds = parseSourceIssueIds(req.body);
|
|
1578
|
+
const { desiredSkills: requestedDesiredSkills, instructionsBundle, sourceIssueId: _sourceIssueId, sourceIssueIds: _sourceIssueIds, ...hireInput } = req.body;
|
|
1579
|
+
hireInput.adapterType = assertKnownAdapterType(hireInput.adapterType);
|
|
1580
|
+
const rawHireAdapterConfig = (hireInput.adapterConfig ?? {});
|
|
1581
|
+
assertNoNewAgentLegacyPromptTemplate(hireInput.adapterType, rawHireAdapterConfig);
|
|
1582
|
+
assertNoAgentAdapterConfigMutation(req, rawHireAdapterConfig);
|
|
1583
|
+
assertNoAgentRuntimeConfigAdapterConfigMutation(req, hireInput.runtimeConfig);
|
|
1584
|
+
const requestedAdapterConfig = applyCreateDefaultsByAdapterType(hireInput.adapterType, rawHireAdapterConfig);
|
|
1585
|
+
const desiredSkillAssignment = await resolveDesiredSkillAssignment(companyId, hireInput.adapterType, requestedAdapterConfig, Array.isArray(requestedDesiredSkills) ? requestedDesiredSkills : undefined);
|
|
1586
|
+
const normalizedAdapterConfig = await normalizeMediatedAdapterConfigForPersistence({
|
|
1587
|
+
companyId,
|
|
1588
|
+
adapterType: hireInput.adapterType,
|
|
1589
|
+
adapterConfig: desiredSkillAssignment.adapterConfig,
|
|
1590
|
+
});
|
|
1591
|
+
const normalizedRuntimeConfig = await normalizeRuntimeConfigAdapterConfigsForPersistence(companyId, hireInput.adapterType, normalizeNewAgentRuntimeConfig(hireInput.runtimeConfig), normalizedAdapterConfig);
|
|
1592
|
+
const normalizedHireInput = {
|
|
1593
|
+
...hireInput,
|
|
1594
|
+
adapterConfig: normalizedAdapterConfig,
|
|
1595
|
+
runtimeConfig: normalizedRuntimeConfig,
|
|
1596
|
+
};
|
|
1597
|
+
const company = await db
|
|
1598
|
+
.select()
|
|
1599
|
+
.from(companies)
|
|
1600
|
+
.where(eq(companies.id, companyId))
|
|
1601
|
+
.then((rows) => rows[0] ?? null);
|
|
1602
|
+
if (!company) {
|
|
1603
|
+
res.status(404).json({ error: "Company not found" });
|
|
1604
|
+
return;
|
|
1605
|
+
}
|
|
1606
|
+
const requiresApproval = company.requireBoardApprovalForNewAgents;
|
|
1607
|
+
const status = requiresApproval ? "pending_approval" : "idle";
|
|
1608
|
+
const createdAgent = await svc.create(companyId, {
|
|
1609
|
+
...normalizedHireInput,
|
|
1610
|
+
status,
|
|
1611
|
+
spentMonthlyCents: 0,
|
|
1612
|
+
lastHeartbeatAt: null,
|
|
1613
|
+
});
|
|
1614
|
+
const agent = await materializeDefaultInstructionsBundleForNewAgent(createdAgent, instructionsBundle);
|
|
1615
|
+
let approval = null;
|
|
1616
|
+
const actor = getActorInfo(req);
|
|
1617
|
+
if (requiresApproval) {
|
|
1618
|
+
const requestedAdapterType = normalizedHireInput.adapterType ?? agent.adapterType;
|
|
1619
|
+
const requestedAdapterConfig = redactEventPayload((agent.adapterConfig ?? normalizedHireInput.adapterConfig)) ?? {};
|
|
1620
|
+
const requestedRuntimeConfig = redactEventPayload((normalizedHireInput.runtimeConfig ?? agent.runtimeConfig)) ?? {};
|
|
1621
|
+
const requestedMetadata = redactEventPayload((normalizedHireInput.metadata ?? agent.metadata ?? {})) ?? {};
|
|
1622
|
+
approval = await approvalsSvc.create(companyId, {
|
|
1623
|
+
type: "hire_agent",
|
|
1624
|
+
requestedByAgentId: actor.actorType === "agent" ? actor.actorId : null,
|
|
1625
|
+
requestedByUserId: actor.actorType === "user" ? actor.actorId : null,
|
|
1626
|
+
status: "pending",
|
|
1627
|
+
payload: {
|
|
1628
|
+
name: normalizedHireInput.name,
|
|
1629
|
+
role: normalizedHireInput.role,
|
|
1630
|
+
title: normalizedHireInput.title ?? null,
|
|
1631
|
+
icon: normalizedHireInput.icon ?? null,
|
|
1632
|
+
reportsTo: normalizedHireInput.reportsTo ?? null,
|
|
1633
|
+
capabilities: normalizedHireInput.capabilities ?? null,
|
|
1634
|
+
adapterType: requestedAdapterType,
|
|
1635
|
+
adapterConfig: requestedAdapterConfig,
|
|
1636
|
+
runtimeConfig: requestedRuntimeConfig,
|
|
1637
|
+
budgetMonthlyCents: typeof normalizedHireInput.budgetMonthlyCents === "number"
|
|
1638
|
+
? normalizedHireInput.budgetMonthlyCents
|
|
1639
|
+
: agent.budgetMonthlyCents,
|
|
1640
|
+
desiredSkills: desiredSkillAssignment.desiredSkills,
|
|
1641
|
+
metadata: requestedMetadata,
|
|
1642
|
+
agentId: agent.id,
|
|
1643
|
+
requestedByAgentId: actor.actorType === "agent" ? actor.actorId : null,
|
|
1644
|
+
requestedConfigurationSnapshot: {
|
|
1645
|
+
adapterType: requestedAdapterType,
|
|
1646
|
+
adapterConfig: requestedAdapterConfig,
|
|
1647
|
+
runtimeConfig: requestedRuntimeConfig,
|
|
1648
|
+
desiredSkills: desiredSkillAssignment.desiredSkills,
|
|
1649
|
+
},
|
|
1650
|
+
},
|
|
1651
|
+
decisionNote: null,
|
|
1652
|
+
decidedByUserId: null,
|
|
1653
|
+
decidedAt: null,
|
|
1654
|
+
updatedAt: new Date(),
|
|
1655
|
+
});
|
|
1656
|
+
if (sourceIssueIds.length > 0) {
|
|
1657
|
+
await issueApprovalsSvc.linkManyForApproval(approval.id, sourceIssueIds, {
|
|
1658
|
+
agentId: actor.actorType === "agent" ? actor.actorId : null,
|
|
1659
|
+
userId: actor.actorType === "user" ? actor.actorId : null,
|
|
1660
|
+
});
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
await logActivity(db, {
|
|
1664
|
+
companyId,
|
|
1665
|
+
actorType: actor.actorType,
|
|
1666
|
+
actorId: actor.actorId,
|
|
1667
|
+
agentId: actor.agentId,
|
|
1668
|
+
runId: actor.runId,
|
|
1669
|
+
action: "agent.hire_created",
|
|
1670
|
+
entityType: "agent",
|
|
1671
|
+
entityId: agent.id,
|
|
1672
|
+
details: {
|
|
1673
|
+
name: agent.name,
|
|
1674
|
+
role: agent.role,
|
|
1675
|
+
requiresApproval,
|
|
1676
|
+
approvalId: approval?.id ?? null,
|
|
1677
|
+
issueIds: sourceIssueIds,
|
|
1678
|
+
desiredSkills: desiredSkillAssignment.desiredSkills,
|
|
1679
|
+
},
|
|
1680
|
+
});
|
|
1681
|
+
const telemetryClient = getTelemetryClient();
|
|
1682
|
+
if (telemetryClient) {
|
|
1683
|
+
trackAgentCreated(telemetryClient, { agentRole: agent.role, agentId: agent.id });
|
|
1684
|
+
}
|
|
1685
|
+
await applyDefaultAgentTaskAssignGrant(companyId, agent.id, actor.actorType === "user" ? actor.actorId : null);
|
|
1686
|
+
if (approval) {
|
|
1687
|
+
await logActivity(db, {
|
|
1688
|
+
companyId,
|
|
1689
|
+
actorType: actor.actorType,
|
|
1690
|
+
actorId: actor.actorId,
|
|
1691
|
+
agentId: actor.agentId,
|
|
1692
|
+
runId: actor.runId,
|
|
1693
|
+
action: "approval.created",
|
|
1694
|
+
entityType: "approval",
|
|
1695
|
+
entityId: approval.id,
|
|
1696
|
+
details: { type: approval.type, linkedAgentId: agent.id },
|
|
1697
|
+
});
|
|
1698
|
+
}
|
|
1699
|
+
res.status(201).json({ agent, approval });
|
|
1700
|
+
});
|
|
1701
|
+
router.post("/companies/:companyId/agents", validate(createAgentSchema), async (req, res) => {
|
|
1702
|
+
const companyId = req.params.companyId;
|
|
1703
|
+
await assertCanCreateAgentsForCompany(req, companyId);
|
|
1704
|
+
const company = await db
|
|
1705
|
+
.select()
|
|
1706
|
+
.from(companies)
|
|
1707
|
+
.where(eq(companies.id, companyId))
|
|
1708
|
+
.then((rows) => rows[0] ?? null);
|
|
1709
|
+
if (!company) {
|
|
1710
|
+
res.status(404).json({ error: "Company not found" });
|
|
1711
|
+
return;
|
|
1712
|
+
}
|
|
1713
|
+
if (company.requireBoardApprovalForNewAgents) {
|
|
1714
|
+
throw conflict("Direct agent creation requires board approval. Use POST /api/companies/:companyId/agent-hires to create a pending hire approval.");
|
|
1715
|
+
}
|
|
1716
|
+
const { desiredSkills: requestedDesiredSkills, instructionsBundle, ...createInput } = req.body;
|
|
1717
|
+
createInput.adapterType = assertKnownAdapterType(createInput.adapterType);
|
|
1718
|
+
const rawCreateAdapterConfig = (createInput.adapterConfig ?? {});
|
|
1719
|
+
assertNoNewAgentLegacyPromptTemplate(createInput.adapterType, rawCreateAdapterConfig);
|
|
1720
|
+
assertNoAgentAdapterConfigMutation(req, rawCreateAdapterConfig);
|
|
1721
|
+
assertNoAgentRuntimeConfigAdapterConfigMutation(req, createInput.runtimeConfig);
|
|
1722
|
+
const requestedAdapterConfig = applyCreateDefaultsByAdapterType(createInput.adapterType, rawCreateAdapterConfig);
|
|
1723
|
+
const desiredSkillAssignment = await resolveDesiredSkillAssignment(companyId, createInput.adapterType, requestedAdapterConfig, Array.isArray(requestedDesiredSkills) ? requestedDesiredSkills : undefined);
|
|
1724
|
+
const normalizedAdapterConfig = await normalizeMediatedAdapterConfigForPersistence({
|
|
1725
|
+
companyId,
|
|
1726
|
+
adapterType: createInput.adapterType,
|
|
1727
|
+
adapterConfig: desiredSkillAssignment.adapterConfig,
|
|
1728
|
+
});
|
|
1729
|
+
const normalizedRuntimeConfig = await normalizeRuntimeConfigAdapterConfigsForPersistence(companyId, createInput.adapterType, normalizeNewAgentRuntimeConfig(createInput.runtimeConfig), normalizedAdapterConfig);
|
|
1730
|
+
await assertAgentEnvironmentSelection(companyId, createInput.adapterType, createInput.defaultEnvironmentId);
|
|
1731
|
+
await assertAgentDefaultEnvironmentSelection(companyId, createInput.defaultEnvironmentId, {
|
|
1732
|
+
allowedDrivers: allowedEnvironmentDriversForAgent(createInput.adapterType),
|
|
1733
|
+
allowedSandboxProviders: allowedSandboxProvidersForAgent(createInput.adapterType),
|
|
1734
|
+
});
|
|
1735
|
+
const createdAgent = await svc.create(companyId, {
|
|
1736
|
+
...createInput,
|
|
1737
|
+
adapterConfig: normalizedAdapterConfig,
|
|
1738
|
+
runtimeConfig: normalizedRuntimeConfig,
|
|
1739
|
+
status: "idle",
|
|
1740
|
+
spentMonthlyCents: 0,
|
|
1741
|
+
lastHeartbeatAt: null,
|
|
1742
|
+
});
|
|
1743
|
+
const agent = await materializeDefaultInstructionsBundleForNewAgent(createdAgent, instructionsBundle);
|
|
1744
|
+
const actor = getActorInfo(req);
|
|
1745
|
+
await logActivity(db, {
|
|
1746
|
+
companyId,
|
|
1747
|
+
actorType: actor.actorType,
|
|
1748
|
+
actorId: actor.actorId,
|
|
1749
|
+
agentId: actor.agentId,
|
|
1750
|
+
runId: actor.runId,
|
|
1751
|
+
action: "agent.created",
|
|
1752
|
+
entityType: "agent",
|
|
1753
|
+
entityId: agent.id,
|
|
1754
|
+
details: {
|
|
1755
|
+
name: agent.name,
|
|
1756
|
+
role: agent.role,
|
|
1757
|
+
desiredSkills: desiredSkillAssignment.desiredSkills,
|
|
1758
|
+
},
|
|
1759
|
+
});
|
|
1760
|
+
const telemetryClient = getTelemetryClient();
|
|
1761
|
+
if (telemetryClient) {
|
|
1762
|
+
trackAgentCreated(telemetryClient, { agentRole: agent.role, agentId: agent.id });
|
|
1763
|
+
}
|
|
1764
|
+
await applyDefaultAgentTaskAssignGrant(companyId, agent.id, req.actor.type === "board" ? (req.actor.userId ?? null) : null);
|
|
1765
|
+
if (agent.budgetMonthlyCents > 0) {
|
|
1766
|
+
await budgets.upsertPolicy(companyId, {
|
|
1767
|
+
scopeType: "agent",
|
|
1768
|
+
scopeId: agent.id,
|
|
1769
|
+
amount: agent.budgetMonthlyCents,
|
|
1770
|
+
windowKind: "calendar_month_utc",
|
|
1771
|
+
}, actor.actorType === "user" ? actor.actorId : null);
|
|
1772
|
+
}
|
|
1773
|
+
res.status(201).json(agent);
|
|
1774
|
+
});
|
|
1775
|
+
router.patch("/agents/:id/permissions", validate(updateAgentPermissionsSchema), async (req, res) => {
|
|
1776
|
+
const id = req.params.id;
|
|
1777
|
+
const existing = await svc.getById(id);
|
|
1778
|
+
if (!existing) {
|
|
1779
|
+
res.status(404).json({ error: "Agent not found" });
|
|
1780
|
+
return;
|
|
1781
|
+
}
|
|
1782
|
+
assertCompanyAccess(req, existing.companyId);
|
|
1783
|
+
if (req.actor.type === "agent") {
|
|
1784
|
+
const actorAgent = req.actor.agentId ? await svc.getById(req.actor.agentId) : null;
|
|
1785
|
+
if (!actorAgent || actorAgent.companyId !== existing.companyId) {
|
|
1786
|
+
res.status(403).json({ error: "Forbidden" });
|
|
1787
|
+
return;
|
|
1788
|
+
}
|
|
1789
|
+
if (actorAgent.role !== "ceo") {
|
|
1790
|
+
res.status(403).json({ error: "Only CEO can manage permissions" });
|
|
1791
|
+
return;
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
else {
|
|
1795
|
+
await assertBoardCanManageAgentsForCompany(req, existing.companyId);
|
|
1796
|
+
}
|
|
1797
|
+
const agent = await svc.updatePermissions(id, req.body);
|
|
1798
|
+
if (!agent) {
|
|
1799
|
+
res.status(404).json({ error: "Agent not found" });
|
|
1800
|
+
return;
|
|
1801
|
+
}
|
|
1802
|
+
const effectiveCanAssignTasks = agent.role === "ceo" || Boolean(agent.permissions?.canCreateAgents) || req.body.canAssignTasks;
|
|
1803
|
+
await access.ensureMembership(agent.companyId, "agent", agent.id, "member", "active");
|
|
1804
|
+
await access.setPrincipalPermission(agent.companyId, "agent", agent.id, "tasks:assign", effectiveCanAssignTasks, req.actor.type === "board" ? (req.actor.userId ?? null) : null);
|
|
1805
|
+
const actor = getActorInfo(req);
|
|
1806
|
+
await logActivity(db, {
|
|
1807
|
+
companyId: agent.companyId,
|
|
1808
|
+
actorType: actor.actorType,
|
|
1809
|
+
actorId: actor.actorId,
|
|
1810
|
+
agentId: actor.agentId,
|
|
1811
|
+
runId: actor.runId,
|
|
1812
|
+
action: "agent.permissions_updated",
|
|
1813
|
+
entityType: "agent",
|
|
1814
|
+
entityId: agent.id,
|
|
1815
|
+
details: {
|
|
1816
|
+
canCreateAgents: agent.permissions?.canCreateAgents ?? false,
|
|
1817
|
+
canAssignTasks: effectiveCanAssignTasks,
|
|
1818
|
+
},
|
|
1819
|
+
});
|
|
1820
|
+
res.json(await buildAgentDetail(agent));
|
|
1821
|
+
});
|
|
1822
|
+
router.patch("/agents/:id/instructions-path", validate(updateAgentInstructionsPathSchema), async (req, res) => {
|
|
1823
|
+
if (req.actor.type !== "board") {
|
|
1824
|
+
throw forbidden("Only board-authenticated callers can manage instructions path or bundle configuration");
|
|
1825
|
+
}
|
|
1826
|
+
const id = req.params.id;
|
|
1827
|
+
const existing = await svc.getById(id);
|
|
1828
|
+
if (!existing) {
|
|
1829
|
+
res.status(404).json({ error: "Agent not found" });
|
|
1830
|
+
return;
|
|
1831
|
+
}
|
|
1832
|
+
await assertCanManageInstructionsPath(req, existing);
|
|
1833
|
+
const existingAdapterConfig = asRecord(existing.adapterConfig) ?? {};
|
|
1834
|
+
const explicitKey = asNonEmptyString(req.body.adapterConfigKey);
|
|
1835
|
+
const defaultKey = resolveInstructionsPathKey(existing.adapterType);
|
|
1836
|
+
const adapterConfigKey = explicitKey ?? defaultKey;
|
|
1837
|
+
if (!adapterConfigKey) {
|
|
1838
|
+
res.status(422).json({
|
|
1839
|
+
error: `No default instructions path key for adapter type '${existing.adapterType}'. Provide adapterConfigKey.`,
|
|
1840
|
+
});
|
|
1841
|
+
return;
|
|
1842
|
+
}
|
|
1843
|
+
const nextAdapterConfig = { ...existingAdapterConfig };
|
|
1844
|
+
if (req.body.path === null) {
|
|
1845
|
+
delete nextAdapterConfig[adapterConfigKey];
|
|
1846
|
+
}
|
|
1847
|
+
else {
|
|
1848
|
+
nextAdapterConfig[adapterConfigKey] = resolveInstructionsFilePath(req.body.path, existingAdapterConfig);
|
|
1849
|
+
}
|
|
1850
|
+
const syncedAdapterConfig = syncInstructionsBundleConfigFromFilePath(existing, nextAdapterConfig);
|
|
1851
|
+
const normalizedAdapterConfig = await secretsSvc.normalizeAdapterConfigForPersistence(existing.companyId, syncedAdapterConfig, { strictMode: strictSecretsMode });
|
|
1852
|
+
const actor = getActorInfo(req);
|
|
1853
|
+
const agent = await svc.update(id, { adapterConfig: normalizedAdapterConfig }, {
|
|
1854
|
+
recordRevision: {
|
|
1855
|
+
createdByAgentId: actor.agentId,
|
|
1856
|
+
createdByUserId: actor.actorType === "user" ? actor.actorId : null,
|
|
1857
|
+
source: "instructions_path_patch",
|
|
1858
|
+
},
|
|
1859
|
+
});
|
|
1860
|
+
if (!agent) {
|
|
1861
|
+
res.status(404).json({ error: "Agent not found" });
|
|
1862
|
+
return;
|
|
1863
|
+
}
|
|
1864
|
+
const updatedAdapterConfig = asRecord(agent.adapterConfig) ?? {};
|
|
1865
|
+
const pathValue = asNonEmptyString(updatedAdapterConfig[adapterConfigKey]);
|
|
1866
|
+
await logActivity(db, {
|
|
1867
|
+
companyId: agent.companyId,
|
|
1868
|
+
actorType: actor.actorType,
|
|
1869
|
+
actorId: actor.actorId,
|
|
1870
|
+
agentId: actor.agentId,
|
|
1871
|
+
runId: actor.runId,
|
|
1872
|
+
action: "agent.instructions_path_updated",
|
|
1873
|
+
entityType: "agent",
|
|
1874
|
+
entityId: agent.id,
|
|
1875
|
+
details: {
|
|
1876
|
+
adapterConfigKey,
|
|
1877
|
+
path: pathValue,
|
|
1878
|
+
cleared: req.body.path === null,
|
|
1879
|
+
},
|
|
1880
|
+
});
|
|
1881
|
+
res.json({
|
|
1882
|
+
agentId: agent.id,
|
|
1883
|
+
adapterType: agent.adapterType,
|
|
1884
|
+
adapterConfigKey,
|
|
1885
|
+
path: pathValue,
|
|
1886
|
+
});
|
|
1887
|
+
});
|
|
1888
|
+
router.get("/agents/:id/instructions-bundle", async (req, res) => {
|
|
1889
|
+
const id = req.params.id;
|
|
1890
|
+
const existing = await svc.getById(id);
|
|
1891
|
+
if (!existing) {
|
|
1892
|
+
res.status(404).json({ error: "Agent not found" });
|
|
1893
|
+
return;
|
|
1894
|
+
}
|
|
1895
|
+
await assertCanReadAgent(req, existing);
|
|
1896
|
+
res.json(await instructions.getBundle(existing));
|
|
1897
|
+
});
|
|
1898
|
+
router.patch("/agents/:id/instructions-bundle", validate(updateAgentInstructionsBundleSchema), async (req, res) => {
|
|
1899
|
+
const id = req.params.id;
|
|
1900
|
+
const existing = await svc.getById(id);
|
|
1901
|
+
if (!existing) {
|
|
1902
|
+
res.status(404).json({ error: "Agent not found" });
|
|
1903
|
+
return;
|
|
1904
|
+
}
|
|
1905
|
+
await assertCanManageInstructionsPath(req, existing);
|
|
1906
|
+
const actor = getActorInfo(req);
|
|
1907
|
+
const { bundle, adapterConfig } = await instructions.updateBundle(existing, req.body);
|
|
1908
|
+
const normalizedAdapterConfig = await secretsSvc.normalizeAdapterConfigForPersistence(existing.companyId, adapterConfig, { strictMode: strictSecretsMode });
|
|
1909
|
+
await svc.update(id, { adapterConfig: normalizedAdapterConfig }, {
|
|
1910
|
+
recordRevision: {
|
|
1911
|
+
createdByAgentId: actor.agentId,
|
|
1912
|
+
createdByUserId: actor.actorType === "user" ? actor.actorId : null,
|
|
1913
|
+
source: "instructions_bundle_patch",
|
|
1914
|
+
},
|
|
1915
|
+
});
|
|
1916
|
+
await logActivity(db, {
|
|
1917
|
+
companyId: existing.companyId,
|
|
1918
|
+
actorType: actor.actorType,
|
|
1919
|
+
actorId: actor.actorId,
|
|
1920
|
+
agentId: actor.agentId,
|
|
1921
|
+
runId: actor.runId,
|
|
1922
|
+
action: "agent.instructions_bundle_updated",
|
|
1923
|
+
entityType: "agent",
|
|
1924
|
+
entityId: existing.id,
|
|
1925
|
+
details: {
|
|
1926
|
+
mode: bundle.mode,
|
|
1927
|
+
rootPath: bundle.rootPath,
|
|
1928
|
+
entryFile: bundle.entryFile,
|
|
1929
|
+
clearLegacyPromptTemplate: req.body.clearLegacyPromptTemplate === true,
|
|
1930
|
+
},
|
|
1931
|
+
});
|
|
1932
|
+
res.json(bundle);
|
|
1933
|
+
});
|
|
1934
|
+
router.get("/agents/:id/instructions-bundle/file", async (req, res) => {
|
|
1935
|
+
const id = req.params.id;
|
|
1936
|
+
const existing = await svc.getById(id);
|
|
1937
|
+
if (!existing) {
|
|
1938
|
+
res.status(404).json({ error: "Agent not found" });
|
|
1939
|
+
return;
|
|
1940
|
+
}
|
|
1941
|
+
await assertCanReadAgent(req, existing);
|
|
1942
|
+
const relativePath = typeof req.query.path === "string" ? req.query.path : "";
|
|
1943
|
+
if (!relativePath.trim()) {
|
|
1944
|
+
res.status(422).json({ error: "Query parameter 'path' is required" });
|
|
1945
|
+
return;
|
|
1946
|
+
}
|
|
1947
|
+
res.json(await instructions.readFile(existing, relativePath));
|
|
1948
|
+
});
|
|
1949
|
+
router.put("/agents/:id/instructions-bundle/file", validate(upsertAgentInstructionsFileSchema), async (req, res) => {
|
|
1950
|
+
const id = req.params.id;
|
|
1951
|
+
const existing = await svc.getById(id);
|
|
1952
|
+
if (!existing) {
|
|
1953
|
+
res.status(404).json({ error: "Agent not found" });
|
|
1954
|
+
return;
|
|
1955
|
+
}
|
|
1956
|
+
await assertCanManageInstructionsPath(req, existing);
|
|
1957
|
+
const actor = getActorInfo(req);
|
|
1958
|
+
const result = await instructions.writeFile(existing, req.body.path, req.body.content, {
|
|
1959
|
+
clearLegacyPromptTemplate: req.body.clearLegacyPromptTemplate,
|
|
1960
|
+
});
|
|
1961
|
+
const normalizedAdapterConfig = await secretsSvc.normalizeAdapterConfigForPersistence(existing.companyId, result.adapterConfig, { strictMode: strictSecretsMode });
|
|
1962
|
+
await svc.update(id, { adapterConfig: normalizedAdapterConfig }, {
|
|
1963
|
+
recordRevision: {
|
|
1964
|
+
createdByAgentId: actor.agentId,
|
|
1965
|
+
createdByUserId: actor.actorType === "user" ? actor.actorId : null,
|
|
1966
|
+
source: "instructions_bundle_file_put",
|
|
1967
|
+
},
|
|
1968
|
+
});
|
|
1969
|
+
await logActivity(db, {
|
|
1970
|
+
companyId: existing.companyId,
|
|
1971
|
+
actorType: actor.actorType,
|
|
1972
|
+
actorId: actor.actorId,
|
|
1973
|
+
agentId: actor.agentId,
|
|
1974
|
+
runId: actor.runId,
|
|
1975
|
+
action: "agent.instructions_file_updated",
|
|
1976
|
+
entityType: "agent",
|
|
1977
|
+
entityId: existing.id,
|
|
1978
|
+
details: {
|
|
1979
|
+
path: result.file.path,
|
|
1980
|
+
size: result.file.size,
|
|
1981
|
+
clearLegacyPromptTemplate: req.body.clearLegacyPromptTemplate === true,
|
|
1982
|
+
},
|
|
1983
|
+
});
|
|
1984
|
+
res.json(result.file);
|
|
1985
|
+
});
|
|
1986
|
+
router.delete("/agents/:id/instructions-bundle/file", async (req, res) => {
|
|
1987
|
+
const id = req.params.id;
|
|
1988
|
+
const existing = await svc.getById(id);
|
|
1989
|
+
if (!existing) {
|
|
1990
|
+
res.status(404).json({ error: "Agent not found" });
|
|
1991
|
+
return;
|
|
1992
|
+
}
|
|
1993
|
+
await assertCanManageInstructionsPath(req, existing);
|
|
1994
|
+
const relativePath = typeof req.query.path === "string" ? req.query.path : "";
|
|
1995
|
+
if (!relativePath.trim()) {
|
|
1996
|
+
res.status(422).json({ error: "Query parameter 'path' is required" });
|
|
1997
|
+
return;
|
|
1998
|
+
}
|
|
1999
|
+
const actor = getActorInfo(req);
|
|
2000
|
+
const result = await instructions.deleteFile(existing, relativePath);
|
|
2001
|
+
await logActivity(db, {
|
|
2002
|
+
companyId: existing.companyId,
|
|
2003
|
+
actorType: actor.actorType,
|
|
2004
|
+
actorId: actor.actorId,
|
|
2005
|
+
agentId: actor.agentId,
|
|
2006
|
+
runId: actor.runId,
|
|
2007
|
+
action: "agent.instructions_file_deleted",
|
|
2008
|
+
entityType: "agent",
|
|
2009
|
+
entityId: existing.id,
|
|
2010
|
+
details: {
|
|
2011
|
+
path: relativePath,
|
|
2012
|
+
},
|
|
2013
|
+
});
|
|
2014
|
+
res.json(result.bundle);
|
|
2015
|
+
});
|
|
2016
|
+
router.patch("/agents/:id", validate(updateAgentSchema), async (req, res) => {
|
|
2017
|
+
const id = req.params.id;
|
|
2018
|
+
const existing = await svc.getById(id);
|
|
2019
|
+
if (!existing) {
|
|
2020
|
+
res.status(404).json({ error: "Agent not found" });
|
|
2021
|
+
return;
|
|
2022
|
+
}
|
|
2023
|
+
await assertCanUpdateAgent(req, existing);
|
|
2024
|
+
if (hasOwn(req.body, "permissions")) {
|
|
2025
|
+
res.status(422).json({ error: "Use /api/agents/:id/permissions for permission changes" });
|
|
2026
|
+
return;
|
|
2027
|
+
}
|
|
2028
|
+
const patchData = { ...req.body };
|
|
2029
|
+
const replaceAdapterConfig = patchData.replaceAdapterConfig === true;
|
|
2030
|
+
delete patchData.replaceAdapterConfig;
|
|
2031
|
+
if (hasOwn(patchData, "adapterConfig")) {
|
|
2032
|
+
const adapterConfig = asRecord(patchData.adapterConfig);
|
|
2033
|
+
if (!adapterConfig) {
|
|
2034
|
+
res.status(422).json({ error: "adapterConfig must be an object" });
|
|
2035
|
+
return;
|
|
2036
|
+
}
|
|
2037
|
+
assertNoAgentAdapterConfigMutation(req, adapterConfig);
|
|
2038
|
+
const changingInstructionsConfig = adapterConfigTouchesInstructionsConfig(adapterConfig);
|
|
2039
|
+
if (changingInstructionsConfig) {
|
|
2040
|
+
await assertCanManageInstructionsPath(req, existing);
|
|
2041
|
+
}
|
|
2042
|
+
patchData.adapterConfig = adapterConfig;
|
|
2043
|
+
}
|
|
2044
|
+
const requestedAdapterType = hasOwn(patchData, "adapterType")
|
|
2045
|
+
? assertKnownAdapterType(patchData.adapterType)
|
|
2046
|
+
: existing.adapterType;
|
|
2047
|
+
let requestedRuntimeConfig = null;
|
|
2048
|
+
if (hasOwn(patchData, "runtimeConfig")) {
|
|
2049
|
+
const runtimeConfig = asRecord(patchData.runtimeConfig);
|
|
2050
|
+
if (!runtimeConfig) {
|
|
2051
|
+
res.status(422).json({ error: "runtimeConfig must be an object" });
|
|
2052
|
+
return;
|
|
2053
|
+
}
|
|
2054
|
+
assertNoAgentRuntimeConfigAdapterConfigMutation(req, runtimeConfig);
|
|
2055
|
+
requestedRuntimeConfig = runtimeConfig;
|
|
2056
|
+
}
|
|
2057
|
+
const touchesAdapterConfiguration = hasOwn(patchData, "adapterType") ||
|
|
2058
|
+
hasOwn(patchData, "adapterConfig");
|
|
2059
|
+
if (touchesAdapterConfiguration) {
|
|
2060
|
+
const existingAdapterConfig = asRecord(existing.adapterConfig) ?? {};
|
|
2061
|
+
const changingAdapterType = typeof patchData.adapterType === "string" && patchData.adapterType !== existing.adapterType;
|
|
2062
|
+
const requestedAdapterConfig = hasOwn(patchData, "adapterConfig")
|
|
2063
|
+
? (asRecord(patchData.adapterConfig) ?? {})
|
|
2064
|
+
: null;
|
|
2065
|
+
if (requestedAdapterConfig
|
|
2066
|
+
&& replaceAdapterConfig
|
|
2067
|
+
&& KNOWN_INSTRUCTIONS_BUNDLE_KEYS.some((key) => existingAdapterConfig[key] !== undefined && requestedAdapterConfig[key] === undefined)) {
|
|
2068
|
+
await assertCanManageInstructionsPath(req, existing);
|
|
2069
|
+
}
|
|
2070
|
+
let rawEffectiveAdapterConfig = requestedAdapterConfig ?? existingAdapterConfig;
|
|
2071
|
+
if (requestedAdapterConfig && !changingAdapterType && !replaceAdapterConfig) {
|
|
2072
|
+
rawEffectiveAdapterConfig = { ...existingAdapterConfig, ...requestedAdapterConfig };
|
|
2073
|
+
}
|
|
2074
|
+
if (changingAdapterType) {
|
|
2075
|
+
// Preserve adapter-agnostic keys (env, cwd, etc.) from the existing config
|
|
2076
|
+
// when the adapter type changes. Without this, a PATCH that includes
|
|
2077
|
+
// adapterConfig but omits these keys would silently drop them.
|
|
2078
|
+
const ADAPTER_AGNOSTIC_KEYS = [
|
|
2079
|
+
"env", "cwd", "timeoutSec", "graceSec",
|
|
2080
|
+
"promptTemplate", "bootstrapPromptTemplate",
|
|
2081
|
+
];
|
|
2082
|
+
for (const key of ADAPTER_AGNOSTIC_KEYS) {
|
|
2083
|
+
if (rawEffectiveAdapterConfig[key] === undefined && existingAdapterConfig[key] !== undefined) {
|
|
2084
|
+
rawEffectiveAdapterConfig = { ...rawEffectiveAdapterConfig, [key]: existingAdapterConfig[key] };
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
2087
|
+
rawEffectiveAdapterConfig = preserveInstructionsBundleConfig(existingAdapterConfig, rawEffectiveAdapterConfig);
|
|
2088
|
+
}
|
|
2089
|
+
const effectiveAdapterConfig = applyCreateDefaultsByAdapterType(requestedAdapterType, rawEffectiveAdapterConfig);
|
|
2090
|
+
const normalizedEffectiveAdapterConfig = await normalizeMediatedAdapterConfigForPersistence({
|
|
2091
|
+
companyId: existing.companyId,
|
|
2092
|
+
adapterType: requestedAdapterType,
|
|
2093
|
+
adapterConfig: effectiveAdapterConfig,
|
|
2094
|
+
});
|
|
2095
|
+
patchData.adapterConfig = syncInstructionsBundleConfigFromFilePath(existing, normalizedEffectiveAdapterConfig);
|
|
2096
|
+
}
|
|
2097
|
+
if (requestedRuntimeConfig) {
|
|
2098
|
+
const baseAdapterConfig = asRecord(patchData.adapterConfig) ?? asRecord(existing.adapterConfig) ?? {};
|
|
2099
|
+
patchData.runtimeConfig = await normalizeRuntimeConfigAdapterConfigsForPersistence(existing.companyId, requestedAdapterType, requestedRuntimeConfig, baseAdapterConfig);
|
|
2100
|
+
}
|
|
2101
|
+
if (touchesAdapterConfiguration || Object.prototype.hasOwnProperty.call(patchData, "defaultEnvironmentId")) {
|
|
2102
|
+
await assertAgentDefaultEnvironmentSelection(existing.companyId, Object.prototype.hasOwnProperty.call(patchData, "defaultEnvironmentId")
|
|
2103
|
+
? (typeof patchData.defaultEnvironmentId === "string" ? patchData.defaultEnvironmentId : null)
|
|
2104
|
+
: existing.defaultEnvironmentId, {
|
|
2105
|
+
allowedDrivers: allowedEnvironmentDriversForAgent(requestedAdapterType),
|
|
2106
|
+
allowedSandboxProviders: allowedSandboxProvidersForAgent(requestedAdapterType),
|
|
2107
|
+
});
|
|
2108
|
+
}
|
|
2109
|
+
const actor = getActorInfo(req);
|
|
2110
|
+
const agent = await svc.update(id, patchData, {
|
|
2111
|
+
recordRevision: {
|
|
2112
|
+
createdByAgentId: actor.agentId,
|
|
2113
|
+
createdByUserId: actor.actorType === "user" ? actor.actorId : null,
|
|
2114
|
+
source: "patch",
|
|
2115
|
+
},
|
|
2116
|
+
});
|
|
2117
|
+
if (!agent) {
|
|
2118
|
+
res.status(404).json({ error: "Agent not found" });
|
|
2119
|
+
return;
|
|
2120
|
+
}
|
|
2121
|
+
await logActivity(db, {
|
|
2122
|
+
companyId: agent.companyId,
|
|
2123
|
+
actorType: actor.actorType,
|
|
2124
|
+
actorId: actor.actorId,
|
|
2125
|
+
agentId: actor.agentId,
|
|
2126
|
+
runId: actor.runId,
|
|
2127
|
+
action: "agent.updated",
|
|
2128
|
+
entityType: "agent",
|
|
2129
|
+
entityId: agent.id,
|
|
2130
|
+
details: summarizeAgentUpdateDetails(patchData),
|
|
2131
|
+
});
|
|
2132
|
+
res.json(agent);
|
|
2133
|
+
});
|
|
2134
|
+
router.post("/agents/:id/pause", async (req, res) => {
|
|
2135
|
+
assertBoard(req);
|
|
2136
|
+
const id = req.params.id;
|
|
2137
|
+
if (!(await getAccessibleAgent(req, res, id))) {
|
|
2138
|
+
return;
|
|
2139
|
+
}
|
|
2140
|
+
const agent = await svc.pause(id);
|
|
2141
|
+
if (!agent) {
|
|
2142
|
+
res.status(404).json({ error: "Agent not found" });
|
|
2143
|
+
return;
|
|
2144
|
+
}
|
|
2145
|
+
await heartbeat.cancelActiveForAgent(id);
|
|
2146
|
+
await logActivity(db, {
|
|
2147
|
+
companyId: agent.companyId,
|
|
2148
|
+
actorType: "user",
|
|
2149
|
+
actorId: req.actor.userId ?? "board",
|
|
2150
|
+
action: "agent.paused",
|
|
2151
|
+
entityType: "agent",
|
|
2152
|
+
entityId: agent.id,
|
|
2153
|
+
});
|
|
2154
|
+
res.json(agent);
|
|
2155
|
+
});
|
|
2156
|
+
router.post("/agents/:id/resume", async (req, res) => {
|
|
2157
|
+
assertBoard(req);
|
|
2158
|
+
const id = req.params.id;
|
|
2159
|
+
if (!(await getAccessibleAgent(req, res, id))) {
|
|
2160
|
+
return;
|
|
2161
|
+
}
|
|
2162
|
+
const agent = await svc.resume(id);
|
|
2163
|
+
if (!agent) {
|
|
2164
|
+
res.status(404).json({ error: "Agent not found" });
|
|
2165
|
+
return;
|
|
2166
|
+
}
|
|
2167
|
+
await logActivity(db, {
|
|
2168
|
+
companyId: agent.companyId,
|
|
2169
|
+
actorType: "user",
|
|
2170
|
+
actorId: req.actor.userId ?? "board",
|
|
2171
|
+
action: "agent.resumed",
|
|
2172
|
+
entityType: "agent",
|
|
2173
|
+
entityId: agent.id,
|
|
2174
|
+
});
|
|
2175
|
+
res.json(agent);
|
|
2176
|
+
});
|
|
2177
|
+
router.post("/agents/:id/approve", async (req, res) => {
|
|
2178
|
+
assertBoard(req);
|
|
2179
|
+
const id = req.params.id;
|
|
2180
|
+
const existing = await getAccessibleAgent(req, res, id);
|
|
2181
|
+
if (!existing) {
|
|
2182
|
+
return;
|
|
2183
|
+
}
|
|
2184
|
+
if (existing.status !== "pending_approval") {
|
|
2185
|
+
res.status(409).json({ error: "Only pending approval agents can be approved" });
|
|
2186
|
+
return;
|
|
2187
|
+
}
|
|
2188
|
+
const approval = await svc.activatePendingApproval(id);
|
|
2189
|
+
if (!approval) {
|
|
2190
|
+
res.status(404).json({ error: "Agent not found" });
|
|
2191
|
+
return;
|
|
2192
|
+
}
|
|
2193
|
+
if (!approval.activated) {
|
|
2194
|
+
res.status(409).json({ error: "Only pending approval agents can be approved" });
|
|
2195
|
+
return;
|
|
2196
|
+
}
|
|
2197
|
+
const { agent } = approval;
|
|
2198
|
+
await logActivity(db, {
|
|
2199
|
+
companyId: agent.companyId,
|
|
2200
|
+
actorType: "user",
|
|
2201
|
+
actorId: req.actor.userId ?? "board",
|
|
2202
|
+
action: "agent.approved",
|
|
2203
|
+
entityType: "agent",
|
|
2204
|
+
entityId: agent.id,
|
|
2205
|
+
details: { source: "agent_detail" },
|
|
2206
|
+
});
|
|
2207
|
+
res.json(agent);
|
|
2208
|
+
});
|
|
2209
|
+
router.post("/agents/:id/terminate", async (req, res) => {
|
|
2210
|
+
assertBoard(req);
|
|
2211
|
+
const id = req.params.id;
|
|
2212
|
+
if (!(await getAccessibleAgent(req, res, id))) {
|
|
2213
|
+
return;
|
|
2214
|
+
}
|
|
2215
|
+
const agent = await svc.terminate(id);
|
|
2216
|
+
if (!agent) {
|
|
2217
|
+
res.status(404).json({ error: "Agent not found" });
|
|
2218
|
+
return;
|
|
2219
|
+
}
|
|
2220
|
+
await heartbeat.cancelActiveForAgent(id);
|
|
2221
|
+
await logActivity(db, {
|
|
2222
|
+
companyId: agent.companyId,
|
|
2223
|
+
actorType: "user",
|
|
2224
|
+
actorId: req.actor.userId ?? "board",
|
|
2225
|
+
action: "agent.terminated",
|
|
2226
|
+
entityType: "agent",
|
|
2227
|
+
entityId: agent.id,
|
|
2228
|
+
});
|
|
2229
|
+
res.json(agent);
|
|
2230
|
+
});
|
|
2231
|
+
router.delete("/agents/:id", async (req, res) => {
|
|
2232
|
+
assertBoard(req);
|
|
2233
|
+
const id = req.params.id;
|
|
2234
|
+
if (!(await getAccessibleAgent(req, res, id))) {
|
|
2235
|
+
return;
|
|
2236
|
+
}
|
|
2237
|
+
const agent = await svc.remove(id);
|
|
2238
|
+
if (!agent) {
|
|
2239
|
+
res.status(404).json({ error: "Agent not found" });
|
|
2240
|
+
return;
|
|
2241
|
+
}
|
|
2242
|
+
await logActivity(db, {
|
|
2243
|
+
companyId: agent.companyId,
|
|
2244
|
+
actorType: "user",
|
|
2245
|
+
actorId: req.actor.userId ?? "board",
|
|
2246
|
+
action: "agent.deleted",
|
|
2247
|
+
entityType: "agent",
|
|
2248
|
+
entityId: agent.id,
|
|
2249
|
+
});
|
|
2250
|
+
res.json({ ok: true });
|
|
2251
|
+
});
|
|
2252
|
+
router.get("/agents/:id/keys", async (req, res) => {
|
|
2253
|
+
assertBoard(req);
|
|
2254
|
+
const id = req.params.id;
|
|
2255
|
+
const agent = await getAccessibleAgent(req, res, id);
|
|
2256
|
+
if (!agent) {
|
|
2257
|
+
return;
|
|
2258
|
+
}
|
|
2259
|
+
const keys = await svc.listKeys(id);
|
|
2260
|
+
res.json(keys);
|
|
2261
|
+
});
|
|
2262
|
+
router.post("/agents/:id/keys", validate(createAgentKeySchema), async (req, res) => {
|
|
2263
|
+
assertBoard(req);
|
|
2264
|
+
const id = req.params.id;
|
|
2265
|
+
const agent = await getAccessibleAgent(req, res, id);
|
|
2266
|
+
if (!agent) {
|
|
2267
|
+
return;
|
|
2268
|
+
}
|
|
2269
|
+
const key = await svc.createApiKey(id, req.body.name);
|
|
2270
|
+
await logActivity(db, {
|
|
2271
|
+
companyId: agent.companyId,
|
|
2272
|
+
actorType: "user",
|
|
2273
|
+
actorId: req.actor.userId ?? "board",
|
|
2274
|
+
action: "agent.key_created",
|
|
2275
|
+
entityType: "agent",
|
|
2276
|
+
entityId: agent.id,
|
|
2277
|
+
details: { keyId: key.id, name: key.name },
|
|
2278
|
+
});
|
|
2279
|
+
res.status(201).json(key);
|
|
2280
|
+
});
|
|
2281
|
+
router.delete("/agents/:id/keys/:keyId", async (req, res) => {
|
|
2282
|
+
assertBoard(req);
|
|
2283
|
+
const id = req.params.id;
|
|
2284
|
+
const keyId = req.params.keyId;
|
|
2285
|
+
const agent = await getAccessibleAgent(req, res, id);
|
|
2286
|
+
if (!agent) {
|
|
2287
|
+
return;
|
|
2288
|
+
}
|
|
2289
|
+
const key = await svc.getKeyById(keyId);
|
|
2290
|
+
if (!key || key.agentId !== agent.id) {
|
|
2291
|
+
res.status(404).json({ error: "Key not found" });
|
|
2292
|
+
return;
|
|
2293
|
+
}
|
|
2294
|
+
const revoked = await svc.revokeKey(agent.id, keyId);
|
|
2295
|
+
if (!revoked) {
|
|
2296
|
+
res.status(404).json({ error: "Key not found" });
|
|
2297
|
+
return;
|
|
2298
|
+
}
|
|
2299
|
+
await logActivity(db, {
|
|
2300
|
+
companyId: agent.companyId,
|
|
2301
|
+
actorType: "user",
|
|
2302
|
+
actorId: req.actor.userId ?? "board",
|
|
2303
|
+
action: "agent.key_revoked",
|
|
2304
|
+
entityType: "agent",
|
|
2305
|
+
entityId: agent.id,
|
|
2306
|
+
details: { keyId: key.id, name: key.name },
|
|
2307
|
+
});
|
|
2308
|
+
res.json({ ok: true });
|
|
2309
|
+
});
|
|
2310
|
+
const handleWakeupRoute = async (req, res, opts) => {
|
|
2311
|
+
const id = req.params.id;
|
|
2312
|
+
const agent = await svc.getById(id);
|
|
2313
|
+
if (!agent) {
|
|
2314
|
+
res.status(404).json({ error: "Agent not found" });
|
|
2315
|
+
return;
|
|
2316
|
+
}
|
|
2317
|
+
assertCompanyAccess(req, agent.companyId);
|
|
2318
|
+
if (req.actor.type === "agent") {
|
|
2319
|
+
if (req.actor.agentId !== id) {
|
|
2320
|
+
res.status(403).json({ error: "Agent can only invoke itself" });
|
|
2321
|
+
return;
|
|
2322
|
+
}
|
|
2323
|
+
}
|
|
2324
|
+
else {
|
|
2325
|
+
await assertBoardCanManageAgentsForCompany(req, agent.companyId);
|
|
2326
|
+
}
|
|
2327
|
+
const run = await heartbeat.wakeup(id, {
|
|
2328
|
+
source: opts.source,
|
|
2329
|
+
triggerDetail: req.body.triggerDetail ?? "manual",
|
|
2330
|
+
reason: req.body.reason ?? null,
|
|
2331
|
+
payload: req.body.payload ?? null,
|
|
2332
|
+
idempotencyKey: req.body.idempotencyKey ?? null,
|
|
2333
|
+
requestedByActorType: req.actor.type === "agent" ? "agent" : "user",
|
|
2334
|
+
requestedByActorId: req.actor.type === "agent" ? req.actor.agentId ?? null : req.actor.userId ?? null,
|
|
2335
|
+
contextSnapshot: {
|
|
2336
|
+
triggeredBy: req.actor.type,
|
|
2337
|
+
actorId: req.actor.type === "agent" ? req.actor.agentId : req.actor.userId,
|
|
2338
|
+
forceFreshSession: req.body.forceFreshSession === true,
|
|
2339
|
+
},
|
|
2340
|
+
});
|
|
2341
|
+
if (!run) {
|
|
2342
|
+
res.status(202).json(await opts.skippedResponse(agent));
|
|
2343
|
+
return;
|
|
2344
|
+
}
|
|
2345
|
+
const actor = getActorInfo(req);
|
|
2346
|
+
await logActivity(db, {
|
|
2347
|
+
companyId: agent.companyId,
|
|
2348
|
+
actorType: actor.actorType,
|
|
2349
|
+
actorId: actor.actorId,
|
|
2350
|
+
agentId: actor.agentId,
|
|
2351
|
+
runId: actor.runId,
|
|
2352
|
+
action: "heartbeat.invoked",
|
|
2353
|
+
entityType: "heartbeat_run",
|
|
2354
|
+
entityId: run.id,
|
|
2355
|
+
details: { agentId: id },
|
|
2356
|
+
});
|
|
2357
|
+
res.status(202).json(run);
|
|
2358
|
+
};
|
|
2359
|
+
router.post("/agents/:id/wakeup", validate(wakeAgentSchema), async (req, res) => {
|
|
2360
|
+
await handleWakeupRoute(req, res, {
|
|
2361
|
+
source: req.body.source,
|
|
2362
|
+
skippedResponse: (agent) => buildSkippedWakeupResponse(agent, req.body.payload ?? null),
|
|
2363
|
+
});
|
|
2364
|
+
});
|
|
2365
|
+
router.post("/agents/:id/heartbeat/invoke", async (req, res) => {
|
|
2366
|
+
// Legacy endpoint. Hardcodes `source: "on_demand"` (the prior behavior
|
|
2367
|
+
// before the wakeup/invoke convergence). Reads scope fields directly off
|
|
2368
|
+
// the body without `validate(wakeAgentSchema)` because callers — including
|
|
2369
|
+
// the e2e suite — post an empty body, and the schema rejects undefined
|
|
2370
|
+
// / missing bodies. Only forwards fields the caller actually supplied so
|
|
2371
|
+
// an empty body produces the original fixed-arg `heartbeat.invoke()`
|
|
2372
|
+
// shape exactly.
|
|
2373
|
+
const id = req.params.id;
|
|
2374
|
+
const agent = await svc.getById(id);
|
|
2375
|
+
if (!agent) {
|
|
2376
|
+
res.status(404).json({ error: "Agent not found" });
|
|
2377
|
+
return;
|
|
2378
|
+
}
|
|
2379
|
+
assertCompanyAccess(req, agent.companyId);
|
|
2380
|
+
if (req.actor.type === "agent") {
|
|
2381
|
+
if (req.actor.agentId !== id) {
|
|
2382
|
+
res.status(403).json({ error: "Agent can only invoke itself" });
|
|
2383
|
+
return;
|
|
2384
|
+
}
|
|
2385
|
+
}
|
|
2386
|
+
else {
|
|
2387
|
+
await assertBoardCanManageAgentsForCompany(req, agent.companyId);
|
|
2388
|
+
}
|
|
2389
|
+
const body = (req.body ?? {});
|
|
2390
|
+
const contextSnapshot = {
|
|
2391
|
+
triggeredBy: req.actor.type,
|
|
2392
|
+
actorId: req.actor.type === "agent" ? req.actor.agentId : req.actor.userId,
|
|
2393
|
+
};
|
|
2394
|
+
if (body.forceFreshSession === true) {
|
|
2395
|
+
contextSnapshot.forceFreshSession = true;
|
|
2396
|
+
}
|
|
2397
|
+
const wakeOpts = {
|
|
2398
|
+
source: "on_demand",
|
|
2399
|
+
triggerDetail: typeof body.triggerDetail === "string" ? body.triggerDetail : "manual",
|
|
2400
|
+
requestedByActorType: req.actor.type === "agent" ? "agent" : "user",
|
|
2401
|
+
requestedByActorId: req.actor.type === "agent" ? req.actor.agentId ?? null : req.actor.userId ?? null,
|
|
2402
|
+
contextSnapshot,
|
|
2403
|
+
};
|
|
2404
|
+
if (typeof body.reason === "string" && body.reason.length > 0) {
|
|
2405
|
+
wakeOpts.reason = body.reason;
|
|
2406
|
+
}
|
|
2407
|
+
if (body.payload && typeof body.payload === "object" && !Array.isArray(body.payload)) {
|
|
2408
|
+
wakeOpts.payload = body.payload;
|
|
2409
|
+
}
|
|
2410
|
+
if (typeof body.idempotencyKey === "string" && body.idempotencyKey.length > 0) {
|
|
2411
|
+
wakeOpts.idempotencyKey = body.idempotencyKey;
|
|
2412
|
+
}
|
|
2413
|
+
const run = await heartbeat.wakeup(id, wakeOpts);
|
|
2414
|
+
if (!run) {
|
|
2415
|
+
res.status(202).json({ status: "skipped" });
|
|
2416
|
+
return;
|
|
2417
|
+
}
|
|
2418
|
+
const actor = getActorInfo(req);
|
|
2419
|
+
await logActivity(db, {
|
|
2420
|
+
companyId: agent.companyId,
|
|
2421
|
+
actorType: actor.actorType,
|
|
2422
|
+
actorId: actor.actorId,
|
|
2423
|
+
agentId: actor.agentId,
|
|
2424
|
+
runId: actor.runId,
|
|
2425
|
+
action: "heartbeat.invoked",
|
|
2426
|
+
entityType: "heartbeat_run",
|
|
2427
|
+
entityId: run.id,
|
|
2428
|
+
details: { agentId: id },
|
|
2429
|
+
});
|
|
2430
|
+
res.status(202).json(run);
|
|
2431
|
+
});
|
|
2432
|
+
router.post("/agents/:id/claude-login", async (req, res) => {
|
|
2433
|
+
assertBoard(req);
|
|
2434
|
+
const id = req.params.id;
|
|
2435
|
+
const agent = await svc.getById(id);
|
|
2436
|
+
if (!agent) {
|
|
2437
|
+
res.status(404).json({ error: "Agent not found" });
|
|
2438
|
+
return;
|
|
2439
|
+
}
|
|
2440
|
+
await assertBoardCanManageAgentsForCompany(req, agent.companyId);
|
|
2441
|
+
assertCompanyAccess(req, agent.companyId);
|
|
2442
|
+
if (agent.adapterType !== "claude_local") {
|
|
2443
|
+
res.status(400).json({ error: "Login is only supported for claude_local agents" });
|
|
2444
|
+
return;
|
|
2445
|
+
}
|
|
2446
|
+
const config = asRecord(agent.adapterConfig) ?? {};
|
|
2447
|
+
const { config: runtimeConfig } = await secretsSvc.resolveAdapterConfigForRuntime(agent.companyId, config);
|
|
2448
|
+
const result = await runClaudeLogin({
|
|
2449
|
+
runId: `claude-login-${randomUUID()}`,
|
|
2450
|
+
agent: {
|
|
2451
|
+
id: agent.id,
|
|
2452
|
+
companyId: agent.companyId,
|
|
2453
|
+
name: agent.name,
|
|
2454
|
+
adapterType: agent.adapterType,
|
|
2455
|
+
adapterConfig: agent.adapterConfig,
|
|
2456
|
+
},
|
|
2457
|
+
config: runtimeConfig,
|
|
2458
|
+
});
|
|
2459
|
+
res.json(result);
|
|
2460
|
+
});
|
|
2461
|
+
router.get("/companies/:companyId/heartbeat-runs", async (req, res) => {
|
|
2462
|
+
const companyId = req.params.companyId;
|
|
2463
|
+
assertCompanyAccess(req, companyId);
|
|
2464
|
+
const agentId = req.query.agentId;
|
|
2465
|
+
const limitParam = req.query.limit;
|
|
2466
|
+
const limit = limitParam ? Math.max(1, Math.min(1000, parseInt(limitParam, 10) || 200)) : undefined;
|
|
2467
|
+
const runs = await heartbeat.list(companyId, agentId, limit);
|
|
2468
|
+
res.json(runs);
|
|
2469
|
+
});
|
|
2470
|
+
router.get("/companies/:companyId/live-runs", async (req, res) => {
|
|
2471
|
+
const companyId = req.params.companyId;
|
|
2472
|
+
assertCompanyAccess(req, companyId);
|
|
2473
|
+
// `minCount` is a padding floor for callers that want a minimum number of
|
|
2474
|
+
// recent runs to render (e.g. dashboard cards). It must default to 0 so
|
|
2475
|
+
// callers asking for "live runs" get only actually-live runs — otherwise
|
|
2476
|
+
// every caller with no minCount param gets up to 50 historical runs
|
|
2477
|
+
// padded in and renders bogus "live" counts.
|
|
2478
|
+
const minCount = readLiveRunsQueryInt(req.query.minCount, 50, 0);
|
|
2479
|
+
const limit = readLiveRunsQueryInt(req.query.limit, 50, 50);
|
|
2480
|
+
const columns = {
|
|
2481
|
+
id: heartbeatRuns.id,
|
|
2482
|
+
companyId: heartbeatRuns.companyId,
|
|
2483
|
+
status: heartbeatRuns.status,
|
|
2484
|
+
invocationSource: heartbeatRuns.invocationSource,
|
|
2485
|
+
triggerDetail: heartbeatRuns.triggerDetail,
|
|
2486
|
+
contextCommentId: sql `${heartbeatRuns.contextSnapshot} ->> 'commentId'`.as("contextCommentId"),
|
|
2487
|
+
contextWakeCommentId: sql `${heartbeatRuns.contextSnapshot} ->> 'wakeCommentId'`.as("contextWakeCommentId"),
|
|
2488
|
+
startedAt: heartbeatRuns.startedAt,
|
|
2489
|
+
finishedAt: heartbeatRuns.finishedAt,
|
|
2490
|
+
createdAt: heartbeatRuns.createdAt,
|
|
2491
|
+
agentId: heartbeatRuns.agentId,
|
|
2492
|
+
agentName: agentsTable.name,
|
|
2493
|
+
adapterType: agentsTable.adapterType,
|
|
2494
|
+
logBytes: heartbeatRuns.logBytes,
|
|
2495
|
+
livenessState: heartbeatRuns.livenessState,
|
|
2496
|
+
livenessReason: heartbeatRuns.livenessReason,
|
|
2497
|
+
continuationAttempt: heartbeatRuns.continuationAttempt,
|
|
2498
|
+
lastUsefulActionAt: heartbeatRuns.lastUsefulActionAt,
|
|
2499
|
+
nextAction: heartbeatRuns.nextAction,
|
|
2500
|
+
lastOutputAt: heartbeatRuns.lastOutputAt,
|
|
2501
|
+
lastOutputSeq: heartbeatRuns.lastOutputSeq,
|
|
2502
|
+
lastOutputStream: heartbeatRuns.lastOutputStream,
|
|
2503
|
+
lastOutputBytes: heartbeatRuns.lastOutputBytes,
|
|
2504
|
+
processStartedAt: heartbeatRuns.processStartedAt,
|
|
2505
|
+
issueId: sql `${heartbeatRuns.contextSnapshot} ->> 'issueId'`.as("issueId"),
|
|
2506
|
+
};
|
|
2507
|
+
const liveRunsQuery = db
|
|
2508
|
+
.select(columns)
|
|
2509
|
+
.from(heartbeatRuns)
|
|
2510
|
+
.innerJoin(agentsTable, eq(heartbeatRuns.agentId, agentsTable.id))
|
|
2511
|
+
.where(and(eq(heartbeatRuns.companyId, companyId), inArray(heartbeatRuns.status, ["queued", "running"])))
|
|
2512
|
+
.orderBy(desc(heartbeatRuns.createdAt));
|
|
2513
|
+
const liveRuns = await liveRunsQuery.limit(limit);
|
|
2514
|
+
const targetRunCount = Math.min(minCount, limit);
|
|
2515
|
+
if (targetRunCount > 0 && liveRuns.length < targetRunCount) {
|
|
2516
|
+
const activeIds = liveRuns.map((r) => r.id);
|
|
2517
|
+
const recentRuns = await db
|
|
2518
|
+
.select(columns)
|
|
2519
|
+
.from(heartbeatRuns)
|
|
2520
|
+
.innerJoin(agentsTable, eq(heartbeatRuns.agentId, agentsTable.id))
|
|
2521
|
+
.where(and(eq(heartbeatRuns.companyId, companyId), not(inArray(heartbeatRuns.status, ["queued", "running"])), ...(activeIds.length > 0 ? [not(inArray(heartbeatRuns.id, activeIds))] : [])))
|
|
2522
|
+
.orderBy(desc(heartbeatRuns.createdAt))
|
|
2523
|
+
.limit(targetRunCount - liveRuns.length);
|
|
2524
|
+
const rows = [...liveRuns, ...recentRuns];
|
|
2525
|
+
res.json(await Promise.all(rows.map(async (run) => ({
|
|
2526
|
+
...run,
|
|
2527
|
+
outputSilence: await heartbeat.buildRunOutputSilence(run),
|
|
2528
|
+
}))));
|
|
2529
|
+
return;
|
|
2530
|
+
}
|
|
2531
|
+
res.json(await Promise.all(liveRuns.map(async (run) => ({
|
|
2532
|
+
...run,
|
|
2533
|
+
outputSilence: await heartbeat.buildRunOutputSilence(run),
|
|
2534
|
+
}))));
|
|
2535
|
+
});
|
|
2536
|
+
router.get("/heartbeat-runs/:runId", async (req, res) => {
|
|
2537
|
+
const runId = req.params.runId;
|
|
2538
|
+
const run = await heartbeat.getRun(runId);
|
|
2539
|
+
if (!run) {
|
|
2540
|
+
res.status(404).json({ error: "Heartbeat run not found" });
|
|
2541
|
+
return;
|
|
2542
|
+
}
|
|
2543
|
+
assertCompanyAccess(req, run.companyId);
|
|
2544
|
+
const retryExhaustedReason = await heartbeat.getRetryExhaustedReason(runId);
|
|
2545
|
+
res.json(redactCurrentUserValue({ ...run, retryExhaustedReason, outputSilence: await heartbeat.buildRunOutputSilence(run) }, await getCurrentUserRedactionOptions()));
|
|
2546
|
+
});
|
|
2547
|
+
router.post("/heartbeat-runs/:runId/cancel", async (req, res) => {
|
|
2548
|
+
assertBoard(req);
|
|
2549
|
+
const runId = req.params.runId;
|
|
2550
|
+
const existing = await heartbeat.getRun(runId);
|
|
2551
|
+
if (existing) {
|
|
2552
|
+
assertCompanyAccess(req, existing.companyId);
|
|
2553
|
+
}
|
|
2554
|
+
const run = await heartbeat.cancelRun(runId);
|
|
2555
|
+
if (run) {
|
|
2556
|
+
await logActivity(db, {
|
|
2557
|
+
companyId: run.companyId,
|
|
2558
|
+
actorType: "user",
|
|
2559
|
+
actorId: req.actor.userId ?? "board",
|
|
2560
|
+
action: "heartbeat.cancelled",
|
|
2561
|
+
entityType: "heartbeat_run",
|
|
2562
|
+
entityId: run.id,
|
|
2563
|
+
details: { agentId: run.agentId },
|
|
2564
|
+
});
|
|
2565
|
+
}
|
|
2566
|
+
res.json(run);
|
|
2567
|
+
});
|
|
2568
|
+
router.post("/heartbeat-runs/:runId/watchdog-decisions", async (req, res) => {
|
|
2569
|
+
const runId = req.params.runId;
|
|
2570
|
+
const existing = await heartbeat.getRun(runId);
|
|
2571
|
+
if (!existing) {
|
|
2572
|
+
res.status(404).json({ error: "Heartbeat run not found" });
|
|
2573
|
+
return;
|
|
2574
|
+
}
|
|
2575
|
+
assertCompanyAccess(req, existing.companyId);
|
|
2576
|
+
const decision = typeof req.body?.decision === "string" ? req.body.decision : "";
|
|
2577
|
+
if (!["snooze", "continue", "dismissed_false_positive"].includes(decision)) {
|
|
2578
|
+
res.status(400).json({ error: "Unsupported watchdog decision" });
|
|
2579
|
+
return;
|
|
2580
|
+
}
|
|
2581
|
+
const evaluationIssueId = typeof req.body?.evaluationIssueId === "string" ? req.body.evaluationIssueId : null;
|
|
2582
|
+
const reason = typeof req.body?.reason === "string" ? req.body.reason.slice(0, 4000) : null;
|
|
2583
|
+
const snoozedUntil = decision === "snooze"
|
|
2584
|
+
? new Date(String(req.body?.snoozedUntil ?? ""))
|
|
2585
|
+
: null;
|
|
2586
|
+
if (decision === "snooze" && (!snoozedUntil || Number.isNaN(snoozedUntil.getTime()) || snoozedUntil <= new Date())) {
|
|
2587
|
+
res.status(400).json({ error: "snoozedUntil must be a future ISO datetime" });
|
|
2588
|
+
return;
|
|
2589
|
+
}
|
|
2590
|
+
const row = await recovery.recordWatchdogDecision({
|
|
2591
|
+
runId: existing.id,
|
|
2592
|
+
actor: req.actor,
|
|
2593
|
+
decision: decision,
|
|
2594
|
+
evaluationIssueId,
|
|
2595
|
+
reason,
|
|
2596
|
+
snoozedUntil,
|
|
2597
|
+
createdByRunId: req.actor.runId ?? null,
|
|
2598
|
+
});
|
|
2599
|
+
res.json(row);
|
|
2600
|
+
});
|
|
2601
|
+
router.get("/heartbeat-runs/:runId/events", async (req, res) => {
|
|
2602
|
+
const runId = req.params.runId;
|
|
2603
|
+
const run = await heartbeat.getRun(runId);
|
|
2604
|
+
if (!run) {
|
|
2605
|
+
res.status(404).json({ error: "Heartbeat run not found" });
|
|
2606
|
+
return;
|
|
2607
|
+
}
|
|
2608
|
+
assertCompanyAccess(req, run.companyId);
|
|
2609
|
+
const afterSeq = Number(req.query.afterSeq ?? 0);
|
|
2610
|
+
const limit = Number(req.query.limit ?? 200);
|
|
2611
|
+
const events = await heartbeat.listEvents(runId, Number.isFinite(afterSeq) ? afterSeq : 0, Number.isFinite(limit) ? limit : 200);
|
|
2612
|
+
const currentUserRedactionOptions = await getCurrentUserRedactionOptions();
|
|
2613
|
+
const redactedEvents = events.map((event) => redactCurrentUserValue({
|
|
2614
|
+
...event,
|
|
2615
|
+
payload: redactEventPayload(event.payload),
|
|
2616
|
+
}, currentUserRedactionOptions));
|
|
2617
|
+
res.json(redactedEvents);
|
|
2618
|
+
});
|
|
2619
|
+
router.get("/heartbeat-runs/:runId/log", async (req, res) => {
|
|
2620
|
+
const runId = req.params.runId;
|
|
2621
|
+
const run = await heartbeat.getRunLogAccess(runId);
|
|
2622
|
+
if (!run) {
|
|
2623
|
+
res.status(404).json({ error: "Heartbeat run not found" });
|
|
2624
|
+
return;
|
|
2625
|
+
}
|
|
2626
|
+
assertCompanyAccess(req, run.companyId);
|
|
2627
|
+
const offset = Number(req.query.offset ?? 0);
|
|
2628
|
+
const limitBytes = readRunLogLimitBytes(req.query.limitBytes);
|
|
2629
|
+
const result = await heartbeat.readLog(run, {
|
|
2630
|
+
offset: Number.isFinite(offset) ? offset : 0,
|
|
2631
|
+
limitBytes,
|
|
2632
|
+
});
|
|
2633
|
+
res.set("Cache-Control", "no-cache, no-store");
|
|
2634
|
+
res.json(result);
|
|
2635
|
+
});
|
|
2636
|
+
router.get("/heartbeat-runs/:runId/workspace-operations", async (req, res) => {
|
|
2637
|
+
const runId = req.params.runId;
|
|
2638
|
+
const run = await heartbeat.getRun(runId);
|
|
2639
|
+
if (!run) {
|
|
2640
|
+
res.status(404).json({ error: "Heartbeat run not found" });
|
|
2641
|
+
return;
|
|
2642
|
+
}
|
|
2643
|
+
assertCompanyAccess(req, run.companyId);
|
|
2644
|
+
const context = asRecord(run.contextSnapshot);
|
|
2645
|
+
const executionWorkspaceId = asNonEmptyString(context?.executionWorkspaceId);
|
|
2646
|
+
const operations = await workspaceOperations.listForRun(runId, executionWorkspaceId);
|
|
2647
|
+
res.json(redactCurrentUserValue(operations, await getCurrentUserRedactionOptions()));
|
|
2648
|
+
});
|
|
2649
|
+
router.get("/workspace-operations/:operationId/log", async (req, res) => {
|
|
2650
|
+
const operationId = req.params.operationId;
|
|
2651
|
+
const operation = await workspaceOperations.getById(operationId);
|
|
2652
|
+
if (!operation) {
|
|
2653
|
+
res.status(404).json({ error: "Workspace operation not found" });
|
|
2654
|
+
return;
|
|
2655
|
+
}
|
|
2656
|
+
assertCompanyAccess(req, operation.companyId);
|
|
2657
|
+
const offset = Number(req.query.offset ?? 0);
|
|
2658
|
+
const limitBytes = readRunLogLimitBytes(req.query.limitBytes);
|
|
2659
|
+
const result = await workspaceOperations.readLog(operationId, {
|
|
2660
|
+
offset: Number.isFinite(offset) ? offset : 0,
|
|
2661
|
+
limitBytes,
|
|
2662
|
+
});
|
|
2663
|
+
res.set("Cache-Control", "no-cache, no-store");
|
|
2664
|
+
res.json(result);
|
|
2665
|
+
});
|
|
2666
|
+
router.get("/issues/:issueId/live-runs", async (req, res) => {
|
|
2667
|
+
const rawId = req.params.issueId;
|
|
2668
|
+
const issueSvc = issueService(db);
|
|
2669
|
+
const identifier = normalizeIssueIdentifier(rawId);
|
|
2670
|
+
const issue = identifier ? await issueSvc.getByIdentifier(identifier) : await issueSvc.getById(rawId);
|
|
2671
|
+
if (!issue) {
|
|
2672
|
+
res.status(404).json({ error: "Issue not found" });
|
|
2673
|
+
return;
|
|
2674
|
+
}
|
|
2675
|
+
assertCompanyAccess(req, issue.companyId);
|
|
2676
|
+
const liveRuns = await db
|
|
2677
|
+
.select({
|
|
2678
|
+
id: heartbeatRuns.id,
|
|
2679
|
+
status: heartbeatRuns.status,
|
|
2680
|
+
invocationSource: heartbeatRuns.invocationSource,
|
|
2681
|
+
triggerDetail: heartbeatRuns.triggerDetail,
|
|
2682
|
+
contextCommentId: sql `${heartbeatRuns.contextSnapshot} ->> 'commentId'`.as("contextCommentId"),
|
|
2683
|
+
contextWakeCommentId: sql `${heartbeatRuns.contextSnapshot} ->> 'wakeCommentId'`.as("contextWakeCommentId"),
|
|
2684
|
+
startedAt: heartbeatRuns.startedAt,
|
|
2685
|
+
finishedAt: heartbeatRuns.finishedAt,
|
|
2686
|
+
createdAt: heartbeatRuns.createdAt,
|
|
2687
|
+
agentId: heartbeatRuns.agentId,
|
|
2688
|
+
agentName: agentsTable.name,
|
|
2689
|
+
adapterType: agentsTable.adapterType,
|
|
2690
|
+
logBytes: heartbeatRuns.logBytes,
|
|
2691
|
+
livenessState: heartbeatRuns.livenessState,
|
|
2692
|
+
livenessReason: heartbeatRuns.livenessReason,
|
|
2693
|
+
continuationAttempt: heartbeatRuns.continuationAttempt,
|
|
2694
|
+
lastUsefulActionAt: heartbeatRuns.lastUsefulActionAt,
|
|
2695
|
+
nextAction: heartbeatRuns.nextAction,
|
|
2696
|
+
lastOutputAt: heartbeatRuns.lastOutputAt,
|
|
2697
|
+
lastOutputSeq: heartbeatRuns.lastOutputSeq,
|
|
2698
|
+
lastOutputStream: heartbeatRuns.lastOutputStream,
|
|
2699
|
+
lastOutputBytes: heartbeatRuns.lastOutputBytes,
|
|
2700
|
+
processStartedAt: heartbeatRuns.processStartedAt,
|
|
2701
|
+
})
|
|
2702
|
+
.from(heartbeatRuns)
|
|
2703
|
+
.innerJoin(agentsTable, eq(heartbeatRuns.agentId, agentsTable.id))
|
|
2704
|
+
.where(and(eq(heartbeatRuns.companyId, issue.companyId), inArray(heartbeatRuns.status, ["queued", "running"]), sql `${heartbeatRuns.contextSnapshot} ->> 'issueId' = ${issue.id}`))
|
|
2705
|
+
.orderBy(desc(heartbeatRuns.createdAt));
|
|
2706
|
+
res.json(await Promise.all(liveRuns.map(async (run) => ({
|
|
2707
|
+
...run,
|
|
2708
|
+
outputSilence: await heartbeat.buildRunOutputSilence({ ...run, companyId: issue.companyId }),
|
|
2709
|
+
}))));
|
|
2710
|
+
});
|
|
2711
|
+
router.get("/issues/:issueId/active-run", async (req, res) => {
|
|
2712
|
+
const rawId = req.params.issueId;
|
|
2713
|
+
const issueSvc = issueService(db);
|
|
2714
|
+
const identifier = normalizeIssueIdentifier(rawId);
|
|
2715
|
+
const issue = identifier ? await issueSvc.getByIdentifier(identifier) : await issueSvc.getById(rawId);
|
|
2716
|
+
if (!issue) {
|
|
2717
|
+
res.status(404).json({ error: "Issue not found" });
|
|
2718
|
+
return;
|
|
2719
|
+
}
|
|
2720
|
+
assertCompanyAccess(req, issue.companyId);
|
|
2721
|
+
let run = issue.executionRunId ? await heartbeat.getRunIssueSummary(issue.executionRunId) : null;
|
|
2722
|
+
if (run &&
|
|
2723
|
+
((run.status !== "queued" && run.status !== "running") ||
|
|
2724
|
+
run.issueId !== issue.id)) {
|
|
2725
|
+
run = null;
|
|
2726
|
+
}
|
|
2727
|
+
if (!run && issue.assigneeAgentId && issue.status === "in_progress") {
|
|
2728
|
+
const candidateRun = await heartbeat.getActiveRunIssueSummaryForAgent(issue.assigneeAgentId);
|
|
2729
|
+
const candidateIssueId = asNonEmptyString(candidateRun?.issueId);
|
|
2730
|
+
if (candidateRun && candidateIssueId === issue.id) {
|
|
2731
|
+
run = candidateRun;
|
|
2732
|
+
}
|
|
2733
|
+
}
|
|
2734
|
+
if (!run) {
|
|
2735
|
+
res.json(null);
|
|
2736
|
+
return;
|
|
2737
|
+
}
|
|
2738
|
+
const agent = await svc.getById(run.agentId);
|
|
2739
|
+
if (!agent) {
|
|
2740
|
+
res.json(null);
|
|
2741
|
+
return;
|
|
2742
|
+
}
|
|
2743
|
+
res.json({
|
|
2744
|
+
...run,
|
|
2745
|
+
agentId: agent.id,
|
|
2746
|
+
agentName: agent.name,
|
|
2747
|
+
adapterType: agent.adapterType,
|
|
2748
|
+
outputSilence: await heartbeat.buildRunOutputSilence({ ...run, companyId: issue.companyId }),
|
|
2749
|
+
});
|
|
2750
|
+
});
|
|
2751
|
+
return router;
|
|
2752
|
+
}
|
|
2753
|
+
//# sourceMappingURL=agents.js.map
|