@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.
Files changed (512) hide show
  1. package/dist/agent-runtimes/registry.d.ts.map +1 -1
  2. package/dist/agent-runtimes/registry.js +0 -2
  3. package/dist/agent-runtimes/registry.js.map +1 -1
  4. package/dist/app.d.ts.map +1 -1
  5. package/dist/app.js +1 -5
  6. package/dist/app.js.map +1 -1
  7. package/dist/bootstrap/create-http-app.d.ts +1 -2
  8. package/dist/bootstrap/create-http-app.d.ts.map +1 -1
  9. package/dist/bootstrap/create-http-app.js +13 -12
  10. package/dist/bootstrap/create-http-app.js.map +1 -1
  11. package/dist/bootstrap/register-api-routes.d.ts +1 -2
  12. package/dist/bootstrap/register-api-routes.d.ts.map +1 -1
  13. package/dist/bootstrap/register-api-routes.js +22 -4
  14. package/dist/bootstrap/register-api-routes.js.map +1 -1
  15. package/dist/index.js +2 -0
  16. package/dist/index.js.map +1 -1
  17. package/dist/middleware/logger.js +2 -2
  18. package/dist/middleware/logger.js.map +1 -1
  19. package/dist/middleware/logger.test.js +26 -10
  20. package/dist/middleware/logger.test.js.map +1 -1
  21. package/dist/redaction.d.ts +4 -0
  22. package/dist/redaction.d.ts.map +1 -1
  23. package/dist/redaction.js +15 -0
  24. package/dist/redaction.js.map +1 -1
  25. package/dist/routes/agent-issue-creation.d.ts +25 -0
  26. package/dist/routes/agent-issue-creation.d.ts.map +1 -0
  27. package/dist/routes/agent-issue-creation.js +201 -0
  28. package/dist/routes/agent-issue-creation.js.map +1 -0
  29. package/dist/routes/app-builder.d.ts +3 -1
  30. package/dist/routes/app-builder.d.ts.map +1 -1
  31. package/dist/routes/app-builder.js +30 -22
  32. package/dist/routes/app-builder.js.map +1 -1
  33. package/dist/routes/approvals.d.ts.map +1 -1
  34. package/dist/routes/approvals.js +36 -9
  35. package/dist/routes/approvals.js.map +1 -1
  36. package/dist/routes/chats.d.ts.map +1 -1
  37. package/dist/routes/chats.js +23 -4
  38. package/dist/routes/chats.js.map +1 -1
  39. package/dist/routes/chats.preflight.d.ts +2 -1
  40. package/dist/routes/chats.preflight.d.ts.map +1 -1
  41. package/dist/routes/chats.preflight.js +7 -0
  42. package/dist/routes/chats.preflight.js.map +1 -1
  43. package/dist/routes/chats.stream-routes.d.ts.map +1 -1
  44. package/dist/routes/chats.stream-routes.js +14 -3
  45. package/dist/routes/chats.stream-routes.js.map +1 -1
  46. package/dist/routes/chats.stream-support.d.ts.map +1 -1
  47. package/dist/routes/chats.stream-support.js +8 -0
  48. package/dist/routes/chats.stream-support.js.map +1 -1
  49. package/dist/routes/computer.d.ts +30 -0
  50. package/dist/routes/computer.d.ts.map +1 -0
  51. package/dist/routes/computer.js +231 -0
  52. package/dist/routes/computer.js.map +1 -0
  53. package/dist/routes/goals.d.ts.map +1 -1
  54. package/dist/routes/goals.js +176 -27
  55. package/dist/routes/goals.js.map +1 -1
  56. package/dist/routes/health.d.ts.map +1 -1
  57. package/dist/routes/health.js +2 -0
  58. package/dist/routes/health.js.map +1 -1
  59. package/dist/routes/index.d.ts +1 -0
  60. package/dist/routes/index.d.ts.map +1 -1
  61. package/dist/routes/index.js +1 -0
  62. package/dist/routes/index.js.map +1 -1
  63. package/dist/routes/instance-settings.d.ts.map +1 -1
  64. package/dist/routes/instance-settings.js +3 -0
  65. package/dist/routes/instance-settings.js.map +1 -1
  66. package/dist/routes/issues.comments-attachments.d.ts.map +1 -1
  67. package/dist/routes/issues.comments-attachments.js +90 -8
  68. package/dist/routes/issues.comments-attachments.js.map +1 -1
  69. package/dist/routes/issues.d.ts.map +1 -1
  70. package/dist/routes/issues.js +14 -2
  71. package/dist/routes/issues.js.map +1 -1
  72. package/dist/routes/issues.mutations.d.ts.map +1 -1
  73. package/dist/routes/issues.mutations.js +206 -52
  74. package/dist/routes/issues.mutations.js.map +1 -1
  75. package/dist/routes/managed-mcp-runtime.d.ts.map +1 -1
  76. package/dist/routes/managed-mcp-runtime.js +109 -7
  77. package/dist/routes/managed-mcp-runtime.js.map +1 -1
  78. package/dist/routes/organization-skills.d.ts.map +1 -1
  79. package/dist/routes/organization-skills.js +23 -1
  80. package/dist/routes/organization-skills.js.map +1 -1
  81. package/dist/routes/requests.d.ts +3 -0
  82. package/dist/routes/requests.d.ts.map +1 -0
  83. package/dist/routes/requests.js +122 -0
  84. package/dist/routes/requests.js.map +1 -0
  85. package/dist/routes/rudder-plugins.d.ts +4 -0
  86. package/dist/routes/rudder-plugins.d.ts.map +1 -0
  87. package/dist/routes/rudder-plugins.js +203 -0
  88. package/dist/routes/rudder-plugins.js.map +1 -0
  89. package/dist/services/activity-log.d.ts +0 -3
  90. package/dist/services/activity-log.d.ts.map +1 -1
  91. package/dist/services/activity-log.js +19 -42
  92. package/dist/services/activity-log.js.map +1 -1
  93. package/dist/services/activity.d.ts +3 -3
  94. package/dist/services/agent-issue-creation.d.ts +344 -0
  95. package/dist/services/agent-issue-creation.d.ts.map +1 -0
  96. package/dist/services/agent-issue-creation.js +675 -0
  97. package/dist/services/agent-issue-creation.js.map +1 -0
  98. package/dist/services/agent-run-context.d.ts.map +1 -1
  99. package/dist/services/agent-run-context.js +10 -4
  100. package/dist/services/agent-run-context.js.map +1 -1
  101. package/dist/services/agents.d.ts +14 -14
  102. package/dist/services/agents.d.ts.map +1 -1
  103. package/dist/services/agents.js +2 -1
  104. package/dist/services/agents.js.map +1 -1
  105. package/dist/services/app-builder.d.ts.map +1 -1
  106. package/dist/services/app-builder.js +24 -3
  107. package/dist/services/app-builder.js.map +1 -1
  108. package/dist/services/chat-agent-runs.d.ts +1 -0
  109. package/dist/services/chat-agent-runs.d.ts.map +1 -1
  110. package/dist/services/chat-agent-runs.js +1 -0
  111. package/dist/services/chat-agent-runs.js.map +1 -1
  112. package/dist/services/chat-assistant.d.ts.map +1 -1
  113. package/dist/services/chat-assistant.helpers.d.ts +3 -1
  114. package/dist/services/chat-assistant.helpers.d.ts.map +1 -1
  115. package/dist/services/chat-assistant.helpers.js +25 -0
  116. package/dist/services/chat-assistant.helpers.js.map +1 -1
  117. package/dist/services/chat-assistant.js +57 -11
  118. package/dist/services/chat-assistant.js.map +1 -1
  119. package/dist/services/chat-generation-protocol.test.js +2 -2
  120. package/dist/services/chat-generation-protocol.test.js.map +1 -1
  121. package/dist/services/chat-queued-message-materialization.d.ts +1 -1
  122. package/dist/services/chats.create.d.ts +2 -2
  123. package/dist/services/chats.create.d.ts.map +1 -1
  124. package/dist/services/chats.d.ts +15 -15
  125. package/dist/services/chats.d.ts.map +1 -1
  126. package/dist/services/chats.helpers.d.ts +2 -2
  127. package/dist/services/chats.helpers.d.ts.map +1 -1
  128. package/dist/services/chats.helpers.goal-context.test.d.ts +2 -0
  129. package/dist/services/chats.helpers.goal-context.test.d.ts.map +1 -0
  130. package/dist/services/chats.helpers.goal-context.test.js +40 -0
  131. package/dist/services/chats.helpers.goal-context.test.js.map +1 -0
  132. package/dist/services/chats.helpers.js +27 -2
  133. package/dist/services/chats.helpers.js.map +1 -1
  134. package/dist/services/chats.js.map +1 -1
  135. package/dist/services/computer-broker.d.ts +35 -0
  136. package/dist/services/computer-broker.d.ts.map +1 -0
  137. package/dist/services/computer-broker.js +211 -0
  138. package/dist/services/computer-broker.js.map +1 -0
  139. package/dist/services/computer-broker.test.d.ts +2 -0
  140. package/dist/services/computer-broker.test.d.ts.map +1 -0
  141. package/dist/services/computer-broker.test.js +70 -0
  142. package/dist/services/computer-broker.test.js.map +1 -0
  143. package/dist/services/computer-capability.d.ts +13 -0
  144. package/dist/services/computer-capability.d.ts.map +1 -0
  145. package/dist/services/computer-capability.js +11 -0
  146. package/dist/services/computer-capability.js.map +1 -0
  147. package/dist/services/computer-capability.test.d.ts +2 -0
  148. package/dist/services/computer-capability.test.d.ts.map +1 -0
  149. package/dist/services/computer-capability.test.js +27 -0
  150. package/dist/services/computer-capability.test.js.map +1 -0
  151. package/dist/services/finance.d.ts +2 -2
  152. package/dist/services/goals.d.ts +267 -244
  153. package/dist/services/goals.d.ts.map +1 -1
  154. package/dist/services/goals.js +1279 -297
  155. package/dist/services/goals.js.map +1 -1
  156. package/dist/services/index.d.ts +3 -0
  157. package/dist/services/index.d.ts.map +1 -1
  158. package/dist/services/index.js +3 -0
  159. package/dist/services/index.js.map +1 -1
  160. package/dist/services/instance-settings.d.ts.map +1 -1
  161. package/dist/services/instance-settings.js +8 -3
  162. package/dist/services/instance-settings.js.map +1 -1
  163. package/dist/services/integrations/agent-integrations.d.ts +1 -1
  164. package/dist/services/issues.d.ts +86 -1
  165. package/dist/services/issues.d.ts.map +1 -1
  166. package/dist/services/issues.helpers.d.ts +4 -0
  167. package/dist/services/issues.helpers.d.ts.map +1 -1
  168. package/dist/services/issues.helpers.js +28 -6
  169. package/dist/services/issues.helpers.js.map +1 -1
  170. package/dist/services/issues.js +207 -127
  171. package/dist/services/issues.js.map +1 -1
  172. package/dist/services/knowledge-portability/organization-portability.files.d.ts +1 -1
  173. package/dist/services/knowledge-portability/organization-skills.catalog.d.ts +7 -1
  174. package/dist/services/knowledge-portability/organization-skills.catalog.d.ts.map +1 -1
  175. package/dist/services/knowledge-portability/organization-skills.catalog.js +10 -0
  176. package/dist/services/knowledge-portability/organization-skills.catalog.js.map +1 -1
  177. package/dist/services/knowledge-portability/organization-skills.d.ts +3 -1
  178. package/dist/services/knowledge-portability/organization-skills.d.ts.map +1 -1
  179. package/dist/services/knowledge-portability/organization-skills.js +80 -12
  180. package/dist/services/knowledge-portability/organization-skills.js.map +1 -1
  181. package/dist/services/mcp/managed-bindings.d.ts.map +1 -1
  182. package/dist/services/mcp/managed-bindings.js +18 -2
  183. package/dist/services/mcp/managed-bindings.js.map +1 -1
  184. package/dist/services/mcp/managed-client.d.ts +15 -0
  185. package/dist/services/mcp/managed-client.d.ts.map +1 -1
  186. package/dist/services/mcp/managed-client.js +23 -1
  187. package/dist/services/mcp/managed-client.js.map +1 -1
  188. package/dist/services/mcp/managed-client.test.js +73 -9
  189. package/dist/services/mcp/managed-client.test.js.map +1 -1
  190. package/dist/services/messenger.d.ts +3 -3
  191. package/dist/services/messenger.d.ts.map +1 -1
  192. package/dist/services/messenger.js +201 -65
  193. package/dist/services/messenger.js.map +1 -1
  194. package/dist/services/orgs.d.ts.map +1 -1
  195. package/dist/services/orgs.js +3 -1
  196. package/dist/services/orgs.js.map +1 -1
  197. package/dist/services/product-analytics.d.ts +2 -2
  198. package/dist/services/requests.d.ts +919 -0
  199. package/dist/services/requests.d.ts.map +1 -0
  200. package/dist/services/requests.js +532 -0
  201. package/dist/services/requests.js.map +1 -0
  202. package/dist/services/rudder-plugins.d.ts +107 -0
  203. package/dist/services/rudder-plugins.d.ts.map +1 -0
  204. package/dist/services/rudder-plugins.js +1760 -0
  205. package/dist/services/rudder-plugins.js.map +1 -0
  206. package/dist/services/rudder-plugins.test.d.ts +2 -0
  207. package/dist/services/rudder-plugins.test.d.ts.map +1 -0
  208. package/dist/services/rudder-plugins.test.js +191 -0
  209. package/dist/services/rudder-plugins.test.js.map +1 -0
  210. package/dist/services/runtime-kernel/heartbeat.core.d.ts +1 -0
  211. package/dist/services/runtime-kernel/heartbeat.core.d.ts.map +1 -1
  212. package/dist/services/runtime-kernel/heartbeat.core.js +1 -1
  213. package/dist/services/runtime-kernel/heartbeat.core.js.map +1 -1
  214. package/dist/services/runtime-kernel/heartbeat.d.ts +65 -0
  215. package/dist/services/runtime-kernel/heartbeat.d.ts.map +1 -1
  216. package/dist/services/runtime-kernel/heartbeat.execute.d.ts.map +1 -1
  217. package/dist/services/runtime-kernel/heartbeat.execute.js +21 -5
  218. package/dist/services/runtime-kernel/heartbeat.execute.js.map +1 -1
  219. package/dist/services/runtime-kernel/heartbeat.js +320 -108
  220. package/dist/services/runtime-kernel/heartbeat.js.map +1 -1
  221. package/dist/services/runtime-kernel/heartbeat.recovery.d.ts +1 -0
  222. package/dist/services/runtime-kernel/heartbeat.recovery.d.ts.map +1 -1
  223. package/dist/services/runtime-kernel/heartbeat.recovery.js +39 -4
  224. package/dist/services/runtime-kernel/heartbeat.recovery.js.map +1 -1
  225. package/dist/services/runtime-kernel/heartbeat.sessions.js +1 -1
  226. package/dist/services/runtime-kernel/heartbeat.sessions.js.map +1 -1
  227. package/dist/services/runtime-kernel/heartbeat.terminal.d.ts +14 -1
  228. package/dist/services/runtime-kernel/heartbeat.terminal.d.ts.map +1 -1
  229. package/dist/services/runtime-kernel/heartbeat.terminal.js +46 -1
  230. package/dist/services/runtime-kernel/heartbeat.terminal.js.map +1 -1
  231. package/dist/services/runtime-kernel/heartbeat.wakeup.d.ts +1 -0
  232. package/dist/services/runtime-kernel/heartbeat.wakeup.d.ts.map +1 -1
  233. package/dist/services/runtime-kernel/heartbeat.wakeup.js +124 -6
  234. package/dist/services/runtime-kernel/heartbeat.wakeup.js.map +1 -1
  235. package/dist/services/workspace-runtime.services.d.ts +1 -1
  236. package/package.json +18 -18
  237. package/resources/bundled-skills/app-builder/SKILL.md +39 -9
  238. package/resources/bundled-skills/app-builder/assets/scaffold/app/globals.css +109 -16
  239. package/resources/bundled-skills/app-builder/assets/scaffold/components/contacts-workspace.tsx +176 -152
  240. package/resources/bundled-skills/app-builder/assets/scaffold/components/ui/alert.tsx +12 -0
  241. package/resources/bundled-skills/app-builder/assets/scaffold/components/ui/badge.tsx +27 -0
  242. package/resources/bundled-skills/app-builder/assets/scaffold/components/ui/button.tsx +9 -7
  243. package/resources/bundled-skills/app-builder/assets/scaffold/components/ui/card.tsx +15 -3
  244. package/resources/bundled-skills/app-builder/assets/scaffold/components/ui/empty.tsx +18 -0
  245. package/resources/bundled-skills/app-builder/assets/scaffold/components/ui/field.tsx +23 -0
  246. package/resources/bundled-skills/app-builder/assets/scaffold/components/ui/input.tsx +1 -1
  247. package/resources/bundled-skills/app-builder/assets/scaffold/components/ui/label.tsx +1 -1
  248. package/resources/bundled-skills/app-builder/assets/scaffold/components/ui/separator.tsx +6 -0
  249. package/resources/bundled-skills/app-builder/assets/scaffold/components/ui/skeleton.tsx +6 -0
  250. package/resources/bundled-skills/app-builder/assets/scaffold/components/ui/table.tsx +30 -0
  251. package/resources/bundled-skills/app-builder/assets/scaffold/components.json +20 -0
  252. package/resources/bundled-skills/app-builder/assets/scaffold/next-env.d.ts +1 -1
  253. package/resources/bundled-skills/app-builder/assets/scaffold/next.config.ts +19 -0
  254. package/resources/bundled-skills/app-builder/assets/scaffold/package.json +2 -1
  255. package/resources/bundled-skills/app-builder/assets/scaffold/rudder.app.json +1 -1
  256. package/resources/bundled-skills/app-builder/assets/scaffold/rudder.ui.json +13 -0
  257. package/resources/bundled-skills/app-builder/assets/scaffold/scripts/validate-rudder-ui.mjs +50 -0
  258. package/resources/bundled-skills/app-builder/assets/scaffold/tests/e2e/app.spec.ts +35 -1
  259. package/resources/bundled-skills/app-builder/evals/evals.json +2 -1
  260. package/resources/bundled-skills/app-builder/references/design-guidelines.md +19 -2
  261. package/resources/bundled-skills/app-builder/references/rudder-ui-preset.md +73 -0
  262. package/resources/bundled-skills/app-builder/references/scaffold-contract.md +12 -0
  263. package/resources/bundled-skills/app-builder/references/verification.md +6 -0
  264. package/resources/bundled-skills/app-builder/scripts/report-build-status.mjs +83 -0
  265. package/resources/bundled-skills/rudder-docs/SKILL.md +2 -2
  266. package/resources/bundled-skills/rudder-docs/evals/retrieval-authority-evals.json +40 -0
  267. package/resources/bundled-skills/rudder-docs/evals/trigger-evals.json +2 -2
  268. package/resources/bundled-skills/rudder-docs/references/api-reference.md +0 -2
  269. package/resources/bundled-skills/rudder-docs/references/cli-reference.md +7 -2
  270. package/resources/bundled-skills/rudder-docs/references/operating-practices.md +10 -6
  271. package/resources/bundled-skills/rudder-docs/references/plugin-authoring.md +38 -114
  272. package/resources/bundled-skills/rudder-docs/references/source-map.md +2 -0
  273. package/skills/app-builder/SKILL.md +39 -9
  274. package/skills/app-builder/assets/scaffold/app/globals.css +109 -16
  275. package/skills/app-builder/assets/scaffold/components/contacts-workspace.tsx +176 -152
  276. package/skills/app-builder/assets/scaffold/components/ui/alert.tsx +12 -0
  277. package/skills/app-builder/assets/scaffold/components/ui/badge.tsx +27 -0
  278. package/skills/app-builder/assets/scaffold/components/ui/button.tsx +9 -7
  279. package/skills/app-builder/assets/scaffold/components/ui/card.tsx +15 -3
  280. package/skills/app-builder/assets/scaffold/components/ui/empty.tsx +18 -0
  281. package/skills/app-builder/assets/scaffold/components/ui/field.tsx +23 -0
  282. package/skills/app-builder/assets/scaffold/components/ui/input.tsx +1 -1
  283. package/skills/app-builder/assets/scaffold/components/ui/label.tsx +1 -1
  284. package/skills/app-builder/assets/scaffold/components/ui/separator.tsx +6 -0
  285. package/skills/app-builder/assets/scaffold/components/ui/skeleton.tsx +6 -0
  286. package/skills/app-builder/assets/scaffold/components/ui/table.tsx +30 -0
  287. package/skills/app-builder/assets/scaffold/components.json +20 -0
  288. package/skills/app-builder/assets/scaffold/next-env.d.ts +1 -1
  289. package/skills/app-builder/assets/scaffold/next.config.ts +19 -0
  290. package/skills/app-builder/assets/scaffold/package.json +2 -1
  291. package/skills/app-builder/assets/scaffold/rudder.app.json +1 -1
  292. package/skills/app-builder/assets/scaffold/rudder.ui.json +13 -0
  293. package/skills/app-builder/assets/scaffold/scripts/validate-rudder-ui.mjs +50 -0
  294. package/skills/app-builder/assets/scaffold/tests/e2e/app.spec.ts +35 -1
  295. package/skills/app-builder/evals/evals.json +2 -1
  296. package/skills/app-builder/references/design-guidelines.md +19 -2
  297. package/skills/app-builder/references/rudder-ui-preset.md +73 -0
  298. package/skills/app-builder/references/scaffold-contract.md +12 -0
  299. package/skills/app-builder/references/verification.md +6 -0
  300. package/skills/app-builder/scripts/report-build-status.mjs +83 -0
  301. package/skills/rudder-docs/SKILL.md +2 -2
  302. package/skills/rudder-docs/evals/retrieval-authority-evals.json +40 -0
  303. package/skills/rudder-docs/evals/trigger-evals.json +2 -2
  304. package/skills/rudder-docs/references/api-reference.md +0 -2
  305. package/skills/rudder-docs/references/cli-reference.md +7 -2
  306. package/skills/rudder-docs/references/operating-practices.md +10 -6
  307. package/skills/rudder-docs/references/plugin-authoring.md +38 -114
  308. package/skills/rudder-docs/references/source-map.md +2 -0
  309. package/ui-dist/assets/{LibraryLiveSurface-cHCJmmsl.js → LibraryLiveSurface-D4uCo0l0.js} +1 -1
  310. package/ui-dist/assets/{abnfDiagram-VRR7QNED-BORJvkew.js → abnfDiagram-VRR7QNED-FF2qcR6A.js} +1 -1
  311. package/ui-dist/assets/addon-fit-4gL7JXzc.js +1 -0
  312. package/ui-dist/assets/{arc-DPUzhx87.js → arc-D5pLjh6B.js} +1 -1
  313. package/ui-dist/assets/{architectureDiagram-ZJ3FMSHR-Bz_hY8LM.js → architectureDiagram-ZJ3FMSHR-BvqY-HXg.js} +1 -1
  314. package/ui-dist/assets/{blockDiagram-677ZJIJ3-Bq1X3YkP.js → blockDiagram-677ZJIJ3-BU6ZbJ0s.js} +1 -1
  315. package/ui-dist/assets/{c4Diagram-LMCZKHZV-BawEM9Pc.js → c4Diagram-LMCZKHZV-Cl5Gsovj.js} +1 -1
  316. package/ui-dist/assets/channel-HcmprA3I.js +1 -0
  317. package/ui-dist/assets/{chunk-2Q5K7J3B-D0D5ZOaA.js → chunk-2Q5K7J3B-4a2agt_p.js} +1 -1
  318. package/ui-dist/assets/{chunk-32BRIVSS-T90P7lug.js → chunk-32BRIVSS-CF0oOO2T.js} +1 -1
  319. package/ui-dist/assets/{chunk-5VM5RSS4-jcAERTgh.js → chunk-5VM5RSS4-CsSq2zX2.js} +1 -1
  320. package/ui-dist/assets/{chunk-EX3LRPZG-t9-JXwnR.js → chunk-EX3LRPZG-B6riETVZ.js} +1 -1
  321. package/ui-dist/assets/{chunk-JWPE2WC7-hqgD1saC.js → chunk-JWPE2WC7-S-p1xu0r.js} +1 -1
  322. package/ui-dist/assets/{chunk-MOJQB5TN-BsSUoYzd.js → chunk-MOJQB5TN-mVE4v_LV.js} +1 -1
  323. package/ui-dist/assets/{chunk-RYQCIY6F-YcdDh2Zn.js → chunk-RYQCIY6F-CElbgpgs.js} +1 -1
  324. package/ui-dist/assets/{chunk-V7JOEXUC-ChFyH9Su.js → chunk-V7JOEXUC-8XM3povP.js} +1 -1
  325. package/ui-dist/assets/{chunk-VR4S4FIN-BjVK8IyX.js → chunk-VR4S4FIN-LrThp3ht.js} +1 -1
  326. package/ui-dist/assets/{chunk-XXDRQBXY-D-TvO9vT.js → chunk-XXDRQBXY-D38vLb4t.js} +1 -1
  327. package/ui-dist/assets/classDiagram-OUVF2IWQ-Dpe8LaLf.js +1 -0
  328. package/ui-dist/assets/classDiagram-v2-EOCWNBFH-Dpe8LaLf.js +1 -0
  329. package/ui-dist/assets/{cose-bilkent-JH36ORCC-DBwhASJG.js → cose-bilkent-JH36ORCC-CmpZMaDV.js} +1 -1
  330. package/ui-dist/assets/{cynefin-VYW2F7L2-SlCRmwl2.js → cynefin-VYW2F7L2-D3RnbBvI.js} +1 -1
  331. package/ui-dist/assets/{cynefinDiagram-TSTJHNR4-DImV6N_p.js → cynefinDiagram-TSTJHNR4-DZTcUojp.js} +1 -1
  332. package/ui-dist/assets/{dagre-VKFMJZFB-FH4Wv8Zn.js → dagre-VKFMJZFB-6jXSDi0S.js} +1 -1
  333. package/ui-dist/assets/{diagram-FQU43EPY-CrZvQjId.js → diagram-FQU43EPY-DIMm3mfS.js} +1 -1
  334. package/ui-dist/assets/{diagram-G47NLZAW-B7IGl_T3.js → diagram-G47NLZAW-Cyo22v0_.js} +1 -1
  335. package/ui-dist/assets/{diagram-NH7WQ7WH-CB4i7H1a.js → diagram-NH7WQ7WH-CHDV0nSL.js} +1 -1
  336. package/ui-dist/assets/{diagram-OA4YK3LP-DNyWPYY9.js → diagram-OA4YK3LP-Jijx9vQO.js} +1 -1
  337. package/ui-dist/assets/{diagram-WEI45ONY-BAhxbhpi.js → diagram-WEI45ONY-CcL19HLE.js} +1 -1
  338. package/ui-dist/assets/{ebnfDiagram-CCIWWBDH-ClXtQ9qt.js → ebnfDiagram-CCIWWBDH-D94h-RFQ.js} +1 -1
  339. package/ui-dist/assets/{erDiagram-Q63AITRT-DGXiMYfn.js → erDiagram-Q63AITRT-B3ypUGZX.js} +1 -1
  340. package/ui-dist/assets/{flowDiagram-23GEKE2U-CdZ-02zc.js → flowDiagram-23GEKE2U-DjCT6Y1o.js} +1 -1
  341. package/ui-dist/assets/{ganttDiagram-NO4QXBWP-TxhzNpta.js → ganttDiagram-NO4QXBWP-C3QVhTc9.js} +1 -1
  342. package/ui-dist/assets/{gitGraphDiagram-IHSO6WYX-D6Q_w6wR.js → gitGraphDiagram-IHSO6WYX-D0trBzU5.js} +1 -1
  343. package/ui-dist/assets/{graph-CxNmbpl9.js → graph-D1zFmVUP.js} +1 -1
  344. package/ui-dist/assets/{index-Cx6oIZPm.js → index-B0GEsWui.js} +568 -593
  345. package/ui-dist/assets/{index-BA9tMBhO.js → index-B8bxQLfN.js} +1 -1
  346. package/ui-dist/assets/{index-C_nwI4Yj.js → index-BFktZ2tu.js} +1 -1
  347. package/ui-dist/assets/index-BT7dphT4.css +32 -0
  348. package/ui-dist/assets/{index-BoTz5S-X.js → index-Bk_yesiO.js} +1 -1
  349. package/ui-dist/assets/{index-CXnqg1Oq.js → index-BwutQLG1.js} +1 -1
  350. package/ui-dist/assets/{index-AkhgKOxC.js → index-CB4zr_Ku.js} +1 -1
  351. package/ui-dist/assets/{index-Rau2cGm0.js → index-CC89UPa-.js} +1 -1
  352. package/ui-dist/assets/{index-kbWUQ1um.js → index-CFWOO6yn.js} +1 -1
  353. package/ui-dist/assets/{index-DU2LI7f9.js → index-CK8u9b07.js} +1 -1
  354. package/ui-dist/assets/{index-C12QC1Ex.js → index-CRolpP90.js} +1 -1
  355. package/ui-dist/assets/{index-Bxz75ICF.js → index-CiCRRUVV.js} +1 -1
  356. package/ui-dist/assets/{index-Do78GymY.js → index-D8JG-IU9.js} +1 -1
  357. package/ui-dist/assets/{index-CW21qWFm.js → index-DKCiVvJo.js} +1 -1
  358. package/ui-dist/assets/{index-BujwWE3j.js → index-G2yfKEf_.js} +1 -1
  359. package/ui-dist/assets/{index-BQ87dGIq.js → index-or2C22bw.js} +1 -1
  360. package/ui-dist/assets/{infoDiagram-FWYZ7A6U-CnJd4-Jq.js → infoDiagram-FWYZ7A6U-Co0nkcV4.js} +1 -1
  361. package/ui-dist/assets/{ishikawaDiagram-FXEZZL3T-Bq-qc3pD.js → ishikawaDiagram-FXEZZL3T-CkIg1Jdu.js} +1 -1
  362. package/ui-dist/assets/{journeyDiagram-5HDEW3XC-NSmpUGUA.js → journeyDiagram-5HDEW3XC-AYUT6JuJ.js} +1 -1
  363. package/ui-dist/assets/{kanban-definition-HUTT4EX6-D7pgWtfA.js → kanban-definition-HUTT4EX6-C3H2XW08.js} +1 -1
  364. package/ui-dist/assets/{layout-BRuo6LWe.js → layout-sZWyqNOu.js} +1 -1
  365. package/ui-dist/assets/{linear-CkaWI9Nd.js → linear-BLTYou8N.js} +1 -1
  366. package/ui-dist/assets/{map-CgA6tTpZ.js → map-zKBdHkX8.js} +1 -1
  367. package/ui-dist/assets/{mermaid.core-DrXOt-P6.js → mermaid.core-DbjdFip8.js} +4 -4
  368. package/ui-dist/assets/{mindmap-definition-LN4V7U3C-D9ImeWBe.js → mindmap-definition-LN4V7U3C-DcDNQ4GQ.js} +1 -1
  369. package/ui-dist/assets/{pegDiagram-2B236MQR-Cq-zZ_zz.js → pegDiagram-2B236MQR-BGq1PngU.js} +1 -1
  370. package/ui-dist/assets/{pieDiagram-ENE6RG2P-FgNgy726.js → pieDiagram-ENE6RG2P-BfXWRzNb.js} +1 -1
  371. package/ui-dist/assets/{quadrantDiagram-ABIIQ3AL-BxQbmGmi.js → quadrantDiagram-ABIIQ3AL-AYMVAbo1.js} +1 -1
  372. package/ui-dist/assets/{railroadDiagram-RFXS5EU6-CrpbkPH2.js → railroadDiagram-RFXS5EU6-B0PoG0Ih.js} +1 -1
  373. package/ui-dist/assets/{requirementDiagram-TGXJPOKE-DQ-65ZW6.js → requirementDiagram-TGXJPOKE-Cuuu9wfw.js} +1 -1
  374. package/ui-dist/assets/{sankeyDiagram-HTMAVEWB-Bom0lMZm.js → sankeyDiagram-HTMAVEWB-BceAc42f.js} +1 -1
  375. package/ui-dist/assets/{sequenceDiagram-DBY2YBRQ-C1MSDLDj.js → sequenceDiagram-DBY2YBRQ-cqJ71-Rw.js} +1 -1
  376. package/ui-dist/assets/{sizeCapture-X5ZJPWSS-Bn1vxebD.js → sizeCapture-X5ZJPWSS-DQ6MibbA.js} +1 -1
  377. package/ui-dist/assets/{stateDiagram-2N3HPSRC-GRRQpmzI.js → stateDiagram-2N3HPSRC-Bdl-XY7t.js} +1 -1
  378. package/ui-dist/assets/stateDiagram-v2-6OUMAXLB-Cjj7yPIO.js +1 -0
  379. package/ui-dist/assets/{swimlanes-5IMT3BWC-BcOOCZ4q.js → swimlanes-5IMT3BWC-Dpo2HDtE.js} +2 -2
  380. package/ui-dist/assets/swimlanesDiagram-G3AALYLV-CPmIkNCu.js +8 -0
  381. package/ui-dist/assets/{timeline-definition-FHXFAJF6-gi6amswh.js → timeline-definition-FHXFAJF6-BzmJ-xnC.js} +1 -1
  382. package/ui-dist/assets/{vennDiagram-L72KCM5P-B9uqeyQt.js → vennDiagram-L72KCM5P-BF2EI7vn.js} +1 -1
  383. package/ui-dist/assets/{wardleyDiagram-EHGQE667-BnqWAMiE.js → wardleyDiagram-EHGQE667-DM65x1i1.js} +1 -1
  384. package/ui-dist/assets/xterm-DZP7bLZ5.js +9 -0
  385. package/ui-dist/assets/{xychartDiagram-FW5EYKEG-BJXKltIN.js → xychartDiagram-FW5EYKEG-3Q2t9pdU.js} +1 -1
  386. package/ui-dist/index.html +2 -2
  387. package/dist/bootstrap/plugin-host-runtime.d.ts +0 -116
  388. package/dist/bootstrap/plugin-host-runtime.d.ts.map +0 -1
  389. package/dist/bootstrap/plugin-host-runtime.js +0 -179
  390. package/dist/bootstrap/plugin-host-runtime.js.map +0 -1
  391. package/dist/bundled-plugins/plugin-linear/README.md +0 -22
  392. package/dist/bundled-plugins/plugin-linear/dist/manifest.js +0 -183
  393. package/dist/bundled-plugins/plugin-linear/dist/manifest.js.map +0 -7
  394. package/dist/bundled-plugins/plugin-linear/dist/ui/index.js +0 -1285
  395. package/dist/bundled-plugins/plugin-linear/dist/ui/index.js.map +0 -7
  396. package/dist/bundled-plugins/plugin-linear/dist/worker.js +0 -10699
  397. package/dist/bundled-plugins/plugin-linear/dist/worker.js.map +0 -7
  398. package/dist/bundled-plugins/plugin-linear/package.json +0 -42
  399. package/dist/routes/plugin-ui-static.d.ts +0 -70
  400. package/dist/routes/plugin-ui-static.d.ts.map +0 -1
  401. package/dist/routes/plugin-ui-static.js +0 -375
  402. package/dist/routes/plugin-ui-static.js.map +0 -1
  403. package/dist/routes/plugins.d.ts +0 -120
  404. package/dist/routes/plugins.d.ts.map +0 -1
  405. package/dist/routes/plugins.js +0 -1125
  406. package/dist/routes/plugins.js.map +0 -1
  407. package/dist/routes/plugins.operations-routes.d.ts +0 -28
  408. package/dist/routes/plugins.operations-routes.d.ts.map +0 -1
  409. package/dist/routes/plugins.operations-routes.js +0 -720
  410. package/dist/routes/plugins.operations-routes.js.map +0 -1
  411. package/dist/services/plugin-capability-validator.d.ts +0 -108
  412. package/dist/services/plugin-capability-validator.d.ts.map +0 -1
  413. package/dist/services/plugin-capability-validator.js +0 -268
  414. package/dist/services/plugin-capability-validator.js.map +0 -1
  415. package/dist/services/plugin-config-validator.d.ts +0 -26
  416. package/dist/services/plugin-config-validator.d.ts.map +0 -1
  417. package/dist/services/plugin-config-validator.js +0 -41
  418. package/dist/services/plugin-config-validator.js.map +0 -1
  419. package/dist/services/plugin-dev-watcher.d.ts +0 -47
  420. package/dist/services/plugin-dev-watcher.d.ts.map +0 -1
  421. package/dist/services/plugin-dev-watcher.js +0 -275
  422. package/dist/services/plugin-dev-watcher.js.map +0 -1
  423. package/dist/services/plugin-event-bus.d.ts +0 -149
  424. package/dist/services/plugin-event-bus.d.ts.map +0 -1
  425. package/dist/services/plugin-event-bus.js +0 -258
  426. package/dist/services/plugin-event-bus.js.map +0 -1
  427. package/dist/services/plugin-host-service-cleanup.d.ts +0 -14
  428. package/dist/services/plugin-host-service-cleanup.d.ts.map +0 -1
  429. package/dist/services/plugin-host-service-cleanup.js +0 -37
  430. package/dist/services/plugin-host-service-cleanup.js.map +0 -1
  431. package/dist/services/plugin-host-services.d.ts +0 -13
  432. package/dist/services/plugin-host-services.d.ts.map +0 -1
  433. package/dist/services/plugin-host-services.js +0 -933
  434. package/dist/services/plugin-host-services.js.map +0 -1
  435. package/dist/services/plugin-job-coordinator.d.ts +0 -81
  436. package/dist/services/plugin-job-coordinator.d.ts.map +0 -1
  437. package/dist/services/plugin-job-coordinator.js +0 -172
  438. package/dist/services/plugin-job-coordinator.js.map +0 -1
  439. package/dist/services/plugin-job-scheduler.d.ts +0 -163
  440. package/dist/services/plugin-job-scheduler.d.ts.map +0 -1
  441. package/dist/services/plugin-job-scheduler.js +0 -452
  442. package/dist/services/plugin-job-scheduler.js.map +0 -1
  443. package/dist/services/plugin-job-store.d.ts +0 -208
  444. package/dist/services/plugin-job-store.d.ts.map +0 -1
  445. package/dist/services/plugin-job-store.js +0 -350
  446. package/dist/services/plugin-job-store.js.map +0 -1
  447. package/dist/services/plugin-lifecycle.d.ts +0 -240
  448. package/dist/services/plugin-lifecycle.d.ts.map +0 -1
  449. package/dist/services/plugin-lifecycle.js +0 -439
  450. package/dist/services/plugin-lifecycle.js.map +0 -1
  451. package/dist/services/plugin-loader.core.d.ts +0 -40
  452. package/dist/services/plugin-loader.core.d.ts.map +0 -1
  453. package/dist/services/plugin-loader.core.js +0 -879
  454. package/dist/services/plugin-loader.core.js.map +0 -1
  455. package/dist/services/plugin-loader.d.ts +0 -30
  456. package/dist/services/plugin-loader.d.ts.map +0 -1
  457. package/dist/services/plugin-loader.helpers.d.ts +0 -495
  458. package/dist/services/plugin-loader.helpers.d.ts.map +0 -1
  459. package/dist/services/plugin-loader.helpers.js +0 -238
  460. package/dist/services/plugin-loader.helpers.js.map +0 -1
  461. package/dist/services/plugin-loader.js +0 -30
  462. package/dist/services/plugin-loader.js.map +0 -1
  463. package/dist/services/plugin-loader.worker-paths.d.ts +0 -37
  464. package/dist/services/plugin-loader.worker-paths.d.ts.map +0 -1
  465. package/dist/services/plugin-loader.worker-paths.js +0 -62
  466. package/dist/services/plugin-loader.worker-paths.js.map +0 -1
  467. package/dist/services/plugin-log-retention.d.ts +0 -20
  468. package/dist/services/plugin-log-retention.d.ts.map +0 -1
  469. package/dist/services/plugin-log-retention.js +0 -63
  470. package/dist/services/plugin-log-retention.js.map +0 -1
  471. package/dist/services/plugin-manifest-validator.d.ts +0 -90
  472. package/dist/services/plugin-manifest-validator.d.ts.map +0 -1
  473. package/dist/services/plugin-manifest-validator.js +0 -72
  474. package/dist/services/plugin-manifest-validator.js.map +0 -1
  475. package/dist/services/plugin-registry.d.ts +0 -2542
  476. package/dist/services/plugin-registry.d.ts.map +0 -1
  477. package/dist/services/plugin-registry.js +0 -536
  478. package/dist/services/plugin-registry.js.map +0 -1
  479. package/dist/services/plugin-runtime-sandbox.d.ts +0 -40
  480. package/dist/services/plugin-runtime-sandbox.d.ts.map +0 -1
  481. package/dist/services/plugin-runtime-sandbox.js +0 -154
  482. package/dist/services/plugin-runtime-sandbox.js.map +0 -1
  483. package/dist/services/plugin-secrets-handler.d.ts +0 -81
  484. package/dist/services/plugin-secrets-handler.d.ts.map +0 -1
  485. package/dist/services/plugin-secrets-handler.js +0 -275
  486. package/dist/services/plugin-secrets-handler.js.map +0 -1
  487. package/dist/services/plugin-state-store.d.ts +0 -92
  488. package/dist/services/plugin-state-store.d.ts.map +0 -1
  489. package/dist/services/plugin-state-store.js +0 -190
  490. package/dist/services/plugin-state-store.js.map +0 -1
  491. package/dist/services/plugin-stream-bus.d.ts +0 -29
  492. package/dist/services/plugin-stream-bus.d.ts.map +0 -1
  493. package/dist/services/plugin-stream-bus.js +0 -48
  494. package/dist/services/plugin-stream-bus.js.map +0 -1
  495. package/dist/services/plugin-tool-dispatcher.d.ts +0 -180
  496. package/dist/services/plugin-tool-dispatcher.d.ts.map +0 -1
  497. package/dist/services/plugin-tool-dispatcher.js +0 -224
  498. package/dist/services/plugin-tool-dispatcher.js.map +0 -1
  499. package/dist/services/plugin-tool-registry.d.ts +0 -192
  500. package/dist/services/plugin-tool-registry.d.ts.map +0 -1
  501. package/dist/services/plugin-tool-registry.js +0 -224
  502. package/dist/services/plugin-tool-registry.js.map +0 -1
  503. package/dist/services/plugin-worker-manager.d.ts +0 -260
  504. package/dist/services/plugin-worker-manager.d.ts.map +0 -1
  505. package/dist/services/plugin-worker-manager.js +0 -836
  506. package/dist/services/plugin-worker-manager.js.map +0 -1
  507. package/ui-dist/assets/channel-C08AyYJr.js +0 -1
  508. package/ui-dist/assets/classDiagram-OUVF2IWQ-BIXq6fec.js +0 -1
  509. package/ui-dist/assets/classDiagram-v2-EOCWNBFH-BIXq6fec.js +0 -1
  510. package/ui-dist/assets/index-BmXb0yC1.css +0 -1
  511. package/ui-dist/assets/stateDiagram-v2-6OUMAXLB-KvSJhiLM.js +0 -1
  512. package/ui-dist/assets/swimlanesDiagram-G3AALYLV-Cr92NnJ4.js +0 -8
