@rudderhq/server 0.7.2 → 0.7.4
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/agent-runtimes/registry.d.ts.map +1 -1
- package/dist/agent-runtimes/registry.js +0 -2
- package/dist/agent-runtimes/registry.js.map +1 -1
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +1 -5
- package/dist/app.js.map +1 -1
- package/dist/bootstrap/create-http-app.d.ts +1 -2
- package/dist/bootstrap/create-http-app.d.ts.map +1 -1
- package/dist/bootstrap/create-http-app.js +13 -12
- package/dist/bootstrap/create-http-app.js.map +1 -1
- package/dist/bootstrap/register-api-routes.d.ts +1 -2
- package/dist/bootstrap/register-api-routes.d.ts.map +1 -1
- package/dist/bootstrap/register-api-routes.js +22 -4
- package/dist/bootstrap/register-api-routes.js.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/middleware/logger.js +2 -2
- package/dist/middleware/logger.js.map +1 -1
- package/dist/middleware/logger.test.js +26 -10
- package/dist/middleware/logger.test.js.map +1 -1
- package/dist/redaction.d.ts +4 -0
- package/dist/redaction.d.ts.map +1 -1
- package/dist/redaction.js +15 -0
- package/dist/redaction.js.map +1 -1
- package/dist/routes/agent-issue-creation.d.ts +25 -0
- package/dist/routes/agent-issue-creation.d.ts.map +1 -0
- package/dist/routes/agent-issue-creation.js +201 -0
- package/dist/routes/agent-issue-creation.js.map +1 -0
- package/dist/routes/app-builder.d.ts +3 -1
- package/dist/routes/app-builder.d.ts.map +1 -1
- package/dist/routes/app-builder.js +30 -22
- package/dist/routes/app-builder.js.map +1 -1
- package/dist/routes/approvals.d.ts.map +1 -1
- package/dist/routes/approvals.js +36 -9
- package/dist/routes/approvals.js.map +1 -1
- package/dist/routes/chats.d.ts.map +1 -1
- package/dist/routes/chats.js +23 -4
- package/dist/routes/chats.js.map +1 -1
- package/dist/routes/chats.preflight.d.ts +2 -1
- package/dist/routes/chats.preflight.d.ts.map +1 -1
- package/dist/routes/chats.preflight.js +7 -0
- package/dist/routes/chats.preflight.js.map +1 -1
- package/dist/routes/chats.stream-routes.d.ts.map +1 -1
- package/dist/routes/chats.stream-routes.js +14 -3
- package/dist/routes/chats.stream-routes.js.map +1 -1
- package/dist/routes/chats.stream-support.d.ts.map +1 -1
- package/dist/routes/chats.stream-support.js +8 -0
- package/dist/routes/chats.stream-support.js.map +1 -1
- package/dist/routes/computer.d.ts +30 -0
- package/dist/routes/computer.d.ts.map +1 -0
- package/dist/routes/computer.js +231 -0
- package/dist/routes/computer.js.map +1 -0
- package/dist/routes/goals.d.ts.map +1 -1
- package/dist/routes/goals.js +176 -27
- package/dist/routes/goals.js.map +1 -1
- package/dist/routes/health.d.ts.map +1 -1
- package/dist/routes/health.js +2 -0
- package/dist/routes/health.js.map +1 -1
- package/dist/routes/index.d.ts +1 -0
- package/dist/routes/index.d.ts.map +1 -1
- package/dist/routes/index.js +1 -0
- package/dist/routes/index.js.map +1 -1
- package/dist/routes/instance-settings.d.ts.map +1 -1
- package/dist/routes/instance-settings.js +3 -0
- package/dist/routes/instance-settings.js.map +1 -1
- package/dist/routes/issues.comments-attachments.d.ts.map +1 -1
- package/dist/routes/issues.comments-attachments.js +90 -8
- package/dist/routes/issues.comments-attachments.js.map +1 -1
- package/dist/routes/issues.d.ts.map +1 -1
- package/dist/routes/issues.js +14 -2
- package/dist/routes/issues.js.map +1 -1
- package/dist/routes/issues.mutations.d.ts.map +1 -1
- package/dist/routes/issues.mutations.js +206 -52
- package/dist/routes/issues.mutations.js.map +1 -1
- package/dist/routes/managed-mcp-runtime.d.ts.map +1 -1
- package/dist/routes/managed-mcp-runtime.js +109 -7
- package/dist/routes/managed-mcp-runtime.js.map +1 -1
- package/dist/routes/organization-skills.d.ts.map +1 -1
- package/dist/routes/organization-skills.js +23 -1
- package/dist/routes/organization-skills.js.map +1 -1
- package/dist/routes/requests.d.ts +3 -0
- package/dist/routes/requests.d.ts.map +1 -0
- package/dist/routes/requests.js +122 -0
- package/dist/routes/requests.js.map +1 -0
- package/dist/routes/rudder-plugins.d.ts +4 -0
- package/dist/routes/rudder-plugins.d.ts.map +1 -0
- package/dist/routes/rudder-plugins.js +203 -0
- package/dist/routes/rudder-plugins.js.map +1 -0
- package/dist/services/activity-log.d.ts +0 -3
- package/dist/services/activity-log.d.ts.map +1 -1
- package/dist/services/activity-log.js +19 -42
- package/dist/services/activity-log.js.map +1 -1
- package/dist/services/activity.d.ts +3 -3
- package/dist/services/agent-issue-creation.d.ts +344 -0
- package/dist/services/agent-issue-creation.d.ts.map +1 -0
- package/dist/services/agent-issue-creation.js +675 -0
- package/dist/services/agent-issue-creation.js.map +1 -0
- package/dist/services/agent-run-context.d.ts.map +1 -1
- package/dist/services/agent-run-context.js +10 -4
- package/dist/services/agent-run-context.js.map +1 -1
- package/dist/services/agents.d.ts +14 -14
- package/dist/services/agents.d.ts.map +1 -1
- package/dist/services/agents.js +2 -1
- package/dist/services/agents.js.map +1 -1
- package/dist/services/app-builder.d.ts.map +1 -1
- package/dist/services/app-builder.js +24 -3
- package/dist/services/app-builder.js.map +1 -1
- package/dist/services/chat-agent-runs.d.ts +1 -0
- package/dist/services/chat-agent-runs.d.ts.map +1 -1
- package/dist/services/chat-agent-runs.js +1 -0
- package/dist/services/chat-agent-runs.js.map +1 -1
- package/dist/services/chat-assistant.d.ts.map +1 -1
- package/dist/services/chat-assistant.helpers.d.ts +3 -1
- package/dist/services/chat-assistant.helpers.d.ts.map +1 -1
- package/dist/services/chat-assistant.helpers.js +25 -0
- package/dist/services/chat-assistant.helpers.js.map +1 -1
- package/dist/services/chat-assistant.js +57 -11
- package/dist/services/chat-assistant.js.map +1 -1
- package/dist/services/chat-generation-protocol.test.js +2 -2
- package/dist/services/chat-generation-protocol.test.js.map +1 -1
- package/dist/services/chat-queued-message-materialization.d.ts +1 -1
- package/dist/services/chats.create.d.ts +2 -2
- package/dist/services/chats.create.d.ts.map +1 -1
- package/dist/services/chats.d.ts +15 -15
- package/dist/services/chats.d.ts.map +1 -1
- package/dist/services/chats.helpers.d.ts +2 -2
- package/dist/services/chats.helpers.d.ts.map +1 -1
- package/dist/services/chats.helpers.goal-context.test.d.ts +2 -0
- package/dist/services/chats.helpers.goal-context.test.d.ts.map +1 -0
- package/dist/services/chats.helpers.goal-context.test.js +40 -0
- package/dist/services/chats.helpers.goal-context.test.js.map +1 -0
- package/dist/services/chats.helpers.js +27 -2
- package/dist/services/chats.helpers.js.map +1 -1
- package/dist/services/chats.js.map +1 -1
- package/dist/services/computer-broker.d.ts +35 -0
- package/dist/services/computer-broker.d.ts.map +1 -0
- package/dist/services/computer-broker.js +211 -0
- package/dist/services/computer-broker.js.map +1 -0
- package/dist/services/computer-broker.test.d.ts +2 -0
- package/dist/services/computer-broker.test.d.ts.map +1 -0
- package/dist/services/computer-broker.test.js +70 -0
- package/dist/services/computer-broker.test.js.map +1 -0
- package/dist/services/computer-capability.d.ts +13 -0
- package/dist/services/computer-capability.d.ts.map +1 -0
- package/dist/services/computer-capability.js +11 -0
- package/dist/services/computer-capability.js.map +1 -0
- package/dist/services/computer-capability.test.d.ts +2 -0
- package/dist/services/computer-capability.test.d.ts.map +1 -0
- package/dist/services/computer-capability.test.js +27 -0
- package/dist/services/computer-capability.test.js.map +1 -0
- package/dist/services/finance.d.ts +2 -2
- package/dist/services/goals.d.ts +267 -244
- package/dist/services/goals.d.ts.map +1 -1
- package/dist/services/goals.js +1279 -297
- package/dist/services/goals.js.map +1 -1
- package/dist/services/index.d.ts +3 -0
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +3 -0
- package/dist/services/index.js.map +1 -1
- package/dist/services/instance-settings.d.ts.map +1 -1
- package/dist/services/instance-settings.js +8 -3
- package/dist/services/instance-settings.js.map +1 -1
- package/dist/services/integrations/agent-integrations.d.ts +1 -1
- package/dist/services/issues.d.ts +86 -1
- package/dist/services/issues.d.ts.map +1 -1
- package/dist/services/issues.helpers.d.ts +4 -0
- package/dist/services/issues.helpers.d.ts.map +1 -1
- package/dist/services/issues.helpers.js +28 -6
- package/dist/services/issues.helpers.js.map +1 -1
- package/dist/services/issues.js +207 -127
- package/dist/services/issues.js.map +1 -1
- package/dist/services/knowledge-portability/organization-portability.files.d.ts +1 -1
- package/dist/services/knowledge-portability/organization-skills.catalog.d.ts +7 -1
- package/dist/services/knowledge-portability/organization-skills.catalog.d.ts.map +1 -1
- package/dist/services/knowledge-portability/organization-skills.catalog.js +10 -0
- package/dist/services/knowledge-portability/organization-skills.catalog.js.map +1 -1
- package/dist/services/knowledge-portability/organization-skills.d.ts +3 -1
- package/dist/services/knowledge-portability/organization-skills.d.ts.map +1 -1
- package/dist/services/knowledge-portability/organization-skills.js +80 -12
- package/dist/services/knowledge-portability/organization-skills.js.map +1 -1
- package/dist/services/mcp/managed-bindings.d.ts.map +1 -1
- package/dist/services/mcp/managed-bindings.js +18 -2
- package/dist/services/mcp/managed-bindings.js.map +1 -1
- package/dist/services/mcp/managed-client.d.ts +15 -0
- package/dist/services/mcp/managed-client.d.ts.map +1 -1
- package/dist/services/mcp/managed-client.js +23 -1
- package/dist/services/mcp/managed-client.js.map +1 -1
- package/dist/services/mcp/managed-client.test.js +73 -9
- package/dist/services/mcp/managed-client.test.js.map +1 -1
- package/dist/services/messenger.d.ts +3 -3
- package/dist/services/messenger.d.ts.map +1 -1
- package/dist/services/messenger.js +201 -65
- package/dist/services/messenger.js.map +1 -1
- package/dist/services/orgs.d.ts.map +1 -1
- package/dist/services/orgs.js +3 -1
- package/dist/services/orgs.js.map +1 -1
- package/dist/services/product-analytics.d.ts +2 -2
- package/dist/services/requests.d.ts +919 -0
- package/dist/services/requests.d.ts.map +1 -0
- package/dist/services/requests.js +532 -0
- package/dist/services/requests.js.map +1 -0
- package/dist/services/rudder-plugins.d.ts +107 -0
- package/dist/services/rudder-plugins.d.ts.map +1 -0
- package/dist/services/rudder-plugins.js +1760 -0
- package/dist/services/rudder-plugins.js.map +1 -0
- package/dist/services/rudder-plugins.test.d.ts +2 -0
- package/dist/services/rudder-plugins.test.d.ts.map +1 -0
- package/dist/services/rudder-plugins.test.js +191 -0
- package/dist/services/rudder-plugins.test.js.map +1 -0
- package/dist/services/runtime-kernel/heartbeat.core.d.ts +1 -0
- package/dist/services/runtime-kernel/heartbeat.core.d.ts.map +1 -1
- package/dist/services/runtime-kernel/heartbeat.core.js +1 -1
- package/dist/services/runtime-kernel/heartbeat.core.js.map +1 -1
- package/dist/services/runtime-kernel/heartbeat.d.ts +65 -0
- package/dist/services/runtime-kernel/heartbeat.d.ts.map +1 -1
- package/dist/services/runtime-kernel/heartbeat.execute.d.ts.map +1 -1
- package/dist/services/runtime-kernel/heartbeat.execute.js +21 -5
- package/dist/services/runtime-kernel/heartbeat.execute.js.map +1 -1
- package/dist/services/runtime-kernel/heartbeat.js +320 -108
- package/dist/services/runtime-kernel/heartbeat.js.map +1 -1
- package/dist/services/runtime-kernel/heartbeat.recovery.d.ts +1 -0
- package/dist/services/runtime-kernel/heartbeat.recovery.d.ts.map +1 -1
- package/dist/services/runtime-kernel/heartbeat.recovery.js +39 -4
- package/dist/services/runtime-kernel/heartbeat.recovery.js.map +1 -1
- package/dist/services/runtime-kernel/heartbeat.sessions.js +1 -1
- package/dist/services/runtime-kernel/heartbeat.sessions.js.map +1 -1
- package/dist/services/runtime-kernel/heartbeat.terminal.d.ts +14 -1
- package/dist/services/runtime-kernel/heartbeat.terminal.d.ts.map +1 -1
- package/dist/services/runtime-kernel/heartbeat.terminal.js +46 -1
- package/dist/services/runtime-kernel/heartbeat.terminal.js.map +1 -1
- package/dist/services/runtime-kernel/heartbeat.wakeup.d.ts +1 -0
- package/dist/services/runtime-kernel/heartbeat.wakeup.d.ts.map +1 -1
- package/dist/services/runtime-kernel/heartbeat.wakeup.js +124 -6
- package/dist/services/runtime-kernel/heartbeat.wakeup.js.map +1 -1
- package/dist/services/workspace-runtime.services.d.ts +1 -1
- package/package.json +18 -18
- package/resources/bundled-skills/app-builder/SKILL.md +39 -9
- package/resources/bundled-skills/app-builder/assets/scaffold/app/globals.css +109 -16
- package/resources/bundled-skills/app-builder/assets/scaffold/components/contacts-workspace.tsx +176 -152
- package/resources/bundled-skills/app-builder/assets/scaffold/components/ui/alert.tsx +12 -0
- package/resources/bundled-skills/app-builder/assets/scaffold/components/ui/badge.tsx +27 -0
- package/resources/bundled-skills/app-builder/assets/scaffold/components/ui/button.tsx +9 -7
- package/resources/bundled-skills/app-builder/assets/scaffold/components/ui/card.tsx +15 -3
- package/resources/bundled-skills/app-builder/assets/scaffold/components/ui/empty.tsx +18 -0
- package/resources/bundled-skills/app-builder/assets/scaffold/components/ui/field.tsx +23 -0
- package/resources/bundled-skills/app-builder/assets/scaffold/components/ui/input.tsx +1 -1
- package/resources/bundled-skills/app-builder/assets/scaffold/components/ui/label.tsx +1 -1
- package/resources/bundled-skills/app-builder/assets/scaffold/components/ui/separator.tsx +6 -0
- package/resources/bundled-skills/app-builder/assets/scaffold/components/ui/skeleton.tsx +6 -0
- package/resources/bundled-skills/app-builder/assets/scaffold/components/ui/table.tsx +30 -0
- package/resources/bundled-skills/app-builder/assets/scaffold/components.json +20 -0
- package/resources/bundled-skills/app-builder/assets/scaffold/next-env.d.ts +1 -1
- package/resources/bundled-skills/app-builder/assets/scaffold/next.config.ts +19 -0
- package/resources/bundled-skills/app-builder/assets/scaffold/package.json +2 -1
- package/resources/bundled-skills/app-builder/assets/scaffold/rudder.app.json +1 -1
- package/resources/bundled-skills/app-builder/assets/scaffold/rudder.ui.json +13 -0
- package/resources/bundled-skills/app-builder/assets/scaffold/scripts/validate-rudder-ui.mjs +50 -0
- package/resources/bundled-skills/app-builder/assets/scaffold/tests/e2e/app.spec.ts +35 -1
- package/resources/bundled-skills/app-builder/evals/evals.json +2 -1
- package/resources/bundled-skills/app-builder/references/design-guidelines.md +19 -2
- package/resources/bundled-skills/app-builder/references/rudder-ui-preset.md +73 -0
- package/resources/bundled-skills/app-builder/references/scaffold-contract.md +12 -0
- package/resources/bundled-skills/app-builder/references/verification.md +6 -0
- package/resources/bundled-skills/app-builder/scripts/report-build-status.mjs +83 -0
- package/resources/bundled-skills/rudder-docs/SKILL.md +2 -2
- package/resources/bundled-skills/rudder-docs/evals/retrieval-authority-evals.json +40 -0
- package/resources/bundled-skills/rudder-docs/evals/trigger-evals.json +2 -2
- package/resources/bundled-skills/rudder-docs/references/api-reference.md +0 -2
- package/resources/bundled-skills/rudder-docs/references/cli-reference.md +7 -2
- package/resources/bundled-skills/rudder-docs/references/operating-practices.md +10 -6
- package/resources/bundled-skills/rudder-docs/references/plugin-authoring.md +38 -114
- package/resources/bundled-skills/rudder-docs/references/source-map.md +2 -0
- package/skills/app-builder/SKILL.md +39 -9
- package/skills/app-builder/assets/scaffold/app/globals.css +109 -16
- package/skills/app-builder/assets/scaffold/components/contacts-workspace.tsx +176 -152
- package/skills/app-builder/assets/scaffold/components/ui/alert.tsx +12 -0
- package/skills/app-builder/assets/scaffold/components/ui/badge.tsx +27 -0
- package/skills/app-builder/assets/scaffold/components/ui/button.tsx +9 -7
- package/skills/app-builder/assets/scaffold/components/ui/card.tsx +15 -3
- package/skills/app-builder/assets/scaffold/components/ui/empty.tsx +18 -0
- package/skills/app-builder/assets/scaffold/components/ui/field.tsx +23 -0
- package/skills/app-builder/assets/scaffold/components/ui/input.tsx +1 -1
- package/skills/app-builder/assets/scaffold/components/ui/label.tsx +1 -1
- package/skills/app-builder/assets/scaffold/components/ui/separator.tsx +6 -0
- package/skills/app-builder/assets/scaffold/components/ui/skeleton.tsx +6 -0
- package/skills/app-builder/assets/scaffold/components/ui/table.tsx +30 -0
- package/skills/app-builder/assets/scaffold/components.json +20 -0
- package/skills/app-builder/assets/scaffold/next-env.d.ts +1 -1
- package/skills/app-builder/assets/scaffold/next.config.ts +19 -0
- package/skills/app-builder/assets/scaffold/package.json +2 -1
- package/skills/app-builder/assets/scaffold/rudder.app.json +1 -1
- package/skills/app-builder/assets/scaffold/rudder.ui.json +13 -0
- package/skills/app-builder/assets/scaffold/scripts/validate-rudder-ui.mjs +50 -0
- package/skills/app-builder/assets/scaffold/tests/e2e/app.spec.ts +35 -1
- package/skills/app-builder/evals/evals.json +2 -1
- package/skills/app-builder/references/design-guidelines.md +19 -2
- package/skills/app-builder/references/rudder-ui-preset.md +73 -0
- package/skills/app-builder/references/scaffold-contract.md +12 -0
- package/skills/app-builder/references/verification.md +6 -0
- package/skills/app-builder/scripts/report-build-status.mjs +83 -0
- package/skills/rudder-docs/SKILL.md +2 -2
- package/skills/rudder-docs/evals/retrieval-authority-evals.json +40 -0
- package/skills/rudder-docs/evals/trigger-evals.json +2 -2
- package/skills/rudder-docs/references/api-reference.md +0 -2
- package/skills/rudder-docs/references/cli-reference.md +7 -2
- package/skills/rudder-docs/references/operating-practices.md +10 -6
- package/skills/rudder-docs/references/plugin-authoring.md +38 -114
- package/skills/rudder-docs/references/source-map.md +2 -0
- package/ui-dist/assets/{LibraryLiveSurface-cHCJmmsl.js → LibraryLiveSurface-D4uCo0l0.js} +1 -1
- package/ui-dist/assets/{abnfDiagram-VRR7QNED-BORJvkew.js → abnfDiagram-VRR7QNED-FF2qcR6A.js} +1 -1
- package/ui-dist/assets/addon-fit-4gL7JXzc.js +1 -0
- package/ui-dist/assets/{arc-DPUzhx87.js → arc-D5pLjh6B.js} +1 -1
- package/ui-dist/assets/{architectureDiagram-ZJ3FMSHR-Bz_hY8LM.js → architectureDiagram-ZJ3FMSHR-BvqY-HXg.js} +1 -1
- package/ui-dist/assets/{blockDiagram-677ZJIJ3-Bq1X3YkP.js → blockDiagram-677ZJIJ3-BU6ZbJ0s.js} +1 -1
- package/ui-dist/assets/{c4Diagram-LMCZKHZV-BawEM9Pc.js → c4Diagram-LMCZKHZV-Cl5Gsovj.js} +1 -1
- package/ui-dist/assets/channel-HcmprA3I.js +1 -0
- package/ui-dist/assets/{chunk-2Q5K7J3B-D0D5ZOaA.js → chunk-2Q5K7J3B-4a2agt_p.js} +1 -1
- package/ui-dist/assets/{chunk-32BRIVSS-T90P7lug.js → chunk-32BRIVSS-CF0oOO2T.js} +1 -1
- package/ui-dist/assets/{chunk-5VM5RSS4-jcAERTgh.js → chunk-5VM5RSS4-CsSq2zX2.js} +1 -1
- package/ui-dist/assets/{chunk-EX3LRPZG-t9-JXwnR.js → chunk-EX3LRPZG-B6riETVZ.js} +1 -1
- package/ui-dist/assets/{chunk-JWPE2WC7-hqgD1saC.js → chunk-JWPE2WC7-S-p1xu0r.js} +1 -1
- package/ui-dist/assets/{chunk-MOJQB5TN-BsSUoYzd.js → chunk-MOJQB5TN-mVE4v_LV.js} +1 -1
- package/ui-dist/assets/{chunk-RYQCIY6F-YcdDh2Zn.js → chunk-RYQCIY6F-CElbgpgs.js} +1 -1
- package/ui-dist/assets/{chunk-V7JOEXUC-ChFyH9Su.js → chunk-V7JOEXUC-8XM3povP.js} +1 -1
- package/ui-dist/assets/{chunk-VR4S4FIN-BjVK8IyX.js → chunk-VR4S4FIN-LrThp3ht.js} +1 -1
- package/ui-dist/assets/{chunk-XXDRQBXY-D-TvO9vT.js → chunk-XXDRQBXY-D38vLb4t.js} +1 -1
- package/ui-dist/assets/classDiagram-OUVF2IWQ-Dpe8LaLf.js +1 -0
- package/ui-dist/assets/classDiagram-v2-EOCWNBFH-Dpe8LaLf.js +1 -0
- package/ui-dist/assets/{cose-bilkent-JH36ORCC-DBwhASJG.js → cose-bilkent-JH36ORCC-CmpZMaDV.js} +1 -1
- package/ui-dist/assets/{cynefin-VYW2F7L2-SlCRmwl2.js → cynefin-VYW2F7L2-D3RnbBvI.js} +1 -1
- package/ui-dist/assets/{cynefinDiagram-TSTJHNR4-DImV6N_p.js → cynefinDiagram-TSTJHNR4-DZTcUojp.js} +1 -1
- package/ui-dist/assets/{dagre-VKFMJZFB-FH4Wv8Zn.js → dagre-VKFMJZFB-6jXSDi0S.js} +1 -1
- package/ui-dist/assets/{diagram-FQU43EPY-CrZvQjId.js → diagram-FQU43EPY-DIMm3mfS.js} +1 -1
- package/ui-dist/assets/{diagram-G47NLZAW-B7IGl_T3.js → diagram-G47NLZAW-Cyo22v0_.js} +1 -1
- package/ui-dist/assets/{diagram-NH7WQ7WH-CB4i7H1a.js → diagram-NH7WQ7WH-CHDV0nSL.js} +1 -1
- package/ui-dist/assets/{diagram-OA4YK3LP-DNyWPYY9.js → diagram-OA4YK3LP-Jijx9vQO.js} +1 -1
- package/ui-dist/assets/{diagram-WEI45ONY-BAhxbhpi.js → diagram-WEI45ONY-CcL19HLE.js} +1 -1
- package/ui-dist/assets/{ebnfDiagram-CCIWWBDH-ClXtQ9qt.js → ebnfDiagram-CCIWWBDH-D94h-RFQ.js} +1 -1
- package/ui-dist/assets/{erDiagram-Q63AITRT-DGXiMYfn.js → erDiagram-Q63AITRT-B3ypUGZX.js} +1 -1
- package/ui-dist/assets/{flowDiagram-23GEKE2U-CdZ-02zc.js → flowDiagram-23GEKE2U-DjCT6Y1o.js} +1 -1
- package/ui-dist/assets/{ganttDiagram-NO4QXBWP-TxhzNpta.js → ganttDiagram-NO4QXBWP-C3QVhTc9.js} +1 -1
- package/ui-dist/assets/{gitGraphDiagram-IHSO6WYX-D6Q_w6wR.js → gitGraphDiagram-IHSO6WYX-D0trBzU5.js} +1 -1
- package/ui-dist/assets/{graph-CxNmbpl9.js → graph-D1zFmVUP.js} +1 -1
- package/ui-dist/assets/{index-Cx6oIZPm.js → index-B0GEsWui.js} +568 -593
- package/ui-dist/assets/{index-BA9tMBhO.js → index-B8bxQLfN.js} +1 -1
- package/ui-dist/assets/{index-C_nwI4Yj.js → index-BFktZ2tu.js} +1 -1
- package/ui-dist/assets/index-BT7dphT4.css +32 -0
- package/ui-dist/assets/{index-BoTz5S-X.js → index-Bk_yesiO.js} +1 -1
- package/ui-dist/assets/{index-CXnqg1Oq.js → index-BwutQLG1.js} +1 -1
- package/ui-dist/assets/{index-AkhgKOxC.js → index-CB4zr_Ku.js} +1 -1
- package/ui-dist/assets/{index-Rau2cGm0.js → index-CC89UPa-.js} +1 -1
- package/ui-dist/assets/{index-kbWUQ1um.js → index-CFWOO6yn.js} +1 -1
- package/ui-dist/assets/{index-DU2LI7f9.js → index-CK8u9b07.js} +1 -1
- package/ui-dist/assets/{index-C12QC1Ex.js → index-CRolpP90.js} +1 -1
- package/ui-dist/assets/{index-Bxz75ICF.js → index-CiCRRUVV.js} +1 -1
- package/ui-dist/assets/{index-Do78GymY.js → index-D8JG-IU9.js} +1 -1
- package/ui-dist/assets/{index-CW21qWFm.js → index-DKCiVvJo.js} +1 -1
- package/ui-dist/assets/{index-BujwWE3j.js → index-G2yfKEf_.js} +1 -1
- package/ui-dist/assets/{index-BQ87dGIq.js → index-or2C22bw.js} +1 -1
- package/ui-dist/assets/{infoDiagram-FWYZ7A6U-CnJd4-Jq.js → infoDiagram-FWYZ7A6U-Co0nkcV4.js} +1 -1
- package/ui-dist/assets/{ishikawaDiagram-FXEZZL3T-Bq-qc3pD.js → ishikawaDiagram-FXEZZL3T-CkIg1Jdu.js} +1 -1
- package/ui-dist/assets/{journeyDiagram-5HDEW3XC-NSmpUGUA.js → journeyDiagram-5HDEW3XC-AYUT6JuJ.js} +1 -1
- package/ui-dist/assets/{kanban-definition-HUTT4EX6-D7pgWtfA.js → kanban-definition-HUTT4EX6-C3H2XW08.js} +1 -1
- package/ui-dist/assets/{layout-BRuo6LWe.js → layout-sZWyqNOu.js} +1 -1
- package/ui-dist/assets/{linear-CkaWI9Nd.js → linear-BLTYou8N.js} +1 -1
- package/ui-dist/assets/{map-CgA6tTpZ.js → map-zKBdHkX8.js} +1 -1
- package/ui-dist/assets/{mermaid.core-DrXOt-P6.js → mermaid.core-DbjdFip8.js} +4 -4
- package/ui-dist/assets/{mindmap-definition-LN4V7U3C-D9ImeWBe.js → mindmap-definition-LN4V7U3C-DcDNQ4GQ.js} +1 -1
- package/ui-dist/assets/{pegDiagram-2B236MQR-Cq-zZ_zz.js → pegDiagram-2B236MQR-BGq1PngU.js} +1 -1
- package/ui-dist/assets/{pieDiagram-ENE6RG2P-FgNgy726.js → pieDiagram-ENE6RG2P-BfXWRzNb.js} +1 -1
- package/ui-dist/assets/{quadrantDiagram-ABIIQ3AL-BxQbmGmi.js → quadrantDiagram-ABIIQ3AL-AYMVAbo1.js} +1 -1
- package/ui-dist/assets/{railroadDiagram-RFXS5EU6-CrpbkPH2.js → railroadDiagram-RFXS5EU6-B0PoG0Ih.js} +1 -1
- package/ui-dist/assets/{requirementDiagram-TGXJPOKE-DQ-65ZW6.js → requirementDiagram-TGXJPOKE-Cuuu9wfw.js} +1 -1
- package/ui-dist/assets/{sankeyDiagram-HTMAVEWB-Bom0lMZm.js → sankeyDiagram-HTMAVEWB-BceAc42f.js} +1 -1
- package/ui-dist/assets/{sequenceDiagram-DBY2YBRQ-C1MSDLDj.js → sequenceDiagram-DBY2YBRQ-cqJ71-Rw.js} +1 -1
- package/ui-dist/assets/{sizeCapture-X5ZJPWSS-Bn1vxebD.js → sizeCapture-X5ZJPWSS-DQ6MibbA.js} +1 -1
- package/ui-dist/assets/{stateDiagram-2N3HPSRC-GRRQpmzI.js → stateDiagram-2N3HPSRC-Bdl-XY7t.js} +1 -1
- package/ui-dist/assets/stateDiagram-v2-6OUMAXLB-Cjj7yPIO.js +1 -0
- package/ui-dist/assets/{swimlanes-5IMT3BWC-BcOOCZ4q.js → swimlanes-5IMT3BWC-Dpo2HDtE.js} +2 -2
- package/ui-dist/assets/swimlanesDiagram-G3AALYLV-CPmIkNCu.js +8 -0
- package/ui-dist/assets/{timeline-definition-FHXFAJF6-gi6amswh.js → timeline-definition-FHXFAJF6-BzmJ-xnC.js} +1 -1
- package/ui-dist/assets/{vennDiagram-L72KCM5P-B9uqeyQt.js → vennDiagram-L72KCM5P-BF2EI7vn.js} +1 -1
- package/ui-dist/assets/{wardleyDiagram-EHGQE667-BnqWAMiE.js → wardleyDiagram-EHGQE667-DM65x1i1.js} +1 -1
- package/ui-dist/assets/xterm-DZP7bLZ5.js +9 -0
- package/ui-dist/assets/{xychartDiagram-FW5EYKEG-BJXKltIN.js → xychartDiagram-FW5EYKEG-3Q2t9pdU.js} +1 -1
- package/ui-dist/index.html +2 -2
- package/dist/bootstrap/plugin-host-runtime.d.ts +0 -116
- package/dist/bootstrap/plugin-host-runtime.d.ts.map +0 -1
- package/dist/bootstrap/plugin-host-runtime.js +0 -179
- package/dist/bootstrap/plugin-host-runtime.js.map +0 -1
- package/dist/bundled-plugins/plugin-linear/README.md +0 -22
- package/dist/bundled-plugins/plugin-linear/dist/manifest.js +0 -183
- package/dist/bundled-plugins/plugin-linear/dist/manifest.js.map +0 -7
- package/dist/bundled-plugins/plugin-linear/dist/ui/index.js +0 -1285
- package/dist/bundled-plugins/plugin-linear/dist/ui/index.js.map +0 -7
- package/dist/bundled-plugins/plugin-linear/dist/worker.js +0 -10699
- package/dist/bundled-plugins/plugin-linear/dist/worker.js.map +0 -7
- package/dist/bundled-plugins/plugin-linear/package.json +0 -42
- package/dist/routes/plugin-ui-static.d.ts +0 -70
- package/dist/routes/plugin-ui-static.d.ts.map +0 -1
- package/dist/routes/plugin-ui-static.js +0 -375
- package/dist/routes/plugin-ui-static.js.map +0 -1
- package/dist/routes/plugins.d.ts +0 -120
- package/dist/routes/plugins.d.ts.map +0 -1
- package/dist/routes/plugins.js +0 -1125
- package/dist/routes/plugins.js.map +0 -1
- package/dist/routes/plugins.operations-routes.d.ts +0 -28
- package/dist/routes/plugins.operations-routes.d.ts.map +0 -1
- package/dist/routes/plugins.operations-routes.js +0 -720
- package/dist/routes/plugins.operations-routes.js.map +0 -1
- package/dist/services/plugin-capability-validator.d.ts +0 -108
- package/dist/services/plugin-capability-validator.d.ts.map +0 -1
- package/dist/services/plugin-capability-validator.js +0 -268
- package/dist/services/plugin-capability-validator.js.map +0 -1
- package/dist/services/plugin-config-validator.d.ts +0 -26
- package/dist/services/plugin-config-validator.d.ts.map +0 -1
- package/dist/services/plugin-config-validator.js +0 -41
- package/dist/services/plugin-config-validator.js.map +0 -1
- package/dist/services/plugin-dev-watcher.d.ts +0 -47
- package/dist/services/plugin-dev-watcher.d.ts.map +0 -1
- package/dist/services/plugin-dev-watcher.js +0 -275
- package/dist/services/plugin-dev-watcher.js.map +0 -1
- package/dist/services/plugin-event-bus.d.ts +0 -149
- package/dist/services/plugin-event-bus.d.ts.map +0 -1
- package/dist/services/plugin-event-bus.js +0 -258
- package/dist/services/plugin-event-bus.js.map +0 -1
- package/dist/services/plugin-host-service-cleanup.d.ts +0 -14
- package/dist/services/plugin-host-service-cleanup.d.ts.map +0 -1
- package/dist/services/plugin-host-service-cleanup.js +0 -37
- package/dist/services/plugin-host-service-cleanup.js.map +0 -1
- package/dist/services/plugin-host-services.d.ts +0 -13
- package/dist/services/plugin-host-services.d.ts.map +0 -1
- package/dist/services/plugin-host-services.js +0 -933
- package/dist/services/plugin-host-services.js.map +0 -1
- package/dist/services/plugin-job-coordinator.d.ts +0 -81
- package/dist/services/plugin-job-coordinator.d.ts.map +0 -1
- package/dist/services/plugin-job-coordinator.js +0 -172
- package/dist/services/plugin-job-coordinator.js.map +0 -1
- package/dist/services/plugin-job-scheduler.d.ts +0 -163
- package/dist/services/plugin-job-scheduler.d.ts.map +0 -1
- package/dist/services/plugin-job-scheduler.js +0 -452
- package/dist/services/plugin-job-scheduler.js.map +0 -1
- package/dist/services/plugin-job-store.d.ts +0 -208
- package/dist/services/plugin-job-store.d.ts.map +0 -1
- package/dist/services/plugin-job-store.js +0 -350
- package/dist/services/plugin-job-store.js.map +0 -1
- package/dist/services/plugin-lifecycle.d.ts +0 -240
- package/dist/services/plugin-lifecycle.d.ts.map +0 -1
- package/dist/services/plugin-lifecycle.js +0 -439
- package/dist/services/plugin-lifecycle.js.map +0 -1
- package/dist/services/plugin-loader.core.d.ts +0 -40
- package/dist/services/plugin-loader.core.d.ts.map +0 -1
- package/dist/services/plugin-loader.core.js +0 -879
- package/dist/services/plugin-loader.core.js.map +0 -1
- package/dist/services/plugin-loader.d.ts +0 -30
- package/dist/services/plugin-loader.d.ts.map +0 -1
- package/dist/services/plugin-loader.helpers.d.ts +0 -495
- package/dist/services/plugin-loader.helpers.d.ts.map +0 -1
- package/dist/services/plugin-loader.helpers.js +0 -238
- package/dist/services/plugin-loader.helpers.js.map +0 -1
- package/dist/services/plugin-loader.js +0 -30
- package/dist/services/plugin-loader.js.map +0 -1
- package/dist/services/plugin-loader.worker-paths.d.ts +0 -37
- package/dist/services/plugin-loader.worker-paths.d.ts.map +0 -1
- package/dist/services/plugin-loader.worker-paths.js +0 -62
- package/dist/services/plugin-loader.worker-paths.js.map +0 -1
- package/dist/services/plugin-log-retention.d.ts +0 -20
- package/dist/services/plugin-log-retention.d.ts.map +0 -1
- package/dist/services/plugin-log-retention.js +0 -63
- package/dist/services/plugin-log-retention.js.map +0 -1
- package/dist/services/plugin-manifest-validator.d.ts +0 -90
- package/dist/services/plugin-manifest-validator.d.ts.map +0 -1
- package/dist/services/plugin-manifest-validator.js +0 -72
- package/dist/services/plugin-manifest-validator.js.map +0 -1
- package/dist/services/plugin-registry.d.ts +0 -2542
- package/dist/services/plugin-registry.d.ts.map +0 -1
- package/dist/services/plugin-registry.js +0 -536
- package/dist/services/plugin-registry.js.map +0 -1
- package/dist/services/plugin-runtime-sandbox.d.ts +0 -40
- package/dist/services/plugin-runtime-sandbox.d.ts.map +0 -1
- package/dist/services/plugin-runtime-sandbox.js +0 -154
- package/dist/services/plugin-runtime-sandbox.js.map +0 -1
- package/dist/services/plugin-secrets-handler.d.ts +0 -81
- package/dist/services/plugin-secrets-handler.d.ts.map +0 -1
- package/dist/services/plugin-secrets-handler.js +0 -275
- package/dist/services/plugin-secrets-handler.js.map +0 -1
- package/dist/services/plugin-state-store.d.ts +0 -92
- package/dist/services/plugin-state-store.d.ts.map +0 -1
- package/dist/services/plugin-state-store.js +0 -190
- package/dist/services/plugin-state-store.js.map +0 -1
- package/dist/services/plugin-stream-bus.d.ts +0 -29
- package/dist/services/plugin-stream-bus.d.ts.map +0 -1
- package/dist/services/plugin-stream-bus.js +0 -48
- package/dist/services/plugin-stream-bus.js.map +0 -1
- package/dist/services/plugin-tool-dispatcher.d.ts +0 -180
- package/dist/services/plugin-tool-dispatcher.d.ts.map +0 -1
- package/dist/services/plugin-tool-dispatcher.js +0 -224
- package/dist/services/plugin-tool-dispatcher.js.map +0 -1
- package/dist/services/plugin-tool-registry.d.ts +0 -192
- package/dist/services/plugin-tool-registry.d.ts.map +0 -1
- package/dist/services/plugin-tool-registry.js +0 -224
- package/dist/services/plugin-tool-registry.js.map +0 -1
- package/dist/services/plugin-worker-manager.d.ts +0 -260
- package/dist/services/plugin-worker-manager.d.ts.map +0 -1
- package/dist/services/plugin-worker-manager.js +0 -836
- package/dist/services/plugin-worker-manager.js.map +0 -1
- package/ui-dist/assets/channel-C08AyYJr.js +0 -1
- package/ui-dist/assets/classDiagram-OUVF2IWQ-BIXq6fec.js +0 -1
- package/ui-dist/assets/classDiagram-v2-EOCWNBFH-BIXq6fec.js +0 -1
- package/ui-dist/assets/index-BmXb0yC1.css +0 -1
- package/ui-dist/assets/stateDiagram-v2-6OUMAXLB-KvSJhiLM.js +0 -1
- package/ui-dist/assets/swimlanesDiagram-G3AALYLV-Cr92NnJ4.js +0 -8
package/dist/services/goals.js
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
|
-
import { agentWakeupRequests, agents, approvals, automations, calendarEvents, costEvents, financeEvents, goalActivities, goalChangeProposals, goalFeedbackEntries, goalOwnerAssignments, goalPlans, goalResultProposals, goalStartRequests, goals, heartbeatRuns, issues, projectGoals, projects, } from "@rudderhq/db";
|
|
2
|
-
import { activateGoalSchema } from "@rudderhq/shared";
|
|
3
|
-
import { and, asc, desc, eq, inArray, isNull, or, sql } from "drizzle-orm";
|
|
1
|
+
import { agentWakeupRequests, agents, approvals, assets, authUsers, automations, calendarEvents, costEvents, financeEvents, goalActivities, goalChangeProposals, goalFeedbackEntries, goalOwnerAssignments, goalPlans, goalResultProposals, goalStartRequests, goals, heartbeatRuns, issues, organizations, projectGoals, projects, } from "@rudderhq/db";
|
|
2
|
+
import { activateGoalSchema, parseLibraryEntryMentionHref, parseLibraryFileMentionHref } from "@rudderhq/shared";
|
|
3
|
+
import { and, asc, desc, eq, gt, inArray, isNotNull, isNull, lt, or, sql } from "drizzle-orm";
|
|
4
4
|
import { createHash, randomUUID } from "node:crypto";
|
|
5
|
-
import { conflict, forbidden, notFound, unprocessable } from "../errors.js";
|
|
5
|
+
import { badRequest, conflict, forbidden, notFound, unprocessable } from "../errors.js";
|
|
6
|
+
import { buildDeferredWakePayload, readDeferredWakePayload } from "./runtime-kernel/heartbeat.sessions.js";
|
|
6
7
|
const DEPENDENCY_PREVIEW_LIMIT = 5;
|
|
8
|
+
const GOAL_HISTORY_DEFAULT_LIMIT = 50;
|
|
9
|
+
const GOAL_HISTORY_MAX_LIMIT = 100;
|
|
7
10
|
const TERMINAL_RUN_STATUSES = ["succeeded", "completed", "failed", "cancelled", "timed_out"];
|
|
11
|
+
const CURRENT_PROGRESS_ACTIVITY_KINDS = ["progress", "evidence", "checkpoint", "closeout"];
|
|
8
12
|
const NON_INVOKABLE_AGENT_STATUSES = new Set(["terminated", "pending_approval"]);
|
|
9
13
|
const GOAL_OUTCOME_PATTERNS = [
|
|
10
14
|
/\b(ship|publish|launch|deliver|creat(?:e|ed|ion)|build|complet(?:e|ed|ion)|finish|deploy|release|migrate|implement|fix|resolve|remove|establish|achieve|reach|increase|reduce|decrease|grow|choose|select|decide|approve|pass|verify|maintain|keep|sustain|recommend|submit)\b/i,
|
|
11
15
|
/(?:发布|上线|交付|创建|构建|完成|部署|发版|迁移|实现|修复|解决|删除|建立|达到|提升|增加|降低|减少|增长|选择|决定|批准|通过|验证|保持|维持|产出|提交)/,
|
|
12
|
-
/(?:\d+(?:\.\d+)
|
|
16
|
+
/(?:(?:\d{1,3}(?:,\d{3})+|\d+(?:\.\d+)?)\s*(?:%|percent|x|[kmb]|倍|个|项|天|周|月|年))|(?:[$€£¥¥]\s*\d)/i,
|
|
13
17
|
];
|
|
14
18
|
const GOAL_MODE_PATTERNS = {
|
|
15
19
|
decide: /\b(choose|select|decide|recommend|approve)\b|(?:选择|决定|推荐|批准)/i,
|
|
@@ -28,7 +32,11 @@ const CAPABILITY_STOP_WORDS = new Set([
|
|
|
28
32
|
]);
|
|
29
33
|
function hasConcreteGoalOutcome(input) {
|
|
30
34
|
const combined = `${input.title.trim()} ${input.context?.trim() ?? ""}`;
|
|
31
|
-
|
|
35
|
+
if (input.title.trim().length < 8)
|
|
36
|
+
return false;
|
|
37
|
+
if (GOAL_OUTCOME_PATTERNS.some((pattern) => pattern.test(combined)))
|
|
38
|
+
return true;
|
|
39
|
+
return meaningfulTokens(combined).size >= 4 || /[\p{Script=Han}]{6,}/u.test(combined);
|
|
32
40
|
}
|
|
33
41
|
function goalMode(input) {
|
|
34
42
|
const title = input.title.trim();
|
|
@@ -98,6 +106,413 @@ export function stableGoalHash(value) {
|
|
|
98
106
|
function stringEvidenceRefs(value) {
|
|
99
107
|
return Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
|
|
100
108
|
}
|
|
109
|
+
function publicOrganizationHref(path, orgUrlKey) {
|
|
110
|
+
return orgUrlKey ? `/${encodeURIComponent(orgUrlKey)}${path}` : path;
|
|
111
|
+
}
|
|
112
|
+
function publicEvidenceItems(value, options = {}) {
|
|
113
|
+
return stringEvidenceRefs(value).map((reference, index) => {
|
|
114
|
+
const libraryFile = parseLibraryFileMentionHref(reference);
|
|
115
|
+
if (libraryFile) {
|
|
116
|
+
const name = libraryFile.filePath.split("/").filter(Boolean).at(-1);
|
|
117
|
+
return {
|
|
118
|
+
label: name ? `Library file: ${name}` : "Library file",
|
|
119
|
+
href: publicOrganizationHref(`/library?${new URLSearchParams({ path: libraryFile.filePath }).toString()}`, options.orgUrlKey),
|
|
120
|
+
external: false,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
const libraryEntry = parseLibraryEntryMentionHref(reference);
|
|
124
|
+
if (libraryEntry) {
|
|
125
|
+
const search = new URLSearchParams({ entry: libraryEntry.entryId });
|
|
126
|
+
if (libraryEntry.path)
|
|
127
|
+
search.set("path", libraryEntry.path);
|
|
128
|
+
return {
|
|
129
|
+
label: libraryEntry.path ? `Library entry: ${libraryEntry.path.split("/").filter(Boolean).at(-1) ?? "entry"}` : "Library entry",
|
|
130
|
+
href: publicOrganizationHref(`/library?${search.toString()}`, options.orgUrlKey),
|
|
131
|
+
external: false,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
let parsed = null;
|
|
135
|
+
try {
|
|
136
|
+
parsed = new URL(reference);
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
parsed = null;
|
|
140
|
+
}
|
|
141
|
+
const scheme = parsed?.protocol.replace(/:$/, "").toLowerCase() ?? "";
|
|
142
|
+
const entityId = parsed && !parsed.username && !parsed.password && !parsed.port
|
|
143
|
+
&& (parsed.pathname === "" || parsed.pathname === "/")
|
|
144
|
+
&& /^[a-z0-9][a-z0-9._-]*$/i.test(parsed.hostname)
|
|
145
|
+
? parsed.hostname
|
|
146
|
+
: null;
|
|
147
|
+
if (scheme === "issue" && entityId) {
|
|
148
|
+
return {
|
|
149
|
+
label: `Issue ${index + 1}`,
|
|
150
|
+
href: publicOrganizationHref(`/issues/${encodeURIComponent(entityId)}`, options.orgUrlKey),
|
|
151
|
+
external: false,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
if (scheme === "project" && entityId) {
|
|
155
|
+
return {
|
|
156
|
+
label: `Project ${index + 1}`,
|
|
157
|
+
href: publicOrganizationHref(`/projects/${encodeURIComponent(entityId)}`, options.orgUrlKey),
|
|
158
|
+
external: false,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
if (scheme === "approval" && entityId) {
|
|
162
|
+
return {
|
|
163
|
+
label: `Approval ${index + 1}`,
|
|
164
|
+
href: publicOrganizationHref(`/messenger/approvals/${encodeURIComponent(entityId)}`, options.orgUrlKey),
|
|
165
|
+
external: false,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
if (scheme === "run" && entityId && options.runAgentId) {
|
|
169
|
+
return {
|
|
170
|
+
label: `Supporting work ${index + 1}`,
|
|
171
|
+
href: publicOrganizationHref(`/agents/${encodeURIComponent(options.runAgentId)}/runs/${encodeURIComponent(entityId)}`, options.orgUrlKey),
|
|
172
|
+
external: false,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
if (scheme === "http" || scheme === "https") {
|
|
176
|
+
return { label: `External supporting link ${index + 1}`, href: null, external: true };
|
|
177
|
+
}
|
|
178
|
+
const label = scheme === "artifact" ? "Supporting work" : scheme ? `${scheme} support` : "Supporting work";
|
|
179
|
+
return { label: `${label} ${index + 1}`, href: null, external: false };
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
async function organizationUrlKey(db, orgId) {
|
|
183
|
+
return db.select({ urlKey: organizations.urlKey })
|
|
184
|
+
.from(organizations)
|
|
185
|
+
.where(eq(organizations.id, orgId))
|
|
186
|
+
.then((rows) => rows[0]?.urlKey ?? null);
|
|
187
|
+
}
|
|
188
|
+
function goalRunAttentionReason(status) {
|
|
189
|
+
if (status === "failed")
|
|
190
|
+
return "The Owner Agent could not complete its latest action. Decide whether to retry or adjust the Goal.";
|
|
191
|
+
if (status === "cancelled" || status === "canceled")
|
|
192
|
+
return "The Owner Agent stopped its latest action. Decide whether to retry or adjust the Goal.";
|
|
193
|
+
if (status === "timed_out" || status === "timeout")
|
|
194
|
+
return "The Owner Agent's latest action timed out. Decide whether to retry or adjust the Goal.";
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
const GOAL_HISTORY_KINDS = ["activity", "feedback", "change_proposal", "result_proposal"];
|
|
198
|
+
function encodeGoalHistoryCursor(item) {
|
|
199
|
+
const createdAt = item.createdAt instanceof Date ? item.createdAt.toISOString() : new Date(item.createdAt).toISOString();
|
|
200
|
+
return Buffer.from(JSON.stringify({ version: 1, createdAt, kind: item.kind, id: item.id }))
|
|
201
|
+
.toString("base64url");
|
|
202
|
+
}
|
|
203
|
+
function decodeGoalHistoryCursor(value) {
|
|
204
|
+
if (!value)
|
|
205
|
+
return null;
|
|
206
|
+
try {
|
|
207
|
+
const parsed = JSON.parse(Buffer.from(value, "base64url").toString("utf8"));
|
|
208
|
+
if (parsed.version !== 1
|
|
209
|
+
|| typeof parsed.createdAt !== "string"
|
|
210
|
+
|| Number.isNaN(new Date(parsed.createdAt).getTime())
|
|
211
|
+
|| !GOAL_HISTORY_KINDS.includes(parsed.kind)
|
|
212
|
+
|| typeof parsed.id !== "string"
|
|
213
|
+
|| !parsed.id) {
|
|
214
|
+
throw new Error("invalid cursor payload");
|
|
215
|
+
}
|
|
216
|
+
return parsed;
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
throw badRequest("Invalid Goal history cursor");
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
function historyCursorCondition(timestampColumn, idColumn, kind, cursor) {
|
|
223
|
+
if (!cursor)
|
|
224
|
+
return undefined;
|
|
225
|
+
const cursorDate = new Date(cursor.createdAt);
|
|
226
|
+
const kindOrder = kind.localeCompare(cursor.kind);
|
|
227
|
+
if (kindOrder > 0)
|
|
228
|
+
return or(lt(timestampColumn, cursorDate), eq(timestampColumn, cursorDate));
|
|
229
|
+
if (kindOrder < 0)
|
|
230
|
+
return lt(timestampColumn, cursorDate);
|
|
231
|
+
return or(lt(timestampColumn, cursorDate), and(eq(timestampColumn, cursorDate), gt(idColumn, cursor.id)));
|
|
232
|
+
}
|
|
233
|
+
function publicGoalOutcome(outcome) {
|
|
234
|
+
if (outcome === "achieved")
|
|
235
|
+
return "Goal achieved";
|
|
236
|
+
if (outcome === "not_achieved")
|
|
237
|
+
return "Goal not achieved";
|
|
238
|
+
if (outcome === "maintained")
|
|
239
|
+
return "Goal maintained";
|
|
240
|
+
if (outcome === "breached")
|
|
241
|
+
return "Goal condition breached";
|
|
242
|
+
if (outcome === "completed_with_result")
|
|
243
|
+
return "Goal completed with a measured result";
|
|
244
|
+
if (outcome === "decided")
|
|
245
|
+
return "Goal completed with a decision";
|
|
246
|
+
return "Result needs more evidence";
|
|
247
|
+
}
|
|
248
|
+
const INTERNAL_GOAL_LANGUAGE = [
|
|
249
|
+
[/\bgoal\s+contract\b/gi, "Goal"],
|
|
250
|
+
[/\bcontract\s+revision\b/gi, "Goal update"],
|
|
251
|
+
[/\bcontracts?\b/gi, "agreement"],
|
|
252
|
+
[/\bobjective\s+mode\b/gi, "Goal type"],
|
|
253
|
+
[/\bevaluator\b/gi, "success check"],
|
|
254
|
+
[/\bevidence\s+requirements?\b/gi, "what we need to verify"],
|
|
255
|
+
[/\bautonomy\s+envelope\b/gi, "working boundaries"],
|
|
256
|
+
[/\bhuman\s+authorit(?:y|ies)\b/gi, "decisions that need you"],
|
|
257
|
+
[/\bcontinuation\b/gi, "next step"],
|
|
258
|
+
[/\bchange\s+proposal\b/gi, "Goal update"],
|
|
259
|
+
[/\bresult\s+proposal\b/gi, "result review"],
|
|
260
|
+
[/\bchange_proposal\b/gi, "Goal update"],
|
|
261
|
+
[/\bresult_proposal\b/gi, "result review"],
|
|
262
|
+
[/\bruntime\s+evidence\b/gi, "supporting evidence"],
|
|
263
|
+
[/\brun\s+evidence\b/gi, "supporting work"],
|
|
264
|
+
[/\bpara-memory-files\b/gi, "shared notes"],
|
|
265
|
+
[/\b(?:the\s+)?[`]?shared notes[`]?\s+skill\b/gi, "shared notes"],
|
|
266
|
+
[/\bdaily[- ]note\b/gi, "notes"],
|
|
267
|
+
[/\b(?:runtime\s+)?evidence\s+(?:demonstrates?|shows?)\s+that\b/gi, "Supporting work shows that"],
|
|
268
|
+
];
|
|
269
|
+
export function publicGoalText(value) {
|
|
270
|
+
const mapped = INTERNAL_GOAL_LANGUAGE.reduce((current, [pattern, replacement]) => current.replace(pattern, replacement), value);
|
|
271
|
+
return mapped
|
|
272
|
+
.replace(/\b(?:goal-feedback|goal-start|goal-change-decision|goal-result-evaluation):[0-9a-f-]{8,}\b/gi, "the related update")
|
|
273
|
+
.replace(/\b(?:artifact|run|issue|project|approval|decision|measurement|library-file|library-entry):\/\/[^\s)]+/gi, "supporting work")
|
|
274
|
+
.replace(/\b[0-9a-f]{8}-[0-9a-f-]{27,}\b/gi, "the related item")
|
|
275
|
+
.replace(/\b(?:feedback|activity|proposal|request|run)\s+(?:the related item|[0-9a-f-]{8,})\b/gi, "the related update");
|
|
276
|
+
}
|
|
277
|
+
function publicGoalToken(value) {
|
|
278
|
+
const known = {
|
|
279
|
+
bounded_reversible_work: "bounded, reversible work",
|
|
280
|
+
external_or_irreversible_action: "external or irreversible actions",
|
|
281
|
+
external_publication: "publishing externally",
|
|
282
|
+
authority_expansion: "expanding access",
|
|
283
|
+
acceptance: "accepting the result",
|
|
284
|
+
consequentialChanges: "consequential changes",
|
|
285
|
+
externalPublication: "publishing externally",
|
|
286
|
+
};
|
|
287
|
+
return known[value] ?? value.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").toLowerCase();
|
|
288
|
+
}
|
|
289
|
+
function publicGoalRecord(value) {
|
|
290
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
291
|
+
? value
|
|
292
|
+
: {};
|
|
293
|
+
}
|
|
294
|
+
function publicGoalStrings(value) {
|
|
295
|
+
return Array.isArray(value)
|
|
296
|
+
? value.filter((item) => typeof item === "string" && item.trim().length > 0)
|
|
297
|
+
: [];
|
|
298
|
+
}
|
|
299
|
+
function publicGoalBoundarySummary(value) {
|
|
300
|
+
const record = publicGoalRecord(value);
|
|
301
|
+
const allowed = publicGoalStrings(record.allowed).map(publicGoalToken);
|
|
302
|
+
const approvals = publicGoalStrings(record.requiresHumanApproval).map(publicGoalToken);
|
|
303
|
+
const parts = [
|
|
304
|
+
allowed.length > 0 ? `The Agent may handle ${allowed.join(", ")}.` : null,
|
|
305
|
+
approvals.length > 0 ? `You will be asked before ${approvals.join(", ")}.` : null,
|
|
306
|
+
].filter((part) => Boolean(part));
|
|
307
|
+
return parts.length > 0 ? parts.join(" ") : null;
|
|
308
|
+
}
|
|
309
|
+
function publicGoalAuthoritySummary(value) {
|
|
310
|
+
const decisions = Object.entries(publicGoalRecord(value))
|
|
311
|
+
.filter(([, entry]) => entry === "board_human" || entry === true)
|
|
312
|
+
.map(([key]) => publicGoalToken(key));
|
|
313
|
+
return decisions.length > 0 ? `You decide ${decisions.join(", ")}.` : null;
|
|
314
|
+
}
|
|
315
|
+
function publicGoalCompletionSummary(value) {
|
|
316
|
+
const record = publicGoalRecord(value);
|
|
317
|
+
const requiresEvidence = record.terminalEvidenceRequired === true;
|
|
318
|
+
const requiresAcceptance = record.humanAcceptanceRequired === true;
|
|
319
|
+
if (requiresEvidence && requiresAcceptance)
|
|
320
|
+
return "Supporting work is shown, and you accept the result.";
|
|
321
|
+
if (requiresEvidence)
|
|
322
|
+
return "Supporting work is shown before the result is considered ready.";
|
|
323
|
+
if (requiresAcceptance)
|
|
324
|
+
return "You accept the result when it is ready.";
|
|
325
|
+
return null;
|
|
326
|
+
}
|
|
327
|
+
function publicGoalContractSummary(value) {
|
|
328
|
+
const record = publicGoalRecord(value);
|
|
329
|
+
const outcomeStatement = typeof record.outcomeStatement === "string" && record.outcomeStatement.trim()
|
|
330
|
+
? publicGoalText(record.outcomeStatement)
|
|
331
|
+
: null;
|
|
332
|
+
const criteria = Array.isArray(record.criteria)
|
|
333
|
+
? record.criteria.flatMap((criterion) => {
|
|
334
|
+
const label = publicGoalRecord(criterion).label;
|
|
335
|
+
return typeof label === "string" && label.trim() ? [{ label: publicGoalText(label) }] : [];
|
|
336
|
+
})
|
|
337
|
+
: [];
|
|
338
|
+
const targetTime = typeof record.evaluationDeadline === "string"
|
|
339
|
+
? record.evaluationDeadline
|
|
340
|
+
: typeof record.actionDeadline === "string" ? record.actionDeadline : null;
|
|
341
|
+
return {
|
|
342
|
+
...(outcomeStatement ? { outcomeStatement } : {}),
|
|
343
|
+
...(criteria.length > 0 ? { criteria } : {}),
|
|
344
|
+
...(targetTime ? { targetTime } : {}),
|
|
345
|
+
...(publicGoalBoundarySummary(record.autonomyEnvelope)
|
|
346
|
+
? { boundarySummary: publicGoalBoundarySummary(record.autonomyEnvelope) } : {}),
|
|
347
|
+
...(publicGoalAuthoritySummary(record.humanAuthorities)
|
|
348
|
+
? { approvalSummary: publicGoalAuthoritySummary(record.humanAuthorities) } : {}),
|
|
349
|
+
...(publicGoalCompletionSummary(record.evaluationPolicy)
|
|
350
|
+
? { completionSummary: publicGoalCompletionSummary(record.evaluationPolicy) } : {}),
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
export function publicGoalView(goal) {
|
|
354
|
+
const criteria = Array.isArray(goal.criteria)
|
|
355
|
+
? goal.criteria.flatMap((criterion) => {
|
|
356
|
+
const record = publicGoalRecord(criterion);
|
|
357
|
+
const id = typeof record.id === "string" && record.id.trim() ? record.id : null;
|
|
358
|
+
const label = typeof record.label === "string" && record.label.trim()
|
|
359
|
+
? publicGoalText(record.label)
|
|
360
|
+
: null;
|
|
361
|
+
return id && label ? [{ id, label }] : [];
|
|
362
|
+
})
|
|
363
|
+
: [];
|
|
364
|
+
const evaluation = publicGoalRecord(goal.evaluationResult);
|
|
365
|
+
return {
|
|
366
|
+
id: goal.id,
|
|
367
|
+
orgId: goal.orgId,
|
|
368
|
+
title: goal.title,
|
|
369
|
+
description: goal.description,
|
|
370
|
+
lifecycle: goal.lifecycle,
|
|
371
|
+
status: goal.status,
|
|
372
|
+
outcomeStatement: goal.outcomeStatement ? publicGoalText(goal.outcomeStatement) : goal.outcomeStatement,
|
|
373
|
+
criteria,
|
|
374
|
+
ownerAgentId: goal.ownerAgentId,
|
|
375
|
+
ownerAgentRuntimeOverrides: goal.ownerAgentRuntimeOverrides,
|
|
376
|
+
focus: goal.focus,
|
|
377
|
+
evaluationResult: typeof evaluation.outcome === "string" ? { outcome: evaluation.outcome } : null,
|
|
378
|
+
evaluationDeadline: goal.evaluationDeadline,
|
|
379
|
+
actionDeadline: goal.actionDeadline,
|
|
380
|
+
continuationSummary: goal.continuationSummary ? publicGoalText(goal.continuationSummary) : goal.continuationSummary,
|
|
381
|
+
wakeCondition: goal.wakeCondition ? publicGoalText(goal.wakeCondition) : goal.wakeCondition,
|
|
382
|
+
alignmentQuestion: goal.alignmentQuestion ? publicGoalText(goal.alignmentQuestion) : goal.alignmentQuestion,
|
|
383
|
+
closeReason: goal.closeReason,
|
|
384
|
+
createdAt: goal.createdAt,
|
|
385
|
+
updatedAt: goal.updatedAt,
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
export function publicGoalActivity(activity, options = {}) {
|
|
389
|
+
return {
|
|
390
|
+
id: activity.id,
|
|
391
|
+
orgId: activity.orgId,
|
|
392
|
+
goalId: activity.goalId,
|
|
393
|
+
activityKind: activity.activityKind,
|
|
394
|
+
summary: publicGoalActivitySummary(activity),
|
|
395
|
+
evidence: publicEvidenceItems(activity.evidenceRefs, {
|
|
396
|
+
...options,
|
|
397
|
+
runAgentId: activity.submittedByAgentId,
|
|
398
|
+
}),
|
|
399
|
+
occurredAt: activity.occurredAt,
|
|
400
|
+
createdAt: activity.createdAt,
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
export function publicGoalDetail(input, options = {}) {
|
|
404
|
+
return {
|
|
405
|
+
...publicGoalView(input),
|
|
406
|
+
ownerAssignment: input.ownerAssignment
|
|
407
|
+
? {
|
|
408
|
+
agentId: input.ownerAssignment.agentId,
|
|
409
|
+
assignmentRevision: input.ownerAssignment.assignmentRevision,
|
|
410
|
+
startsAt: input.ownerAssignment.startsAt,
|
|
411
|
+
endsAt: input.ownerAssignment.endsAt,
|
|
412
|
+
}
|
|
413
|
+
: null,
|
|
414
|
+
plan: input.plan
|
|
415
|
+
? {
|
|
416
|
+
revision: input.plan.revision,
|
|
417
|
+
summary: publicGoalText(input.plan.summary),
|
|
418
|
+
}
|
|
419
|
+
: null,
|
|
420
|
+
activities: input.activities.map((activity) => publicGoalActivity(activity, options)),
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
export function publicGoalPlan(plan) {
|
|
424
|
+
return {
|
|
425
|
+
id: plan.id,
|
|
426
|
+
orgId: plan.orgId,
|
|
427
|
+
goalId: plan.goalId,
|
|
428
|
+
revision: plan.revision,
|
|
429
|
+
summary: publicGoalText(plan.summary),
|
|
430
|
+
createdAt: plan.createdAt,
|
|
431
|
+
updatedAt: plan.updatedAt,
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
export function publicGoalOwnerAssignment(assignment) {
|
|
435
|
+
return {
|
|
436
|
+
id: assignment.id,
|
|
437
|
+
orgId: assignment.orgId,
|
|
438
|
+
goalId: assignment.goalId,
|
|
439
|
+
agentId: assignment.agentId,
|
|
440
|
+
assignmentRevision: assignment.assignmentRevision,
|
|
441
|
+
startsAt: assignment.startsAt,
|
|
442
|
+
endsAt: assignment.endsAt,
|
|
443
|
+
createdAt: assignment.createdAt,
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
export function publicGoalFeedback(entry) {
|
|
447
|
+
return {
|
|
448
|
+
id: entry.id,
|
|
449
|
+
orgId: entry.orgId,
|
|
450
|
+
goalId: entry.goalId,
|
|
451
|
+
actorType: entry.actorType,
|
|
452
|
+
actorId: entry.actorId,
|
|
453
|
+
body: entry.body,
|
|
454
|
+
attachments: entry.attachments.map(({ name, mimeType, size }) => ({ name, mimeType, size })),
|
|
455
|
+
feedbackKind: entry.feedbackKind,
|
|
456
|
+
createdAt: entry.createdAt,
|
|
457
|
+
updatedAt: entry.updatedAt,
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
export function publicGoalChangeProposal(proposal, options = {}) {
|
|
461
|
+
return {
|
|
462
|
+
id: proposal.id,
|
|
463
|
+
approvalId: proposal.approvalId,
|
|
464
|
+
status: proposal.status,
|
|
465
|
+
rationale: publicGoalText(proposal.rationale),
|
|
466
|
+
evidence: publicEvidenceItems(proposal.evidenceRefs, { ...options, runAgentId: proposal.proposedByAgentId }),
|
|
467
|
+
beforeSummary: publicGoalContractSummary(proposal.beforeContract),
|
|
468
|
+
afterSummary: publicGoalContractSummary(proposal.afterContract),
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
export function publicGoalActivitySummary(activity) {
|
|
472
|
+
if (hasTechnicalRunSummary(activity.summary))
|
|
473
|
+
return "The Agent shared an update that needs attention.";
|
|
474
|
+
const summary = publicGoalText(activity.summary);
|
|
475
|
+
if (activity.activityKind !== "closeout")
|
|
476
|
+
return summary;
|
|
477
|
+
const legacyOutcome = /^Goal evaluated as\s+(.+)$/i.exec(summary)?.[1]?.trim();
|
|
478
|
+
return legacyOutcome ? publicGoalOutcome(legacyOutcome) : summary;
|
|
479
|
+
}
|
|
480
|
+
export function publicGoalResultProposal(proposal, options = {}) {
|
|
481
|
+
const preflight = publicGoalRecord(proposal.preflight);
|
|
482
|
+
const candidate = publicGoalRecord(proposal.candidate);
|
|
483
|
+
const outcome = typeof preflight.outcome === "string" ? preflight.outcome : "inconclusive";
|
|
484
|
+
const resultValue = preflight.resultValue ?? candidate.resultValue;
|
|
485
|
+
const decision = typeof preflight.decision === "string"
|
|
486
|
+
? preflight.decision
|
|
487
|
+
: typeof candidate.decision === "string" ? candidate.decision : null;
|
|
488
|
+
const criteria = Array.isArray(preflight.criteria)
|
|
489
|
+
? preflight.criteria.flatMap((criterion) => {
|
|
490
|
+
const record = publicGoalRecord(criterion);
|
|
491
|
+
const id = typeof record.id === "string" && record.id.trim() ? record.id : null;
|
|
492
|
+
const status = typeof record.status === "string" ? record.status : "unknown";
|
|
493
|
+
if (!id)
|
|
494
|
+
return [];
|
|
495
|
+
return [{
|
|
496
|
+
id,
|
|
497
|
+
status,
|
|
498
|
+
missingEvidenceCount: Array.isArray(record.missingEvidence) ? record.missingEvidence.length : 0,
|
|
499
|
+
}];
|
|
500
|
+
})
|
|
501
|
+
: [];
|
|
502
|
+
return {
|
|
503
|
+
id: proposal.id,
|
|
504
|
+
status: proposal.status,
|
|
505
|
+
outcome,
|
|
506
|
+
outcomeLabel: outcome === "completed_with_result" && resultValue !== undefined
|
|
507
|
+
? `Completed with result: ${String(resultValue)}`
|
|
508
|
+
: outcome === "decided" && decision
|
|
509
|
+
? `Decision reached: ${decision}`
|
|
510
|
+
: publicGoalOutcome(outcome),
|
|
511
|
+
criteria,
|
|
512
|
+
evidence: publicEvidenceItems(candidate.evidenceRefs, { ...options, runAgentId: proposal.proposedByAgentId }),
|
|
513
|
+
riskSummary: proposal.riskSummary ? publicGoalText(proposal.riskSummary) : proposal.riskSummary,
|
|
514
|
+
};
|
|
515
|
+
}
|
|
101
516
|
function resultSummary(run) {
|
|
102
517
|
const candidates = [run.resultSummaryJson, run.resultJson];
|
|
103
518
|
for (const candidate of candidates) {
|
|
@@ -109,6 +524,85 @@ function resultSummary(run) {
|
|
|
109
524
|
}
|
|
110
525
|
return run.error?.trim() || `Agent run is ${run.status}.`;
|
|
111
526
|
}
|
|
527
|
+
const PROVIDER_RESULT_ENVELOPE_PATTERN = /__RUDDER_RESULT_[a-z0-9-]+__/i;
|
|
528
|
+
function publicProviderSummary(value) {
|
|
529
|
+
const envelope = PROVIDER_RESULT_ENVELOPE_PATTERN.exec(value);
|
|
530
|
+
if (!envelope)
|
|
531
|
+
return value;
|
|
532
|
+
const payloadText = value.slice(envelope.index + envelope[0].length).trim();
|
|
533
|
+
try {
|
|
534
|
+
const payload = JSON.parse(payloadText);
|
|
535
|
+
if (typeof payload.body === "string" && payload.body.trim())
|
|
536
|
+
return payload.body.trim();
|
|
537
|
+
}
|
|
538
|
+
catch {
|
|
539
|
+
// Keep the visible text before a malformed internal envelope.
|
|
540
|
+
}
|
|
541
|
+
return value.slice(0, envelope.index).trim() || value;
|
|
542
|
+
}
|
|
543
|
+
const TECHNICAL_RUN_SUMMARY_PATTERNS = [
|
|
544
|
+
/(?:failed query|stack trace|traceback|drizzlequeryerror|connection ended|node_modules|\b(?:ECONN|ENOTFOUND|ETIMEDOUT)\b)/i,
|
|
545
|
+
/(?:^|\s)(?:error|exception):/i,
|
|
546
|
+
/\bat\s+[\w./-]+:\d+(?::\d+)?/i,
|
|
547
|
+
/(?:process adapter|missing command|adapter failure)/i,
|
|
548
|
+
];
|
|
549
|
+
function hasTechnicalRunSummary(value) {
|
|
550
|
+
return TECHNICAL_RUN_SUMMARY_PATTERNS.some((pattern) => pattern.test(value));
|
|
551
|
+
}
|
|
552
|
+
export function publicRunSummary(run) {
|
|
553
|
+
const rawSummary = publicProviderSummary(resultSummary(run));
|
|
554
|
+
if (hasTechnicalRunSummary(rawSummary)) {
|
|
555
|
+
if (["queued", "pending", "running", "started"].includes(run.status)) {
|
|
556
|
+
return "The Agent is working on this Goal.";
|
|
557
|
+
}
|
|
558
|
+
if (["succeeded", "completed"].includes(run.status)) {
|
|
559
|
+
return "The Agent completed its latest action.";
|
|
560
|
+
}
|
|
561
|
+
if (run.status === "failed")
|
|
562
|
+
return "The Agent could not complete its latest action.";
|
|
563
|
+
if (["cancelled", "canceled"].includes(run.status))
|
|
564
|
+
return "The Agent stopped its latest action.";
|
|
565
|
+
if (["timed_out", "timeout"].includes(run.status))
|
|
566
|
+
return "The Agent's latest action needs attention.";
|
|
567
|
+
return "The Agent has a new update on this Goal.";
|
|
568
|
+
}
|
|
569
|
+
const internalRunPrefix = rawSummary.match(/^Agent run\s+[\w-]+\s+is\s+[^:]+(?::\s*(.+))?$/i);
|
|
570
|
+
if (internalRunPrefix) {
|
|
571
|
+
const detail = internalRunPrefix[1]?.trim();
|
|
572
|
+
if (detail && ["succeeded", "completed"].includes(run.status))
|
|
573
|
+
return publicGoalText(detail);
|
|
574
|
+
if (["queued", "pending", "running", "started"].includes(run.status))
|
|
575
|
+
return "The Agent is working on this Goal.";
|
|
576
|
+
if (["succeeded", "completed"].includes(run.status))
|
|
577
|
+
return "The Agent completed its latest action.";
|
|
578
|
+
if (run.status === "failed")
|
|
579
|
+
return "The Agent could not complete its latest action.";
|
|
580
|
+
if (["cancelled", "canceled"].includes(run.status))
|
|
581
|
+
return "The Agent stopped its latest action.";
|
|
582
|
+
if (["timed_out", "timeout"].includes(run.status))
|
|
583
|
+
return "The Agent's latest action needs attention.";
|
|
584
|
+
return "The Agent has a new update on this Goal.";
|
|
585
|
+
}
|
|
586
|
+
const summary = publicGoalText(rawSummary);
|
|
587
|
+
const detailAfterInternalPrefix = summary.match(/^Agent run\s+[\w-]+\s+is\s+[^:]+:\s*(.+)$/i)?.[1]?.trim();
|
|
588
|
+
if (detailAfterInternalPrefix)
|
|
589
|
+
return detailAfterInternalPrefix;
|
|
590
|
+
if (!/\bagent\s+run\b/i.test(summary))
|
|
591
|
+
return summary;
|
|
592
|
+
if (["queued", "pending", "running", "started"].includes(run.status)) {
|
|
593
|
+
return "The Agent is working on this Goal.";
|
|
594
|
+
}
|
|
595
|
+
if (["succeeded", "completed"].includes(run.status)) {
|
|
596
|
+
return "The Agent completed its latest action.";
|
|
597
|
+
}
|
|
598
|
+
if (run.status === "failed")
|
|
599
|
+
return "The Agent could not complete its latest action.";
|
|
600
|
+
if (["cancelled", "canceled"].includes(run.status))
|
|
601
|
+
return "The Agent stopped its latest action.";
|
|
602
|
+
if (["timed_out", "timeout"].includes(run.status))
|
|
603
|
+
return "The Agent's latest action needs attention.";
|
|
604
|
+
return "The Agent has a new update on this Goal.";
|
|
605
|
+
}
|
|
112
606
|
function sameHashPayload(left, right) {
|
|
113
607
|
return stableGoalHash(left) === stableGoalHash(right);
|
|
114
608
|
}
|
|
@@ -185,7 +679,13 @@ export function compileGoalStartPreview(input, owner) {
|
|
|
185
679
|
packetHash: null,
|
|
186
680
|
packet: null,
|
|
187
681
|
review: null,
|
|
682
|
+
blockers: [{
|
|
683
|
+
code: "outcome_required",
|
|
684
|
+
field: "goal",
|
|
685
|
+
message: "Describe the result or decision someone should be able to verify. Include what changes and, when useful, a measure or deadline.",
|
|
686
|
+
}],
|
|
188
687
|
alignmentQuestion: "What observable result or decision should this Goal produce, and how will we know it worked?",
|
|
688
|
+
warning: null,
|
|
189
689
|
};
|
|
190
690
|
}
|
|
191
691
|
if (!owner || owner.id !== input.ownerAgentId || NON_INVOKABLE_AGENT_STATUSES.has(owner.status)) {
|
|
@@ -194,29 +694,29 @@ export function compileGoalStartPreview(input, owner) {
|
|
|
194
694
|
packetHash: null,
|
|
195
695
|
packet: null,
|
|
196
696
|
review: null,
|
|
697
|
+
blockers: [{
|
|
698
|
+
code: "owner_required",
|
|
699
|
+
field: "ownerAgentId",
|
|
700
|
+
message: "Select an available Agent from this organization to own and start the Goal.",
|
|
701
|
+
}],
|
|
197
702
|
alignmentQuestion: "Which available same-organization Agent should own and advance this Goal?",
|
|
703
|
+
warning: null,
|
|
198
704
|
};
|
|
199
705
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
packetHash: null,
|
|
204
|
-
packet: null,
|
|
205
|
-
review: null,
|
|
206
|
-
alignmentQuestion: "The assigned Agent does not appear to cover this Goal. Which better-matched Agent should own it?",
|
|
207
|
-
};
|
|
208
|
-
}
|
|
706
|
+
const warning = ownerCanAdvanceGoal(owner, input)
|
|
707
|
+
? null
|
|
708
|
+
: "This Agent may not be the best match for this Goal. You selected this Agent, so you can still start it or choose another Agent.";
|
|
209
709
|
const mode = goalMode(input);
|
|
210
710
|
const context = input.context?.trim() || null;
|
|
211
711
|
const success = context
|
|
212
|
-
? `
|
|
712
|
+
? `We will know it worked when ${title} and this context is satisfied: ${context}`
|
|
213
713
|
: mode === "decide"
|
|
214
714
|
? `A documented decision resolves: ${title}`
|
|
215
715
|
: mode === "maximize"
|
|
216
|
-
? `
|
|
716
|
+
? `Progress is measured toward: ${title}`
|
|
217
717
|
: mode === "maintain"
|
|
218
|
-
? `
|
|
219
|
-
: `
|
|
718
|
+
? `The condition remains true: ${title}`
|
|
719
|
+
: `A reviewable result shows that ${title}`;
|
|
220
720
|
const firstAction = mode === "decide"
|
|
221
721
|
? "Gather the decision inputs and return with a clear recommendation and tradeoffs."
|
|
222
722
|
: mode === "maximize"
|
|
@@ -249,7 +749,7 @@ export function compileGoalStartPreview(input, owner) {
|
|
|
249
749
|
initialPlan: { summary: firstAction },
|
|
250
750
|
initialContinuation: {
|
|
251
751
|
kind: "commitment",
|
|
252
|
-
summary:
|
|
752
|
+
summary: firstAction,
|
|
253
753
|
},
|
|
254
754
|
});
|
|
255
755
|
const packet = {
|
|
@@ -271,7 +771,9 @@ export function compileGoalStartPreview(input, owner) {
|
|
|
271
771
|
ownerAgentId: owner.id,
|
|
272
772
|
targetTime: input.targetTime,
|
|
273
773
|
},
|
|
774
|
+
blockers: [],
|
|
274
775
|
alignmentQuestion: null,
|
|
776
|
+
warning,
|
|
275
777
|
};
|
|
276
778
|
}
|
|
277
779
|
export async function getGoalDependencies(db, goal) {
|
|
@@ -339,6 +841,13 @@ async function requireGoal(db, id) {
|
|
|
339
841
|
throw notFound("Goal not found");
|
|
340
842
|
return goal;
|
|
341
843
|
}
|
|
844
|
+
async function requireGoalForUpdate(db, id) {
|
|
845
|
+
const goal = await db.select().from(goals).where(eq(goals.id, id))
|
|
846
|
+
.for("update").then((rows) => rows[0] ?? null);
|
|
847
|
+
if (!goal)
|
|
848
|
+
throw notFound("Goal not found");
|
|
849
|
+
return goal;
|
|
850
|
+
}
|
|
342
851
|
function positiveOutcome(mode, outcome) {
|
|
343
852
|
return (mode === "target" && outcome === "achieved")
|
|
344
853
|
|| (mode === "maximize" && outcome === "completed_with_result")
|
|
@@ -455,7 +964,7 @@ function assertSameResultProposalPayload(existing, input, candidateHash) {
|
|
|
455
964
|
}
|
|
456
965
|
}
|
|
457
966
|
export function goalService(db) {
|
|
458
|
-
function facetFor(goal, pendingChange, readyResult) {
|
|
967
|
+
function facetFor(goal, pendingChange, readyResult, pendingWakeup = null, externalWorkFact = null) {
|
|
459
968
|
if (goal.lifecycle === "closed")
|
|
460
969
|
return "closed";
|
|
461
970
|
if (goal.lifecycle === "draft")
|
|
@@ -464,12 +973,139 @@ export function goalService(db) {
|
|
|
464
973
|
return "ready_for_acceptance";
|
|
465
974
|
if (pendingChange)
|
|
466
975
|
return "needs_attention";
|
|
976
|
+
if (pendingWakeup?.status === "deferred_goal_focus")
|
|
977
|
+
return "waiting_focus";
|
|
978
|
+
if (pendingWakeup?.status === "deferred_goal_blocked"
|
|
979
|
+
|| pendingWakeup?.status === "deferred_agent_paused")
|
|
980
|
+
return "needs_attention";
|
|
981
|
+
if (goalRunAttentionReason(externalWorkFact?.runStatus))
|
|
982
|
+
return "needs_attention";
|
|
467
983
|
if (goal.continuationKind === "wait")
|
|
468
984
|
return "waiting_external";
|
|
469
985
|
return "agent_advancing";
|
|
470
986
|
}
|
|
471
|
-
|
|
472
|
-
|
|
987
|
+
function goalWakeupAttentionReason(wakeup) {
|
|
988
|
+
if (wakeup?.status === "deferred_agent_paused") {
|
|
989
|
+
return "The Owner Agent is paused. Resume it to continue this Goal.";
|
|
990
|
+
}
|
|
991
|
+
if (wakeup?.status !== "deferred_goal_blocked")
|
|
992
|
+
return null;
|
|
993
|
+
if (wakeup.error === "heartbeat.wakeOnDemand.disabled") {
|
|
994
|
+
return "The Owner Agent is not accepting on-demand work. Update the Agent or choose another Owner.";
|
|
995
|
+
}
|
|
996
|
+
if (wakeup.error === "agent.unavailable") {
|
|
997
|
+
return "The Owner Agent is unavailable. Make it available or choose another Owner.";
|
|
998
|
+
}
|
|
999
|
+
if (wakeup.error === "budget.blocked") {
|
|
1000
|
+
return "The Owner Agent is blocked by a budget limit. Resolve the budget decision to continue.";
|
|
1001
|
+
}
|
|
1002
|
+
return "The Owner Agent cannot start this work yet. Resolve its blocking condition to continue.";
|
|
1003
|
+
}
|
|
1004
|
+
function pendingGoalWakeup(goal) {
|
|
1005
|
+
return db.select({
|
|
1006
|
+
status: agentWakeupRequests.status,
|
|
1007
|
+
error: agentWakeupRequests.error,
|
|
1008
|
+
}).from(agentWakeupRequests).where(and(eq(agentWakeupRequests.orgId, goal.orgId), sql `${agentWakeupRequests.payload} ->> 'goalId' = ${goal.id}`, inArray(agentWakeupRequests.status, [
|
|
1009
|
+
"deferred_goal_focus",
|
|
1010
|
+
"deferred_goal_blocked",
|
|
1011
|
+
"deferred_agent_paused",
|
|
1012
|
+
]), isNull(agentWakeupRequests.runId))).orderBy(desc(agentWakeupRequests.requestedAt)).limit(1)
|
|
1013
|
+
.then((rows) => rows[0] ?? null);
|
|
1014
|
+
}
|
|
1015
|
+
async function ensureGoalWakeupIntent(database, goal, input) {
|
|
1016
|
+
if (!goal.ownerAgentId)
|
|
1017
|
+
throw conflict("Goal has no Owner Agent to wake");
|
|
1018
|
+
const taskKey = `goal:${goal.id}:${input.event}:${input.eventId}`;
|
|
1019
|
+
const idempotencyKey = input.event === "goal_started"
|
|
1020
|
+
? `goal-start:${input.eventId}`
|
|
1021
|
+
: input.event === "goal_feedback"
|
|
1022
|
+
? `goal-feedback:${input.eventId}`
|
|
1023
|
+
: `goal-change-decision:${input.eventId}:${input.decision?.decision ?? "unknown"}`;
|
|
1024
|
+
const payload = {
|
|
1025
|
+
event: input.event,
|
|
1026
|
+
goalId: goal.id,
|
|
1027
|
+
taskKey,
|
|
1028
|
+
...(input.event === "goal_started" ? { goalStartRequestId: input.eventId } : {}),
|
|
1029
|
+
...(input.feedback ? { feedbackId: input.feedback.id } : {}),
|
|
1030
|
+
...(input.decision ? { goalChangeProposalId: input.eventId, decision: input.decision.decision } : {}),
|
|
1031
|
+
};
|
|
1032
|
+
const contextSnapshot = {
|
|
1033
|
+
goalId: goal.id,
|
|
1034
|
+
taskKey,
|
|
1035
|
+
goal: {
|
|
1036
|
+
id: goal.id,
|
|
1037
|
+
title: goal.title,
|
|
1038
|
+
description: goal.description,
|
|
1039
|
+
outcomeStatement: goal.outcomeStatement,
|
|
1040
|
+
objectiveMode: goal.objectiveMode,
|
|
1041
|
+
contractRevision: goal.contractRevision,
|
|
1042
|
+
criteria: goal.criteria,
|
|
1043
|
+
autonomyEnvelope: goal.autonomyEnvelope,
|
|
1044
|
+
humanAuthorities: goal.humanAuthorities,
|
|
1045
|
+
evaluationPolicy: goal.evaluationPolicy,
|
|
1046
|
+
actionDeadline: goal.actionDeadline,
|
|
1047
|
+
evaluationDeadline: goal.evaluationDeadline,
|
|
1048
|
+
},
|
|
1049
|
+
goalContinuation: {
|
|
1050
|
+
kind: goal.continuationKind,
|
|
1051
|
+
summary: goal.continuationSummary,
|
|
1052
|
+
wakeCondition: goal.wakeCondition,
|
|
1053
|
+
},
|
|
1054
|
+
...(input.feedback ? {
|
|
1055
|
+
goalFeedback: {
|
|
1056
|
+
id: input.feedback.id,
|
|
1057
|
+
body: input.feedback.body,
|
|
1058
|
+
kind: input.feedback.kind,
|
|
1059
|
+
},
|
|
1060
|
+
} : {}),
|
|
1061
|
+
...(input.decision ? {
|
|
1062
|
+
goalDecision: {
|
|
1063
|
+
proposalId: input.eventId,
|
|
1064
|
+
decision: input.decision.decision,
|
|
1065
|
+
note: input.decision.note,
|
|
1066
|
+
status: input.decision.status,
|
|
1067
|
+
},
|
|
1068
|
+
} : {}),
|
|
1069
|
+
};
|
|
1070
|
+
const storedPayload = buildDeferredWakePayload(payload, contextSnapshot);
|
|
1071
|
+
const [inserted] = await database.insert(agentWakeupRequests).values({
|
|
1072
|
+
orgId: goal.orgId,
|
|
1073
|
+
agentId: goal.ownerAgentId,
|
|
1074
|
+
source: "on_demand",
|
|
1075
|
+
triggerDetail: "system",
|
|
1076
|
+
reason: input.event,
|
|
1077
|
+
payload: storedPayload,
|
|
1078
|
+
status: "queued",
|
|
1079
|
+
requestedByActorType: input.actor.actorType,
|
|
1080
|
+
requestedByActorId: input.actor.actorId,
|
|
1081
|
+
idempotencyKey,
|
|
1082
|
+
}).onConflictDoNothing().returning();
|
|
1083
|
+
const wakeup = inserted ?? await database.select().from(agentWakeupRequests).where(and(eq(agentWakeupRequests.orgId, goal.orgId), eq(agentWakeupRequests.agentId, goal.ownerAgentId), eq(agentWakeupRequests.idempotencyKey, idempotencyKey))).then((rows) => rows[0] ?? null);
|
|
1084
|
+
if (!wakeup)
|
|
1085
|
+
throw conflict("Goal Owner wakeup could not be queued");
|
|
1086
|
+
if (!sameHashPayload(readDeferredWakePayload(wakeup.payload), payload)) {
|
|
1087
|
+
throw conflict("Goal Owner wakeup key was reused with a different payload");
|
|
1088
|
+
}
|
|
1089
|
+
const requestedByActorType = wakeup.requestedByActorType === "user"
|
|
1090
|
+
|| wakeup.requestedByActorType === "agent"
|
|
1091
|
+
|| wakeup.requestedByActorType === "system"
|
|
1092
|
+
? wakeup.requestedByActorType
|
|
1093
|
+
: "system";
|
|
1094
|
+
return {
|
|
1095
|
+
ownerAgentId: goal.ownerAgentId,
|
|
1096
|
+
wakeupRequestId: wakeup.id,
|
|
1097
|
+
source: "on_demand",
|
|
1098
|
+
triggerDetail: "system",
|
|
1099
|
+
reason: input.event,
|
|
1100
|
+
payload,
|
|
1101
|
+
contextSnapshot,
|
|
1102
|
+
requestedByActorType,
|
|
1103
|
+
requestedByActorId: wakeup.requestedByActorId,
|
|
1104
|
+
idempotencyKey,
|
|
1105
|
+
};
|
|
1106
|
+
}
|
|
1107
|
+
async function latestExternalWorkFact(goal, preferredRunId = null) {
|
|
1108
|
+
const [issueFacts, linkedProjectFacts, directRunFacts] = await Promise.all([
|
|
473
1109
|
db.select({
|
|
474
1110
|
id: issues.id,
|
|
475
1111
|
identifier: issues.identifier,
|
|
@@ -485,6 +1121,16 @@ export function goalService(db) {
|
|
|
485
1121
|
status: projects.status,
|
|
486
1122
|
updatedAt: projects.updatedAt,
|
|
487
1123
|
}).from(projects).leftJoin(projectGoals, and(eq(projectGoals.projectId, projects.id), eq(projectGoals.orgId, goal.orgId))).where(and(eq(projects.orgId, goal.orgId), or(eq(projects.goalId, goal.id), eq(projectGoals.goalId, goal.id)))),
|
|
1124
|
+
goal.ownerAgentId
|
|
1125
|
+
? db.select({
|
|
1126
|
+
id: heartbeatRuns.id,
|
|
1127
|
+
status: heartbeatRuns.status,
|
|
1128
|
+
updatedAt: heartbeatRuns.updatedAt,
|
|
1129
|
+
resultJson: heartbeatRuns.resultJson,
|
|
1130
|
+
resultSummaryJson: heartbeatRuns.resultSummaryJson,
|
|
1131
|
+
error: heartbeatRuns.error,
|
|
1132
|
+
}).from(heartbeatRuns).where(and(eq(heartbeatRuns.orgId, goal.orgId), eq(heartbeatRuns.agentId, goal.ownerAgentId), sql `${heartbeatRuns.contextSnapshot} ->> 'goalId' = ${goal.id}`))
|
|
1133
|
+
: Promise.resolve([]),
|
|
488
1134
|
]);
|
|
489
1135
|
const runIds = [...new Set(issueFacts.flatMap((issue) => [issue.checkoutRunId, issue.executionRunId]).filter((id) => Boolean(id)))];
|
|
490
1136
|
const runFacts = runIds.length > 0
|
|
@@ -497,36 +1143,120 @@ export function goalService(db) {
|
|
|
497
1143
|
error: heartbeatRuns.error,
|
|
498
1144
|
}).from(heartbeatRuns).where(and(eq(heartbeatRuns.orgId, goal.orgId), inArray(heartbeatRuns.id, runIds)))
|
|
499
1145
|
: [];
|
|
500
|
-
const
|
|
1146
|
+
const relatedFacts = [
|
|
501
1147
|
...issueFacts.map((issue) => ({
|
|
502
|
-
id: `
|
|
503
|
-
kind: "
|
|
1148
|
+
id: `work-status:issue:${issue.id}`,
|
|
1149
|
+
kind: "work_status",
|
|
504
1150
|
summary: `Issue ${issue.identifier ?? issue.title} is ${issue.status}.`,
|
|
505
1151
|
occurredAt: issue.updatedAt,
|
|
506
1152
|
sourceId: issue.id,
|
|
507
1153
|
sourceRunId: issue.executionRunId ?? issue.checkoutRunId,
|
|
508
|
-
|
|
1154
|
+
runStatus: null,
|
|
1155
|
+
evidenceRefs: [],
|
|
509
1156
|
})),
|
|
510
1157
|
...linkedProjectFacts.map((project) => ({
|
|
511
|
-
id: `
|
|
512
|
-
kind: "
|
|
1158
|
+
id: `work-status:project:${project.id}`,
|
|
1159
|
+
kind: "work_status",
|
|
513
1160
|
summary: `Project ${project.name} is ${project.status}.`,
|
|
514
1161
|
occurredAt: project.updatedAt,
|
|
515
1162
|
sourceId: project.id,
|
|
516
1163
|
sourceRunId: null,
|
|
517
|
-
|
|
1164
|
+
runStatus: null,
|
|
1165
|
+
evidenceRefs: [],
|
|
518
1166
|
})),
|
|
519
1167
|
...runFacts.map((run) => ({
|
|
520
|
-
id: `
|
|
521
|
-
kind: "
|
|
522
|
-
summary:
|
|
1168
|
+
id: `work-status:run:${run.id}`,
|
|
1169
|
+
kind: "work_status",
|
|
1170
|
+
summary: publicRunSummary(run),
|
|
523
1171
|
occurredAt: run.updatedAt,
|
|
524
1172
|
sourceId: run.id,
|
|
525
1173
|
sourceRunId: run.id,
|
|
526
|
-
|
|
1174
|
+
runStatus: run.status,
|
|
1175
|
+
evidenceRefs: [],
|
|
527
1176
|
})),
|
|
528
1177
|
];
|
|
529
|
-
|
|
1178
|
+
const directFacts = directRunFacts
|
|
1179
|
+
.filter((run) => !runFacts.some((linkedRun) => linkedRun.id === run.id))
|
|
1180
|
+
.map((run) => ({
|
|
1181
|
+
id: `work-status:run:${run.id}`,
|
|
1182
|
+
kind: "work_status",
|
|
1183
|
+
summary: publicRunSummary(run),
|
|
1184
|
+
occurredAt: run.updatedAt,
|
|
1185
|
+
sourceId: run.id,
|
|
1186
|
+
sourceRunId: run.id,
|
|
1187
|
+
runStatus: run.status,
|
|
1188
|
+
evidenceRefs: [],
|
|
1189
|
+
}));
|
|
1190
|
+
// Run facts use a public, namespaced source id while retaining the raw run id
|
|
1191
|
+
// separately for evidence links and attention actions.
|
|
1192
|
+
const allRunFacts = [...relatedFacts, ...directFacts].filter((fact) => fact.id.startsWith("work-status:run:"));
|
|
1193
|
+
const activeRunFacts = allRunFacts.filter((fact) => fact.runStatus && !TERMINAL_RUN_STATUSES.includes(fact.runStatus));
|
|
1194
|
+
if (activeRunFacts.length > 0) {
|
|
1195
|
+
return activeRunFacts.sort((left, right) => {
|
|
1196
|
+
const timeDelta = right.occurredAt.getTime() - left.occurredAt.getTime();
|
|
1197
|
+
return timeDelta !== 0 ? timeDelta : right.id.localeCompare(left.id);
|
|
1198
|
+
})[0] ?? null;
|
|
1199
|
+
}
|
|
1200
|
+
const terminalRunFacts = allRunFacts.filter((fact) => (fact.runStatus && TERMINAL_RUN_STATUSES.includes(fact.runStatus)));
|
|
1201
|
+
if (terminalRunFacts.length > 0) {
|
|
1202
|
+
const preferredTerminal = preferredRunId
|
|
1203
|
+
? terminalRunFacts.find((fact) => fact.sourceRunId === preferredRunId) ?? null
|
|
1204
|
+
: null;
|
|
1205
|
+
const latestTerminal = terminalRunFacts.sort((left, right) => {
|
|
1206
|
+
const timeDelta = right.occurredAt.getTime() - left.occurredAt.getTime();
|
|
1207
|
+
return timeDelta !== 0 ? timeDelta : right.id.localeCompare(left.id);
|
|
1208
|
+
})[0] ?? null;
|
|
1209
|
+
// Preserve the Run attached to the current evidence. A newer failed,
|
|
1210
|
+
// cancelled, or timed-out Run still takes priority because it needs
|
|
1211
|
+
// human attention; a generic successful Run must not hide the checkpoint
|
|
1212
|
+
// the user is currently inspecting.
|
|
1213
|
+
if (preferredTerminal) {
|
|
1214
|
+
const latestNeedsAttention = latestTerminal?.runStatus === "failed"
|
|
1215
|
+
|| latestTerminal?.runStatus === "cancelled"
|
|
1216
|
+
|| latestTerminal?.runStatus === "canceled"
|
|
1217
|
+
|| latestTerminal?.runStatus === "timed_out"
|
|
1218
|
+
|| latestTerminal?.runStatus === "timeout";
|
|
1219
|
+
if (!latestNeedsAttention || preferredTerminal.occurredAt >= latestTerminal.occurredAt) {
|
|
1220
|
+
return preferredTerminal;
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
return latestTerminal;
|
|
1224
|
+
}
|
|
1225
|
+
const preferredRun = preferredRunId
|
|
1226
|
+
? allRunFacts.find((fact) => fact.sourceRunId === preferredRunId) ?? null
|
|
1227
|
+
: null;
|
|
1228
|
+
if (preferredRun)
|
|
1229
|
+
return preferredRun;
|
|
1230
|
+
const facts = relatedFacts.length > 0 ? relatedFacts : directFacts;
|
|
1231
|
+
return facts.sort((left, right) => {
|
|
1232
|
+
const timeDelta = right.occurredAt.getTime() - left.occurredAt.getTime();
|
|
1233
|
+
return timeDelta !== 0 ? timeDelta : right.id.localeCompare(left.id);
|
|
1234
|
+
})[0] ?? null;
|
|
1235
|
+
}
|
|
1236
|
+
async function requireGoalRun(database, goal, runId, actorAgentId = null) {
|
|
1237
|
+
const run = await database.select({
|
|
1238
|
+
id: heartbeatRuns.id,
|
|
1239
|
+
orgId: heartbeatRuns.orgId,
|
|
1240
|
+
agentId: heartbeatRuns.agentId,
|
|
1241
|
+
status: heartbeatRuns.status,
|
|
1242
|
+
contextSnapshot: heartbeatRuns.contextSnapshot,
|
|
1243
|
+
}).from(heartbeatRuns).where(eq(heartbeatRuns.id, runId)).then((rows) => rows[0] ?? null);
|
|
1244
|
+
if (!run || run.orgId !== goal.orgId) {
|
|
1245
|
+
throw unprocessable("Goal Activity run must belong to the same organization");
|
|
1246
|
+
}
|
|
1247
|
+
if (actorAgentId && run.agentId !== actorAgentId) {
|
|
1248
|
+
throw unprocessable("Goal Activity run must belong to the acting Agent");
|
|
1249
|
+
}
|
|
1250
|
+
const context = run.contextSnapshot ?? {};
|
|
1251
|
+
if (context.goalId === goal.id)
|
|
1252
|
+
return run;
|
|
1253
|
+
const issueId = typeof context.issueId === "string" ? context.issueId : null;
|
|
1254
|
+
const linkedIssue = issueId
|
|
1255
|
+
? await database.select({ goalId: issues.goalId }).from(issues).where(and(eq(issues.id, issueId), eq(issues.orgId, goal.orgId))).then((rows) => rows[0] ?? null)
|
|
1256
|
+
: null;
|
|
1257
|
+
if (linkedIssue?.goalId === goal.id)
|
|
1258
|
+
return run;
|
|
1259
|
+
throw unprocessable("Goal Activity run must be linked to this Goal");
|
|
530
1260
|
}
|
|
531
1261
|
async function recordFeedback(database, goal, input, actorUserId) {
|
|
532
1262
|
assertCanonicalActiveGoal(goal);
|
|
@@ -542,9 +1272,8 @@ export function goalService(db) {
|
|
|
542
1272
|
if (existing.contentHash !== contentHash) {
|
|
543
1273
|
throw conflict("Goal feedback idempotency key was reused with a different payload");
|
|
544
1274
|
}
|
|
545
|
-
return existing;
|
|
546
1275
|
}
|
|
547
|
-
const [inserted] = await database.insert(goalFeedbackEntries).values({
|
|
1276
|
+
const [inserted] = existing ? [] : await database.insert(goalFeedbackEntries).values({
|
|
548
1277
|
orgId: goal.orgId,
|
|
549
1278
|
goalId: goal.id,
|
|
550
1279
|
actorType: "user",
|
|
@@ -555,34 +1284,28 @@ export function goalService(db) {
|
|
|
555
1284
|
feedbackKind: input.feedbackKind,
|
|
556
1285
|
idempotencyKey: input.idempotencyKey,
|
|
557
1286
|
}).onConflictDoNothing().returning();
|
|
558
|
-
const feedback = inserted ?? await database.select().from(goalFeedbackEntries).where(and(eq(goalFeedbackEntries.goalId, goal.id), eq(goalFeedbackEntries.idempotencyKey, input.idempotencyKey))).then((rows) => rows[0] ?? null);
|
|
1287
|
+
const feedback = existing ?? inserted ?? await database.select().from(goalFeedbackEntries).where(and(eq(goalFeedbackEntries.goalId, goal.id), eq(goalFeedbackEntries.idempotencyKey, input.idempotencyKey))).then((rows) => rows[0] ?? null);
|
|
559
1288
|
if (!feedback)
|
|
560
1289
|
throw conflict("Goal feedback could not be recorded");
|
|
561
1290
|
if (feedback.contentHash !== contentHash) {
|
|
562
1291
|
throw conflict("Goal feedback idempotency key was reused with a different payload");
|
|
563
1292
|
}
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
if (!wakeup)
|
|
581
|
-
throw conflict("Goal feedback Owner wakeup could not be queued");
|
|
582
|
-
return database.update(goalFeedbackEntries).set({
|
|
583
|
-
routedWakeupRequestId: wakeup.id,
|
|
584
|
-
updatedAt: new Date(),
|
|
585
|
-
}).where(and(eq(goalFeedbackEntries.id, feedback.id), eq(goalFeedbackEntries.orgId, goal.orgId))).returning().then((rows) => rows[0] ?? feedback);
|
|
1293
|
+
const dispatch = await ensureGoalWakeupIntent(database, goal, {
|
|
1294
|
+
event: "goal_feedback",
|
|
1295
|
+
eventId: feedback.id,
|
|
1296
|
+
actor: { actorType: "user", actorId: actorUserId },
|
|
1297
|
+
feedback: { id: feedback.id, body: feedback.body, kind: feedback.feedbackKind },
|
|
1298
|
+
});
|
|
1299
|
+
if (feedback.routedWakeupRequestId && feedback.routedWakeupRequestId !== dispatch.wakeupRequestId) {
|
|
1300
|
+
throw conflict("Goal feedback is linked to a different Owner wakeup");
|
|
1301
|
+
}
|
|
1302
|
+
const routedFeedback = feedback.routedWakeupRequestId
|
|
1303
|
+
? feedback
|
|
1304
|
+
: await database.update(goalFeedbackEntries).set({
|
|
1305
|
+
routedWakeupRequestId: dispatch.wakeupRequestId,
|
|
1306
|
+
updatedAt: new Date(),
|
|
1307
|
+
}).where(and(eq(goalFeedbackEntries.id, feedback.id), eq(goalFeedbackEntries.orgId, goal.orgId), isNull(goalFeedbackEntries.routedWakeupRequestId))).returning().then((rows) => rows[0] ?? feedback);
|
|
1308
|
+
return { feedback: routedFeedback, dispatch };
|
|
586
1309
|
}
|
|
587
1310
|
async function evaluateInTransaction(database, current, input, actorAgentId) {
|
|
588
1311
|
assertCanonicalActiveGoal(current);
|
|
@@ -634,137 +1357,235 @@ export function goalService(db) {
|
|
|
634
1357
|
contractRevision: current.contractRevision,
|
|
635
1358
|
submittedByAgentId: actorAgentId,
|
|
636
1359
|
agentOwnerRefAtTime: current.ownerAgentId,
|
|
637
|
-
activityKind: "
|
|
638
|
-
summary:
|
|
1360
|
+
activityKind: "closeout",
|
|
1361
|
+
summary: publicGoalOutcome(evaluation.outcome),
|
|
639
1362
|
evidenceRefs: input.evidenceRefs,
|
|
640
1363
|
idempotencyKey: acceptedProposal ? `goal-result-evaluation:${acceptedProposal.id}` : null,
|
|
641
1364
|
}).onConflictDoNothing();
|
|
642
1365
|
return goal;
|
|
643
1366
|
}
|
|
644
|
-
async function
|
|
1367
|
+
async function historyFor(id, options = {}) {
|
|
645
1368
|
const goal = await requireGoal(db, id);
|
|
646
|
-
const
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
db.select().from(
|
|
656
|
-
|
|
657
|
-
db.select().from(goalResultProposals).where(eq(goalResultProposals.goalId, id))
|
|
658
|
-
.orderBy(desc(goalResultProposals.createdAt)).limit(100),
|
|
1369
|
+
const orgUrlKey = await organizationUrlKey(db, goal.orgId);
|
|
1370
|
+
const cursor = decodeGoalHistoryCursor(options.cursor);
|
|
1371
|
+
const limit = options.limit ?? GOAL_HISTORY_DEFAULT_LIMIT;
|
|
1372
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > GOAL_HISTORY_MAX_LIMIT) {
|
|
1373
|
+
throw badRequest(`Goal history limit must be between 1 and ${GOAL_HISTORY_MAX_LIMIT}`);
|
|
1374
|
+
}
|
|
1375
|
+
const perSourceLimit = limit + 1;
|
|
1376
|
+
const [activityRows, feedbackRows, changeRows, resultRows] = await Promise.all([
|
|
1377
|
+
db.select().from(goalActivities).where(and(eq(goalActivities.goalId, id), historyCursorCondition(goalActivities.occurredAt, goalActivities.id, "activity", cursor))).orderBy(desc(goalActivities.occurredAt), asc(goalActivities.id)).limit(perSourceLimit),
|
|
1378
|
+
db.select().from(goalFeedbackEntries).where(and(eq(goalFeedbackEntries.goalId, id), historyCursorCondition(goalFeedbackEntries.createdAt, goalFeedbackEntries.id, "feedback", cursor))).orderBy(desc(goalFeedbackEntries.createdAt), asc(goalFeedbackEntries.id)).limit(perSourceLimit),
|
|
1379
|
+
db.select().from(goalChangeProposals).where(and(eq(goalChangeProposals.goalId, id), historyCursorCondition(goalChangeProposals.createdAt, goalChangeProposals.id, "change_proposal", cursor))).orderBy(desc(goalChangeProposals.createdAt), asc(goalChangeProposals.id)).limit(perSourceLimit),
|
|
1380
|
+
db.select().from(goalResultProposals).where(and(eq(goalResultProposals.goalId, id), historyCursorCondition(goalResultProposals.createdAt, goalResultProposals.id, "result_proposal", cursor))).orderBy(desc(goalResultProposals.createdAt), asc(goalResultProposals.id)).limit(perSourceLimit),
|
|
659
1381
|
]);
|
|
660
|
-
const
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
? {
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
:
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
const
|
|
682
|
-
const
|
|
683
|
-
const
|
|
684
|
-
const
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
:
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
})
|
|
709
|
-
...
|
|
1382
|
+
const agentIds = Array.from(new Set([
|
|
1383
|
+
...activityRows.map((row) => row.submittedByAgentId),
|
|
1384
|
+
...changeRows.map((row) => row.proposedByAgentId),
|
|
1385
|
+
...resultRows.map((row) => row.proposedByAgentId),
|
|
1386
|
+
].filter((value) => Boolean(value))));
|
|
1387
|
+
const userIds = Array.from(new Set(feedbackRows.map((row) => row.actorId).filter(Boolean)));
|
|
1388
|
+
const attachmentAssetIds = Array.from(new Set(feedbackRows.flatMap((row) => row.attachments).flatMap((attachment) => {
|
|
1389
|
+
const match = attachment.uri.match(/^asset:\/\/([0-9a-f-]{36})$/i);
|
|
1390
|
+
return match?.[1] ? [match[1]] : [];
|
|
1391
|
+
})));
|
|
1392
|
+
const [agentRows, userRows, assetRows] = await Promise.all([
|
|
1393
|
+
agentIds.length > 0
|
|
1394
|
+
? db.select({ id: agents.id, name: agents.name }).from(agents).where(and(eq(agents.orgId, goal.orgId), inArray(agents.id, agentIds)))
|
|
1395
|
+
: Promise.resolve([]),
|
|
1396
|
+
userIds.length > 0
|
|
1397
|
+
? db.select({ id: authUsers.id, name: authUsers.name }).from(authUsers).where(inArray(authUsers.id, userIds))
|
|
1398
|
+
: Promise.resolve([]),
|
|
1399
|
+
attachmentAssetIds.length > 0
|
|
1400
|
+
? db.select({ id: assets.id }).from(assets).where(and(eq(assets.orgId, goal.orgId), inArray(assets.id, attachmentAssetIds)))
|
|
1401
|
+
: Promise.resolve([]),
|
|
1402
|
+
]);
|
|
1403
|
+
const agentNames = new Map(agentRows.map((row) => [row.id, row.name]));
|
|
1404
|
+
const userNames = new Map(userRows.map((row) => [row.id, row.name]));
|
|
1405
|
+
const validAssetIds = new Set(assetRows.map((row) => row.id));
|
|
1406
|
+
const actorName = (actorType, actorId) => {
|
|
1407
|
+
if (actorType === "system")
|
|
1408
|
+
return "System";
|
|
1409
|
+
if (actorType === "agent")
|
|
1410
|
+
return actorId ? agentNames.get(actorId) ?? "Former agent" : "System";
|
|
1411
|
+
return actorId ? userNames.get(actorId) ?? "Board user" : "Board user";
|
|
1412
|
+
};
|
|
1413
|
+
const items = [
|
|
1414
|
+
...activityRows.map((activity) => {
|
|
1415
|
+
const actorType = activity.submittedByAgentId ? "agent" : "system";
|
|
1416
|
+
return {
|
|
1417
|
+
id: activity.id,
|
|
1418
|
+
kind: "activity",
|
|
1419
|
+
summary: publicGoalActivitySummary(activity),
|
|
1420
|
+
createdAt: activity.occurredAt,
|
|
1421
|
+
evidence: publicEvidenceItems(activity.evidenceRefs, {
|
|
1422
|
+
orgUrlKey,
|
|
1423
|
+
runAgentId: activity.submittedByAgentId,
|
|
1424
|
+
}),
|
|
1425
|
+
actorType,
|
|
1426
|
+
actorId: activity.submittedByAgentId,
|
|
1427
|
+
actorName: actorName(actorType, activity.submittedByAgentId),
|
|
1428
|
+
attachments: [],
|
|
1429
|
+
};
|
|
1430
|
+
}),
|
|
1431
|
+
...feedbackRows.map((entry) => ({
|
|
710
1432
|
id: entry.id,
|
|
711
1433
|
kind: "feedback",
|
|
712
1434
|
summary: entry.body,
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
actorType:
|
|
1435
|
+
createdAt: entry.createdAt,
|
|
1436
|
+
evidence: [],
|
|
1437
|
+
actorType: "user",
|
|
716
1438
|
actorId: entry.actorId,
|
|
717
|
-
|
|
1439
|
+
actorName: actorName("user", entry.actorId),
|
|
1440
|
+
attachments: entry.attachments.map((attachment) => {
|
|
1441
|
+
const match = attachment.uri.match(/^asset:\/\/([0-9a-f-]{36})$/i);
|
|
1442
|
+
const assetId = match?.[1] ?? null;
|
|
1443
|
+
return {
|
|
1444
|
+
name: attachment.name,
|
|
1445
|
+
mimeType: attachment.mimeType ?? null,
|
|
1446
|
+
size: attachment.size ?? null,
|
|
1447
|
+
contentPath: assetId && validAssetIds.has(assetId) ? `/api/assets/${assetId}/content` : null,
|
|
1448
|
+
};
|
|
1449
|
+
}),
|
|
718
1450
|
feedbackKind: entry.feedbackKind,
|
|
719
1451
|
})),
|
|
720
|
-
...
|
|
1452
|
+
...changeRows.map((proposal) => ({
|
|
721
1453
|
id: proposal.id,
|
|
722
1454
|
kind: "change_proposal",
|
|
723
|
-
summary: proposal.rationale,
|
|
724
|
-
|
|
725
|
-
|
|
1455
|
+
summary: publicGoalText(proposal.rationale),
|
|
1456
|
+
createdAt: proposal.createdAt,
|
|
1457
|
+
evidence: publicEvidenceItems(proposal.evidenceRefs, {
|
|
1458
|
+
orgUrlKey,
|
|
1459
|
+
runAgentId: proposal.proposedByAgentId,
|
|
1460
|
+
}),
|
|
1461
|
+
actorType: "agent",
|
|
1462
|
+
actorId: proposal.proposedByAgentId,
|
|
1463
|
+
actorName: actorName("agent", proposal.proposedByAgentId),
|
|
1464
|
+
attachments: [],
|
|
726
1465
|
approvalId: proposal.approvalId,
|
|
727
1466
|
status: proposal.status,
|
|
728
|
-
evidenceRefs: proposal.evidenceRefs,
|
|
729
1467
|
})),
|
|
730
|
-
...
|
|
1468
|
+
...resultRows.map((proposal) => ({
|
|
731
1469
|
id: proposal.id,
|
|
732
1470
|
kind: "result_proposal",
|
|
733
|
-
summary:
|
|
734
|
-
|
|
735
|
-
|
|
1471
|
+
summary: publicGoalText(`${publicGoalOutcome(proposal.preflight.outcome)}. ${proposal.riskSummary}`),
|
|
1472
|
+
createdAt: proposal.createdAt,
|
|
1473
|
+
evidence: publicEvidenceItems(proposal.candidate.evidenceRefs, {
|
|
1474
|
+
orgUrlKey,
|
|
1475
|
+
runAgentId: proposal.proposedByAgentId,
|
|
1476
|
+
}),
|
|
1477
|
+
actorType: "agent",
|
|
1478
|
+
actorId: proposal.proposedByAgentId,
|
|
1479
|
+
actorName: actorName("agent", proposal.proposedByAgentId),
|
|
1480
|
+
attachments: [],
|
|
736
1481
|
status: proposal.status,
|
|
737
|
-
evidenceRefs: proposal.candidate.evidenceRefs,
|
|
738
1482
|
})),
|
|
739
|
-
|
|
740
|
-
|
|
1483
|
+
].sort((left, right) => {
|
|
1484
|
+
const timeDifference = new Date(right.createdAt).getTime() - new Date(left.createdAt).getTime();
|
|
1485
|
+
if (timeDifference !== 0)
|
|
1486
|
+
return timeDifference;
|
|
1487
|
+
const kindDifference = left.kind.localeCompare(right.kind);
|
|
1488
|
+
return kindDifference !== 0 ? kindDifference : left.id.localeCompare(right.id);
|
|
1489
|
+
});
|
|
1490
|
+
const pageItems = items.slice(0, limit);
|
|
1491
|
+
return {
|
|
1492
|
+
items: pageItems,
|
|
1493
|
+
nextCursor: items.length > limit && pageItems.length > 0
|
|
1494
|
+
? encodeGoalHistoryCursor(pageItems[pageItems.length - 1])
|
|
1495
|
+
: null,
|
|
1496
|
+
};
|
|
1497
|
+
}
|
|
1498
|
+
async function workspaceFor(id) {
|
|
1499
|
+
const goal = await requireGoal(db, id);
|
|
1500
|
+
const orgUrlKey = await organizationUrlKey(db, goal.orgId);
|
|
1501
|
+
const [historyPage, changes, results, evidenceActivity, pendingWakeup] = await Promise.all([
|
|
1502
|
+
historyFor(id),
|
|
1503
|
+
db.select().from(goalChangeProposals).where(and(eq(goalChangeProposals.goalId, id), eq(goalChangeProposals.status, "pending"))).orderBy(asc(goalChangeProposals.createdAt)),
|
|
1504
|
+
db.select().from(goalResultProposals).where(and(eq(goalResultProposals.goalId, id), inArray(goalResultProposals.status, ["ready", "accepted"]))).orderBy(desc(goalResultProposals.createdAt)),
|
|
1505
|
+
db.select().from(goalActivities).where(and(eq(goalActivities.goalId, id), inArray(goalActivities.activityKind, [...CURRENT_PROGRESS_ACTIVITY_KINDS]), or(isNotNull(goalActivities.runRef), sql `${goalActivities.idempotencyKey} like 'goal-result-evidence:%'`), sql `jsonb_array_length(${goalActivities.evidenceRefs}) > 0`)).orderBy(desc(goalActivities.occurredAt), desc(goalActivities.createdAt)).limit(1)
|
|
1506
|
+
.then((rows) => rows[0] ?? null),
|
|
1507
|
+
pendingGoalWakeup(goal),
|
|
1508
|
+
]);
|
|
1509
|
+
const externalWorkFact = await latestExternalWorkFact(goal, typeof evidenceActivity?.runRef === "string" ? evidenceActivity.runRef : null);
|
|
1510
|
+
const acceptedResult = results.find((proposal) => proposal.status === "accepted") ?? null;
|
|
1511
|
+
const progressSource = evidenceActivity
|
|
1512
|
+
? {
|
|
1513
|
+
summary: acceptedResult
|
|
1514
|
+
? publicGoalOutcome(acceptedResult.preflight.outcome)
|
|
1515
|
+
: publicGoalActivitySummary(evidenceActivity),
|
|
1516
|
+
sourceActivityId: evidenceActivity.id,
|
|
1517
|
+
evidence: publicEvidenceItems(evidenceActivity.evidenceRefs, {
|
|
1518
|
+
orgUrlKey,
|
|
1519
|
+
runAgentId: evidenceActivity.submittedByAgentId,
|
|
1520
|
+
}),
|
|
1521
|
+
}
|
|
1522
|
+
: null;
|
|
1523
|
+
const pendingChange = changes[0] ?? null;
|
|
1524
|
+
const readyResult = results.find((proposal) => proposal.status === "ready") ?? null;
|
|
1525
|
+
const runAttentionReason = goalRunAttentionReason(externalWorkFact?.runStatus);
|
|
1526
|
+
const facet = facetFor(goal, pendingChange, readyResult, pendingWakeup, externalWorkFact);
|
|
1527
|
+
const wakeupAttentionReason = goalWakeupAttentionReason(pendingWakeup);
|
|
1528
|
+
const attention = goal.lifecycle === "closed"
|
|
1529
|
+
? null
|
|
1530
|
+
: readyResult
|
|
1531
|
+
? {
|
|
1532
|
+
kind: "result_proposal",
|
|
1533
|
+
reason: "Review the proposed Goal result and decide whether it is sufficient.",
|
|
1534
|
+
sourceId: readyResult.id,
|
|
1535
|
+
}
|
|
1536
|
+
: pendingChange
|
|
1537
|
+
? {
|
|
1538
|
+
kind: "change_proposal",
|
|
1539
|
+
reason: publicGoalText(pendingChange.rationale),
|
|
1540
|
+
sourceId: pendingChange.id,
|
|
1541
|
+
}
|
|
1542
|
+
: goal.lifecycle === "draft" && goal.alignmentQuestion
|
|
1543
|
+
? { kind: "alignment_question", reason: publicGoalText(goal.alignmentQuestion), sourceId: goal.id }
|
|
1544
|
+
: wakeupAttentionReason
|
|
1545
|
+
? { kind: "owner_blocked", reason: wakeupAttentionReason, sourceId: pendingWakeup?.status ?? null }
|
|
1546
|
+
: runAttentionReason
|
|
1547
|
+
? { kind: "owner_blocked", reason: runAttentionReason, sourceId: externalWorkFact?.sourceRunId ?? externalWorkFact?.sourceId ?? null }
|
|
1548
|
+
: null;
|
|
1549
|
+
const publicGoal = publicGoalView(goal);
|
|
741
1550
|
return {
|
|
742
|
-
goal:
|
|
1551
|
+
goal: publicGoal,
|
|
743
1552
|
facet,
|
|
744
|
-
currentGoal: { summary:
|
|
1553
|
+
currentGoal: { summary: publicGoal.outcomeStatement ?? goal.title, updatedFromEvidence: Boolean(progressSource) },
|
|
745
1554
|
currentProgress: progressSource
|
|
746
1555
|
? {
|
|
747
1556
|
summary: progressSource.summary,
|
|
748
1557
|
sourceActivityId: progressSource.sourceActivityId,
|
|
749
|
-
|
|
1558
|
+
evidence: progressSource.evidence,
|
|
750
1559
|
}
|
|
751
1560
|
: {
|
|
752
1561
|
summary: "No evidence-backed progress has been recorded yet.",
|
|
753
1562
|
sourceActivityId: null,
|
|
754
|
-
|
|
1563
|
+
evidence: [],
|
|
755
1564
|
},
|
|
756
|
-
agentAction:
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
1565
|
+
agentAction: readyResult
|
|
1566
|
+
? null
|
|
1567
|
+
: externalWorkFact
|
|
1568
|
+
? {
|
|
1569
|
+
summary: externalWorkFact.summary,
|
|
1570
|
+
sourceIds: [externalWorkFact.sourceId],
|
|
1571
|
+
...(externalWorkFact.runStatus ? { status: externalWorkFact.runStatus } : {}),
|
|
1572
|
+
}
|
|
1573
|
+
: null,
|
|
1574
|
+
nextStep: readyResult
|
|
1575
|
+
? { summary: "Review the proposed result above.", wakeCondition: null }
|
|
1576
|
+
: pendingChange
|
|
1577
|
+
? { summary: "Review the proposed Goal update above.", wakeCondition: null }
|
|
1578
|
+
: goal.continuationKind && goal.continuationSummary
|
|
1579
|
+
? {
|
|
1580
|
+
summary: publicGoalText(goal.continuationSummary),
|
|
1581
|
+
wakeCondition: goal.wakeCondition ? publicGoalText(goal.wakeCondition) : null,
|
|
1582
|
+
}
|
|
1583
|
+
: null,
|
|
764
1584
|
attention,
|
|
765
|
-
timeline,
|
|
766
|
-
|
|
767
|
-
|
|
1585
|
+
timeline: historyPage.items,
|
|
1586
|
+
timelineNextCursor: historyPage.nextCursor,
|
|
1587
|
+
changeProposals: changes.map((proposal) => publicGoalChangeProposal(proposal, { orgUrlKey })),
|
|
1588
|
+
resultProposals: results.map((proposal) => publicGoalResultProposal(proposal, { orgUrlKey })),
|
|
768
1589
|
};
|
|
769
1590
|
}
|
|
770
1591
|
return {
|
|
@@ -775,6 +1596,7 @@ export function goalService(db) {
|
|
|
775
1596
|
getResultProposalById: (id) => db.select().from(goalResultProposals)
|
|
776
1597
|
.where(eq(goalResultProposals.id, id)).then((rows) => rows[0] ?? null),
|
|
777
1598
|
dependencies: (goal) => getGoalDependencies(db, goal),
|
|
1599
|
+
history: historyFor,
|
|
778
1600
|
previewStart: async (orgId, input) => {
|
|
779
1601
|
if (!hasConcreteGoalOutcome(input))
|
|
780
1602
|
return compileGoalStartPreview(input, null);
|
|
@@ -793,7 +1615,7 @@ export function goalService(db) {
|
|
|
793
1615
|
: null;
|
|
794
1616
|
return compileGoalStartPreview(input, owner);
|
|
795
1617
|
},
|
|
796
|
-
start: async (orgId, input) => {
|
|
1618
|
+
start: async (orgId, input, actor = { actorType: "system", actorId: null }) => {
|
|
797
1619
|
const computedHash = stableGoalHash(input.packet);
|
|
798
1620
|
if (computedHash !== input.packetHash) {
|
|
799
1621
|
throw conflict("Goal Start packet hash does not match the submitted packet");
|
|
@@ -812,7 +1634,12 @@ export function goalService(db) {
|
|
|
812
1634
|
const goal = await db.select().from(goals).where(and(eq(goals.id, replay.goalId), eq(goals.orgId, orgId))).then((rows) => rows[0] ?? null);
|
|
813
1635
|
if (!goal)
|
|
814
1636
|
throw conflict("Completed Goal Start request has no Goal");
|
|
815
|
-
|
|
1637
|
+
const dispatch = await ensureGoalWakeupIntent(db, goal, {
|
|
1638
|
+
event: "goal_started",
|
|
1639
|
+
eventId: replay.id,
|
|
1640
|
+
actor,
|
|
1641
|
+
});
|
|
1642
|
+
return { goal, replayed: true, dispatch };
|
|
816
1643
|
}
|
|
817
1644
|
return db.transaction(async (tx) => {
|
|
818
1645
|
const database = tx;
|
|
@@ -822,8 +1649,8 @@ export function goalService(db) {
|
|
|
822
1649
|
context: input.packet.description,
|
|
823
1650
|
ownerAgentId: input.packet.ownerAgentId,
|
|
824
1651
|
targetTime: input.packet.activation.evaluationDeadline ?? null,
|
|
825
|
-
})) {
|
|
826
|
-
throw unprocessable("Goal owner capabilities no longer match the Goal");
|
|
1652
|
+
}) && !input.allowCapabilityMismatch) {
|
|
1653
|
+
throw unprocessable("Goal owner capabilities no longer match the Goal; confirm the warning before starting");
|
|
827
1654
|
}
|
|
828
1655
|
const [insertedRequest] = await database.insert(goalStartRequests).values({
|
|
829
1656
|
orgId,
|
|
@@ -844,7 +1671,12 @@ export function goalService(db) {
|
|
|
844
1671
|
const existingGoal = await database.select().from(goals).where(and(eq(goals.id, existing.goalId), eq(goals.orgId, orgId))).then((rows) => rows[0] ?? null);
|
|
845
1672
|
if (!existingGoal)
|
|
846
1673
|
throw conflict("Completed Goal Start request has no Goal");
|
|
847
|
-
|
|
1674
|
+
const dispatch = await ensureGoalWakeupIntent(database, existingGoal, {
|
|
1675
|
+
event: "goal_started",
|
|
1676
|
+
eventId: existing.id,
|
|
1677
|
+
actor,
|
|
1678
|
+
});
|
|
1679
|
+
return { goal: existingGoal, replayed: true, dispatch };
|
|
848
1680
|
}
|
|
849
1681
|
let draft;
|
|
850
1682
|
if (input.draftGoalId) {
|
|
@@ -920,7 +1752,7 @@ export function goalService(db) {
|
|
|
920
1752
|
submittedByAgentId: null,
|
|
921
1753
|
agentOwnerRefAtTime: input.packet.ownerAgentId,
|
|
922
1754
|
activityKind: "progress",
|
|
923
|
-
summary: `
|
|
1755
|
+
summary: publicGoalText(`The Agent is starting with: ${activation.initialContinuation.summary}`),
|
|
924
1756
|
evidenceRefs: [],
|
|
925
1757
|
idempotencyKey: `goal-start:${insertedRequest.id}`,
|
|
926
1758
|
});
|
|
@@ -933,7 +1765,12 @@ export function goalService(db) {
|
|
|
933
1765
|
}).where(and(eq(goalStartRequests.id, insertedRequest.id), eq(goalStartRequests.status, "pending"))).returning().then((rows) => rows[0] ?? null);
|
|
934
1766
|
if (!completedRequest)
|
|
935
1767
|
throw conflict("Goal Start request could not be completed");
|
|
936
|
-
|
|
1768
|
+
const dispatch = await ensureGoalWakeupIntent(database, goal, {
|
|
1769
|
+
event: "goal_started",
|
|
1770
|
+
eventId: completedRequest.id,
|
|
1771
|
+
actor,
|
|
1772
|
+
});
|
|
1773
|
+
return { goal, replayed: false, dispatch };
|
|
937
1774
|
});
|
|
938
1775
|
},
|
|
939
1776
|
workspace: workspaceFor,
|
|
@@ -941,40 +1778,45 @@ export function goalService(db) {
|
|
|
941
1778
|
const organizationGoals = await db.select().from(goals).where(eq(goals.orgId, orgId))
|
|
942
1779
|
.orderBy(asc(goals.createdAt));
|
|
943
1780
|
return Promise.all(organizationGoals.map(async (goal) => {
|
|
944
|
-
const [progress,
|
|
945
|
-
db.select().from(goalActivities).where(and(eq(goalActivities.goalId, goal.id), sql `jsonb_array_length(${goalActivities.evidenceRefs}) > 0`)).orderBy(desc(goalActivities.occurredAt), desc(goalActivities.createdAt)).limit(1)
|
|
1781
|
+
const [progress, pendingChange, readyResult, owner, pendingWakeup, externalWorkFact] = await Promise.all([
|
|
1782
|
+
db.select().from(goalActivities).where(and(eq(goalActivities.goalId, goal.id), inArray(goalActivities.activityKind, [...CURRENT_PROGRESS_ACTIVITY_KINDS]), or(isNotNull(goalActivities.runRef), sql `${goalActivities.idempotencyKey} like 'goal-result-evidence:%'`), sql `jsonb_array_length(${goalActivities.evidenceRefs}) > 0`)).orderBy(desc(goalActivities.occurredAt), desc(goalActivities.createdAt)).limit(1)
|
|
946
1783
|
.then((rows) => rows[0] ?? null),
|
|
947
|
-
latestExternalCheckpoint(goal),
|
|
948
1784
|
db.select().from(goalChangeProposals).where(and(eq(goalChangeProposals.goalId, goal.id), eq(goalChangeProposals.status, "pending"))).orderBy(asc(goalChangeProposals.createdAt)).limit(1).then((rows) => rows[0] ?? null),
|
|
949
1785
|
db.select().from(goalResultProposals).where(and(eq(goalResultProposals.goalId, goal.id), eq(goalResultProposals.status, "ready"))).orderBy(asc(goalResultProposals.createdAt)).limit(1).then((rows) => rows[0] ?? null),
|
|
950
1786
|
goal.ownerAgentId
|
|
951
1787
|
? db.select({ name: agents.name }).from(agents).where(and(eq(agents.id, goal.ownerAgentId), eq(agents.orgId, orgId))).then((rows) => rows[0] ?? null)
|
|
952
1788
|
: Promise.resolve(null),
|
|
1789
|
+
pendingGoalWakeup(goal),
|
|
1790
|
+
latestExternalWorkFact(goal),
|
|
953
1791
|
]);
|
|
954
|
-
const currentProgress =
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
1792
|
+
const currentProgress = progress
|
|
1793
|
+
? publicGoalActivitySummary(progress)
|
|
1794
|
+
: "No evidence-backed progress has been recorded yet.";
|
|
1795
|
+
const attentionReason = goal.lifecycle === "closed"
|
|
1796
|
+
? null
|
|
1797
|
+
: readyResult
|
|
1798
|
+
? `Review the proposed Goal result: ${publicGoalOutcome(readyResult.preflight.outcome)}`
|
|
1799
|
+
: pendingChange?.rationale
|
|
1800
|
+
?? (goal.lifecycle === "draft" ? goal.alignmentQuestion : null)
|
|
1801
|
+
?? goalWakeupAttentionReason(pendingWakeup)
|
|
1802
|
+
?? goalRunAttentionReason(externalWorkFact?.runStatus);
|
|
963
1803
|
return {
|
|
964
1804
|
id: goal.id,
|
|
965
1805
|
orgId: goal.orgId,
|
|
966
1806
|
title: goal.title,
|
|
967
1807
|
lifecycle: goal.lifecycle,
|
|
968
1808
|
status: goal.status,
|
|
969
|
-
facet: facetFor(goal, pendingChange, readyResult),
|
|
1809
|
+
facet: facetFor(goal, pendingChange, readyResult, pendingWakeup, externalWorkFact),
|
|
970
1810
|
ownerAgentId: goal.ownerAgentId,
|
|
971
1811
|
ownerName: owner?.name ?? null,
|
|
972
1812
|
currentProgress,
|
|
973
1813
|
progressSummary: currentProgress,
|
|
974
|
-
nextAction: goal.continuationSummary,
|
|
975
|
-
nextStepSummary: goal.continuationSummary
|
|
1814
|
+
nextAction: goal.continuationSummary ? publicGoalText(goal.continuationSummary) : null,
|
|
1815
|
+
nextStepSummary: goal.continuationSummary
|
|
1816
|
+
? publicGoalText(goal.continuationSummary)
|
|
1817
|
+
: "No next step has been recorded.",
|
|
976
1818
|
targetTime: goal.evaluationDeadline ?? goal.actionDeadline,
|
|
977
|
-
attentionReason,
|
|
1819
|
+
attentionReason: attentionReason ? publicGoalText(attentionReason) : null,
|
|
978
1820
|
focus: goal.focus,
|
|
979
1821
|
updatedAt: goal.updatedAt,
|
|
980
1822
|
};
|
|
@@ -992,23 +1834,60 @@ export function goalService(db) {
|
|
|
992
1834
|
]);
|
|
993
1835
|
return { ...goal, ownerAssignment, plan, activities };
|
|
994
1836
|
},
|
|
995
|
-
create: async (orgId, data) =>
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1837
|
+
create: async (orgId, data) => {
|
|
1838
|
+
if (data.ownerAgentId)
|
|
1839
|
+
await requireInvokableOwner(db, orgId, data.ownerAgentId);
|
|
1840
|
+
return db.insert(goals).values({
|
|
1841
|
+
orgId,
|
|
1842
|
+
title: data.title,
|
|
1843
|
+
description: data.description ?? null,
|
|
1844
|
+
alignmentQuestion: data.alignmentQuestion ?? null,
|
|
1845
|
+
evaluationDeadline: data.targetTime ?? null,
|
|
1846
|
+
level: "task",
|
|
1847
|
+
status: "planned",
|
|
1848
|
+
lifecycle: "draft",
|
|
1849
|
+
parentId: null,
|
|
1850
|
+
ownerAgentId: data.ownerAgentId ?? null,
|
|
1851
|
+
ownerAgentRuntimeOverrides: data.ownerAgentRuntimeOverrides ?? null,
|
|
1852
|
+
}).returning().then((rows) => rows[0]);
|
|
1853
|
+
},
|
|
1006
1854
|
update: async (id, data, actorAgentId = null) => {
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1855
|
+
return db.transaction(async (tx) => {
|
|
1856
|
+
const database = tx;
|
|
1857
|
+
const current = await requireGoalForUpdate(database, id);
|
|
1858
|
+
assertGoalOwner(current, actorAgentId);
|
|
1859
|
+
if (current.lifecycle === "closed") {
|
|
1860
|
+
throw conflict("Closed Goals are read-only");
|
|
1861
|
+
}
|
|
1862
|
+
if (current.lifecycle !== "draft" && actorAgentId) {
|
|
1863
|
+
throw forbidden("Active Goal direction changes require a human-approved Goal change proposal");
|
|
1864
|
+
}
|
|
1865
|
+
if ((data.ownerAgentId !== undefined || data.targetTime !== undefined) && current.lifecycle !== "draft") {
|
|
1866
|
+
throw conflict("Owner and target time can only be edited while a Goal is a Draft");
|
|
1867
|
+
}
|
|
1868
|
+
if (data.ownerAgentId)
|
|
1869
|
+
await requireInvokableOwner(database, current.orgId, data.ownerAgentId);
|
|
1870
|
+
const { title, description, alignmentQuestion, ownerAgentId, ownerAgentRuntimeOverrides, targetTime } = data;
|
|
1871
|
+
const ownerChanged = ownerAgentId !== undefined && ownerAgentId !== current.ownerAgentId;
|
|
1872
|
+
const changedGoal = await database.update(goals).set({
|
|
1873
|
+
title,
|
|
1874
|
+
description,
|
|
1875
|
+
alignmentQuestion,
|
|
1876
|
+
...(ownerAgentId !== undefined ? { ownerAgentId } : {}),
|
|
1877
|
+
...(ownerAgentRuntimeOverrides !== undefined
|
|
1878
|
+
? { ownerAgentRuntimeOverrides }
|
|
1879
|
+
: ownerChanged
|
|
1880
|
+
? { ownerAgentRuntimeOverrides: null }
|
|
1881
|
+
: {}),
|
|
1882
|
+
...(targetTime !== undefined ? { evaluationDeadline: targetTime } : {}),
|
|
1883
|
+
updatedAt: new Date(),
|
|
1884
|
+
})
|
|
1885
|
+
.where(and(eq(goals.id, id), eq(goals.lifecycle, current.lifecycle)))
|
|
1886
|
+
.returning().then((rows) => rows[0] ?? null);
|
|
1887
|
+
if (!changedGoal)
|
|
1888
|
+
throw conflict("Goal changed before update; reload and retry");
|
|
1889
|
+
return changedGoal;
|
|
1890
|
+
});
|
|
1012
1891
|
},
|
|
1013
1892
|
activate: async (id, input, actorAgentId = null) => {
|
|
1014
1893
|
const current = await requireGoal(db, id);
|
|
@@ -1062,7 +1941,7 @@ export function goalService(db) {
|
|
|
1062
1941
|
submittedByAgentId: actorAgentId,
|
|
1063
1942
|
agentOwnerRefAtTime: input.ownerAgentId,
|
|
1064
1943
|
activityKind: "progress",
|
|
1065
|
-
summary: `
|
|
1944
|
+
summary: publicGoalText(`The Agent is starting with: ${input.initialContinuation.summary}`),
|
|
1066
1945
|
evidenceRefs: [],
|
|
1067
1946
|
});
|
|
1068
1947
|
return goal;
|
|
@@ -1070,72 +1949,80 @@ export function goalService(db) {
|
|
|
1070
1949
|
return result;
|
|
1071
1950
|
},
|
|
1072
1951
|
updatePlan: async (id, input, actorAgentId = null) => {
|
|
1073
|
-
const current = await requireGoal(db, id);
|
|
1074
|
-
assertCanonicalActiveGoal(current);
|
|
1075
|
-
assertGoalOwner(current, actorAgentId);
|
|
1076
|
-
const revision = current.planRevision + 1;
|
|
1077
1952
|
return db.transaction(async (tx) => {
|
|
1078
|
-
const
|
|
1953
|
+
const database = tx;
|
|
1954
|
+
const current = await requireGoalForUpdate(database, id);
|
|
1955
|
+
assertCanonicalActiveGoal(current);
|
|
1956
|
+
assertGoalOwner(current, actorAgentId);
|
|
1957
|
+
const revision = current.planRevision + 1;
|
|
1958
|
+
const [plan] = await database.insert(goalPlans).values({
|
|
1079
1959
|
orgId: current.orgId,
|
|
1080
1960
|
goalId: id,
|
|
1081
1961
|
revision,
|
|
1082
1962
|
...input,
|
|
1083
1963
|
createdByAgentId: actorAgentId,
|
|
1084
1964
|
}).returning();
|
|
1085
|
-
await
|
|
1965
|
+
const changedGoal = await database.update(goals).set({ planRevision: revision, updatedAt: new Date() })
|
|
1966
|
+
.where(and(eq(goals.id, id), eq(goals.lifecycle, "active"), eq(goals.planRevision, current.planRevision)))
|
|
1967
|
+
.returning().then((rows) => rows[0] ?? null);
|
|
1968
|
+
if (!changedGoal)
|
|
1969
|
+
throw conflict("Goal changed before Plan update; reload and retry");
|
|
1086
1970
|
return plan;
|
|
1087
1971
|
});
|
|
1088
1972
|
},
|
|
1089
1973
|
listActivities: (id) => db.select().from(goalActivities).where(eq(goalActivities.goalId, id))
|
|
1090
1974
|
.orderBy(desc(goalActivities.occurredAt), desc(goalActivities.createdAt)).limit(100),
|
|
1091
1975
|
createActivity: async (id, input, actorAgentId = null) => {
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
const run = await db.select({ id: heartbeatRuns.id, orgId: heartbeatRuns.orgId, status: heartbeatRuns.status })
|
|
1100
|
-
.from(heartbeatRuns).where(eq(heartbeatRuns.id, input.runRef)).then((rows) => rows[0] ?? null);
|
|
1101
|
-
if (!run || run.orgId !== current.orgId)
|
|
1102
|
-
throw unprocessable("Goal Activity run must belong to the same organization");
|
|
1103
|
-
if (input.activityKind === "closeout" && !TERMINAL_RUN_STATUSES.includes(run.status)) {
|
|
1104
|
-
throw conflict("A closeout Activity requires a terminal Run");
|
|
1976
|
+
return db.transaction(async (tx) => {
|
|
1977
|
+
const database = tx;
|
|
1978
|
+
const current = await requireGoalForUpdate(database, id);
|
|
1979
|
+
assertCanonicalActiveGoal(current);
|
|
1980
|
+
assertGoalOwner(current, actorAgentId);
|
|
1981
|
+
if (actorAgentId && !input.runRef) {
|
|
1982
|
+
throw unprocessable("Goal Activity must be attributed to the acting Agent's Run");
|
|
1105
1983
|
}
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1984
|
+
if (input.activityKind === "closeout" && !input.runRef) {
|
|
1985
|
+
throw unprocessable("A closeout Activity requires a Run reference");
|
|
1986
|
+
}
|
|
1987
|
+
if (input.runRef) {
|
|
1988
|
+
const run = await requireGoalRun(database, current, input.runRef, actorAgentId);
|
|
1989
|
+
if (input.activityKind === "closeout" && !TERMINAL_RUN_STATUSES.includes(run.status)) {
|
|
1990
|
+
throw conflict("A closeout Activity requires a terminal Run");
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
1993
|
+
const [activity] = await database.insert(goalActivities).values({
|
|
1994
|
+
orgId: current.orgId,
|
|
1995
|
+
goalId: id,
|
|
1996
|
+
contractRevision: current.contractRevision,
|
|
1997
|
+
submittedByAgentId: actorAgentId,
|
|
1998
|
+
agentOwnerRefAtTime: current.ownerAgentId,
|
|
1999
|
+
activityKind: input.activityKind ?? "progress",
|
|
2000
|
+
commitmentRef: input.commitmentRef ?? null,
|
|
2001
|
+
runRef: input.runRef ?? null,
|
|
2002
|
+
summary: input.summary,
|
|
2003
|
+
evidenceRefs: input.evidenceRefs,
|
|
2004
|
+
idempotencyKey: input.idempotencyKey ?? null,
|
|
2005
|
+
occurredAt: input.occurredAt ?? new Date(),
|
|
2006
|
+
}).onConflictDoNothing().returning();
|
|
2007
|
+
if (activity)
|
|
2008
|
+
return activity;
|
|
2009
|
+
if (input.idempotencyKey) {
|
|
2010
|
+
const existingIdempotent = await database.select().from(goalActivities).where(and(eq(goalActivities.goalId, id), eq(goalActivities.idempotencyKey, input.idempotencyKey))).then((rows) => rows[0] ?? null);
|
|
2011
|
+
if (existingIdempotent)
|
|
2012
|
+
return existingIdempotent;
|
|
2013
|
+
}
|
|
2014
|
+
if (input.activityKind === "closeout" && input.runRef) {
|
|
2015
|
+
const existingCloseout = await database.select().from(goalActivities).where(and(eq(goalActivities.goalId, id), eq(goalActivities.runRef, input.runRef), eq(goalActivities.activityKind, "closeout"))).then((rows) => rows[0] ?? null);
|
|
2016
|
+
if (existingCloseout)
|
|
2017
|
+
throw conflict("A closeout Activity already exists for this Run");
|
|
2018
|
+
}
|
|
2019
|
+
throw conflict("Goal Activity was already recorded");
|
|
2020
|
+
});
|
|
1134
2021
|
},
|
|
1135
2022
|
feedback: async (id, input, actorUserId) => {
|
|
1136
2023
|
return db.transaction(async (tx) => {
|
|
1137
2024
|
const database = tx;
|
|
1138
|
-
const current = await
|
|
2025
|
+
const current = await requireGoalForUpdate(database, id);
|
|
1139
2026
|
return recordFeedback(database, current, input, actorUserId);
|
|
1140
2027
|
});
|
|
1141
2028
|
},
|
|
@@ -1145,7 +2032,7 @@ export function goalService(db) {
|
|
|
1145
2032
|
const normalizedPatch = normalizeContractPatch(input.afterContract);
|
|
1146
2033
|
return db.transaction(async (tx) => {
|
|
1147
2034
|
const database = tx;
|
|
1148
|
-
const current = await
|
|
2035
|
+
const current = await requireGoalForUpdate(database, id);
|
|
1149
2036
|
assertCanonicalActiveGoal(current);
|
|
1150
2037
|
assertGoalOwner(current, actorAgentId);
|
|
1151
2038
|
const existing = await database.select().from(goalChangeProposals).where(and(eq(goalChangeProposals.goalId, id), eq(goalChangeProposals.idempotencyKey, input.idempotencyKey))).then((rows) => rows[0] ?? null);
|
|
@@ -1154,7 +2041,7 @@ export function goalService(db) {
|
|
|
1154
2041
|
return existing;
|
|
1155
2042
|
}
|
|
1156
2043
|
if (current.contractRevision !== input.expectedContractRevision) {
|
|
1157
|
-
throw conflict("Goal
|
|
2044
|
+
throw conflict("Goal changed before the proposed update could be created", {
|
|
1158
2045
|
expectedContractRevision: input.expectedContractRevision,
|
|
1159
2046
|
currentContractRevision: current.contractRevision,
|
|
1160
2047
|
});
|
|
@@ -1172,7 +2059,7 @@ export function goalService(db) {
|
|
|
1172
2059
|
evaluationPolicy: mergedContract.evaluationPolicy,
|
|
1173
2060
|
actionDeadline: mergedContract.actionDeadline,
|
|
1174
2061
|
evaluationDeadline: mergedContract.evaluationDeadline,
|
|
1175
|
-
initialPlan: { summary: "Validate the proposed
|
|
2062
|
+
initialPlan: { summary: "Validate the proposed Goal update" },
|
|
1176
2063
|
initialContinuation: {
|
|
1177
2064
|
kind: current.continuationKind,
|
|
1178
2065
|
summary: current.continuationSummary,
|
|
@@ -1187,18 +2074,24 @@ export function goalService(db) {
|
|
|
1187
2074
|
if (!linkedApproval
|
|
1188
2075
|
|| linkedApproval.orgId !== current.orgId
|
|
1189
2076
|
|| linkedApproval.type !== "goal_change"
|
|
1190
|
-
|| linkedApproval.status !== "pending"
|
|
2077
|
+
|| linkedApproval.status !== "pending"
|
|
2078
|
+
|| linkedApproval.requestedByAgentId !== actorAgentId) {
|
|
1191
2079
|
throw unprocessable("Goal change approval must be pending and belong to the same organization");
|
|
1192
2080
|
}
|
|
1193
|
-
await database.update(approvals).set({
|
|
2081
|
+
const boundApproval = await database.update(approvals).set({
|
|
1194
2082
|
payload: {
|
|
1195
|
-
...linkedApproval.payload,
|
|
1196
2083
|
goalId: id,
|
|
1197
2084
|
proposalId,
|
|
1198
2085
|
expectedContractRevision: current.contractRevision,
|
|
2086
|
+
beforeContract,
|
|
2087
|
+
afterContract: normalizedPatch,
|
|
2088
|
+
rationale: input.rationale,
|
|
2089
|
+
evidenceRefs: input.evidenceRefs,
|
|
1199
2090
|
},
|
|
1200
2091
|
updatedAt: new Date(),
|
|
1201
|
-
}).where(eq(approvals.id, approvalId));
|
|
2092
|
+
}).where(and(eq(approvals.id, approvalId), eq(approvals.status, "pending"))).returning().then((rows) => rows[0] ?? null);
|
|
2093
|
+
if (!boundApproval)
|
|
2094
|
+
throw conflict("Goal change approval changed before it could be linked");
|
|
1202
2095
|
}
|
|
1203
2096
|
else {
|
|
1204
2097
|
await database.insert(approvals).values({
|
|
@@ -1249,21 +2142,28 @@ export function goalService(db) {
|
|
|
1249
2142
|
.where(eq(goalChangeProposals.id, proposalId)).then((rows) => rows[0] ?? null);
|
|
1250
2143
|
if (!proposal)
|
|
1251
2144
|
throw notFound("Goal change proposal not found");
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
2145
|
+
const approval = await database.select().from(approvals).where(and(eq(approvals.id, proposal.approvalId), eq(approvals.orgId, proposal.orgId))).then((rows) => rows[0] ?? null);
|
|
2146
|
+
if (!approval || approval.type !== "goal_change") {
|
|
2147
|
+
throw unprocessable("Goal change proposal approval is invalid");
|
|
2148
|
+
}
|
|
2149
|
+
if ((input.decision === "approve" && proposal.status === "applied")
|
|
2150
|
+
|| (input.decision === "reject" && proposal.status === "rejected")) {
|
|
2151
|
+
const current = await requireGoalForUpdate(database, proposal.goalId);
|
|
2152
|
+
const dispatch = await ensureGoalWakeupIntent(database, current, {
|
|
2153
|
+
event: "goal_change_decided",
|
|
2154
|
+
eventId: proposal.id,
|
|
2155
|
+
actor: { actorType: "user", actorId: actorUserId },
|
|
2156
|
+
decision: { decision: input.decision, note: approval.decisionNote, status: proposal.status },
|
|
2157
|
+
});
|
|
2158
|
+
return { proposal, stale: false, dispatch };
|
|
2159
|
+
}
|
|
1256
2160
|
if (proposal.status !== "pending") {
|
|
1257
2161
|
throw conflict(`Goal change proposal is already ${proposal.status}`);
|
|
1258
2162
|
}
|
|
1259
|
-
const current = await
|
|
2163
|
+
const current = await requireGoalForUpdate(database, proposal.goalId);
|
|
1260
2164
|
assertCanonicalActiveGoal(current);
|
|
1261
2165
|
if (current.orgId !== proposal.orgId)
|
|
1262
2166
|
throw unprocessable("Goal change proposal organization mismatch");
|
|
1263
|
-
const approval = await database.select().from(approvals).where(and(eq(approvals.id, proposal.approvalId), eq(approvals.orgId, proposal.orgId))).then((rows) => rows[0] ?? null);
|
|
1264
|
-
if (!approval || approval.type !== "goal_change") {
|
|
1265
|
-
throw unprocessable("Goal change proposal approval is invalid");
|
|
1266
|
-
}
|
|
1267
2167
|
if (input.decision === "reject") {
|
|
1268
2168
|
const now = new Date();
|
|
1269
2169
|
await database.update(approvals).set({
|
|
@@ -1290,13 +2190,21 @@ export function goalService(db) {
|
|
|
1290
2190
|
evidenceRefs: proposal.evidenceRefs,
|
|
1291
2191
|
idempotencyKey: `goal-change:${proposal.id}:rejected`,
|
|
1292
2192
|
}).onConflictDoNothing();
|
|
1293
|
-
|
|
2193
|
+
const dispatch = await ensureGoalWakeupIntent(database, current, {
|
|
2194
|
+
event: "goal_change_decided",
|
|
2195
|
+
eventId: proposal.id,
|
|
2196
|
+
actor: { actorType: "user", actorId: actorUserId },
|
|
2197
|
+
decision: { decision: "reject", note: input.note ?? null, status: rejected.status },
|
|
2198
|
+
});
|
|
2199
|
+
return { proposal: rejected, stale: false, dispatch };
|
|
1294
2200
|
}
|
|
1295
2201
|
if (current.contractRevision !== proposal.expectedContractRevision) {
|
|
1296
2202
|
const now = new Date();
|
|
1297
2203
|
await database.update(approvals).set({
|
|
1298
|
-
status: "
|
|
1299
|
-
decisionNote: input.note
|
|
2204
|
+
status: "cancelled",
|
|
2205
|
+
decisionNote: input.note
|
|
2206
|
+
? `${input.note}\n\nNot applied because the Goal changed before this decision.`
|
|
2207
|
+
: "Not applied because the Goal changed before this decision.",
|
|
1300
2208
|
decidedByUserId: actorUserId,
|
|
1301
2209
|
decidedAt: now,
|
|
1302
2210
|
updatedAt: now,
|
|
@@ -1319,7 +2227,7 @@ export function goalService(db) {
|
|
|
1319
2227
|
evaluationPolicy: merged.evaluationPolicy,
|
|
1320
2228
|
actionDeadline: merged.actionDeadline,
|
|
1321
2229
|
evaluationDeadline: merged.evaluationDeadline,
|
|
1322
|
-
initialPlan: { summary: "Apply the approved
|
|
2230
|
+
initialPlan: { summary: "Apply the approved Goal update" },
|
|
1323
2231
|
initialContinuation: {
|
|
1324
2232
|
kind: current.continuationKind,
|
|
1325
2233
|
summary: current.continuationSummary,
|
|
@@ -1351,7 +2259,7 @@ export function goalService(db) {
|
|
|
1351
2259
|
updatedAt: now,
|
|
1352
2260
|
}).where(and(eq(goals.id, current.id), eq(goals.orgId, proposal.orgId), eq(goals.lifecycle, "active"), eq(goals.contractRevision, proposal.expectedContractRevision))).returning().then((rows) => rows[0] ?? null);
|
|
1353
2261
|
if (!changedGoal)
|
|
1354
|
-
throw conflict("Goal
|
|
2262
|
+
throw conflict("Goal changed before the approved update could be applied");
|
|
1355
2263
|
await database.insert(goalActivities).values({
|
|
1356
2264
|
orgId: current.orgId,
|
|
1357
2265
|
goalId: current.id,
|
|
@@ -1359,7 +2267,7 @@ export function goalService(db) {
|
|
|
1359
2267
|
submittedByAgentId: proposal.proposedByAgentId,
|
|
1360
2268
|
agentOwnerRefAtTime: current.ownerAgentId,
|
|
1361
2269
|
activityKind: "decision_requested",
|
|
1362
|
-
summary: `
|
|
2270
|
+
summary: `Goal updated after approval: ${proposal.rationale}`,
|
|
1363
2271
|
evidenceRefs: proposal.evidenceRefs,
|
|
1364
2272
|
idempotencyKey: `goal-change:${proposal.id}:applied`,
|
|
1365
2273
|
}).onConflictDoNothing();
|
|
@@ -1371,39 +2279,56 @@ export function goalService(db) {
|
|
|
1371
2279
|
}).where(and(eq(goalChangeProposals.id, proposal.id), eq(goalChangeProposals.status, "pending"))).returning().then((rows) => rows[0] ?? null);
|
|
1372
2280
|
if (!applied)
|
|
1373
2281
|
throw conflict("Goal change proposal changed before application");
|
|
1374
|
-
|
|
2282
|
+
const dispatch = await ensureGoalWakeupIntent(database, changedGoal, {
|
|
2283
|
+
event: "goal_change_decided",
|
|
2284
|
+
eventId: proposal.id,
|
|
2285
|
+
actor: { actorType: "user", actorId: actorUserId },
|
|
2286
|
+
decision: { decision: "approve", note: input.note ?? null, status: applied.status },
|
|
2287
|
+
});
|
|
2288
|
+
return { proposal: applied, stale: false, dispatch };
|
|
1375
2289
|
});
|
|
1376
2290
|
if (result.stale) {
|
|
1377
|
-
throw conflict("Goal
|
|
2291
|
+
throw conflict("Goal changed before the approved update could be applied", {
|
|
1378
2292
|
expectedContractRevision: result.proposal.expectedContractRevision,
|
|
1379
2293
|
currentContractRevision: result.currentRevision,
|
|
1380
2294
|
});
|
|
1381
2295
|
}
|
|
1382
|
-
|
|
2296
|
+
if (!result.dispatch)
|
|
2297
|
+
throw conflict("Goal change decision could not queue the Owner continuation");
|
|
2298
|
+
return { proposal: result.proposal, dispatch: result.dispatch };
|
|
1383
2299
|
},
|
|
1384
|
-
createResultProposal: async (id, input, actorAgentId) => {
|
|
2300
|
+
createResultProposal: async (id, input, actorAgentId, actorRunId = null) => {
|
|
1385
2301
|
if (!actorAgentId)
|
|
1386
2302
|
throw forbidden("Only the Goal Owner Agent can submit a Result Proposal");
|
|
1387
2303
|
const candidate = evaluationCandidate(input);
|
|
1388
2304
|
const candidateHash = stableGoalHash(candidate);
|
|
1389
2305
|
return db.transaction(async (tx) => {
|
|
1390
2306
|
const database = tx;
|
|
1391
|
-
const current = await
|
|
2307
|
+
const current = await requireGoalForUpdate(database, id);
|
|
2308
|
+
assertGoalOwner(current, actorAgentId);
|
|
2309
|
+
if (actorRunId)
|
|
2310
|
+
await requireGoalRun(database, current, actorRunId, actorAgentId);
|
|
1392
2311
|
const existing = await database.select().from(goalResultProposals).where(and(eq(goalResultProposals.goalId, id), eq(goalResultProposals.idempotencyKey, input.idempotencyKey))).then((rows) => rows[0] ?? null);
|
|
1393
2312
|
if (existing) {
|
|
1394
2313
|
assertSameResultProposalPayload(existing, input, candidateHash);
|
|
1395
2314
|
return existing;
|
|
1396
2315
|
}
|
|
1397
2316
|
assertCanonicalActiveGoal(current);
|
|
1398
|
-
assertGoalOwner(current, actorAgentId);
|
|
1399
2317
|
if (input.contractRevision !== current.contractRevision) {
|
|
1400
|
-
throw conflict("Goal
|
|
2318
|
+
throw conflict("Goal changed before the result could be proposed", {
|
|
1401
2319
|
expectedContractRevision: input.contractRevision,
|
|
1402
2320
|
currentContractRevision: current.contractRevision,
|
|
1403
2321
|
});
|
|
1404
2322
|
}
|
|
1405
2323
|
const preflight = reduceGoalEvaluation(current, candidate);
|
|
1406
2324
|
const status = isTerminalEvaluation(preflight.outcome) ? "ready" : "inconclusive";
|
|
2325
|
+
if (status === "ready") {
|
|
2326
|
+
const readyProposal = await database.select({ id: goalResultProposals.id })
|
|
2327
|
+
.from(goalResultProposals).where(and(eq(goalResultProposals.goalId, id), eq(goalResultProposals.status, "ready"))).limit(1).then((rows) => rows[0] ?? null);
|
|
2328
|
+
if (readyProposal) {
|
|
2329
|
+
throw conflict("This Goal already has a Result Proposal ready for review");
|
|
2330
|
+
}
|
|
2331
|
+
}
|
|
1407
2332
|
const [proposal] = await database.insert(goalResultProposals).values({
|
|
1408
2333
|
orgId: current.orgId,
|
|
1409
2334
|
goalId: current.id,
|
|
@@ -1417,6 +2342,13 @@ export function goalService(db) {
|
|
|
1417
2342
|
proposedByAgentId: actorAgentId,
|
|
1418
2343
|
}).onConflictDoNothing().returning();
|
|
1419
2344
|
const persisted = proposal ?? await database.select().from(goalResultProposals).where(and(eq(goalResultProposals.goalId, id), eq(goalResultProposals.idempotencyKey, input.idempotencyKey))).then((rows) => rows[0] ?? null);
|
|
2345
|
+
if (!persisted && status === "ready") {
|
|
2346
|
+
const readyProposal = await database.select({ id: goalResultProposals.id })
|
|
2347
|
+
.from(goalResultProposals).where(and(eq(goalResultProposals.goalId, id), eq(goalResultProposals.status, "ready"))).limit(1).then((rows) => rows[0] ?? null);
|
|
2348
|
+
if (readyProposal) {
|
|
2349
|
+
throw conflict("This Goal already has a Result Proposal ready for review");
|
|
2350
|
+
}
|
|
2351
|
+
}
|
|
1420
2352
|
if (!persisted)
|
|
1421
2353
|
throw conflict("Goal Result Proposal could not be recorded");
|
|
1422
2354
|
assertSameResultProposalPayload(persisted, input, candidateHash);
|
|
@@ -1428,12 +2360,25 @@ export function goalService(db) {
|
|
|
1428
2360
|
submittedByAgentId: actorAgentId,
|
|
1429
2361
|
agentOwnerRefAtTime: current.ownerAgentId,
|
|
1430
2362
|
activityKind: "bottleneck",
|
|
2363
|
+
runRef: actorRunId,
|
|
1431
2364
|
summary: inconclusiveResultSummary(preflight, input.riskSummary),
|
|
1432
2365
|
evidenceRefs: candidate.evidenceRefs,
|
|
1433
2366
|
idempotencyKey: `goal-result-inconclusive:${input.idempotencyKey}`,
|
|
1434
2367
|
}).onConflictDoNothing();
|
|
1435
2368
|
return persisted;
|
|
1436
2369
|
}
|
|
2370
|
+
await database.insert(goalActivities).values({
|
|
2371
|
+
orgId: current.orgId,
|
|
2372
|
+
goalId: current.id,
|
|
2373
|
+
contractRevision: current.contractRevision,
|
|
2374
|
+
submittedByAgentId: actorAgentId,
|
|
2375
|
+
agentOwnerRefAtTime: current.ownerAgentId,
|
|
2376
|
+
activityKind: "evidence",
|
|
2377
|
+
runRef: actorRunId,
|
|
2378
|
+
summary: `${publicGoalOutcome(preflight.outcome)} is ready for review. ${input.riskSummary}`,
|
|
2379
|
+
evidenceRefs: candidate.evidenceRefs,
|
|
2380
|
+
idempotencyKey: `goal-result-evidence:${input.idempotencyKey}`,
|
|
2381
|
+
}).onConflictDoNothing();
|
|
1437
2382
|
return persisted;
|
|
1438
2383
|
});
|
|
1439
2384
|
},
|
|
@@ -1444,7 +2389,7 @@ export function goalService(db) {
|
|
|
1444
2389
|
.where(eq(goalResultProposals.id, proposalId)).then((rows) => rows[0] ?? null);
|
|
1445
2390
|
if (!proposal)
|
|
1446
2391
|
throw notFound("Goal Result Proposal not found");
|
|
1447
|
-
const current = await
|
|
2392
|
+
const current = await requireGoalForUpdate(database, proposal.goalId);
|
|
1448
2393
|
if (current.orgId !== proposal.orgId)
|
|
1449
2394
|
throw unprocessable("Goal Result Proposal organization mismatch");
|
|
1450
2395
|
if (proposal.consumedAt) {
|
|
@@ -1458,7 +2403,7 @@ export function goalService(db) {
|
|
|
1458
2403
|
}
|
|
1459
2404
|
assertCanonicalActiveGoal(current);
|
|
1460
2405
|
if (proposal.contractRevision !== current.contractRevision) {
|
|
1461
|
-
throw conflict("Goal
|
|
2406
|
+
throw conflict("Goal changed before the result could be accepted", {
|
|
1462
2407
|
expectedContractRevision: proposal.contractRevision,
|
|
1463
2408
|
currentContractRevision: current.contractRevision,
|
|
1464
2409
|
});
|
|
@@ -1484,10 +2429,27 @@ export function goalService(db) {
|
|
|
1484
2429
|
const latestProposal = await database.select().from(goalResultProposals)
|
|
1485
2430
|
.where(eq(goalResultProposals.id, proposal.id)).then((rows) => rows[0] ?? null);
|
|
1486
2431
|
if (latestProposal?.consumedAt && latestProposal.acceptanceIdempotencyKey === input.idempotencyKey) {
|
|
1487
|
-
return
|
|
2432
|
+
return requireGoalForUpdate(database, proposal.goalId);
|
|
1488
2433
|
}
|
|
1489
2434
|
throw conflict("Goal Result Proposal changed before acceptance");
|
|
1490
2435
|
}
|
|
2436
|
+
const pendingChanges = await database.select({
|
|
2437
|
+
id: goalChangeProposals.id,
|
|
2438
|
+
approvalId: goalChangeProposals.approvalId,
|
|
2439
|
+
}).from(goalChangeProposals).where(and(eq(goalChangeProposals.goalId, current.id), eq(goalChangeProposals.status, "pending")));
|
|
2440
|
+
if (pendingChanges.length > 0) {
|
|
2441
|
+
await database.update(goalChangeProposals).set({
|
|
2442
|
+
status: "superseded",
|
|
2443
|
+
updatedAt: now,
|
|
2444
|
+
}).where(and(eq(goalChangeProposals.goalId, current.id), eq(goalChangeProposals.status, "pending")));
|
|
2445
|
+
await database.update(approvals).set({
|
|
2446
|
+
status: "cancelled",
|
|
2447
|
+
decisionNote: "Closed because the Goal result was accepted before this proposed change was applied.",
|
|
2448
|
+
decidedByUserId: actorUserId,
|
|
2449
|
+
decidedAt: now,
|
|
2450
|
+
updatedAt: now,
|
|
2451
|
+
}).where(and(inArray(approvals.id, pendingChanges.map((change) => change.approvalId)), inArray(approvals.status, ["pending", "revision_requested"])));
|
|
2452
|
+
}
|
|
1491
2453
|
return evaluateInTransaction(database, current, {
|
|
1492
2454
|
...proposal.candidate,
|
|
1493
2455
|
resultProposalId: proposal.id,
|
|
@@ -1505,19 +2467,28 @@ export function goalService(db) {
|
|
|
1505
2467
|
if (proposal.rejectionFeedback !== input.feedback) {
|
|
1506
2468
|
throw conflict("Goal Result Proposal rejection was replayed with different feedback");
|
|
1507
2469
|
}
|
|
1508
|
-
|
|
2470
|
+
const current = await requireGoalForUpdate(database, proposal.goalId);
|
|
2471
|
+
if (current.lifecycle !== "active")
|
|
2472
|
+
return { proposal, dispatch: null };
|
|
2473
|
+
const { dispatch } = await recordFeedback(database, current, {
|
|
2474
|
+
body: input.feedback,
|
|
2475
|
+
attachments: [],
|
|
2476
|
+
feedbackKind: "ordinary",
|
|
2477
|
+
idempotencyKey: input.idempotencyKey,
|
|
2478
|
+
}, actorUserId);
|
|
2479
|
+
return { proposal, dispatch };
|
|
1509
2480
|
}
|
|
1510
2481
|
if (proposal.status !== "ready") {
|
|
1511
2482
|
throw conflict(`Goal Result Proposal is already ${proposal.status}`);
|
|
1512
2483
|
}
|
|
1513
|
-
const current = await
|
|
2484
|
+
const current = await requireGoalForUpdate(database, proposal.goalId);
|
|
1514
2485
|
assertCanonicalActiveGoal(current);
|
|
1515
2486
|
if (current.orgId !== proposal.orgId)
|
|
1516
2487
|
throw unprocessable("Goal Result Proposal organization mismatch");
|
|
1517
2488
|
if (current.contractRevision !== proposal.contractRevision) {
|
|
1518
|
-
throw conflict("Goal
|
|
2489
|
+
throw conflict("Goal changed before the result could be returned for more work");
|
|
1519
2490
|
}
|
|
1520
|
-
await recordFeedback(database, current, {
|
|
2491
|
+
const { dispatch } = await recordFeedback(database, current, {
|
|
1521
2492
|
body: input.feedback,
|
|
1522
2493
|
attachments: [],
|
|
1523
2494
|
feedbackKind: "ordinary",
|
|
@@ -1534,24 +2505,25 @@ export function goalService(db) {
|
|
|
1534
2505
|
}).where(and(eq(goalResultProposals.id, proposal.id), eq(goalResultProposals.status, "ready"))).returning().then((rows) => rows[0] ?? null);
|
|
1535
2506
|
if (!rejected)
|
|
1536
2507
|
throw conflict("Goal Result Proposal changed before rejection");
|
|
1537
|
-
return rejected;
|
|
2508
|
+
return { proposal: rejected, dispatch };
|
|
1538
2509
|
});
|
|
1539
2510
|
},
|
|
1540
2511
|
assignOwner: async (id, input, actorAgentId = null) => {
|
|
1541
|
-
const current = await requireGoal(db, id);
|
|
1542
|
-
assertCanonicalActiveGoal(current);
|
|
1543
|
-
assertGoalOwner(current, actorAgentId);
|
|
1544
|
-
await requireInvokableOwner(db, current.orgId, input.agentId);
|
|
1545
2512
|
return db.transaction(async (tx) => {
|
|
1546
|
-
const
|
|
2513
|
+
const database = tx;
|
|
2514
|
+
const current = await requireGoalForUpdate(database, id);
|
|
2515
|
+
assertCanonicalActiveGoal(current);
|
|
2516
|
+
assertGoalOwner(current, actorAgentId);
|
|
2517
|
+
await requireInvokableOwner(database, current.orgId, input.agentId);
|
|
2518
|
+
const previous = await database.select().from(goalOwnerAssignments)
|
|
1547
2519
|
.where(and(eq(goalOwnerAssignments.goalId, id), isNull(goalOwnerAssignments.endsAt)))
|
|
1548
2520
|
.then((rows) => rows[0] ?? null);
|
|
1549
2521
|
if (previous?.agentId === input.agentId)
|
|
1550
2522
|
return previous;
|
|
1551
2523
|
const now = new Date();
|
|
1552
2524
|
if (previous)
|
|
1553
|
-
await
|
|
1554
|
-
const [assignment] = await
|
|
2525
|
+
await database.update(goalOwnerAssignments).set({ endsAt: now }).where(eq(goalOwnerAssignments.id, previous.id));
|
|
2526
|
+
const [assignment] = await database.insert(goalOwnerAssignments).values({
|
|
1555
2527
|
orgId: current.orgId,
|
|
1556
2528
|
goalId: id,
|
|
1557
2529
|
agentId: input.agentId,
|
|
@@ -1559,26 +2531,36 @@ export function goalService(db) {
|
|
|
1559
2531
|
assignedByAuthorityRef: input.authorityRef ?? null,
|
|
1560
2532
|
startsAt: now,
|
|
1561
2533
|
}).returning();
|
|
1562
|
-
await
|
|
2534
|
+
const changedGoal = await database.update(goals).set({ ownerAgentId: input.agentId, ownerAgentRuntimeOverrides: null, updatedAt: now })
|
|
2535
|
+
.where(and(eq(goals.id, id), eq(goals.lifecycle, "active")))
|
|
2536
|
+
.returning().then((rows) => rows[0] ?? null);
|
|
2537
|
+
if (!changedGoal)
|
|
2538
|
+
throw conflict("Goal changed before Owner assignment; reload and retry");
|
|
1563
2539
|
return assignment;
|
|
1564
2540
|
});
|
|
1565
2541
|
},
|
|
1566
2542
|
setFocus: async (id, focus, actorAgentId = null) => {
|
|
1567
|
-
const current = await requireGoal(db, id);
|
|
1568
|
-
if (focus)
|
|
1569
|
-
assertCanonicalActiveGoal(current);
|
|
1570
|
-
assertGoalOwner(current, actorAgentId);
|
|
1571
2543
|
return db.transaction(async (tx) => {
|
|
2544
|
+
const database = tx;
|
|
2545
|
+
const candidate = await requireGoal(database, id);
|
|
2546
|
+
await database.execute(sql `select id from organizations where id = ${candidate.orgId} for update`);
|
|
2547
|
+
const current = await requireGoalForUpdate(database, id);
|
|
2548
|
+
if (current.orgId !== candidate.orgId)
|
|
2549
|
+
throw conflict("Goal organization changed before Focus update; reload and retry");
|
|
2550
|
+
assertCanonicalActiveGoal(current);
|
|
2551
|
+
assertGoalOwner(current, actorAgentId);
|
|
1572
2552
|
if (focus)
|
|
1573
|
-
await
|
|
1574
|
-
const [goal] = await
|
|
2553
|
+
await database.update(goals).set({ focus: false }).where(and(eq(goals.orgId, current.orgId), eq(goals.lifecycle, "active")));
|
|
2554
|
+
const [goal] = await database.update(goals).set({ focus, updatedAt: new Date() }).where(and(eq(goals.id, id), eq(goals.lifecycle, "active"))).returning();
|
|
2555
|
+
if (!goal)
|
|
2556
|
+
throw conflict("Goal changed before Focus update; reload and retry");
|
|
1575
2557
|
return goal;
|
|
1576
2558
|
});
|
|
1577
2559
|
},
|
|
1578
2560
|
evaluate: async (id, input, actorAgentId = null) => {
|
|
1579
2561
|
return db.transaction(async (tx) => {
|
|
1580
2562
|
const database = tx;
|
|
1581
|
-
const current = await
|
|
2563
|
+
const current = await requireGoalForUpdate(database, id);
|
|
1582
2564
|
return evaluateInTransaction(database, current, input, actorAgentId);
|
|
1583
2565
|
});
|
|
1584
2566
|
},
|