@penclipai/server 2026.704.0 → 2026.714.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/index.d.ts +1 -1
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js.map +1 -1
- package/dist/adapters/process/execute.d.ts.map +1 -1
- package/dist/adapters/process/execute.js +1 -0
- package/dist/adapters/process/execute.js.map +1 -1
- package/dist/adapters/registry.d.ts.map +1 -1
- package/dist/adapters/registry.js +79 -51
- package/dist/adapters/registry.js.map +1 -1
- package/dist/adapters/utils.d.ts +5 -0
- package/dist/adapters/utils.d.ts.map +1 -1
- package/dist/adapters/utils.js.map +1 -1
- package/dist/agent-auth-jwt.d.ts +5 -1
- package/dist/agent-auth-jwt.d.ts.map +1 -1
- package/dist/agent-auth-jwt.js +72 -20
- package/dist/agent-auth-jwt.js.map +1 -1
- package/dist/app.d.ts +2 -0
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +10 -0
- package/dist/app.js.map +1 -1
- package/dist/built-ins/agents/reflection-coach/AGENTS.md +47 -0
- package/dist/built-ins/agents/reflection-coach/routines/recent-agent-reflection.md +77 -0
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +2 -2
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +208 -116
- package/dist/index.js.map +1 -1
- package/dist/middleware/api-compression.d.ts +8 -0
- package/dist/middleware/api-compression.d.ts.map +1 -0
- package/dist/middleware/api-compression.js +207 -0
- package/dist/middleware/api-compression.js.map +1 -0
- package/dist/middleware/auth.d.ts.map +1 -1
- package/dist/middleware/auth.js +130 -3
- package/dist/middleware/auth.js.map +1 -1
- package/dist/middleware/error-handler.d.ts.map +1 -1
- package/dist/middleware/error-handler.js +27 -0
- package/dist/middleware/error-handler.js.map +1 -1
- package/dist/onboarding-assets/default/AGENTS.md +1 -0
- package/dist/redaction.d.ts.map +1 -1
- package/dist/redaction.js +11 -0
- package/dist/redaction.js.map +1 -1
- package/dist/routes/access.d.ts.map +1 -1
- package/dist/routes/access.js +4 -3
- package/dist/routes/access.js.map +1 -1
- package/dist/routes/adapters.d.ts.map +1 -1
- package/dist/routes/adapters.js +2 -0
- package/dist/routes/adapters.js.map +1 -1
- package/dist/routes/agents.d.ts.map +1 -1
- package/dist/routes/agents.js +222 -87
- package/dist/routes/agents.js.map +1 -1
- package/dist/routes/attention.d.ts +3 -0
- package/dist/routes/attention.d.ts.map +1 -0
- package/dist/routes/attention.js +24 -0
- package/dist/routes/attention.js.map +1 -0
- package/dist/routes/authz.d.ts.map +1 -1
- package/dist/routes/authz.js +29 -1
- package/dist/routes/authz.js.map +1 -1
- package/dist/routes/board-chat.d.ts.map +1 -1
- package/dist/routes/board-chat.js +4 -1
- package/dist/routes/board-chat.js.map +1 -1
- package/dist/routes/built-in-agents.d.ts +3 -0
- package/dist/routes/built-in-agents.d.ts.map +1 -0
- package/dist/routes/built-in-agents.js +267 -0
- package/dist/routes/built-in-agents.js.map +1 -0
- package/dist/routes/cases.d.ts +46 -0
- package/dist/routes/cases.d.ts.map +1 -0
- package/dist/routes/cases.js +1395 -0
- package/dist/routes/cases.js.map +1 -0
- package/dist/routes/companies.d.ts.map +1 -1
- package/dist/routes/companies.js +4 -1
- package/dist/routes/companies.js.map +1 -1
- package/dist/routes/company-skills.d.ts.map +1 -1
- package/dist/routes/company-skills.js +453 -33
- package/dist/routes/company-skills.js.map +1 -1
- package/dist/routes/environments.d.ts.map +1 -1
- package/dist/routes/environments.js +71 -8
- package/dist/routes/environments.js.map +1 -1
- package/dist/routes/execution-workspaces.d.ts.map +1 -1
- package/dist/routes/execution-workspaces.js +89 -3
- package/dist/routes/execution-workspaces.js.map +1 -1
- package/dist/routes/health.d.ts +2 -0
- package/dist/routes/health.d.ts.map +1 -1
- package/dist/routes/health.js +30 -0
- package/dist/routes/health.js.map +1 -1
- package/dist/routes/inbox-dismissals.d.ts.map +1 -1
- package/dist/routes/inbox-dismissals.js +80 -19
- package/dist/routes/inbox-dismissals.js.map +1 -1
- package/dist/routes/index.d.ts +2 -0
- package/dist/routes/index.d.ts.map +1 -1
- package/dist/routes/index.js +2 -0
- package/dist/routes/index.js.map +1 -1
- package/dist/routes/issues.d.ts +25 -0
- package/dist/routes/issues.d.ts.map +1 -1
- package/dist/routes/issues.js +1455 -46
- package/dist/routes/issues.js.map +1 -1
- package/dist/routes/openapi.d.ts.map +1 -1
- package/dist/routes/openapi.js +448 -11
- package/dist/routes/openapi.js.map +1 -1
- package/dist/routes/pipelines.js +2 -2
- package/dist/routes/pipelines.js.map +1 -1
- package/dist/routes/resource-memberships.d.ts.map +1 -1
- package/dist/routes/resource-memberships.js +13 -5
- package/dist/routes/resource-memberships.js.map +1 -1
- package/dist/routes/secrets.d.ts.map +1 -1
- package/dist/routes/secrets.js +261 -1
- package/dist/routes/secrets.js.map +1 -1
- package/dist/routes/sidebar-badges.d.ts.map +1 -1
- package/dist/routes/sidebar-badges.js +18 -2
- package/dist/routes/sidebar-badges.js.map +1 -1
- package/dist/routes/user-profiles.d.ts.map +1 -1
- package/dist/routes/user-profiles.js +5 -4
- package/dist/routes/user-profiles.js.map +1 -1
- package/dist/server-info.d.ts +2 -0
- package/dist/server-info.d.ts.map +1 -1
- package/dist/server-info.js +20 -5
- package/dist/server-info.js.map +1 -1
- package/dist/services/access.d.ts +4 -4
- package/dist/services/access.d.ts.map +1 -1
- package/dist/services/activity.d.ts +3 -3
- package/dist/services/activity.d.ts.map +1 -1
- package/dist/services/activity.js +5 -4
- package/dist/services/activity.js.map +1 -1
- package/dist/services/adapter-registry-bootstrap.reconcile.test.js +1 -0
- package/dist/services/adapter-registry-bootstrap.reconcile.test.js.map +1 -1
- package/dist/services/agent-secret-bindings.d.ts +15 -0
- package/dist/services/agent-secret-bindings.d.ts.map +1 -1
- package/dist/services/agent-secret-bindings.js +43 -0
- package/dist/services/agent-secret-bindings.js.map +1 -1
- package/dist/services/agents.d.ts +194 -133
- package/dist/services/agents.d.ts.map +1 -1
- package/dist/services/agents.js +107 -4
- package/dist/services/agents.js.map +1 -1
- package/dist/services/approvals.d.ts +10 -10
- package/dist/services/approvals.d.ts.map +1 -1
- package/dist/services/approvals.js +9 -1
- package/dist/services/approvals.js.map +1 -1
- package/dist/services/assets.d.ts +5 -5
- package/dist/services/attention.d.ts +11 -0
- package/dist/services/attention.d.ts.map +1 -0
- package/dist/services/attention.js +1024 -0
- package/dist/services/attention.js.map +1 -0
- package/dist/services/authorization.d.ts +15 -2
- package/dist/services/authorization.d.ts.map +1 -1
- package/dist/services/authorization.js +324 -8
- package/dist/services/authorization.js.map +1 -1
- package/dist/services/board-auth.d.ts +2 -2
- package/dist/services/budgets.d.ts.map +1 -1
- package/dist/services/budgets.js +8 -7
- package/dist/services/budgets.js.map +1 -1
- package/dist/services/built-in-agent-metadata.d.ts +9 -0
- package/dist/services/built-in-agent-metadata.d.ts.map +1 -0
- package/dist/services/built-in-agent-metadata.js +39 -0
- package/dist/services/built-in-agent-metadata.js.map +1 -0
- package/dist/services/built-in-agents.d.ts +394 -0
- package/dist/services/built-in-agents.d.ts.map +1 -0
- package/dist/services/built-in-agents.js +1426 -0
- package/dist/services/built-in-agents.js.map +1 -0
- package/dist/services/change-consent-gate.d.ts +18 -0
- package/dist/services/change-consent-gate.d.ts.map +1 -0
- package/dist/services/change-consent-gate.js +170 -0
- package/dist/services/change-consent-gate.js.map +1 -0
- package/dist/services/companies.d.ts +14 -8
- package/dist/services/companies.d.ts.map +1 -1
- package/dist/services/companies.js +6 -1
- package/dist/services/companies.js.map +1 -1
- package/dist/services/company-artifacts.d.ts +4 -1
- package/dist/services/company-artifacts.d.ts.map +1 -1
- package/dist/services/company-artifacts.js +9 -1
- package/dist/services/company-artifacts.js.map +1 -1
- package/dist/services/company-member-roles.d.ts.map +1 -1
- package/dist/services/company-member-roles.js +2 -0
- package/dist/services/company-member-roles.js.map +1 -1
- package/dist/services/company-portability.d.ts +1 -1
- package/dist/services/company-portability.d.ts.map +1 -1
- package/dist/services/company-portability.js +113 -8
- package/dist/services/company-portability.js.map +1 -1
- package/dist/services/company-search.d.ts.map +1 -1
- package/dist/services/company-search.js +843 -294
- package/dist/services/company-search.js.map +1 -1
- package/dist/services/company-skills.d.ts +64 -3
- package/dist/services/company-skills.d.ts.map +1 -1
- package/dist/services/company-skills.js +1205 -20
- package/dist/services/company-skills.js.map +1 -1
- package/dist/services/costs.d.ts +9 -8
- package/dist/services/costs.d.ts.map +1 -1
- package/dist/services/costs.js +9 -2
- package/dist/services/costs.js.map +1 -1
- package/dist/services/dashboard.d.ts +2 -0
- package/dist/services/dashboard.d.ts.map +1 -1
- package/dist/services/dashboard.js +60 -17
- package/dist/services/dashboard.js.map +1 -1
- package/dist/services/database-backup-health.d.ts +34 -0
- package/dist/services/database-backup-health.d.ts.map +1 -0
- package/dist/services/database-backup-health.js +104 -0
- package/dist/services/database-backup-health.js.map +1 -0
- package/dist/services/document-annotations.d.ts +169 -1
- package/dist/services/document-annotations.d.ts.map +1 -1
- package/dist/services/document-annotations.js +272 -1
- package/dist/services/document-annotations.js.map +1 -1
- package/dist/services/documents.d.ts +368 -0
- package/dist/services/documents.d.ts.map +1 -1
- package/dist/services/documents.js +2 -2
- package/dist/services/documents.js.map +1 -1
- package/dist/services/environment-config.d.ts.map +1 -1
- package/dist/services/environment-config.js +6 -0
- package/dist/services/environment-config.js.map +1 -1
- package/dist/services/environment-custom-image-runtime.d.ts +28 -0
- package/dist/services/environment-custom-image-runtime.d.ts.map +1 -1
- package/dist/services/environment-custom-image-runtime.js +106 -7
- package/dist/services/environment-custom-image-runtime.js.map +1 -1
- package/dist/services/environment-custom-images.d.ts +31 -1
- package/dist/services/environment-custom-images.d.ts.map +1 -1
- package/dist/services/environment-custom-images.js +110 -4
- package/dist/services/environment-custom-images.js.map +1 -1
- package/dist/services/environment-execution-target.d.ts.map +1 -1
- package/dist/services/environment-execution-target.js +1 -3
- package/dist/services/environment-execution-target.js.map +1 -1
- package/dist/services/environment-probe.d.ts +1 -0
- package/dist/services/environment-probe.d.ts.map +1 -1
- package/dist/services/environment-probe.js +99 -5
- package/dist/services/environment-probe.js.map +1 -1
- package/dist/services/environments.d.ts +3 -1
- package/dist/services/environments.d.ts.map +1 -1
- package/dist/services/environments.js +98 -3
- package/dist/services/environments.js.map +1 -1
- package/dist/services/execution-workspace-policy.d.ts +29 -3
- package/dist/services/execution-workspace-policy.d.ts.map +1 -1
- package/dist/services/execution-workspace-policy.js +44 -1
- package/dist/services/execution-workspace-policy.js.map +1 -1
- package/dist/services/execution-workspaces.d.ts +64 -1
- package/dist/services/execution-workspaces.d.ts.map +1 -1
- package/dist/services/execution-workspaces.js +634 -3
- package/dist/services/execution-workspaces.js.map +1 -1
- package/dist/services/external-objects.d.ts +26 -26
- package/dist/services/feedback.d.ts +2 -2
- package/dist/services/finance.d.ts +5 -5
- package/dist/services/goals.d.ts +8 -8
- package/dist/services/heartbeat-stop-metadata.d.ts +2 -2
- package/dist/services/heartbeat-stop-metadata.d.ts.map +1 -1
- package/dist/services/heartbeat-stop-metadata.js +4 -0
- package/dist/services/heartbeat-stop-metadata.js.map +1 -1
- package/dist/services/heartbeat-stop-metadata.test.js +9 -0
- package/dist/services/heartbeat-stop-metadata.test.js.map +1 -1
- package/dist/services/heartbeat.d.ts +133 -28
- package/dist/services/heartbeat.d.ts.map +1 -1
- package/dist/services/heartbeat.js +1963 -239
- package/dist/services/heartbeat.js.map +1 -1
- package/dist/services/inbox-dismissals.d.ts +26 -0
- package/dist/services/inbox-dismissals.d.ts.map +1 -1
- package/dist/services/inbox-dismissals.js +33 -18
- package/dist/services/inbox-dismissals.js.map +1 -1
- package/dist/services/index.d.ts +3 -1
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +3 -1
- package/dist/services/index.js.map +1 -1
- package/dist/services/instance-settings.d.ts +25 -1
- package/dist/services/instance-settings.d.ts.map +1 -1
- package/dist/services/instance-settings.js +103 -5
- package/dist/services/instance-settings.js.map +1 -1
- package/dist/services/issue-continuation-summary.js +1 -1
- package/dist/services/issue-continuation-summary.js.map +1 -1
- package/dist/services/issue-recovery-actions.d.ts +3 -0
- package/dist/services/issue-recovery-actions.d.ts.map +1 -1
- package/dist/services/issue-recovery-actions.js +9 -0
- package/dist/services/issue-recovery-actions.js.map +1 -1
- package/dist/services/issue-rewake-throttle.d.ts +92 -0
- package/dist/services/issue-rewake-throttle.d.ts.map +1 -0
- package/dist/services/issue-rewake-throttle.js +139 -0
- package/dist/services/issue-rewake-throttle.js.map +1 -0
- package/dist/services/issue-thread-interactions.d.ts +8 -1
- package/dist/services/issue-thread-interactions.d.ts.map +1 -1
- package/dist/services/issue-thread-interactions.js +231 -16
- package/dist/services/issue-thread-interactions.js.map +1 -1
- package/dist/services/issue-visibility.d.ts +4 -0
- package/dist/services/issue-visibility.d.ts.map +1 -0
- package/dist/services/issue-visibility.js +9 -0
- package/dist/services/issue-visibility.js.map +1 -0
- package/dist/services/issues.d.ts +225 -105
- package/dist/services/issues.d.ts.map +1 -1
- package/dist/services/issues.js +540 -18
- package/dist/services/issues.js.map +1 -1
- package/dist/services/local-service-supervisor.d.ts +3 -0
- package/dist/services/local-service-supervisor.d.ts.map +1 -1
- package/dist/services/local-service-supervisor.js +51 -0
- package/dist/services/local-service-supervisor.js.map +1 -1
- package/dist/services/pipeline-case-outputs.d.ts.map +1 -1
- package/dist/services/pipeline-case-outputs.js +2 -1
- package/dist/services/pipeline-case-outputs.js.map +1 -1
- package/dist/services/pipelines-aggregation.d.ts.map +1 -1
- package/dist/services/pipelines-aggregation.js +3 -2
- package/dist/services/pipelines-aggregation.js.map +1 -1
- package/dist/services/pipelines.d.ts +165 -165
- package/dist/services/pipelines.d.ts.map +1 -1
- package/dist/services/pipelines.js +6 -4
- package/dist/services/pipelines.js.map +1 -1
- package/dist/services/plugin-database.d.ts +2 -2
- package/dist/services/plugin-environment-driver.d.ts +1 -1
- package/dist/services/plugin-host-services.d.ts.map +1 -1
- package/dist/services/plugin-host-services.js +3 -1
- package/dist/services/plugin-host-services.js.map +1 -1
- package/dist/services/plugin-managed-routines.d.ts +1 -0
- package/dist/services/plugin-managed-routines.d.ts.map +1 -1
- package/dist/services/plugin-registry.d.ts +21 -21
- package/dist/services/productivity-review.d.ts +1 -0
- package/dist/services/productivity-review.d.ts.map +1 -1
- package/dist/services/productivity-review.js +6 -5
- package/dist/services/productivity-review.js.map +1 -1
- package/dist/services/projects.d.ts +9 -9
- package/dist/services/recovery/service.d.ts +82 -54
- package/dist/services/recovery/service.d.ts.map +1 -1
- package/dist/services/recovery/service.js +200 -35
- package/dist/services/recovery/service.js.map +1 -1
- package/dist/services/recovery/successful-run-handoff.d.ts +1 -0
- package/dist/services/recovery/successful-run-handoff.d.ts.map +1 -1
- package/dist/services/recovery/successful-run-handoff.js +2 -0
- package/dist/services/recovery/successful-run-handoff.js.map +1 -1
- package/dist/services/recovery/successful-run-handoff.test.js +20 -0
- package/dist/services/recovery/successful-run-handoff.test.js.map +1 -1
- package/dist/services/resource-memberships.d.ts +13 -10
- package/dist/services/resource-memberships.d.ts.map +1 -1
- package/dist/services/resource-memberships.js +89 -20
- package/dist/services/resource-memberships.js.map +1 -1
- package/dist/services/responsible-user-denial-run-outcomes.d.ts +60 -0
- package/dist/services/responsible-user-denial-run-outcomes.d.ts.map +1 -0
- package/dist/services/responsible-user-denial-run-outcomes.js +56 -0
- package/dist/services/responsible-user-denial-run-outcomes.js.map +1 -0
- package/dist/services/responsible-user-denial-run-outcomes.test.d.ts +2 -0
- package/dist/services/responsible-user-denial-run-outcomes.test.d.ts.map +1 -0
- package/dist/services/responsible-user-denial-run-outcomes.test.js +75 -0
- package/dist/services/responsible-user-denial-run-outcomes.test.js.map +1 -0
- package/dist/services/routines.d.ts +30 -8
- package/dist/services/routines.d.ts.map +1 -1
- package/dist/services/routines.js +241 -18
- package/dist/services/routines.js.map +1 -1
- package/dist/services/run-liveness.d.ts.map +1 -1
- package/dist/services/run-liveness.js +21 -0
- package/dist/services/run-liveness.js.map +1 -1
- package/dist/services/run-log-store.d.ts +1 -0
- package/dist/services/run-log-store.d.ts.map +1 -1
- package/dist/services/run-log-store.js +4 -0
- package/dist/services/run-log-store.js.map +1 -1
- package/dist/services/run-scratch.d.ts +42 -0
- package/dist/services/run-scratch.d.ts.map +1 -0
- package/dist/services/run-scratch.js +111 -0
- package/dist/services/run-scratch.js.map +1 -0
- package/dist/services/run-scratch.test.d.ts +2 -0
- package/dist/services/run-scratch.test.d.ts.map +1 -0
- package/dist/services/run-scratch.test.js +98 -0
- package/dist/services/run-scratch.test.js.map +1 -0
- package/dist/services/secrets.d.ts +1261 -64
- package/dist/services/secrets.d.ts.map +1 -1
- package/dist/services/secrets.js +1089 -96
- package/dist/services/secrets.js.map +1 -1
- package/dist/services/task-watchdogs.d.ts +1 -0
- package/dist/services/task-watchdogs.d.ts.map +1 -1
- package/dist/services/task-watchdogs.js +78 -9
- package/dist/services/task-watchdogs.js.map +1 -1
- package/dist/services/work-timeline.d.ts.map +1 -1
- package/dist/services/work-timeline.js +34 -2
- package/dist/services/work-timeline.js.map +1 -1
- package/dist/services/workspace-runtime-read-model.d.ts +30 -29
- package/dist/services/workspace-runtime-read-model.d.ts.map +1 -1
- package/dist/services/workspace-runtime-read-model.js.map +1 -1
- package/dist/services/workspace-runtime.d.ts +59 -13
- package/dist/services/workspace-runtime.d.ts.map +1 -1
- package/dist/services/workspace-runtime.js +1121 -102
- package/dist/services/workspace-runtime.js.map +1 -1
- package/dist/skills-catalog/catalog/bundled/paperclip-operations/reflection-coach/SKILL.md +202 -0
- package/dist/skills-catalog/catalog/bundled/product/paperclip-capsules/SKILL.md +1 -1
- package/dist/skills-catalog/catalog/bundled/product/wireframe/SKILL.md +1 -1
- package/dist/skills-catalog/catalog/optional/content/release-announcement/SKILL.md +90 -0
- package/dist/skills-catalog/catalog/optional/finance/ramp/SKILL.md +98 -0
- package/dist/skills-catalog/generated/catalog.json +84 -12
- package/dist/ui-branding.d.ts +7 -0
- package/dist/ui-branding.d.ts.map +1 -1
- package/dist/ui-branding.js +8 -2
- package/dist/ui-branding.js.map +1 -1
- package/dist/version.d.ts +14 -0
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +114 -3
- package/dist/version.js.map +1 -1
- package/package.json +19 -20
- package/skills/paperclip/SKILL.md +45 -10
- package/skills/paperclip/references/api-reference.md +246 -1
- package/skills/paperclip/references/cases.md +295 -0
- package/skills/paperclip/references/company-skills.md +1 -1
- package/skills/paperclip-board/SKILL.md +3 -4
- package/skills/paperclip-converting-plans-to-tasks/SKILL.md +3 -7
- package/skills/para-memory-files/SKILL.md +3 -7
- package/ui-dist/assets/{abnfDiagram-VRR7QNED-BsFz2IiG.js → abnfDiagram-VRR7QNED-Dtgfy1PK.js} +1 -1
- package/ui-dist/assets/{arc-HZhAwbOs.js → arc-BGdvNvs8.js} +1 -1
- package/ui-dist/assets/{architectureDiagram-ZJ3FMSHR-BteMjuOn.js → architectureDiagram-ZJ3FMSHR-CK19SQDx.js} +1 -1
- package/ui-dist/assets/{blockDiagram-677ZJIJ3-Iq66uZjf.js → blockDiagram-677ZJIJ3-Kh4N3XG4.js} +1 -1
- package/ui-dist/assets/{browser-ponyfill-BBsuyZJt.js → browser-ponyfill-B3hXtEFW.js} +1 -1
- package/ui-dist/assets/{c4Diagram-LMCZKHZV-_nlglj7I.js → c4Diagram-LMCZKHZV-CBHpGrgU.js} +1 -1
- package/ui-dist/assets/channel-4fjB8e8N.js +1 -0
- package/ui-dist/assets/{chunk-2Q5K7J3B-Cvz7WwSQ.js → chunk-2Q5K7J3B-BdHbqi7k.js} +1 -1
- package/ui-dist/assets/{chunk-32BRIVSS-LLubniCS.js → chunk-32BRIVSS-DxtzZzpt.js} +1 -1
- package/ui-dist/assets/{chunk-5VM5RSS4-BOst5hcr.js → chunk-5VM5RSS4-DMKxBbgu.js} +1 -1
- package/ui-dist/assets/{chunk-EX3LRPZG-D5WNShVm.js → chunk-EX3LRPZG-BaIOPi4v.js} +1 -1
- package/ui-dist/assets/{chunk-JWPE2WC7-CA07x4av.js → chunk-JWPE2WC7-DGo3MPmr.js} +1 -1
- package/ui-dist/assets/{chunk-MOJQB5TN-Clfi83rK.js → chunk-MOJQB5TN-BEhQg0uD.js} +1 -1
- package/ui-dist/assets/{chunk-RYQCIY6F-BhZ6p7YJ.js → chunk-RYQCIY6F-HPJ6zRpJ.js} +1 -1
- package/ui-dist/assets/{chunk-V7JOEXUC-CdexJGoV.js → chunk-V7JOEXUC-Bl_6cAJD.js} +1 -1
- package/ui-dist/assets/{chunk-VR4S4FIN-8vjmBhr1.js → chunk-VR4S4FIN-KMjgGCsw.js} +1 -1
- package/ui-dist/assets/{chunk-XXDRQBXY-CqmCaplj.js → chunk-XXDRQBXY-oWGAIKjr.js} +1 -1
- package/ui-dist/assets/classDiagram-OUVF2IWQ-CTXxO5l9.js +1 -0
- package/ui-dist/assets/classDiagram-v2-EOCWNBFH-CTXxO5l9.js +1 -0
- package/ui-dist/assets/{cose-bilkent-JH36ORCC-CY-zAygY.js → cose-bilkent-JH36ORCC-Ckqse1pH.js} +1 -1
- package/ui-dist/assets/{cynefin-VYW2F7L2-BQSQ1HKr.js → cynefin-VYW2F7L2-DfZ8Hzqi.js} +1 -1
- package/ui-dist/assets/{cynefinDiagram-TSTJHNR4-NJCuFrZL.js → cynefinDiagram-TSTJHNR4-BkDQo9oi.js} +1 -1
- package/ui-dist/assets/{dagre-VKFMJZFB-DAUPIc8g.js → dagre-VKFMJZFB-ClghXQ6E.js} +1 -1
- package/ui-dist/assets/{diagram-FQU43EPY-DvMwJlCi.js → diagram-FQU43EPY-D5a0CwpV.js} +1 -1
- package/ui-dist/assets/{diagram-G47NLZAW-DPY0Jz_y.js → diagram-G47NLZAW-hVjYR_xl.js} +1 -1
- package/ui-dist/assets/{diagram-NH7WQ7WH-BpopeEhp.js → diagram-NH7WQ7WH-BlDPgD5Z.js} +1 -1
- package/ui-dist/assets/{diagram-OA4YK3LP-Qpu05z9X.js → diagram-OA4YK3LP-D0iZKIS3.js} +1 -1
- package/ui-dist/assets/{diagram-WEI45ONY-DHnvw9XU.js → diagram-WEI45ONY-m6hrfD0J.js} +1 -1
- package/ui-dist/assets/{ebnfDiagram-CCIWWBDH-D61S54_7.js → ebnfDiagram-CCIWWBDH-D4SQYLod.js} +1 -1
- package/ui-dist/assets/{erDiagram-Q63AITRT-pplAN_x2.js → erDiagram-Q63AITRT-Byzb6-I5.js} +1 -1
- package/ui-dist/assets/{flowDiagram-23GEKE2U-BaBcbyDq.js → flowDiagram-23GEKE2U-CaTUkFO6.js} +1 -1
- package/ui-dist/assets/{ganttDiagram-NO4QXBWP-BOJSII2b.js → ganttDiagram-NO4QXBWP-DmdMQ3ES.js} +1 -1
- package/ui-dist/assets/{gitGraphDiagram-IHSO6WYX-aYAaO4a2.js → gitGraphDiagram-IHSO6WYX-Bt-1g2CS.js} +1 -1
- package/ui-dist/assets/{index-CZxYajxF.js → index-2Dk9yO0W.js} +1 -1
- package/ui-dist/assets/{index-B8H0SHw1.js → index-8jLpgFNU.js} +1 -1
- package/ui-dist/assets/{index-3EKATXgc.js → index-AP_iimxs.js} +1 -1
- package/ui-dist/assets/{index-C2JgdjPa.js → index-BNJasQY9.js} +1 -1
- package/ui-dist/assets/{index-BaiskB3o.js → index-BO-aNmyX.js} +1 -1
- package/ui-dist/assets/{index-B5YenxAQ.js → index-BPCzCn1Y.js} +1 -1
- package/ui-dist/assets/{index-Da2kqT1M.js → index-BSLnLTb2.js} +1 -1
- package/ui-dist/assets/{index-By0oeEvz.js → index-CCZ2qgHN.js} +1 -1
- package/ui-dist/assets/{index-bUFCSEgv.js → index-CLZXR1k9.js} +1 -1
- package/ui-dist/assets/{index-CJlw4l9L.js → index-CTNfrE1s.js} +1 -1
- package/ui-dist/assets/{index-TmpY6Xh_.js → index-ChNd25s7.js} +1 -1
- package/ui-dist/assets/{index-BRgdHUNR.js → index-CjHiMOEO.js} +1 -1
- package/ui-dist/assets/{index-DrL6pHSJ.js → index-CmeJQpXZ.js} +1 -1
- package/ui-dist/assets/index-CyuYtn7U.css +1 -0
- package/ui-dist/assets/{index-CKKytnOd.js → index-CzZjM8eh.js} +1 -1
- package/ui-dist/assets/{index-Dceamza_.js → index-D-6T0Aft.js} +1 -1
- package/ui-dist/assets/{index-CjDqB1SX.js → index-DG_7V1pG.js} +1 -1
- package/ui-dist/assets/index-DIKcgxCt.js +676 -0
- package/ui-dist/assets/{index-BKVeJ3Jh.js → index-DiUerHmi.js} +1 -1
- package/ui-dist/assets/{index-C-nKjFZh.js → index-NZ7v5Fc2.js} +1 -1
- package/ui-dist/assets/{index-CvPPuNs1.js → index-Py70EvBg.js} +1 -1
- package/ui-dist/assets/{index-LHkZdu0h.js → index-R3aYn0qM.js} +1 -1
- package/ui-dist/assets/{index-BT8b79Bd.js → index-YXpm7gW-.js} +1 -1
- package/ui-dist/assets/{index-CemenVOf.js → index-n4q3gzki.js} +1 -1
- package/ui-dist/assets/{index-D3ODiUup.js → index-vlZ3GoYY.js} +1 -1
- package/ui-dist/assets/{infoDiagram-FWYZ7A6U-C7_90qGq.js → infoDiagram-FWYZ7A6U-jdi5ciVg.js} +1 -1
- package/ui-dist/assets/{ishikawaDiagram-FXEZZL3T-T-nPPdTt.js → ishikawaDiagram-FXEZZL3T-CMz5VvYp.js} +1 -1
- package/ui-dist/assets/{journeyDiagram-5HDEW3XC-C3hqQN0R.js → journeyDiagram-5HDEW3XC-DK6dk2R-.js} +1 -1
- package/ui-dist/assets/{kanban-definition-HUTT4EX6-CPoD9DUm.js → kanban-definition-HUTT4EX6-DX5cZL-D.js} +1 -1
- package/ui-dist/assets/{linear-D-S8yr3Z.js → linear-CI96ABKp.js} +1 -1
- package/ui-dist/assets/{mermaid.core-DhvH8shK.js → mermaid.core-DnsEVfbd.js} +4 -4
- package/ui-dist/assets/{mindmap-definition-LN4V7U3C-CxCIKrKo.js → mindmap-definition-LN4V7U3C-BdDCY7hf.js} +1 -1
- package/ui-dist/assets/{pegDiagram-2B236MQR-D4G-PC6N.js → pegDiagram-2B236MQR-DuY4TF-f.js} +1 -1
- package/ui-dist/assets/{pieDiagram-ENE6RG2P-N4X8UvqY.js → pieDiagram-ENE6RG2P-bgFw7dm3.js} +1 -1
- package/ui-dist/assets/{quadrantDiagram-ABIIQ3AL-CZohoDln.js → quadrantDiagram-ABIIQ3AL-BCZVWyWS.js} +1 -1
- package/ui-dist/assets/{railroadDiagram-RFXS5EU6-fMGQVhwF.js → railroadDiagram-RFXS5EU6-D9V5_Cgc.js} +1 -1
- package/ui-dist/assets/{requirementDiagram-TGXJPOKE-CK0IuUmk.js → requirementDiagram-TGXJPOKE-CItsya2r.js} +1 -1
- package/ui-dist/assets/{sankeyDiagram-HTMAVEWB-pSfb3CqG.js → sankeyDiagram-HTMAVEWB-Cz9IHsZO.js} +1 -1
- package/ui-dist/assets/{sequenceDiagram-DBY2YBRQ-CbhkqIeq.js → sequenceDiagram-DBY2YBRQ-hMSqTiwK.js} +1 -1
- package/ui-dist/assets/{sizeCapture-X5ZJPWSS-B6vqqPx-.js → sizeCapture-X5ZJPWSS-0g9M5wMf.js} +1 -1
- package/ui-dist/assets/{stateDiagram-2N3HPSRC-DIJf9h5C.js → stateDiagram-2N3HPSRC-F8_YhgWl.js} +1 -1
- package/ui-dist/assets/stateDiagram-v2-6OUMAXLB-fZImUBtq.js +1 -0
- package/ui-dist/assets/{swimlanes-5IMT3BWC-DIX_LV8O.js → swimlanes-5IMT3BWC-CCZqPbpE.js} +2 -2
- package/ui-dist/assets/swimlanesDiagram-G3AALYLV-9C1UT51R.js +8 -0
- package/ui-dist/assets/{timeline-definition-FHXFAJF6-CD-Pxb_g.js → timeline-definition-FHXFAJF6-BeUIr18x.js} +1 -1
- package/ui-dist/assets/{vennDiagram-L72KCM5P-BAZsWL0R.js → vennDiagram-L72KCM5P-D3d5h_G4.js} +1 -1
- package/ui-dist/assets/{wardleyDiagram-EHGQE667-CnNyfwX5.js → wardleyDiagram-EHGQE667-Bzt-As9A.js} +1 -1
- package/ui-dist/assets/{xychartDiagram-FW5EYKEG-j9YgsD3Y.js → xychartDiagram-FW5EYKEG-C2mly0xf.js} +1 -1
- package/ui-dist/fonts/InterVariable-Italic.woff2 +0 -0
- package/ui-dist/fonts/InterVariable.woff2 +0 -0
- package/ui-dist/fonts/NOTICE.md +17 -0
- package/ui-dist/index.html +2 -2
- package/ui-dist/locales/en/common.json +1142 -5
- package/ui-dist/locales/zh-CN/common.json +1140 -3
- package/ui-dist/assets/channel-CsiI2LYw.js +0 -1
- package/ui-dist/assets/classDiagram-OUVF2IWQ-CBn1Jqo8.js +0 -1
- package/ui-dist/assets/classDiagram-v2-EOCWNBFH-CBn1Jqo8.js +0 -1
- package/ui-dist/assets/index-BsL8K1vL.css +0 -1
- package/ui-dist/assets/index-D__fTsb2.js +0 -643
- package/ui-dist/assets/stateDiagram-v2-6OUMAXLB-DpwLD7YL.js +0 -1
- package/ui-dist/assets/swimlanesDiagram-G3AALYLV-Cklauutg.js +0 -8
|
@@ -0,0 +1,1024 @@
|
|
|
1
|
+
import { and, asc, desc, eq, gt, inArray, isNotNull, isNull, notInArray, sql } from "drizzle-orm";
|
|
2
|
+
import { agents, approvals, assets, companies, heartbeatRunEvents, heartbeatRuns, inboxDismissals, invites, issueApprovals, issueAttachments, issueDocuments, issueRecoveryActions, issueRelations, issueThreadInteractions, issues, joinRequests, documents, projects, projectWorkspaces, } from "@penclipai/db";
|
|
3
|
+
import { deriveProjectUrlKey } from "@penclipai/shared";
|
|
4
|
+
import { PRODUCTIVITY_REVIEW_ORIGIN_KIND } from "./productivity-review.js";
|
|
5
|
+
import { budgetService } from "./budgets.js";
|
|
6
|
+
import { issueService } from "./issues.js";
|
|
7
|
+
import { parseIssueExecutionState } from "./issue-execution-policy.js";
|
|
8
|
+
const ATTENTION_SOURCE_KINDS = [
|
|
9
|
+
"approval",
|
|
10
|
+
"issue_thread_interaction",
|
|
11
|
+
"join_request",
|
|
12
|
+
"recovery_action",
|
|
13
|
+
"productivity_review",
|
|
14
|
+
"blocker_attention",
|
|
15
|
+
"review",
|
|
16
|
+
"failed_run",
|
|
17
|
+
"budget_alert",
|
|
18
|
+
"agent_error_alert",
|
|
19
|
+
];
|
|
20
|
+
const SEVERITY_RANK = {
|
|
21
|
+
critical: 0,
|
|
22
|
+
high: 1,
|
|
23
|
+
medium: 2,
|
|
24
|
+
low: 3,
|
|
25
|
+
};
|
|
26
|
+
const SOURCE_RANK = {
|
|
27
|
+
failed_run: 0,
|
|
28
|
+
recovery_action: 1,
|
|
29
|
+
blocker_attention: 2,
|
|
30
|
+
budget_alert: 3,
|
|
31
|
+
agent_error_alert: 4,
|
|
32
|
+
approval: 5,
|
|
33
|
+
issue_thread_interaction: 6,
|
|
34
|
+
review: 7,
|
|
35
|
+
productivity_review: 8,
|
|
36
|
+
join_request: 9,
|
|
37
|
+
};
|
|
38
|
+
const PENDING_INTERACTION_STATUSES = ["pending"];
|
|
39
|
+
const OPEN_RECOVERY_STATUSES = ["active", "escalated"];
|
|
40
|
+
const HUMAN_RECOVERY_OWNER_TYPES = ["user", "board"];
|
|
41
|
+
const PRODUCTIVITY_REVIEW_TERMINAL_STATUSES = ["done", "cancelled"];
|
|
42
|
+
const FAILED_RUN_STATUSES = ["failed", "timed_out"];
|
|
43
|
+
const DETAIL_EXCERPT_LENGTH = 160;
|
|
44
|
+
const DETAIL_IMAGE_LIMIT = 3;
|
|
45
|
+
function emptyCounts() {
|
|
46
|
+
return Object.fromEntries(ATTENTION_SOURCE_KINDS.map((kind) => [kind, 0]));
|
|
47
|
+
}
|
|
48
|
+
function toIso(value) {
|
|
49
|
+
if (!value)
|
|
50
|
+
return new Date(0).toISOString();
|
|
51
|
+
return value instanceof Date ? value.toISOString() : new Date(value).toISOString();
|
|
52
|
+
}
|
|
53
|
+
function timestamp(value) {
|
|
54
|
+
if (!value)
|
|
55
|
+
return 0;
|
|
56
|
+
const time = new Date(value).getTime();
|
|
57
|
+
return Number.isFinite(time) ? time : 0;
|
|
58
|
+
}
|
|
59
|
+
function activeDismissalState(dismissalByKey, dismissalKey, activityAt, now) {
|
|
60
|
+
const dismissal = dismissalByKey.get(dismissalKey);
|
|
61
|
+
if (!dismissal)
|
|
62
|
+
return null;
|
|
63
|
+
const dismissedAt = toIso(dismissal.dismissedAt);
|
|
64
|
+
const snoozedUntil = dismissal.snoozedUntil ? toIso(dismissal.snoozedUntil) : null;
|
|
65
|
+
const isActive = dismissal.kind === "snooze"
|
|
66
|
+
? dismissal.snoozedUntil != null && timestamp(dismissal.snoozedUntil) > now
|
|
67
|
+
: timestamp(dismissal.dismissedAt) >= timestamp(activityAt);
|
|
68
|
+
return {
|
|
69
|
+
kind: dismissal.kind,
|
|
70
|
+
dismissedAt,
|
|
71
|
+
snoozedUntil,
|
|
72
|
+
isActive,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function stripMarkdown(value) {
|
|
76
|
+
return value
|
|
77
|
+
.replace(/```[\s\S]*?```/g, " ")
|
|
78
|
+
.replace(/`([^`]+)`/g, "$1")
|
|
79
|
+
.replace(/!\[[^\]]*\]\([^)]*\)/g, " ")
|
|
80
|
+
.replace(/\[([^\]]+)\]\([^)]*\)/g, "$1")
|
|
81
|
+
.replace(/^#{1,6}\s+/gm, "")
|
|
82
|
+
.replace(/[>*_~#-]+/g, " ")
|
|
83
|
+
.replace(/\s+/g, " ")
|
|
84
|
+
.trim();
|
|
85
|
+
}
|
|
86
|
+
function excerpt(value, maxLength = DETAIL_EXCERPT_LENGTH) {
|
|
87
|
+
if (typeof value !== "string")
|
|
88
|
+
return null;
|
|
89
|
+
const cleaned = stripMarkdown(value);
|
|
90
|
+
if (!cleaned)
|
|
91
|
+
return null;
|
|
92
|
+
if (cleaned.length <= maxLength)
|
|
93
|
+
return cleaned;
|
|
94
|
+
return `${cleaned.slice(0, Math.max(0, maxLength - 1)).trimEnd()}...`;
|
|
95
|
+
}
|
|
96
|
+
function readRecord(value) {
|
|
97
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
98
|
+
}
|
|
99
|
+
function readArray(value) {
|
|
100
|
+
return Array.isArray(value) ? value : [];
|
|
101
|
+
}
|
|
102
|
+
function readString(value) {
|
|
103
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
104
|
+
}
|
|
105
|
+
function isPlanDocumentTarget(payload) {
|
|
106
|
+
const target = readRecord(payload.target);
|
|
107
|
+
return target.type === "issue_document" && target.key === "plan";
|
|
108
|
+
}
|
|
109
|
+
function issueContext(issue) {
|
|
110
|
+
return {
|
|
111
|
+
project: issue?.project ?? null,
|
|
112
|
+
workspace: issue?.workspace ?? null,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function issueImages(imageMap, issueId) {
|
|
116
|
+
return issueId ? imageMap.get(issueId) ?? [] : [];
|
|
117
|
+
}
|
|
118
|
+
function genericDetail(summary, images) {
|
|
119
|
+
return { kind: "generic", summaryExcerpt: excerpt(summary), images };
|
|
120
|
+
}
|
|
121
|
+
function approvalDetail(type, payload) {
|
|
122
|
+
return {
|
|
123
|
+
kind: "approval",
|
|
124
|
+
approvalType: type,
|
|
125
|
+
summaryExcerpt: excerpt(payload.summary ?? payload.title ?? payload.recommendedAction),
|
|
126
|
+
images: [],
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
function interactionDetail(input) {
|
|
130
|
+
if (input.kind === "request_confirmation" && isPlanDocumentTarget(input.payload)) {
|
|
131
|
+
return {
|
|
132
|
+
kind: "plan_approval",
|
|
133
|
+
issueTitle: input.issue?.title ?? null,
|
|
134
|
+
planTitle: input.planDocument?.title ?? "Plan",
|
|
135
|
+
summaryExcerpt: excerpt(input.planDocument?.body ?? input.payload.detailsMarkdown ?? input.payload.prompt),
|
|
136
|
+
images: input.images,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
if (input.kind === "ask_user_questions") {
|
|
140
|
+
const questions = readArray(input.payload.questions).map(readRecord);
|
|
141
|
+
return {
|
|
142
|
+
kind: "questions",
|
|
143
|
+
questionCount: questions.length,
|
|
144
|
+
firstQuestionText: readString(questions[0]?.prompt),
|
|
145
|
+
images: input.images,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
if (input.kind === "suggest_tasks") {
|
|
149
|
+
const tasks = readArray(input.payload.tasks).map(readRecord);
|
|
150
|
+
return {
|
|
151
|
+
kind: "suggested_tasks",
|
|
152
|
+
taskCount: tasks.length,
|
|
153
|
+
firstTaskTitle: readString(tasks[0]?.title),
|
|
154
|
+
images: input.images,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
if (input.kind === "request_checkbox_confirmation") {
|
|
158
|
+
return {
|
|
159
|
+
kind: "checkbox_confirmation",
|
|
160
|
+
optionCount: readArray(input.payload.options).length,
|
|
161
|
+
promptExcerpt: excerpt(input.payload.prompt),
|
|
162
|
+
images: input.images,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
if (input.kind === "request_item_verdicts") {
|
|
166
|
+
return {
|
|
167
|
+
kind: "item_verdicts",
|
|
168
|
+
itemCount: readArray(input.payload.items).length,
|
|
169
|
+
promptExcerpt: excerpt(input.payload.prompt),
|
|
170
|
+
images: input.images,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
kind: "confirmation",
|
|
175
|
+
promptExcerpt: excerpt(input.payload.prompt ?? input.payload.detailsMarkdown),
|
|
176
|
+
isPlanTarget: false,
|
|
177
|
+
images: input.images,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
function issueHref(prefix, issue) {
|
|
181
|
+
return `/${prefix}/issues/${issue.identifier ?? issue.id}`;
|
|
182
|
+
}
|
|
183
|
+
function issueSubject(prefix, issue) {
|
|
184
|
+
return {
|
|
185
|
+
kind: "issue",
|
|
186
|
+
id: issue.id,
|
|
187
|
+
companyId: issue.companyId,
|
|
188
|
+
title: issue.title,
|
|
189
|
+
identifier: issue.identifier,
|
|
190
|
+
status: issue.status,
|
|
191
|
+
href: issueHref(prefix, issue),
|
|
192
|
+
metadata: {
|
|
193
|
+
priority: issue.priority,
|
|
194
|
+
assigneeAgentId: issue.assigneeAgentId,
|
|
195
|
+
assigneeUserId: issue.assigneeUserId,
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
function itemId(sourceKind, dedupKey) {
|
|
200
|
+
return `${sourceKind}:${dedupKey}`;
|
|
201
|
+
}
|
|
202
|
+
function decisionVerbs(...verbs) {
|
|
203
|
+
return verbs;
|
|
204
|
+
}
|
|
205
|
+
function createItem(input) {
|
|
206
|
+
return {
|
|
207
|
+
...input,
|
|
208
|
+
id: itemId(input.sourceKind, input.dedupKey),
|
|
209
|
+
dismissalKey: `attention:${input.dedupKey}`,
|
|
210
|
+
dismissal: null,
|
|
211
|
+
project: input.project ?? null,
|
|
212
|
+
workspace: input.workspace ?? null,
|
|
213
|
+
detail: input.detail ?? null,
|
|
214
|
+
rank: 0,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
function compareAttentionItems(left, right) {
|
|
218
|
+
const timeDiff = timestamp(right.activityAt) - timestamp(left.activityAt);
|
|
219
|
+
if (timeDiff !== 0)
|
|
220
|
+
return timeDiff;
|
|
221
|
+
const severityDiff = SEVERITY_RANK[left.severity] - SEVERITY_RANK[right.severity];
|
|
222
|
+
if (severityDiff !== 0)
|
|
223
|
+
return severityDiff;
|
|
224
|
+
const sourceDiff = SOURCE_RANK[left.sourceKind] - SOURCE_RANK[right.sourceKind];
|
|
225
|
+
if (sourceDiff !== 0)
|
|
226
|
+
return sourceDiff;
|
|
227
|
+
return left.dedupKey.localeCompare(right.dedupKey);
|
|
228
|
+
}
|
|
229
|
+
function betterDuplicate(left, right) {
|
|
230
|
+
return compareAttentionItems(left, right) <= 0 ? left : right;
|
|
231
|
+
}
|
|
232
|
+
function approvalTitle(type, payload) {
|
|
233
|
+
const title = typeof payload.title === "string" ? payload.title.trim() : "";
|
|
234
|
+
if (title)
|
|
235
|
+
return title;
|
|
236
|
+
const summary = typeof payload.summary === "string" ? payload.summary.trim() : "";
|
|
237
|
+
if (summary)
|
|
238
|
+
return summary;
|
|
239
|
+
return type.replaceAll("_", " ");
|
|
240
|
+
}
|
|
241
|
+
function interactionLabel(kind) {
|
|
242
|
+
switch (kind) {
|
|
243
|
+
case "request_confirmation":
|
|
244
|
+
return "Confirmation requested";
|
|
245
|
+
case "request_checkbox_confirmation":
|
|
246
|
+
return "Selection confirmation requested";
|
|
247
|
+
case "ask_user_questions":
|
|
248
|
+
return "Questions need answers";
|
|
249
|
+
case "suggest_tasks":
|
|
250
|
+
return "Suggested tasks need a decision";
|
|
251
|
+
case "request_item_verdicts":
|
|
252
|
+
return "Item verdicts need a decision";
|
|
253
|
+
default:
|
|
254
|
+
return "Interaction needs a decision";
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
function interactionVerbs(kind, payload) {
|
|
258
|
+
if (kind === "ask_user_questions") {
|
|
259
|
+
return decisionVerbs({
|
|
260
|
+
id: "respond",
|
|
261
|
+
label: "Respond",
|
|
262
|
+
description: "Submit answers to the pending questions.",
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
if (kind === "request_confirmation") {
|
|
266
|
+
const acceptLabel = typeof payload.acceptLabel === "string" && payload.acceptLabel.trim()
|
|
267
|
+
? payload.acceptLabel.trim()
|
|
268
|
+
: "Confirm";
|
|
269
|
+
const rejectLabel = typeof payload.rejectLabel === "string" && payload.rejectLabel.trim()
|
|
270
|
+
? payload.rejectLabel.trim()
|
|
271
|
+
: "Decline";
|
|
272
|
+
return decisionVerbs({
|
|
273
|
+
id: "accept",
|
|
274
|
+
label: acceptLabel,
|
|
275
|
+
description: "Accept the pending confirmation.",
|
|
276
|
+
}, {
|
|
277
|
+
id: "reject",
|
|
278
|
+
label: rejectLabel,
|
|
279
|
+
description: "Decline the pending confirmation.",
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
return decisionVerbs({
|
|
283
|
+
id: "accept",
|
|
284
|
+
label: "Accept",
|
|
285
|
+
description: "Accept the pending interaction.",
|
|
286
|
+
}, {
|
|
287
|
+
id: "reject",
|
|
288
|
+
label: "Reject",
|
|
289
|
+
description: "Reject the pending interaction and provide a reason when required.",
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
function budgetObservedPercent(amountObserved, amountLimit) {
|
|
293
|
+
return amountLimit > 0 ? Math.round((amountObserved / amountLimit) * 10_000) / 100 : 0;
|
|
294
|
+
}
|
|
295
|
+
async function companyPrefix(db, companyId) {
|
|
296
|
+
const row = await db
|
|
297
|
+
.select({ issuePrefix: companies.issuePrefix })
|
|
298
|
+
.from(companies)
|
|
299
|
+
.where(eq(companies.id, companyId))
|
|
300
|
+
.then((rows) => rows[0] ?? null);
|
|
301
|
+
return row?.issuePrefix ?? "PAP";
|
|
302
|
+
}
|
|
303
|
+
async function dismissalByKey(db, companyId, userId) {
|
|
304
|
+
if (!userId)
|
|
305
|
+
return new Map();
|
|
306
|
+
const rows = await db
|
|
307
|
+
.select({
|
|
308
|
+
itemKey: inboxDismissals.itemKey,
|
|
309
|
+
kind: inboxDismissals.kind,
|
|
310
|
+
dismissedAt: inboxDismissals.dismissedAt,
|
|
311
|
+
snoozedUntil: inboxDismissals.snoozedUntil,
|
|
312
|
+
})
|
|
313
|
+
.from(inboxDismissals)
|
|
314
|
+
.where(and(eq(inboxDismissals.companyId, companyId), eq(inboxDismissals.userId, userId)));
|
|
315
|
+
return new Map(rows.map((row) => [row.itemKey, {
|
|
316
|
+
kind: row.kind,
|
|
317
|
+
dismissedAt: row.dismissedAt,
|
|
318
|
+
snoozedUntil: row.snoozedUntil,
|
|
319
|
+
}]));
|
|
320
|
+
}
|
|
321
|
+
async function issueSummaryMap(db, companyId, issueIds) {
|
|
322
|
+
const ids = [...new Set(issueIds.filter((value) => Boolean(value)))];
|
|
323
|
+
if (ids.length === 0)
|
|
324
|
+
return new Map();
|
|
325
|
+
const rows = await db
|
|
326
|
+
.select({
|
|
327
|
+
id: issues.id,
|
|
328
|
+
companyId: issues.companyId,
|
|
329
|
+
identifier: issues.identifier,
|
|
330
|
+
title: issues.title,
|
|
331
|
+
status: issues.status,
|
|
332
|
+
priority: issues.priority,
|
|
333
|
+
assigneeAgentId: issues.assigneeAgentId,
|
|
334
|
+
assigneeUserId: issues.assigneeUserId,
|
|
335
|
+
createdAt: issues.createdAt,
|
|
336
|
+
updatedAt: issues.updatedAt,
|
|
337
|
+
projectId: projects.id,
|
|
338
|
+
projectName: projects.name,
|
|
339
|
+
projectColor: projects.color,
|
|
340
|
+
projectIcon: projects.icon,
|
|
341
|
+
workspaceId: projectWorkspaces.id,
|
|
342
|
+
workspaceName: projectWorkspaces.name,
|
|
343
|
+
})
|
|
344
|
+
.from(issues)
|
|
345
|
+
.leftJoin(projects, and(eq(issues.projectId, projects.id), eq(projects.companyId, companyId)))
|
|
346
|
+
.leftJoin(projectWorkspaces, and(eq(issues.projectWorkspaceId, projectWorkspaces.id), eq(projectWorkspaces.companyId, companyId)))
|
|
347
|
+
.where(and(eq(issues.companyId, companyId), inArray(issues.id, ids), isNull(issues.hiddenAt)));
|
|
348
|
+
return new Map(rows.map((row) => [row.id, {
|
|
349
|
+
id: row.id,
|
|
350
|
+
companyId: row.companyId,
|
|
351
|
+
identifier: row.identifier,
|
|
352
|
+
title: row.title,
|
|
353
|
+
status: row.status,
|
|
354
|
+
priority: row.priority,
|
|
355
|
+
assigneeAgentId: row.assigneeAgentId,
|
|
356
|
+
assigneeUserId: row.assigneeUserId,
|
|
357
|
+
createdAt: row.createdAt,
|
|
358
|
+
updatedAt: row.updatedAt,
|
|
359
|
+
project: row.projectId && row.projectName ? {
|
|
360
|
+
id: row.projectId,
|
|
361
|
+
name: row.projectName,
|
|
362
|
+
urlKey: deriveProjectUrlKey(row.projectName, row.projectId),
|
|
363
|
+
color: row.projectColor,
|
|
364
|
+
icon: row.projectIcon,
|
|
365
|
+
} : null,
|
|
366
|
+
workspace: row.workspaceId && row.workspaceName ? {
|
|
367
|
+
id: row.workspaceId,
|
|
368
|
+
name: row.workspaceName,
|
|
369
|
+
} : null,
|
|
370
|
+
}]));
|
|
371
|
+
}
|
|
372
|
+
async function issueImageMap(db, companyId, issueIds) {
|
|
373
|
+
const ids = [...new Set(issueIds.filter((value) => Boolean(value)))];
|
|
374
|
+
if (ids.length === 0)
|
|
375
|
+
return new Map();
|
|
376
|
+
const rows = await db
|
|
377
|
+
.select({
|
|
378
|
+
issueId: issueAttachments.issueId,
|
|
379
|
+
assetId: issueAttachments.assetId,
|
|
380
|
+
originalFilename: assets.originalFilename,
|
|
381
|
+
})
|
|
382
|
+
.from(issueAttachments)
|
|
383
|
+
.innerJoin(assets, eq(issueAttachments.assetId, assets.id))
|
|
384
|
+
.where(and(eq(issueAttachments.companyId, companyId), eq(assets.companyId, companyId), inArray(issueAttachments.issueId, ids), sql `${assets.contentType} like 'image/%'`))
|
|
385
|
+
.orderBy(asc(issueAttachments.issueId), asc(issueAttachments.createdAt), asc(issueAttachments.id));
|
|
386
|
+
const map = new Map();
|
|
387
|
+
for (const row of rows) {
|
|
388
|
+
const images = map.get(row.issueId) ?? [];
|
|
389
|
+
if (images.length >= DETAIL_IMAGE_LIMIT)
|
|
390
|
+
continue;
|
|
391
|
+
images.push({ assetId: row.assetId, alt: row.originalFilename ?? null });
|
|
392
|
+
map.set(row.issueId, images);
|
|
393
|
+
}
|
|
394
|
+
return map;
|
|
395
|
+
}
|
|
396
|
+
async function planDocumentMap(db, companyId, issueIds) {
|
|
397
|
+
const ids = [...new Set(issueIds.filter((value) => Boolean(value)))];
|
|
398
|
+
if (ids.length === 0)
|
|
399
|
+
return new Map();
|
|
400
|
+
const rows = await db
|
|
401
|
+
.select({
|
|
402
|
+
issueId: issueDocuments.issueId,
|
|
403
|
+
title: documents.title,
|
|
404
|
+
body: documents.latestBody,
|
|
405
|
+
})
|
|
406
|
+
.from(issueDocuments)
|
|
407
|
+
.innerJoin(documents, eq(issueDocuments.documentId, documents.id))
|
|
408
|
+
.where(and(eq(issueDocuments.companyId, companyId), eq(documents.companyId, companyId), eq(issueDocuments.key, "plan"), inArray(issueDocuments.issueId, ids)));
|
|
409
|
+
return new Map(rows.map((row) => [row.issueId, { title: row.title, body: row.body }]));
|
|
410
|
+
}
|
|
411
|
+
async function blockingIssueMap(db, companyId, blockedIssueIds) {
|
|
412
|
+
const ids = [...new Set(blockedIssueIds.filter((value) => Boolean(value)))];
|
|
413
|
+
if (ids.length === 0)
|
|
414
|
+
return new Map();
|
|
415
|
+
const rows = await db
|
|
416
|
+
.select({
|
|
417
|
+
blockedIssueId: issueRelations.relatedIssueId,
|
|
418
|
+
id: issues.id,
|
|
419
|
+
identifier: issues.identifier,
|
|
420
|
+
title: issues.title,
|
|
421
|
+
})
|
|
422
|
+
.from(issueRelations)
|
|
423
|
+
.innerJoin(issues, eq(issueRelations.issueId, issues.id))
|
|
424
|
+
.where(and(eq(issueRelations.companyId, companyId), eq(issues.companyId, companyId), eq(issueRelations.type, "blocks"), inArray(issueRelations.relatedIssueId, ids), isNull(issues.hiddenAt)))
|
|
425
|
+
.orderBy(asc(issueRelations.relatedIssueId), asc(issueRelations.createdAt), asc(issueRelations.id));
|
|
426
|
+
const map = new Map();
|
|
427
|
+
for (const row of rows) {
|
|
428
|
+
if (!map.has(row.blockedIssueId)) {
|
|
429
|
+
map.set(row.blockedIssueId, { id: row.id, identifier: row.identifier, title: row.title });
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
return map;
|
|
433
|
+
}
|
|
434
|
+
function readRunIssueId(contextSnapshot) {
|
|
435
|
+
const issueId = contextSnapshot?.issueId ?? contextSnapshot?.taskId;
|
|
436
|
+
return typeof issueId === "string" && issueId.length > 0 ? issueId : null;
|
|
437
|
+
}
|
|
438
|
+
export function attentionService(db) {
|
|
439
|
+
return {
|
|
440
|
+
list: async (companyId, options = {}) => {
|
|
441
|
+
const prefix = await companyPrefix(db, companyId);
|
|
442
|
+
const dismissals = await dismissalByKey(db, companyId, options.userId);
|
|
443
|
+
const includeDismissed = options.includeDismissed === true;
|
|
444
|
+
const now = Date.now();
|
|
445
|
+
const collected = [];
|
|
446
|
+
const add = (item) => {
|
|
447
|
+
const dismissal = activeDismissalState(dismissals, item.dismissalKey, item.activityAt, now);
|
|
448
|
+
if (!includeDismissed && dismissal?.isActive)
|
|
449
|
+
return;
|
|
450
|
+
collected.push({ ...item, dismissal });
|
|
451
|
+
};
|
|
452
|
+
const pendingApprovals = await db
|
|
453
|
+
.select({
|
|
454
|
+
id: approvals.id,
|
|
455
|
+
type: approvals.type,
|
|
456
|
+
status: approvals.status,
|
|
457
|
+
requestedByAgentId: approvals.requestedByAgentId,
|
|
458
|
+
requestedByUserId: approvals.requestedByUserId,
|
|
459
|
+
payload: approvals.payload,
|
|
460
|
+
createdAt: approvals.createdAt,
|
|
461
|
+
updatedAt: approvals.updatedAt,
|
|
462
|
+
})
|
|
463
|
+
.from(approvals)
|
|
464
|
+
.where(and(eq(approvals.companyId, companyId), eq(approvals.status, "pending")))
|
|
465
|
+
.orderBy(desc(approvals.updatedAt), desc(approvals.id));
|
|
466
|
+
for (const approval of pendingApprovals) {
|
|
467
|
+
const dedupKey = `approval:${approval.id}`;
|
|
468
|
+
const title = approvalTitle(approval.type, approval.payload);
|
|
469
|
+
add(createItem({
|
|
470
|
+
companyId,
|
|
471
|
+
sourceKind: "approval",
|
|
472
|
+
subject: {
|
|
473
|
+
kind: "approval",
|
|
474
|
+
id: approval.id,
|
|
475
|
+
companyId,
|
|
476
|
+
title,
|
|
477
|
+
identifier: null,
|
|
478
|
+
status: approval.status,
|
|
479
|
+
href: `/${prefix}/approvals/${approval.id}`,
|
|
480
|
+
metadata: {
|
|
481
|
+
type: approval.type,
|
|
482
|
+
requestedByAgentId: approval.requestedByAgentId,
|
|
483
|
+
requestedByUserId: approval.requestedByUserId,
|
|
484
|
+
},
|
|
485
|
+
},
|
|
486
|
+
whyNow: "Approval is pending a board decision.",
|
|
487
|
+
decisionVerbs: decisionVerbs({ id: "approve", label: "Approve", description: "Approve the request." }, { id: "reject", label: "Reject", description: "Reject the request." }, { id: "request_revision", label: "Request revision", description: "Send the request back for changes." }),
|
|
488
|
+
inlineResolvable: approval.type !== "request_board_approval",
|
|
489
|
+
entryRule: "approvals.status = 'pending'",
|
|
490
|
+
exitRule: "Approval leaves pending status.",
|
|
491
|
+
dedupKey,
|
|
492
|
+
severity: "medium",
|
|
493
|
+
activityAt: toIso(approval.updatedAt),
|
|
494
|
+
createdAt: toIso(approval.createdAt),
|
|
495
|
+
updatedAt: toIso(approval.updatedAt),
|
|
496
|
+
relatedIssue: null,
|
|
497
|
+
detail: approvalDetail(approval.type, approval.payload),
|
|
498
|
+
}));
|
|
499
|
+
}
|
|
500
|
+
const interactionRows = await db
|
|
501
|
+
.select({
|
|
502
|
+
id: issueThreadInteractions.id,
|
|
503
|
+
issueId: issueThreadInteractions.issueId,
|
|
504
|
+
kind: issueThreadInteractions.kind,
|
|
505
|
+
status: issueThreadInteractions.status,
|
|
506
|
+
title: issueThreadInteractions.title,
|
|
507
|
+
summary: issueThreadInteractions.summary,
|
|
508
|
+
payload: issueThreadInteractions.payload,
|
|
509
|
+
createdAt: issueThreadInteractions.createdAt,
|
|
510
|
+
updatedAt: issueThreadInteractions.updatedAt,
|
|
511
|
+
})
|
|
512
|
+
.from(issueThreadInteractions)
|
|
513
|
+
.where(and(eq(issueThreadInteractions.companyId, companyId), inArray(issueThreadInteractions.status, [...PENDING_INTERACTION_STATUSES])))
|
|
514
|
+
.orderBy(desc(issueThreadInteractions.updatedAt), desc(issueThreadInteractions.id));
|
|
515
|
+
const interactionIssueMap = await issueSummaryMap(db, companyId, interactionRows.map((row) => row.issueId));
|
|
516
|
+
const interactionImageMap = await issueImageMap(db, companyId, interactionRows.map((row) => row.issueId));
|
|
517
|
+
const interactionPlanDocumentMap = await planDocumentMap(db, companyId, interactionRows.map((row) => row.issueId));
|
|
518
|
+
for (const interaction of interactionRows) {
|
|
519
|
+
const issue = interactionIssueMap.get(interaction.issueId) ?? null;
|
|
520
|
+
const payload = readRecord(interaction.payload);
|
|
521
|
+
const detail = interactionDetail({
|
|
522
|
+
kind: interaction.kind,
|
|
523
|
+
payload,
|
|
524
|
+
issue,
|
|
525
|
+
planDocument: interactionPlanDocumentMap.get(interaction.issueId) ?? null,
|
|
526
|
+
images: issueImages(interactionImageMap, interaction.issueId),
|
|
527
|
+
});
|
|
528
|
+
const isPlanTarget = detail.kind === "plan_approval";
|
|
529
|
+
const dedupKey = `interaction:${interaction.id}`;
|
|
530
|
+
add(createItem({
|
|
531
|
+
companyId,
|
|
532
|
+
sourceKind: "issue_thread_interaction",
|
|
533
|
+
subject: {
|
|
534
|
+
kind: "interaction",
|
|
535
|
+
id: interaction.id,
|
|
536
|
+
companyId,
|
|
537
|
+
title: isPlanTarget && issue ? `Plan approval - ${issue.title}` : interaction.title ?? interaction.summary ?? interactionLabel(interaction.kind),
|
|
538
|
+
identifier: null,
|
|
539
|
+
status: interaction.status,
|
|
540
|
+
href: issue ? `${issueHref(prefix, issue)}#interaction-${interaction.id}` : null,
|
|
541
|
+
metadata: {
|
|
542
|
+
kind: interaction.kind,
|
|
543
|
+
issueId: interaction.issueId,
|
|
544
|
+
isPlanTarget,
|
|
545
|
+
targetDocumentKey: isPlanTarget ? "plan" : null,
|
|
546
|
+
},
|
|
547
|
+
},
|
|
548
|
+
whyNow: `${interactionLabel(interaction.kind)} on an issue thread.`,
|
|
549
|
+
decisionVerbs: interactionVerbs(interaction.kind, payload),
|
|
550
|
+
inlineResolvable: true,
|
|
551
|
+
entryRule: "issue_thread_interactions.status = 'pending'",
|
|
552
|
+
exitRule: "Interaction resolves, expires, fails, or is cancelled.",
|
|
553
|
+
dedupKey,
|
|
554
|
+
severity: "medium",
|
|
555
|
+
activityAt: toIso(interaction.updatedAt),
|
|
556
|
+
createdAt: toIso(interaction.createdAt),
|
|
557
|
+
updatedAt: toIso(interaction.updatedAt),
|
|
558
|
+
relatedIssue: issue ? issueSubject(prefix, issue) : null,
|
|
559
|
+
...issueContext(issue),
|
|
560
|
+
detail,
|
|
561
|
+
}));
|
|
562
|
+
}
|
|
563
|
+
const pendingJoins = await db
|
|
564
|
+
.select({
|
|
565
|
+
id: joinRequests.id,
|
|
566
|
+
requestType: joinRequests.requestType,
|
|
567
|
+
status: joinRequests.status,
|
|
568
|
+
requestingUserId: joinRequests.requestingUserId,
|
|
569
|
+
requestEmailSnapshot: joinRequests.requestEmailSnapshot,
|
|
570
|
+
agentName: joinRequests.agentName,
|
|
571
|
+
adapterType: joinRequests.adapterType,
|
|
572
|
+
createdAt: joinRequests.createdAt,
|
|
573
|
+
updatedAt: joinRequests.updatedAt,
|
|
574
|
+
})
|
|
575
|
+
.from(joinRequests)
|
|
576
|
+
.innerJoin(invites, eq(joinRequests.inviteId, invites.id))
|
|
577
|
+
.where(and(eq(joinRequests.companyId, companyId), eq(invites.companyId, companyId), eq(joinRequests.status, "pending_approval")))
|
|
578
|
+
.orderBy(desc(joinRequests.updatedAt), desc(joinRequests.id));
|
|
579
|
+
for (const join of pendingJoins) {
|
|
580
|
+
const label = join.requestType === "agent"
|
|
581
|
+
? join.agentName ?? "Agent join request"
|
|
582
|
+
: join.requestEmailSnapshot ?? join.requestingUserId ?? "Human join request";
|
|
583
|
+
const dedupKey = `join:${join.id}`;
|
|
584
|
+
add(createItem({
|
|
585
|
+
companyId,
|
|
586
|
+
sourceKind: "join_request",
|
|
587
|
+
subject: {
|
|
588
|
+
kind: "join_request",
|
|
589
|
+
id: join.id,
|
|
590
|
+
companyId,
|
|
591
|
+
title: label,
|
|
592
|
+
identifier: null,
|
|
593
|
+
status: join.status,
|
|
594
|
+
href: `/${prefix}/settings/access`,
|
|
595
|
+
metadata: {
|
|
596
|
+
requestType: join.requestType,
|
|
597
|
+
requestingUserId: join.requestingUserId,
|
|
598
|
+
adapterType: join.adapterType,
|
|
599
|
+
},
|
|
600
|
+
},
|
|
601
|
+
whyNow: "Join request is pending approval.",
|
|
602
|
+
decisionVerbs: decisionVerbs({ id: "approve", label: "Approve", description: "Approve this join request." }, { id: "reject", label: "Reject", description: "Reject this join request." }),
|
|
603
|
+
inlineResolvable: true,
|
|
604
|
+
entryRule: "join_requests.status = 'pending_approval'",
|
|
605
|
+
exitRule: "Join request is approved or rejected.",
|
|
606
|
+
dedupKey,
|
|
607
|
+
severity: "medium",
|
|
608
|
+
activityAt: toIso(join.updatedAt),
|
|
609
|
+
createdAt: toIso(join.createdAt),
|
|
610
|
+
updatedAt: toIso(join.updatedAt),
|
|
611
|
+
relatedIssue: null,
|
|
612
|
+
detail: genericDetail(label, []),
|
|
613
|
+
}));
|
|
614
|
+
}
|
|
615
|
+
const recoveryRows = await db
|
|
616
|
+
.select()
|
|
617
|
+
.from(issueRecoveryActions)
|
|
618
|
+
.where(and(eq(issueRecoveryActions.companyId, companyId), inArray(issueRecoveryActions.status, [...OPEN_RECOVERY_STATUSES]), inArray(issueRecoveryActions.ownerType, [...HUMAN_RECOVERY_OWNER_TYPES])))
|
|
619
|
+
.orderBy(desc(issueRecoveryActions.updatedAt), desc(issueRecoveryActions.id));
|
|
620
|
+
const recoveryIssueMap = await issueSummaryMap(db, companyId, recoveryRows.flatMap((row) => [row.sourceIssueId, row.recoveryIssueId]));
|
|
621
|
+
const recoveryImageMap = await issueImageMap(db, companyId, recoveryRows.map((row) => row.sourceIssueId));
|
|
622
|
+
for (const recovery of recoveryRows) {
|
|
623
|
+
const sourceIssue = recoveryIssueMap.get(recovery.sourceIssueId) ?? null;
|
|
624
|
+
const recoveryIssue = recovery.recoveryIssueId ? recoveryIssueMap.get(recovery.recoveryIssueId) ?? null : null;
|
|
625
|
+
const dedupKey = `recovery:${recovery.kind}:${recovery.sourceIssueId}:${recovery.cause}:${recovery.fingerprint}`;
|
|
626
|
+
add(createItem({
|
|
627
|
+
companyId,
|
|
628
|
+
sourceKind: "recovery_action",
|
|
629
|
+
subject: {
|
|
630
|
+
kind: "recovery_action",
|
|
631
|
+
id: recovery.id,
|
|
632
|
+
companyId,
|
|
633
|
+
title: recovery.nextAction,
|
|
634
|
+
identifier: null,
|
|
635
|
+
status: recovery.status,
|
|
636
|
+
href: recoveryIssue ? issueHref(prefix, recoveryIssue) : sourceIssue ? issueHref(prefix, sourceIssue) : null,
|
|
637
|
+
metadata: {
|
|
638
|
+
kind: recovery.kind,
|
|
639
|
+
cause: recovery.cause,
|
|
640
|
+
ownerType: recovery.ownerType,
|
|
641
|
+
ownerUserId: recovery.ownerUserId,
|
|
642
|
+
sourceIssueId: recovery.sourceIssueId,
|
|
643
|
+
recoveryIssueId: recovery.recoveryIssueId,
|
|
644
|
+
},
|
|
645
|
+
},
|
|
646
|
+
whyNow: recovery.status === "escalated"
|
|
647
|
+
? "Recovery action escalated to a human owner."
|
|
648
|
+
: "Recovery action is assigned to a human owner.",
|
|
649
|
+
decisionVerbs: decisionVerbs({ id: "resolve", label: "Resolve", description: "Record the recovery outcome." }, { id: "reassign", label: "Reassign", description: "Move the recovery to another owner." }, { id: "cancel", label: "Cancel", description: "Cancel the recovery action." }),
|
|
650
|
+
inlineResolvable: false,
|
|
651
|
+
entryRule: "issue_recovery_actions.status in ('active','escalated') and owner_type in ('user','board')",
|
|
652
|
+
exitRule: "Recovery action resolves, is cancelled, or moves back to an agent/system owner.",
|
|
653
|
+
dedupKey,
|
|
654
|
+
severity: recovery.status === "escalated" ? "high" : "medium",
|
|
655
|
+
activityAt: toIso(recovery.updatedAt),
|
|
656
|
+
createdAt: toIso(recovery.createdAt),
|
|
657
|
+
updatedAt: toIso(recovery.updatedAt),
|
|
658
|
+
relatedIssue: sourceIssue ? issueSubject(prefix, sourceIssue) : null,
|
|
659
|
+
...issueContext(sourceIssue),
|
|
660
|
+
detail: genericDetail(recovery.nextAction, issueImages(recoveryImageMap, recovery.sourceIssueId)),
|
|
661
|
+
}));
|
|
662
|
+
}
|
|
663
|
+
const productivityRows = await db
|
|
664
|
+
.select({
|
|
665
|
+
id: issues.id,
|
|
666
|
+
companyId: issues.companyId,
|
|
667
|
+
identifier: issues.identifier,
|
|
668
|
+
title: issues.title,
|
|
669
|
+
status: issues.status,
|
|
670
|
+
priority: issues.priority,
|
|
671
|
+
originId: issues.originId,
|
|
672
|
+
originFingerprint: issues.originFingerprint,
|
|
673
|
+
assigneeAgentId: issues.assigneeAgentId,
|
|
674
|
+
assigneeUserId: issues.assigneeUserId,
|
|
675
|
+
createdAt: issues.createdAt,
|
|
676
|
+
updatedAt: issues.updatedAt,
|
|
677
|
+
})
|
|
678
|
+
.from(issues)
|
|
679
|
+
.where(and(eq(issues.companyId, companyId), eq(issues.originKind, PRODUCTIVITY_REVIEW_ORIGIN_KIND), isNull(issues.hiddenAt), isNotNull(issues.assigneeUserId), notInArray(issues.status, [...PRODUCTIVITY_REVIEW_TERMINAL_STATUSES])))
|
|
680
|
+
.orderBy(desc(issues.updatedAt), desc(issues.id));
|
|
681
|
+
const productivitySourceMap = await issueSummaryMap(db, companyId, productivityRows.map((row) => row.originId));
|
|
682
|
+
const productivityReviewMap = await issueSummaryMap(db, companyId, productivityRows.map((row) => row.id));
|
|
683
|
+
const productivityImageMap = await issueImageMap(db, companyId, productivityRows.map((row) => row.id));
|
|
684
|
+
for (const review of productivityRows) {
|
|
685
|
+
const reviewIssue = productivityReviewMap.get(review.id);
|
|
686
|
+
if (!reviewIssue)
|
|
687
|
+
continue;
|
|
688
|
+
const sourceIssue = review.originId ? productivitySourceMap.get(review.originId) ?? null : null;
|
|
689
|
+
const dedupKey = `productivity_review:${review.originFingerprint ?? review.originId ?? review.id}`;
|
|
690
|
+
add(createItem({
|
|
691
|
+
companyId,
|
|
692
|
+
sourceKind: "productivity_review",
|
|
693
|
+
subject: issueSubject(prefix, reviewIssue),
|
|
694
|
+
whyNow: "Productivity review is awaiting a human decision.",
|
|
695
|
+
decisionVerbs: decisionVerbs({ id: "resolve", label: "Resolve", description: "Record a productivity review outcome." }, { id: "dismiss", label: "Dismiss", description: "Dismiss this review for now." }, { id: "reassign", label: "Reassign", description: "Move the review to another owner." }),
|
|
696
|
+
inlineResolvable: false,
|
|
697
|
+
entryRule: "Open issue_productivity_review issue assigned to a user.",
|
|
698
|
+
exitRule: "Review issue is done/cancelled or no longer assigned to a user.",
|
|
699
|
+
dedupKey,
|
|
700
|
+
severity: review.priority === "critical" ? "critical" : review.priority === "high" ? "high" : "medium",
|
|
701
|
+
activityAt: toIso(review.updatedAt),
|
|
702
|
+
createdAt: toIso(review.createdAt),
|
|
703
|
+
updatedAt: toIso(review.updatedAt),
|
|
704
|
+
relatedIssue: sourceIssue ? issueSubject(prefix, sourceIssue) : null,
|
|
705
|
+
...issueContext(reviewIssue),
|
|
706
|
+
detail: genericDetail(sourceIssue?.title ?? review.title, issueImages(productivityImageMap, review.id)),
|
|
707
|
+
}));
|
|
708
|
+
}
|
|
709
|
+
const blockedIssues = await issueService(db).list(companyId, { status: "blocked", includeBlockedBy: true });
|
|
710
|
+
const blockedIssueSummaries = await issueSummaryMap(db, companyId, blockedIssues.map((issue) => issue.id));
|
|
711
|
+
const blockedImageMap = await issueImageMap(db, companyId, blockedIssues.map((issue) => issue.id));
|
|
712
|
+
const blockingIssues = await blockingIssueMap(db, companyId, blockedIssues.map((issue) => issue.id));
|
|
713
|
+
for (const issue of blockedIssues) {
|
|
714
|
+
const blockerAttention = issue.blockerAttention;
|
|
715
|
+
if (blockerAttention?.state !== "stalled")
|
|
716
|
+
continue;
|
|
717
|
+
const issueSummary = blockedIssueSummaries.get(issue.id) ?? null;
|
|
718
|
+
const summarizedIssue = issueSummary ?? issue;
|
|
719
|
+
const sample = blockerAttention.sampleStalledBlockerIdentifier ?? blockerAttention.sampleBlockerIdentifier ?? issue.identifier ?? issue.id;
|
|
720
|
+
const blockingIssue = blockingIssues.get(issue.id) ?? { id: null, identifier: sample, title: null };
|
|
721
|
+
const dedupKey = `blocker:${issue.id}:${sample}`;
|
|
722
|
+
add(createItem({
|
|
723
|
+
companyId,
|
|
724
|
+
sourceKind: "blocker_attention",
|
|
725
|
+
subject: issueSubject(prefix, summarizedIssue),
|
|
726
|
+
whyNow: "Blocked dependency chain is stalled and needs a human to choose the next owner or action.",
|
|
727
|
+
decisionVerbs: decisionVerbs({ id: "unblock", label: "Unblock", description: "Repair or replace the stalled blocker path." }, { id: "reassign", label: "Reassign", description: "Assign the stalled blocker to a live owner." }, { id: "nudge", label: "Nudge", description: "Wake or prompt the current owner." }),
|
|
728
|
+
inlineResolvable: false,
|
|
729
|
+
entryRule: "blocked issue has blockerAttention.state = 'stalled'",
|
|
730
|
+
exitRule: "Blocker chain is no longer stalled or the issue leaves blocked status.",
|
|
731
|
+
dedupKey,
|
|
732
|
+
severity: "high",
|
|
733
|
+
activityAt: toIso(issue.updatedAt),
|
|
734
|
+
createdAt: toIso(issue.createdAt),
|
|
735
|
+
updatedAt: toIso(issue.updatedAt),
|
|
736
|
+
relatedIssue: null,
|
|
737
|
+
...issueContext(issueSummary),
|
|
738
|
+
detail: { kind: "blocker", blockingIssue, images: issueImages(blockedImageMap, issue.id) },
|
|
739
|
+
}));
|
|
740
|
+
}
|
|
741
|
+
const reviewRows = await db
|
|
742
|
+
.select({
|
|
743
|
+
id: issues.id,
|
|
744
|
+
companyId: issues.companyId,
|
|
745
|
+
identifier: issues.identifier,
|
|
746
|
+
title: issues.title,
|
|
747
|
+
status: issues.status,
|
|
748
|
+
priority: issues.priority,
|
|
749
|
+
assigneeAgentId: issues.assigneeAgentId,
|
|
750
|
+
assigneeUserId: issues.assigneeUserId,
|
|
751
|
+
executionState: issues.executionState,
|
|
752
|
+
createdAt: issues.createdAt,
|
|
753
|
+
updatedAt: issues.updatedAt,
|
|
754
|
+
})
|
|
755
|
+
.from(issues)
|
|
756
|
+
.where(and(eq(issues.companyId, companyId), eq(issues.status, "in_review"), isNull(issues.hiddenAt)))
|
|
757
|
+
.orderBy(desc(issues.updatedAt), desc(issues.id));
|
|
758
|
+
const reviewIssueIds = reviewRows.map((row) => row.id);
|
|
759
|
+
const pendingReviewApprovalRows = reviewIssueIds.length === 0
|
|
760
|
+
? []
|
|
761
|
+
: await db
|
|
762
|
+
.select({ issueId: issueApprovals.issueId, approvalId: approvals.id })
|
|
763
|
+
.from(issueApprovals)
|
|
764
|
+
.innerJoin(approvals, eq(issueApprovals.approvalId, approvals.id))
|
|
765
|
+
.where(and(eq(issueApprovals.companyId, companyId), eq(approvals.companyId, companyId), inArray(issueApprovals.issueId, reviewIssueIds), eq(approvals.status, "pending")));
|
|
766
|
+
const pendingApprovalByIssueId = new Map(pendingReviewApprovalRows.map((row) => [row.issueId, row.approvalId]));
|
|
767
|
+
const reviewIssueMap = await issueSummaryMap(db, companyId, reviewIssueIds);
|
|
768
|
+
const reviewImageMap = await issueImageMap(db, companyId, reviewIssueIds);
|
|
769
|
+
for (const review of reviewRows) {
|
|
770
|
+
const state = parseIssueExecutionState(review.executionState);
|
|
771
|
+
const currentParticipant = state?.status === "pending" ? state.currentParticipant : null;
|
|
772
|
+
const hasHumanParticipant = currentParticipant?.type === "user";
|
|
773
|
+
const pendingApprovalId = pendingApprovalByIssueId.get(review.id) ?? null;
|
|
774
|
+
if (!hasHumanParticipant && !review.assigneeUserId && !pendingApprovalId)
|
|
775
|
+
continue;
|
|
776
|
+
const issue = reviewIssueMap.get(review.id);
|
|
777
|
+
if (!issue)
|
|
778
|
+
continue;
|
|
779
|
+
const dedupKey = `review:${review.id}`;
|
|
780
|
+
add(createItem({
|
|
781
|
+
companyId,
|
|
782
|
+
sourceKind: "review",
|
|
783
|
+
subject: issueSubject(prefix, issue),
|
|
784
|
+
whyNow: pendingApprovalId
|
|
785
|
+
? "Issue is in review with a linked pending approval."
|
|
786
|
+
: hasHumanParticipant
|
|
787
|
+
? "Issue is in review and the current execution participant is a user."
|
|
788
|
+
: "Issue is in review and assigned to a user.",
|
|
789
|
+
decisionVerbs: decisionVerbs({ id: "approve", label: "Approve", description: "Approve the review and advance the issue." }, { id: "request_changes", label: "Request changes", description: "Return the issue to the assignee with changes requested." }),
|
|
790
|
+
inlineResolvable: false,
|
|
791
|
+
entryRule: "issues.status = 'in_review' and human reviewer, user assignee, or linked pending approval exists.",
|
|
792
|
+
exitRule: "Issue leaves in_review or the human review path resolves.",
|
|
793
|
+
dedupKey,
|
|
794
|
+
severity: "medium",
|
|
795
|
+
activityAt: toIso(review.updatedAt),
|
|
796
|
+
createdAt: toIso(review.createdAt),
|
|
797
|
+
updatedAt: toIso(review.updatedAt),
|
|
798
|
+
relatedIssue: null,
|
|
799
|
+
...issueContext(issue),
|
|
800
|
+
detail: genericDetail(review.title, issueImages(reviewImageMap, review.id)),
|
|
801
|
+
}));
|
|
802
|
+
}
|
|
803
|
+
const exhaustedRunRows = await db
|
|
804
|
+
.select({
|
|
805
|
+
id: heartbeatRuns.id,
|
|
806
|
+
companyId: heartbeatRuns.companyId,
|
|
807
|
+
agentId: heartbeatRuns.agentId,
|
|
808
|
+
agentName: agents.name,
|
|
809
|
+
status: heartbeatRuns.status,
|
|
810
|
+
error: heartbeatRuns.error,
|
|
811
|
+
errorCode: heartbeatRuns.errorCode,
|
|
812
|
+
contextSnapshot: heartbeatRuns.contextSnapshot,
|
|
813
|
+
createdAt: heartbeatRuns.createdAt,
|
|
814
|
+
updatedAt: heartbeatRuns.updatedAt,
|
|
815
|
+
finishedAt: heartbeatRuns.finishedAt,
|
|
816
|
+
exhaustionMessage: heartbeatRunEvents.message,
|
|
817
|
+
})
|
|
818
|
+
.from(heartbeatRuns)
|
|
819
|
+
.innerJoin(agents, eq(heartbeatRuns.agentId, agents.id))
|
|
820
|
+
.innerJoin(heartbeatRunEvents, eq(heartbeatRunEvents.runId, heartbeatRuns.id))
|
|
821
|
+
.where(and(eq(heartbeatRuns.companyId, companyId), eq(agents.companyId, companyId), notInArray(agents.status, ["terminated"]), inArray(heartbeatRuns.status, [...FAILED_RUN_STATUSES]), eq(heartbeatRunEvents.companyId, companyId), eq(heartbeatRunEvents.eventType, "lifecycle"), sql `${heartbeatRunEvents.message} like 'Bounded retry exhausted%'`))
|
|
822
|
+
.orderBy(desc(heartbeatRuns.createdAt), desc(heartbeatRunEvents.id));
|
|
823
|
+
const latestExhaustedByRunId = new Map();
|
|
824
|
+
for (const row of exhaustedRunRows) {
|
|
825
|
+
if (!latestExhaustedByRunId.has(row.id))
|
|
826
|
+
latestExhaustedByRunId.set(row.id, row);
|
|
827
|
+
}
|
|
828
|
+
const failedRows = [...latestExhaustedByRunId.values()];
|
|
829
|
+
const failedIssueIds = failedRows.map((row) => readRunIssueId(row.contextSnapshot));
|
|
830
|
+
const failedIssueMap = await issueSummaryMap(db, companyId, failedIssueIds);
|
|
831
|
+
const failedImageMap = await issueImageMap(db, companyId, failedIssueIds);
|
|
832
|
+
const failedAgentIds = [...new Set(failedRows.map((row) => row.agentId))];
|
|
833
|
+
const oldestFailedRunCreatedAt = failedRows.reduce((oldest, row) => {
|
|
834
|
+
if (!oldest || row.createdAt < oldest)
|
|
835
|
+
return row.createdAt;
|
|
836
|
+
return oldest;
|
|
837
|
+
}, null);
|
|
838
|
+
const latestRunCreatedAtByKey = new Map();
|
|
839
|
+
if (oldestFailedRunCreatedAt && failedAgentIds.length > 0) {
|
|
840
|
+
const newerRuns = await db
|
|
841
|
+
.select({
|
|
842
|
+
agentId: heartbeatRuns.agentId,
|
|
843
|
+
createdAt: heartbeatRuns.createdAt,
|
|
844
|
+
contextSnapshot: heartbeatRuns.contextSnapshot,
|
|
845
|
+
})
|
|
846
|
+
.from(heartbeatRuns)
|
|
847
|
+
.where(and(eq(heartbeatRuns.companyId, companyId), inArray(heartbeatRuns.agentId, failedAgentIds), gt(heartbeatRuns.createdAt, oldestFailedRunCreatedAt)));
|
|
848
|
+
for (const newerRun of newerRuns) {
|
|
849
|
+
const newerRunKey = `${newerRun.agentId}:${readRunIssueId(newerRun.contextSnapshot) ?? ""}`;
|
|
850
|
+
const latestCreatedAt = latestRunCreatedAtByKey.get(newerRunKey);
|
|
851
|
+
if (!latestCreatedAt || newerRun.createdAt > latestCreatedAt) {
|
|
852
|
+
latestRunCreatedAtByKey.set(newerRunKey, newerRun.createdAt);
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
for (const run of failedRows) {
|
|
857
|
+
const issueId = readRunIssueId(run.contextSnapshot);
|
|
858
|
+
const runKey = `${run.agentId}:${issueId ?? ""}`;
|
|
859
|
+
const hasNewerRun = (latestRunCreatedAtByKey.get(runKey)?.getTime() ?? 0) > run.createdAt.getTime();
|
|
860
|
+
if (hasNewerRun)
|
|
861
|
+
continue;
|
|
862
|
+
const issue = issueId ? failedIssueMap.get(issueId) ?? null : null;
|
|
863
|
+
const dedupKey = `run:${run.id}`;
|
|
864
|
+
add(createItem({
|
|
865
|
+
companyId,
|
|
866
|
+
sourceKind: "failed_run",
|
|
867
|
+
subject: {
|
|
868
|
+
kind: "run",
|
|
869
|
+
id: run.id,
|
|
870
|
+
companyId,
|
|
871
|
+
title: `${run.agentName} run ${run.status}`,
|
|
872
|
+
identifier: null,
|
|
873
|
+
status: run.status,
|
|
874
|
+
href: `/${prefix}/agents/${run.agentId}/runs/${run.id}`,
|
|
875
|
+
metadata: {
|
|
876
|
+
agentId: run.agentId,
|
|
877
|
+
agentName: run.agentName,
|
|
878
|
+
issueId,
|
|
879
|
+
errorCode: run.errorCode,
|
|
880
|
+
error: run.error,
|
|
881
|
+
retryExhaustedReason: run.exhaustionMessage,
|
|
882
|
+
},
|
|
883
|
+
},
|
|
884
|
+
whyNow: "Run failed after automatic retries were exhausted.",
|
|
885
|
+
decisionVerbs: decisionVerbs({ id: "retry", label: "Retry", description: "Retry the failed run or issue." }, { id: "reassign", label: "Reassign", description: "Move the work to another owner." }, { id: "dismiss", label: "Dismiss", description: "Dismiss this failed-run attention row." }),
|
|
886
|
+
inlineResolvable: true,
|
|
887
|
+
entryRule: "latest failed/timed_out run has a Bounded retry exhausted lifecycle event.",
|
|
888
|
+
exitRule: "A newer run exists for the same issue/agent pair or the row is dismissed.",
|
|
889
|
+
dedupKey,
|
|
890
|
+
severity: "high",
|
|
891
|
+
activityAt: toIso(run.finishedAt ?? run.updatedAt ?? run.createdAt),
|
|
892
|
+
createdAt: toIso(run.createdAt),
|
|
893
|
+
updatedAt: toIso(run.updatedAt),
|
|
894
|
+
relatedIssue: issue ? issueSubject(prefix, issue) : null,
|
|
895
|
+
...issueContext(issue),
|
|
896
|
+
detail: {
|
|
897
|
+
kind: "failed_run",
|
|
898
|
+
agentName: run.agentName,
|
|
899
|
+
failureReasonExcerpt: excerpt(run.error ?? run.exhaustionMessage ?? run.errorCode),
|
|
900
|
+
images: issueImages(failedImageMap, issueId),
|
|
901
|
+
},
|
|
902
|
+
}));
|
|
903
|
+
}
|
|
904
|
+
const budgetOverview = await budgetService(db).overview(companyId);
|
|
905
|
+
for (const incident of budgetOverview.activeIncidents) {
|
|
906
|
+
const observedPercent = budgetObservedPercent(incident.amountObserved, incident.amountLimit);
|
|
907
|
+
if (incident.thresholdType !== "hard" && observedPercent < 85)
|
|
908
|
+
continue;
|
|
909
|
+
const dedupKey = `budget:${incident.policyId}:${toIso(incident.windowStart)}:${incident.thresholdType}`;
|
|
910
|
+
add(createItem({
|
|
911
|
+
companyId,
|
|
912
|
+
sourceKind: "budget_alert",
|
|
913
|
+
subject: {
|
|
914
|
+
kind: "budget_incident",
|
|
915
|
+
id: incident.id,
|
|
916
|
+
companyId,
|
|
917
|
+
title: `${incident.scopeName} budget ${incident.thresholdType === "hard" ? "hard stop" : "warning"}`,
|
|
918
|
+
identifier: null,
|
|
919
|
+
status: incident.status,
|
|
920
|
+
href: `/${prefix}/costs`,
|
|
921
|
+
metadata: {
|
|
922
|
+
policyId: incident.policyId,
|
|
923
|
+
scopeType: incident.scopeType,
|
|
924
|
+
scopeId: incident.scopeId,
|
|
925
|
+
thresholdType: incident.thresholdType,
|
|
926
|
+
amountObserved: incident.amountObserved,
|
|
927
|
+
amountLimit: incident.amountLimit,
|
|
928
|
+
observedPercent,
|
|
929
|
+
approvalId: incident.approvalId,
|
|
930
|
+
approvalStatus: incident.approvalStatus,
|
|
931
|
+
},
|
|
932
|
+
},
|
|
933
|
+
whyNow: incident.thresholdType === "hard"
|
|
934
|
+
? "Budget hard stop was reached."
|
|
935
|
+
: "Budget crossed the 85% warning threshold.",
|
|
936
|
+
decisionVerbs: decisionVerbs({ id: "raise_budget_and_resume", label: "Raise budget", description: "Raise the budget and resume paused work." }, { id: "keep_paused", label: "Keep paused", description: "Dismiss or keep the budget stop in place." }),
|
|
937
|
+
inlineResolvable: true,
|
|
938
|
+
entryRule: "open budget incident is hard, or soft with observed spend >= 85% of limit.",
|
|
939
|
+
exitRule: "Budget incident is resolved or dismissed.",
|
|
940
|
+
dedupKey,
|
|
941
|
+
severity: incident.thresholdType === "hard" ? "high" : "medium",
|
|
942
|
+
activityAt: toIso(incident.updatedAt),
|
|
943
|
+
createdAt: toIso(incident.createdAt),
|
|
944
|
+
updatedAt: toIso(incident.updatedAt),
|
|
945
|
+
relatedIssue: null,
|
|
946
|
+
detail: {
|
|
947
|
+
kind: "budget",
|
|
948
|
+
observedPercent,
|
|
949
|
+
amountObserved: incident.amountObserved,
|
|
950
|
+
amountLimit: incident.amountLimit,
|
|
951
|
+
images: [],
|
|
952
|
+
},
|
|
953
|
+
}));
|
|
954
|
+
}
|
|
955
|
+
const erroredAgents = await db
|
|
956
|
+
.select({
|
|
957
|
+
id: agents.id,
|
|
958
|
+
companyId: agents.companyId,
|
|
959
|
+
name: agents.name,
|
|
960
|
+
role: agents.role,
|
|
961
|
+
status: agents.status,
|
|
962
|
+
errorReason: agents.errorReason,
|
|
963
|
+
createdAt: agents.createdAt,
|
|
964
|
+
updatedAt: agents.updatedAt,
|
|
965
|
+
})
|
|
966
|
+
.from(agents)
|
|
967
|
+
.where(and(eq(agents.companyId, companyId), eq(agents.status, "error")))
|
|
968
|
+
.orderBy(desc(agents.updatedAt), desc(agents.id));
|
|
969
|
+
for (const agent of erroredAgents) {
|
|
970
|
+
const dedupKey = `agent_error:${agent.id}`;
|
|
971
|
+
add(createItem({
|
|
972
|
+
companyId,
|
|
973
|
+
sourceKind: "agent_error_alert",
|
|
974
|
+
subject: {
|
|
975
|
+
kind: "agent",
|
|
976
|
+
id: agent.id,
|
|
977
|
+
companyId,
|
|
978
|
+
title: agent.name,
|
|
979
|
+
identifier: null,
|
|
980
|
+
status: agent.status,
|
|
981
|
+
href: `/${prefix}/agents/${agent.id}`,
|
|
982
|
+
metadata: { role: agent.role, errorReason: agent.errorReason },
|
|
983
|
+
},
|
|
984
|
+
whyNow: "Agent is in error status and needs operator action or dismissal.",
|
|
985
|
+
decisionVerbs: decisionVerbs({ id: "inspect", label: "Inspect", description: "Inspect the agent error." }, { id: "dismiss", label: "Dismiss", description: "Dismiss this alert." }),
|
|
986
|
+
inlineResolvable: true,
|
|
987
|
+
entryRule: "agents.status = 'error'",
|
|
988
|
+
exitRule: "Agent leaves error status or the row is dismissed.",
|
|
989
|
+
dedupKey,
|
|
990
|
+
severity: "high",
|
|
991
|
+
activityAt: toIso(agent.updatedAt),
|
|
992
|
+
createdAt: toIso(agent.createdAt),
|
|
993
|
+
updatedAt: toIso(agent.updatedAt),
|
|
994
|
+
relatedIssue: null,
|
|
995
|
+
detail: {
|
|
996
|
+
kind: "agent_error",
|
|
997
|
+
agentName: agent.name,
|
|
998
|
+
failureReasonExcerpt: excerpt(agent.errorReason),
|
|
999
|
+
images: [],
|
|
1000
|
+
},
|
|
1001
|
+
}));
|
|
1002
|
+
}
|
|
1003
|
+
const deduped = new Map();
|
|
1004
|
+
for (const item of collected) {
|
|
1005
|
+
const current = deduped.get(item.dedupKey);
|
|
1006
|
+
deduped.set(item.dedupKey, current ? betterDuplicate(current, item) : item);
|
|
1007
|
+
}
|
|
1008
|
+
const items = [...deduped.values()]
|
|
1009
|
+
.sort(compareAttentionItems)
|
|
1010
|
+
.map((item, index) => ({ ...item, rank: index + 1 }));
|
|
1011
|
+
const countsBySourceKind = emptyCounts();
|
|
1012
|
+
for (const item of items)
|
|
1013
|
+
countsBySourceKind[item.sourceKind] += 1;
|
|
1014
|
+
return {
|
|
1015
|
+
companyId,
|
|
1016
|
+
generatedAt: new Date().toISOString(),
|
|
1017
|
+
totalCount: items.length,
|
|
1018
|
+
countsBySourceKind,
|
|
1019
|
+
items,
|
|
1020
|
+
};
|
|
1021
|
+
},
|
|
1022
|
+
};
|
|
1023
|
+
}
|
|
1024
|
+
//# sourceMappingURL=attention.js.map
|