@@ -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+)?\s*(?:%|percent|x|倍|个|项|天|周|月|年))|(?:[$€£¥¥]\s*\d)/i,
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
- return input.title.trim().length >= 8 && GOAL_OUTCOME_PATTERNS.some((pattern) => pattern.test(combined));
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
- if (!ownerCanAdvanceGoal(owner, input)) {
201
- return {
202
- valid: false,
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
- ? `Evidence demonstrates that ${title} while satisfying: ${context}`
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
- ? `Measured evidence demonstrates progress toward: ${title}`
716
+ ? `Progress is measured toward: ${title}`
217
717
  : mode === "maintain"
218
- ? `Evidence confirms the condition remained true: ${title}`
219
- : `An inspectable artifact demonstrates that ${title}`;
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: "Advance the first bounded action and report evidence or a named blocker.",
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
- async function latestExternalCheckpoint(goal) {
472
- const [issueFacts, linkedProjectFacts] = await Promise.all([
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 facts = [
1146
+ const relatedFacts = [
501
1147
  ...issueFacts.map((issue) => ({
502
- id: `checkpoint:issue:${issue.id}`,
503
- kind: "checkpoint",
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
- evidenceRefs: [`issue://${issue.id}`],
1154
+ runStatus: null,
1155
+ evidenceRefs: [],
509
1156
  })),
510
1157
  ...linkedProjectFacts.map((project) => ({
511
- id: `checkpoint:project:${project.id}`,
512
- kind: "checkpoint",
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
- evidenceRefs: [`project://${project.id}`],
1164
+ runStatus: null,
1165
+ evidenceRefs: [],
518
1166
  })),
519
1167
  ...runFacts.map((run) => ({
520
- id: `checkpoint:run:${run.id}`,
521
- kind: "checkpoint",
522
- summary: `Agent run ${run.id.slice(0, 8)} is ${run.status}: ${resultSummary(run)}`,
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
- evidenceRefs: [`run://${run.id}`],
1174
+ runStatus: run.status,
1175
+ evidenceRefs: [],
527
1176
  })),
528
1177
  ];
529
- return facts.sort((left, right) => right.occurredAt.getTime() - left.occurredAt.getTime())[0] ?? null;
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
- if (feedback.routedWakeupRequestId)
565
- return feedback;
566
- const wakeupKey = `goal-feedback:${feedback.id}`;
567
- const [insertedWakeup] = await database.insert(agentWakeupRequests).values({
568
- orgId: goal.orgId,
569
- agentId: goal.ownerAgentId,
570
- source: "goal_feedback",
571
- triggerDetail: "goal_feedback",
572
- reason: "Goal feedback requires Owner review",
573
- payload: { goalId: goal.id, feedbackId: feedback.id },
574
- status: "queued",
575
- requestedByActorType: "user",
576
- requestedByActorId: actorUserId,
577
- idempotencyKey: wakeupKey,
578
- }).onConflictDoNothing().returning();
579
- const wakeup = insertedWakeup ?? await database.select().from(agentWakeupRequests).where(and(eq(agentWakeupRequests.orgId, goal.orgId), eq(agentWakeupRequests.agentId, goal.ownerAgentId), eq(agentWakeupRequests.idempotencyKey, wakeupKey))).then((rows) => rows[0] ?? null);
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: "evidence",
638
- summary: `Goal evaluated as ${evaluation.outcome}`,
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 workspaceFor(id) {
1367
+ async function historyFor(id, options = {}) {
645
1368
  const goal = await requireGoal(db, id);
646
- const [ownerAssignment, plan, activities, feedback, changes, results] = await Promise.all([
647
- db.select().from(goalOwnerAssignments).where(and(eq(goalOwnerAssignments.goalId, id), isNull(goalOwnerAssignments.endsAt))).then((rows) => rows[0] ?? null),
648
- goal.planRevision > 0
649
- ? db.select().from(goalPlans).where(and(eq(goalPlans.goalId, id), eq(goalPlans.revision, goal.planRevision))).then((rows) => rows[0] ?? null)
650
- : Promise.resolve(null),
651
- db.select().from(goalActivities).where(eq(goalActivities.goalId, id))
652
- .orderBy(desc(goalActivities.occurredAt), desc(goalActivities.createdAt)).limit(100),
653
- db.select().from(goalFeedbackEntries).where(eq(goalFeedbackEntries.goalId, id))
654
- .orderBy(desc(goalFeedbackEntries.createdAt)).limit(100),
655
- db.select().from(goalChangeProposals).where(eq(goalChangeProposals.goalId, id))
656
- .orderBy(desc(goalChangeProposals.createdAt)).limit(100),
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 externalCheckpoint = await latestExternalCheckpoint(goal);
661
- const evidenceActivity = activities.find((activity) => stringEvidenceRefs(activity.evidenceRefs).length > 0) ?? null;
662
- const progressSource = [
663
- evidenceActivity
664
- ? {
665
- summary: evidenceActivity.summary,
666
- sourceActivityId: evidenceActivity.id,
667
- evidenceRefs: stringEvidenceRefs(evidenceActivity.evidenceRefs),
668
- occurredAt: evidenceActivity.occurredAt,
669
- }
670
- : null,
671
- externalCheckpoint
672
- ? {
673
- summary: externalCheckpoint.summary,
674
- sourceActivityId: null,
675
- evidenceRefs: externalCheckpoint.evidenceRefs,
676
- occurredAt: externalCheckpoint.occurredAt,
677
- }
678
- : null,
679
- ].filter((value) => Boolean(value))
680
- .sort((left, right) => right.occurredAt.getTime() - left.occurredAt.getTime())[0] ?? null;
681
- const pendingChange = changes.find((proposal) => proposal.status === "pending") ?? null;
682
- const readyResult = results.find((proposal) => proposal.status === "ready") ?? null;
683
- const facet = facetFor(goal, pendingChange, readyResult);
684
- const attention = readyResult
685
- ? {
686
- kind: "result_proposal",
687
- reason: `Review the proposed Goal result: ${readyResult.preflight.outcome}`,
688
- sourceId: readyResult.id,
689
- }
690
- : pendingChange
691
- ? {
692
- kind: "change_proposal",
693
- reason: pendingChange.rationale,
694
- sourceId: pendingChange.id,
695
- }
696
- : goal.lifecycle === "draft" && goal.alignmentQuestion
697
- ? { kind: "alignment_question", reason: goal.alignmentQuestion, sourceId: goal.id }
698
- : null;
699
- const timeline = [
700
- ...activities.map((activity) => ({
701
- id: activity.id,
702
- kind: "activity",
703
- summary: activity.summary,
704
- occurredAt: activity.occurredAt,
705
- sourceId: activity.id,
706
- sourceRunId: activity.runRef,
707
- evidenceRefs: stringEvidenceRefs(activity.evidenceRefs),
708
- })),
709
- ...feedback.map((entry) => ({
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
- occurredAt: entry.createdAt,
714
- sourceId: entry.id,
715
- actorType: entry.actorType,
1435
+ createdAt: entry.createdAt,
1436
+ evidence: [],
1437
+ actorType: "user",
716
1438
  actorId: entry.actorId,
717
- attachments: entry.attachments,
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
- ...changes.map((proposal) => ({
1452
+ ...changeRows.map((proposal) => ({
721
1453
  id: proposal.id,
722
1454
  kind: "change_proposal",
723
- summary: proposal.rationale,
724
- occurredAt: proposal.createdAt,
725
- sourceId: proposal.id,
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
- ...results.map((proposal) => ({
1468
+ ...resultRows.map((proposal) => ({
731
1469
  id: proposal.id,
732
1470
  kind: "result_proposal",
733
- summary: `Result proposed as ${proposal.preflight.outcome}: ${proposal.riskSummary}`,
734
- occurredAt: proposal.createdAt,
735
- sourceId: proposal.id,
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
- ...(externalCheckpoint ? [externalCheckpoint] : []),
740
- ].sort((left, right) => right.occurredAt.getTime() - left.occurredAt.getTime());
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: { ...goal, ownerAssignment, plan, activities },
1551
+ goal: publicGoal,
743
1552
  facet,
744
- currentGoal: { summary: goal.outcomeStatement ?? goal.title, revision: goal.contractRevision },
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
- evidenceRefs: progressSource.evidenceRefs,
1558
+ evidence: progressSource.evidence,
750
1559
  }
751
1560
  : {
752
1561
  summary: "No evidence-backed progress has been recorded yet.",
753
1562
  sourceActivityId: null,
754
- evidenceRefs: [],
1563
+ evidence: [],
755
1564
  },
756
- agentAction: plan ? { summary: plan.summary, sourcePlanId: plan.id, sourceIds: [plan.id] } : null,
757
- nextStep: goal.continuationKind && goal.continuationSummary
758
- ? {
759
- kind: goal.continuationKind,
760
- summary: goal.continuationSummary,
761
- wakeCondition: goal.wakeCondition,
762
- }
763
- : null,
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
- changeProposals: changes,
767
- resultProposals: results,
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
- return { goal, replayed: true };
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
- return { goal: existingGoal, replayed: true };
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: `Goal activated with initial ${activation.initialContinuation.kind}: ${activation.initialContinuation.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
- return { goal, replayed: false };
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, externalCheckpoint, pendingChange, readyResult, owner] = await Promise.all([
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
- progress ? { summary: progress.summary, occurredAt: progress.occurredAt } : null,
956
- externalCheckpoint ? { summary: externalCheckpoint.summary, occurredAt: externalCheckpoint.occurredAt } : null,
957
- ].filter((value) => Boolean(value))
958
- .sort((left, right) => right.occurredAt.getTime() - left.occurredAt.getTime())[0]?.summary
959
- ?? "No evidence-backed progress has been recorded yet.";
960
- const attentionReason = readyResult
961
- ? `Review the proposed Goal result: ${readyResult.preflight.outcome}`
962
- : pendingChange?.rationale ?? (goal.lifecycle === "draft" ? goal.alignmentQuestion : null);
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 ?? "No next step has been recorded.",
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) => db.insert(goals).values({
996
- orgId,
997
- title: data.title,
998
- description: data.description ?? null,
999
- alignmentQuestion: data.alignmentQuestion ?? null,
1000
- level: "task",
1001
- status: "planned",
1002
- lifecycle: "draft",
1003
- parentId: null,
1004
- ownerAgentId: null,
1005
- }).returning().then((rows) => rows[0]),
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
- const current = await requireGoal(db, id);
1008
- assertGoalOwner(current, actorAgentId);
1009
- const { title, description, alignmentQuestion } = data;
1010
- return db.update(goals).set({ title, description, alignmentQuestion, updatedAt: new Date() })
1011
- .where(eq(goals.id, id)).returning().then((rows) => rows[0] ?? null);
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: `Goal activated with initial ${input.initialContinuation.kind}: ${input.initialContinuation.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 [plan] = await tx.insert(goalPlans).values({
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 tx.update(goals).set({ planRevision: revision, updatedAt: new Date() }).where(eq(goals.id, id));
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
- const current = await requireGoal(db, id);
1093
- assertCanonicalActiveGoal(current);
1094
- assertGoalOwner(current, actorAgentId);
1095
- if (input.activityKind === "closeout" && !input.runRef) {
1096
- throw unprocessable("A closeout Activity requires a Run reference");
1097
- }
1098
- if (input.runRef) {
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
- const [activity] = await db.insert(goalActivities).values({
1108
- orgId: current.orgId,
1109
- goalId: id,
1110
- contractRevision: current.contractRevision,
1111
- submittedByAgentId: actorAgentId,
1112
- agentOwnerRefAtTime: current.ownerAgentId,
1113
- activityKind: input.activityKind ?? "progress",
1114
- commitmentRef: input.commitmentRef ?? null,
1115
- runRef: input.runRef ?? null,
1116
- summary: input.summary,
1117
- evidenceRefs: input.evidenceRefs,
1118
- idempotencyKey: input.idempotencyKey ?? null,
1119
- occurredAt: input.occurredAt ?? new Date(),
1120
- }).onConflictDoNothing().returning();
1121
- if (activity)
1122
- return activity;
1123
- if (input.idempotencyKey) {
1124
- const existingIdempotent = await db.select().from(goalActivities).where(and(eq(goalActivities.goalId, id), eq(goalActivities.idempotencyKey, input.idempotencyKey))).then((rows) => rows[0] ?? null);
1125
- if (existingIdempotent)
1126
- return existingIdempotent;
1127
- }
1128
- if (input.activityKind === "closeout" && input.runRef) {
1129
- const existingCloseout = await db.select().from(goalActivities).where(and(eq(goalActivities.goalId, id), eq(goalActivities.runRef, input.runRef), eq(goalActivities.activityKind, "closeout"))).then((rows) => rows[0] ?? null);
1130
- if (existingCloseout)
1131
- throw conflict("A closeout Activity already exists for this Run");
1132
- }
1133
- throw conflict("Goal Activity was already recorded");
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 requireGoal(database, id);
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 requireGoal(database, id);
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 Contract revision changed before proposal creation", {
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 Contract revision" },
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
- if (input.decision === "approve" && proposal.status === "applied")
1253
- return { proposal, stale: false };
1254
- if (input.decision === "reject" && proposal.status === "rejected")
1255
- return { proposal, stale: false };
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 requireGoal(database, proposal.goalId);
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
- return { proposal: rejected, stale: false };
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: "approved",
1299
- decisionNote: input.note ?? null,
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 Contract revision" },
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 Contract revision changed before proposal application");
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: `Applied Goal Contract revision ${nextRevision}: ${proposal.rationale}`,
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
- return { proposal: applied, stale: false };
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 Contract revision changed before proposal application", {
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
- return result.proposal;
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 requireGoal(database, id);
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 Contract revision changed before Result Proposal creation", {
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 requireGoal(database, proposal.goalId);
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 Contract revision changed before Result Proposal acceptance", {
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 requireGoal(database, proposal.goalId);
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
- return proposal;
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 requireGoal(database, proposal.goalId);
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 Contract revision changed before Result Proposal rejection");
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 previous = await tx.select().from(goalOwnerAssignments)
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 tx.update(goalOwnerAssignments).set({ endsAt: now }).where(eq(goalOwnerAssignments.id, previous.id));
1554
- const [assignment] = await tx.insert(goalOwnerAssignments).values({
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 tx.update(goals).set({ ownerAgentId: input.agentId, updatedAt: now }).where(eq(goals.id, id));
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 tx.update(goals).set({ focus: false }).where(eq(goals.orgId, current.orgId));
1574
- const [goal] = await tx.update(goals).set({ focus, updatedAt: new Date() }).where(eq(goals.id, id)).returning();
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 requireGoal(database, id);
2563
+ const current = await requireGoalForUpdate(database, id);
1582
2564
  return evaluateInTransaction(database, current, input, actorAgentId);
1583
2565
  });
1584
2566
  },