@rudderhq/server 0.2.0-canary.9 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (196) hide show
  1. package/dist/bootstrap/register-api-routes.d.ts.map +1 -1
  2. package/dist/bootstrap/register-api-routes.js +2 -0
  3. package/dist/bootstrap/register-api-routes.js.map +1 -1
  4. package/dist/bundled-plugins/plugin-linear/dist/ui/index.js +8 -1
  5. package/dist/bundled-plugins/plugin-linear/dist/ui/index.js.map +2 -2
  6. package/dist/bundled-plugins/plugin-linear/dist/worker.js +20 -5
  7. package/dist/bundled-plugins/plugin-linear/dist/worker.js.map +2 -2
  8. package/dist/home-paths.d.ts +2 -0
  9. package/dist/home-paths.d.ts.map +1 -1
  10. package/dist/home-paths.js +6 -1
  11. package/dist/home-paths.js.map +1 -1
  12. package/dist/index.d.ts +11 -0
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +55 -2
  15. package/dist/index.js.map +1 -1
  16. package/dist/langfuse-transcript.d.ts.map +1 -1
  17. package/dist/langfuse-transcript.js +16 -2
  18. package/dist/langfuse-transcript.js.map +1 -1
  19. package/dist/middleware/auth.d.ts.map +1 -1
  20. package/dist/middleware/auth.js +54 -1
  21. package/dist/middleware/auth.js.map +1 -1
  22. package/dist/onboarding-assets/ceo/HEARTBEAT.md +8 -4
  23. package/dist/onboarding-assets/default/HEARTBEAT.md +7 -4
  24. package/dist/routes/agents.d.ts.map +1 -1
  25. package/dist/routes/agents.js +79 -4
  26. package/dist/routes/agents.js.map +1 -1
  27. package/dist/routes/approvals.d.ts.map +1 -1
  28. package/dist/routes/approvals.js +47 -2
  29. package/dist/routes/approvals.js.map +1 -1
  30. package/dist/routes/chats.d.ts.map +1 -1
  31. package/dist/routes/chats.js +300 -92
  32. package/dist/routes/chats.js.map +1 -1
  33. package/dist/routes/costs.d.ts.map +1 -1
  34. package/dist/routes/costs.js +20 -0
  35. package/dist/routes/costs.js.map +1 -1
  36. package/dist/routes/issues.d.ts.map +1 -1
  37. package/dist/routes/issues.js +236 -22
  38. package/dist/routes/issues.js.map +1 -1
  39. package/dist/routes/onboarding.d.ts +3 -0
  40. package/dist/routes/onboarding.d.ts.map +1 -0
  41. package/dist/routes/onboarding.js +545 -0
  42. package/dist/routes/onboarding.js.map +1 -0
  43. package/dist/routes/orgs.d.ts.map +1 -1
  44. package/dist/routes/orgs.js +22 -0
  45. package/dist/routes/orgs.js.map +1 -1
  46. package/dist/services/activity.d.ts.map +1 -1
  47. package/dist/services/activity.js +32 -1
  48. package/dist/services/activity.js.map +1 -1
  49. package/dist/services/agent-run-context.d.ts +1 -0
  50. package/dist/services/agent-run-context.d.ts.map +1 -1
  51. package/dist/services/agent-run-context.js +1 -0
  52. package/dist/services/agent-run-context.js.map +1 -1
  53. package/dist/services/agents.d.ts +13 -13
  54. package/dist/services/automations.d.ts +2 -2
  55. package/dist/services/calendar.d.ts +4 -4
  56. package/dist/services/chat-assistant.d.ts +11 -2
  57. package/dist/services/chat-assistant.d.ts.map +1 -1
  58. package/dist/services/chat-assistant.js +143 -8
  59. package/dist/services/chat-assistant.js.map +1 -1
  60. package/dist/services/chats.d.ts +112 -13
  61. package/dist/services/chats.d.ts.map +1 -1
  62. package/dist/services/chats.js +218 -38
  63. package/dist/services/chats.js.map +1 -1
  64. package/dist/services/costs.d.ts +21 -0
  65. package/dist/services/costs.d.ts.map +1 -1
  66. package/dist/services/costs.js +102 -2
  67. package/dist/services/costs.js.map +1 -1
  68. package/dist/services/finance.d.ts +2 -2
  69. package/dist/services/goals.d.ts +12 -12
  70. package/dist/services/instance-settings.d.ts.map +1 -1
  71. package/dist/services/instance-settings.js +27 -16
  72. package/dist/services/instance-settings.js.map +1 -1
  73. package/dist/services/issue-approvals.d.ts +16 -2
  74. package/dist/services/issue-approvals.d.ts.map +1 -1
  75. package/dist/services/issue-approvals.js +27 -4
  76. package/dist/services/issue-approvals.js.map +1 -1
  77. package/dist/services/issue-review-wakeup.d.ts +49 -1
  78. package/dist/services/issue-review-wakeup.d.ts.map +1 -1
  79. package/dist/services/issue-review-wakeup.js +39 -2
  80. package/dist/services/issue-review-wakeup.js.map +1 -1
  81. package/dist/services/issues.d.ts +2 -1
  82. package/dist/services/issues.d.ts.map +1 -1
  83. package/dist/services/issues.js +126 -5
  84. package/dist/services/issues.js.map +1 -1
  85. package/dist/services/knowledge-portability/organization-skills.d.ts +1 -0
  86. package/dist/services/knowledge-portability/organization-skills.d.ts.map +1 -1
  87. package/dist/services/knowledge-portability/organization-skills.js +3 -2
  88. package/dist/services/knowledge-portability/organization-skills.js.map +1 -1
  89. package/dist/services/messenger.d.ts +5 -0
  90. package/dist/services/messenger.d.ts.map +1 -1
  91. package/dist/services/messenger.js +154 -15
  92. package/dist/services/messenger.js.map +1 -1
  93. package/dist/services/organization-workspace-browser.d.ts.map +1 -1
  94. package/dist/services/organization-workspace-browser.js +64 -9
  95. package/dist/services/organization-workspace-browser.js.map +1 -1
  96. package/dist/services/orgs.d.ts +1 -1
  97. package/dist/services/plugin-registry.d.ts +4 -4
  98. package/dist/services/projects.d.ts +1 -1
  99. package/dist/services/runtime-kernel/heartbeat.d.ts.map +1 -1
  100. package/dist/services/runtime-kernel/heartbeat.js +571 -31
  101. package/dist/services/runtime-kernel/heartbeat.js.map +1 -1
  102. package/dist/services/secrets.d.ts +5 -5
  103. package/dist/services/workspace-backups.d.ts.map +1 -1
  104. package/dist/services/workspace-backups.js +6 -0
  105. package/dist/services/workspace-backups.js.map +1 -1
  106. package/dist/services/workspace-runtime.d.ts.map +1 -1
  107. package/dist/services/workspace-runtime.js +2 -0
  108. package/dist/services/workspace-runtime.js.map +1 -1
  109. package/package.json +13 -13
  110. package/resources/bundled-skills/rudder/SKILL.md +72 -7
  111. package/resources/bundled-skills/rudder/references/cli-reference.md +34 -9
  112. package/resources/bundled-skills/rudder/references/organization-skills.md +12 -7
  113. package/resources/bundled-skills/rudder-create-agent/references/cli-reference.md +1 -0
  114. package/skills/rudder/SKILL.md +72 -7
  115. package/skills/rudder/references/cli-reference.md +34 -9
  116. package/skills/rudder/references/organization-skills.md +12 -7
  117. package/skills/rudder-create-agent/references/cli-reference.md +1 -0
  118. package/ui-dist/assets/{_basePickBy-aX2f6dVl.js → _basePickBy-EvWeCTRb.js} +1 -1
  119. package/ui-dist/assets/{_baseUniq-BYwL7heN.js → _baseUniq-C_DXAETg.js} +1 -1
  120. package/ui-dist/assets/{arc-BG9f5pwY.js → arc-BWTkVM-u.js} +1 -1
  121. package/ui-dist/assets/{architectureDiagram-2XIMDMQ5-BFFQoJJ1.js → architectureDiagram-2XIMDMQ5-yyX54Dgl.js} +1 -1
  122. package/ui-dist/assets/{blockDiagram-WCTKOSBZ-Bvx1IB1z.js → blockDiagram-WCTKOSBZ-DleWvS8P.js} +1 -1
  123. package/ui-dist/assets/{c4Diagram-IC4MRINW-DJbCE4sh.js → c4Diagram-IC4MRINW-CltWqWC_.js} +1 -1
  124. package/ui-dist/assets/channel-Gdzxe2a1.js +1 -0
  125. package/ui-dist/assets/{chunk-4BX2VUAB-BOVbLFsN.js → chunk-4BX2VUAB-CA6RvGN7.js} +1 -1
  126. package/ui-dist/assets/{chunk-55IACEB6-D5pKj6S9.js → chunk-55IACEB6-D_EpF39w.js} +1 -1
  127. package/ui-dist/assets/{chunk-FMBD7UC4-OY5xuJeR.js → chunk-FMBD7UC4-CYMkBnLy.js} +1 -1
  128. package/ui-dist/assets/{chunk-JSJVCQXG-C5X67KZg.js → chunk-JSJVCQXG-CIY2Cb1T.js} +1 -1
  129. package/ui-dist/assets/{chunk-KX2RTZJC-C-4PZ9Q_.js → chunk-KX2RTZJC-BUyGoIKj.js} +1 -1
  130. package/ui-dist/assets/{chunk-NQ4KR5QH-XysPlqPj.js → chunk-NQ4KR5QH-DkntSLtY.js} +1 -1
  131. package/ui-dist/assets/{chunk-QZHKN3VN-B5wEbFHo.js → chunk-QZHKN3VN-DeEs3yL0.js} +1 -1
  132. package/ui-dist/assets/{chunk-WL4C6EOR-BanwYFa2.js → chunk-WL4C6EOR-Va8TkdTb.js} +1 -1
  133. package/ui-dist/assets/classDiagram-VBA2DB6C-BN6WyuN3.js +1 -0
  134. package/ui-dist/assets/classDiagram-v2-RAHNMMFH-BN6WyuN3.js +1 -0
  135. package/ui-dist/assets/clone-DL9OCUyP.js +1 -0
  136. package/ui-dist/assets/{cose-bilkent-S5V4N54A-Cd4q2swD.js → cose-bilkent-S5V4N54A-Bb6NLaVm.js} +1 -1
  137. package/ui-dist/assets/{dagre-KLK3FWXG-B_VyOhf3.js → dagre-KLK3FWXG-DpqLnZ3A.js} +1 -1
  138. package/ui-dist/assets/{diagram-E7M64L7V-BRoG4Mz6.js → diagram-E7M64L7V-D7J8NbEW.js} +1 -1
  139. package/ui-dist/assets/{diagram-IFDJBPK2-CRU_A9p9.js → diagram-IFDJBPK2-Ds2u81Zi.js} +1 -1
  140. package/ui-dist/assets/{diagram-P4PSJMXO-BYSQDbfb.js → diagram-P4PSJMXO-BwBplO7L.js} +1 -1
  141. package/ui-dist/assets/{erDiagram-INFDFZHY-v5j1kyWr.js → erDiagram-INFDFZHY-Ba-Ynr8U.js} +1 -1
  142. package/ui-dist/assets/{flowDiagram-PKNHOUZH-C06ZQgTj.js → flowDiagram-PKNHOUZH-FnOXpXb_.js} +1 -1
  143. package/ui-dist/assets/{ganttDiagram-A5KZAMGK-Dw9p5nQ1.js → ganttDiagram-A5KZAMGK-B8-MpUjy.js} +1 -1
  144. package/ui-dist/assets/{gitGraphDiagram-K3NZZRJ6-CrpXRIaP.js → gitGraphDiagram-K3NZZRJ6-DvyBGQTF.js} +1 -1
  145. package/ui-dist/assets/{graph-ClTUmULf.js → graph-BdpIVR-I.js} +1 -1
  146. package/ui-dist/assets/{index-DK13xhRv.js → index-3CPMGfu4.js} +1 -1
  147. package/ui-dist/assets/index-44A3IjSd.css +1 -0
  148. package/ui-dist/assets/{index-L6M3nVxh.js → index-B4seykMn.js} +1 -1
  149. package/ui-dist/assets/{index-Bpc2gRVo.js → index-B5Lq7qho.js} +1 -1
  150. package/ui-dist/assets/{index-DkDkjZ-D.js → index-BKWZYXO6.js} +1 -1
  151. package/ui-dist/assets/{index-DxzAgTWd.js → index-BO-P9C91.js} +1 -1
  152. package/ui-dist/assets/{index-BvGogi9q.js → index-BO9KiNr0.js} +1 -1
  153. package/ui-dist/assets/{index-Btwy7Cp-.js → index-Bd_GitJ7.js} +1 -1
  154. package/ui-dist/assets/{index-DNlWBtHa.js → index-BeyQP4jc.js} +1 -1
  155. package/ui-dist/assets/{index-4uxadHo5.js → index-Bp3rYm9R.js} +1 -1
  156. package/ui-dist/assets/{index-DWFMs9uk.js → index-CBAKsDOH.js} +1 -1
  157. package/ui-dist/assets/{index-T81awgzh.js → index-CWPEuLky.js} +1 -1
  158. package/ui-dist/assets/{index-DAhPD1Ss.js → index-Ce0xbQ5p.js} +1 -1
  159. package/ui-dist/assets/{index-_x9smX4T.js → index-ChyWxMPa.js} +1 -1
  160. package/ui-dist/assets/{index-CIr7H9OI.js → index-CkEo4bIl.js} +1 -1
  161. package/ui-dist/assets/{index-sLGLHxIu.js → index-CvzsgQH3.js} +1 -1
  162. package/ui-dist/assets/{index-D-6z8wxx.js → index-DF0X3XZi.js} +1 -1
  163. package/ui-dist/assets/{index-BVfM9ax8.js → index-DNFqhIup.js} +1 -1
  164. package/ui-dist/assets/index-Dfi8PbGx.js +1484 -0
  165. package/ui-dist/assets/{index-C_BTFRTX.js → index-Dys_qAzR.js} +1 -1
  166. package/ui-dist/assets/{index-Cr7n11UG.js → index-DzKALBsQ.js} +1 -1
  167. package/ui-dist/assets/{index-CqYInp-c.js → index-Qe9bMaYk.js} +1 -1
  168. package/ui-dist/assets/{index-CQWmziMF.js → index-baeevrWz.js} +1 -1
  169. package/ui-dist/assets/{index-BYC_xlrx.js → index-bs5pLhnN.js} +1 -1
  170. package/ui-dist/assets/{infoDiagram-LFFYTUFH-BA3VxOIU.js → infoDiagram-LFFYTUFH-51Iz4iFI.js} +1 -1
  171. package/ui-dist/assets/{ishikawaDiagram-PHBUUO56-DGrizi0S.js → ishikawaDiagram-PHBUUO56-XMkPw0tW.js} +1 -1
  172. package/ui-dist/assets/{journeyDiagram-4ABVD52K-6ey34a7e.js → journeyDiagram-4ABVD52K-DAX0bTCG.js} +1 -1
  173. package/ui-dist/assets/{kanban-definition-K7BYSVSG-CwNnmsam.js → kanban-definition-K7BYSVSG-DndcgBkd.js} +1 -1
  174. package/ui-dist/assets/{layout-buNxvllr.js → layout-DE8DhR5g.js} +1 -1
  175. package/ui-dist/assets/{linear-BPWhxaRl.js → linear-B6lAW9Wb.js} +1 -1
  176. package/ui-dist/assets/{mermaid.core-Cajx0s-z.js → mermaid.core-BG--kYhA.js} +4 -4
  177. package/ui-dist/assets/{mindmap-definition-YRQLILUH-Bf5InEp-.js → mindmap-definition-YRQLILUH-DkjV0oE3.js} +1 -1
  178. package/ui-dist/assets/{pieDiagram-SKSYHLDU-CZFz7NWC.js → pieDiagram-SKSYHLDU-D03TjqYu.js} +1 -1
  179. package/ui-dist/assets/{quadrantDiagram-337W2JSQ-XBmKVoc9.js → quadrantDiagram-337W2JSQ-C0oqv-xU.js} +1 -1
  180. package/ui-dist/assets/{requirementDiagram-Z7DCOOCP-BkgdDv0H.js → requirementDiagram-Z7DCOOCP-okIS8feM.js} +1 -1
  181. package/ui-dist/assets/{sankeyDiagram-WA2Y5GQK-CASFR28i.js → sankeyDiagram-WA2Y5GQK-WOnxUdkO.js} +1 -1
  182. package/ui-dist/assets/{sequenceDiagram-2WXFIKYE-BzY7LMRv.js → sequenceDiagram-2WXFIKYE-RVCXfMRR.js} +1 -1
  183. package/ui-dist/assets/{stateDiagram-RAJIS63D-C9UMSk36.js → stateDiagram-RAJIS63D-CZFHvVtT.js} +1 -1
  184. package/ui-dist/assets/stateDiagram-v2-FVOUBMTO-DgYYudAJ.js +1 -0
  185. package/ui-dist/assets/{timeline-definition-YZTLITO2-D6m4R4xe.js → timeline-definition-YZTLITO2-S0uy5mlJ.js} +1 -1
  186. package/ui-dist/assets/{treemap-KZPCXAKY-7V9mnT8T.js → treemap-KZPCXAKY-Bhyg_yHs.js} +1 -1
  187. package/ui-dist/assets/{vennDiagram-LZ73GAT5-Ci-sfAyq.js → vennDiagram-LZ73GAT5-EnVupOQz.js} +1 -1
  188. package/ui-dist/assets/{xychartDiagram-JWTSCODW-BayXhRSu.js → xychartDiagram-JWTSCODW-BYpdJxGK.js} +1 -1
  189. package/ui-dist/index.html +2 -2
  190. package/ui-dist/assets/channel-ClX7n84B.js +0 -1
  191. package/ui-dist/assets/classDiagram-VBA2DB6C-DvWbsnVz.js +0 -1
  192. package/ui-dist/assets/classDiagram-v2-RAHNMMFH-DvWbsnVz.js +0 -1
  193. package/ui-dist/assets/clone-Dla3A8ZA.js +0 -1
  194. package/ui-dist/assets/index-CSANx6ee.css +0 -1
  195. package/ui-dist/assets/index-DCa9-Sy-.js +0 -1439
  196. package/ui-dist/assets/stateDiagram-v2-FVOUBMTO-DWVhbAdj.js +0 -1
@@ -1,12 +1,12 @@
1
1
  import { Router } from "express";
2
2
  import multer from "multer";
3
3
  import { buildIssueDocumentsPrompt } from "@rudderhq/agent-runtime-utils/server-utils";
4
- import { addIssueCommentSchema, createIssueAttachmentMetadataSchema, createIssueWorkspaceAttachmentSchema, createIssueWorkProductSchema, createIssueLabelSchema, checkoutIssueSchema, createIssueSchema, linkIssueApprovalSchema, issueDocumentKeySchema, reorderIssueSchema, updateIssueLabelSchema, updateIssueWorkProductSchema, upsertIssueDocumentSchema, updateIssueSchema, } from "@rudderhq/shared";
4
+ import { addIssueCommentSchema, createIssueAttachmentMetadataSchema, createIssueWorkspaceAttachmentSchema, createIssueWorkProductSchema, createIssueLabelSchema, checkoutIssueSchema, createIssueSchema, linkIssueApprovalSchema, reportIssueCommitSchema, issueDocumentKeySchema, reorderIssueSchema, updateIssueLabelSchema, updateIssueWorkProductSchema, upsertIssueDocumentSchema, updateIssueSchema, isUuidLike, } from "@rudderhq/shared";
5
5
  import { validate } from "../middleware/validate.js";
6
6
  import { accessService, agentService, executionWorkspaceService, goalService, heartbeatService, issueApprovalService, issueService, documentService, logActivity, projectService, automationService, workProductService, } from "../services/index.js";
7
7
  import { organizationWorkspaceBrowserService } from "../services/organization-workspace-browser.js";
8
8
  import { logger } from "../middleware/logger.js";
9
- import { forbidden, HttpError, unauthorized } from "../errors.js";
9
+ import { forbidden, HttpError, unauthorized, unprocessable } from "../errors.js";
10
10
  import { assertBoard, assertCompanyAccess, getActorInfo } from "./authz.js";
11
11
  import { shouldWakeAssigneeOnCheckout } from "./issues-checkout-wakeup.js";
12
12
  import { isAllowedContentType, MAX_ATTACHMENT_BYTES } from "../attachment-types.js";
@@ -139,6 +139,41 @@ export function issueRoutes(db, storage) {
139
139
  }
140
140
  return true;
141
141
  }
142
+ function readIssueIdFromRunContext(contextSnapshot) {
143
+ if (!contextSnapshot || typeof contextSnapshot !== "object")
144
+ return null;
145
+ const issueId = contextSnapshot.issueId;
146
+ return typeof issueId === "string" && issueId.trim() ? issueId.trim() : null;
147
+ }
148
+ async function resolveAgentCommitRunId(req, res, issue) {
149
+ if (req.actor.type !== "agent")
150
+ return { ok: true, runId: null };
151
+ const actorAgentId = req.actor.agentId;
152
+ if (!actorAgentId) {
153
+ res.status(403).json({ error: "Agent authentication required" });
154
+ return { ok: false };
155
+ }
156
+ const runId = req.actor.runId?.trim();
157
+ if (!runId)
158
+ return { ok: true, runId: null };
159
+ if (!isUuidLike(runId)) {
160
+ res.status(403).json({ error: "Run context is not valid for this issue" });
161
+ return { ok: false };
162
+ }
163
+ const run = await heartbeat.getRun(runId);
164
+ const runIssueId = readIssueIdFromRunContext(run?.contextSnapshot);
165
+ const runBoundToIssue = issue.checkoutRunId === runId ||
166
+ issue.executionRunId === runId ||
167
+ runIssueId === issue.id;
168
+ if (!run ||
169
+ run.orgId !== issue.orgId ||
170
+ run.agentId !== actorAgentId ||
171
+ !runBoundToIssue) {
172
+ res.status(403).json({ error: "Run context is not valid for this issue" });
173
+ return { ok: false };
174
+ }
175
+ return { ok: true, runId };
176
+ }
142
177
  async function normalizeIssueIdentifier(rawId) {
143
178
  if (/^[A-Z]+-\d+$/i.test(rawId)) {
144
179
  const issue = await svc.getByIdentifier(rawId);
@@ -158,6 +193,29 @@ export function issueRoutes(db, storage) {
158
193
  function isReviewerAgentForIssue(actor, issue) {
159
194
  return actor.actorType === "agent" && Boolean(actor.agentId) && actor.agentId === issue.reviewerAgentId;
160
195
  }
196
+ function statusForReviewDecision(decision) {
197
+ switch (decision) {
198
+ case "approve":
199
+ return "done";
200
+ case "request_changes":
201
+ return "in_progress";
202
+ case "blocked":
203
+ return "blocked";
204
+ case "needs_followup":
205
+ return null;
206
+ default:
207
+ return null;
208
+ }
209
+ }
210
+ function statusAcceptsReviewerDecision(status) {
211
+ return status === "in_review" || status === "blocked";
212
+ }
213
+ function reviewerDecisionRequiresHumanHandoff(decision) {
214
+ return decision === "blocked";
215
+ }
216
+ function commitSubject(message) {
217
+ return message.split(/\r?\n/, 1)[0]?.trim() || message.trim();
218
+ }
161
219
  // Resolve issue identifiers (e.g. "PAP-39") to UUIDs for all /issues/:id routes
162
220
  router.param("id", async (req, res, next, rawId) => {
163
221
  try {
@@ -816,7 +874,7 @@ export function issueRoutes(db, storage) {
816
874
  if (!(await assertCanManageIssueApprovalLinks(req, res, issue.orgId)))
817
875
  return;
818
876
  const actor = getActorInfo(req);
819
- await issueApprovalsSvc.link(id, req.body.approvalId, {
877
+ const link = await issueApprovalsSvc.link(id, req.body.approvalId, {
820
878
  agentId: actor.agentId,
821
879
  userId: actor.actorType === "user" ? actor.actorId : null,
822
880
  });
@@ -829,7 +887,10 @@ export function issueRoutes(db, storage) {
829
887
  action: "issue.approval_linked",
830
888
  entityType: "issue",
831
889
  entityId: issue.id,
832
- details: { approvalId: req.body.approvalId },
890
+ details: {
891
+ approvalId: req.body.approvalId,
892
+ ...(link?.createdAt ? { linkCreatedAt: link.createdAt.toISOString() } : {}),
893
+ },
833
894
  });
834
895
  const approvals = await issueApprovalsSvc.listApprovalsForIssue(id);
835
896
  res.status(201).json(approvals);
@@ -866,10 +927,18 @@ export function issueRoutes(db, storage) {
866
927
  await assertCanAssignTasks(req, orgId);
867
928
  }
868
929
  const actor = getActorInfo(req);
869
- const issue = await svc.create(orgId, {
930
+ const createInput = {
870
931
  ...req.body,
871
932
  createdByAgentId: actor.agentId,
872
933
  createdByUserId: actor.actorType === "user" ? actor.actorId : null,
934
+ };
935
+ const hasExplicitAssignee = Object.prototype.hasOwnProperty.call(req.body, "assigneeAgentId") ||
936
+ Object.prototype.hasOwnProperty.call(req.body, "assigneeUserId");
937
+ if (actor.actorType === "agent" && actor.agentId && !hasExplicitAssignee) {
938
+ createInput.assigneeAgentId = actor.agentId;
939
+ }
940
+ const issue = await svc.create(orgId, {
941
+ ...createInput,
873
942
  });
874
943
  await logActivity(db, {
875
944
  orgId,
@@ -933,18 +1002,36 @@ export function issueRoutes(db, storage) {
933
1002
  return;
934
1003
  const actor = getActorInfo(req);
935
1004
  const isClosed = existing.status === "done" || existing.status === "cancelled";
936
- const { comment: commentBody, reopen: reopenRequested, hiddenAt: hiddenAtRaw, ...updateFields } = req.body;
1005
+ const { comment: commentBody, reopen: reopenRequested, hiddenAt: hiddenAtRaw, reviewDecision, ...updateFields } = req.body;
937
1006
  if (hiddenAtRaw !== undefined) {
938
1007
  updateFields.hiddenAt = hiddenAtRaw ? new Date(hiddenAtRaw) : null;
939
1008
  }
940
1009
  if (commentBody && reopenRequested === true && isClosed && updateFields.status === undefined) {
941
1010
  updateFields.status = "todo";
942
1011
  }
1012
+ if (reviewDecision !== undefined) {
1013
+ if (!commentBody) {
1014
+ throw unprocessable("Reviewer decisions require a comment");
1015
+ }
1016
+ if (!statusAcceptsReviewerDecision(existing.status)) {
1017
+ throw unprocessable("Reviewer decisions can only be recorded while the issue is in_review or blocked");
1018
+ }
1019
+ if (actor.actorType === "agent" && !isReviewerAgentForIssue(actor, existing)) {
1020
+ throw forbidden("Only the reviewer agent can record a reviewer decision");
1021
+ }
1022
+ const decisionStatus = statusForReviewDecision(reviewDecision);
1023
+ if (decisionStatus) {
1024
+ updateFields.status = decisionStatus;
1025
+ }
1026
+ else {
1027
+ delete updateFields.status;
1028
+ }
1029
+ }
943
1030
  let reviewedCompletionNormalized = false;
944
1031
  if (updateFields.status === "done" &&
945
1032
  issueHasReviewer(existing) &&
946
1033
  actor.actorType === "agent" &&
947
- !(existing.status === "in_review" && isReviewerAgentForIssue(actor, existing))) {
1034
+ !(statusAcceptsReviewerDecision(existing.status) && isReviewerAgentForIssue(actor, existing))) {
948
1035
  updateFields.status = "in_review";
949
1036
  reviewedCompletionNormalized = true;
950
1037
  }
@@ -1045,20 +1132,66 @@ export function issueRoutes(db, storage) {
1045
1132
  },
1046
1133
  });
1047
1134
  }
1135
+ if (reviewDecision !== undefined) {
1136
+ const reviewOutcome = reviewDecision === "blocked" ? "human_handoff" : "review_closed";
1137
+ await logActivity(db, {
1138
+ orgId: issue.orgId,
1139
+ actorType: actor.actorType,
1140
+ actorId: actor.actorId,
1141
+ agentId: actor.agentId,
1142
+ runId: actor.runId,
1143
+ action: "issue.review_decision_recorded",
1144
+ entityType: "issue",
1145
+ entityId: issue.id,
1146
+ details: {
1147
+ decision: reviewDecision,
1148
+ outcome: reviewOutcome,
1149
+ operatorActionRequired: reviewOutcome === "human_handoff",
1150
+ status: issue.status,
1151
+ identifier: issue.identifier,
1152
+ commentId: comment?.id ?? null,
1153
+ },
1154
+ });
1155
+ if (reviewerDecisionRequiresHumanHandoff(reviewDecision)) {
1156
+ await logActivity(db, {
1157
+ orgId: issue.orgId,
1158
+ actorType: actor.actorType,
1159
+ actorId: actor.actorId,
1160
+ agentId: actor.agentId,
1161
+ runId: actor.runId,
1162
+ action: "issue.human_intervention_required",
1163
+ entityType: "issue",
1164
+ entityId: issue.id,
1165
+ details: {
1166
+ decision: reviewDecision,
1167
+ status: issue.status,
1168
+ identifier: issue.identifier,
1169
+ issueTitle: issue.title,
1170
+ commentId: comment?.id ?? null,
1171
+ previousReviewerAgentId: existing.reviewerAgentId,
1172
+ previousReviewerUserId: existing.reviewerUserId,
1173
+ nextAction: "Human/operator intervention is required before agent review can continue.",
1174
+ },
1175
+ });
1176
+ }
1177
+ }
1048
1178
  const assigneeChanged = assigneeWillChange;
1049
1179
  const reviewerChanged = reviewerWillChange;
1050
1180
  const statusChangedFromBacklog = existing.status === "backlog" &&
1051
1181
  issue.status !== "backlog" &&
1052
- req.body.status !== undefined;
1182
+ updateFields.status !== undefined;
1053
1183
  const statusChangedToInReview = existing.status !== "in_review" &&
1054
1184
  issue.status === "in_review" &&
1055
- req.body.status !== undefined;
1056
- const statusChangedFromReviewToInProgress = existing.status === "in_review" &&
1057
- issue.status === "in_progress" &&
1058
- req.body.status !== undefined;
1059
- const reviewerChangedInReview = reviewerChanged &&
1060
- existing.status === "in_review" &&
1061
- issue.status === "in_review";
1185
+ updateFields.status !== undefined;
1186
+ const statusChangedToBlocked = existing.status !== "blocked" &&
1187
+ issue.status === "blocked" &&
1188
+ updateFields.status !== undefined;
1189
+ const statusReturnedFromReviewToAssignee = statusAcceptsReviewerDecision(existing.status) &&
1190
+ (issue.status === "in_progress" || issue.status === "todo") &&
1191
+ updateFields.status !== undefined;
1192
+ const reviewerChangedInReviewableStatus = reviewerChanged &&
1193
+ statusAcceptsReviewerDecision(existing.status) &&
1194
+ statusAcceptsReviewerDecision(issue.status);
1062
1195
  // Merge all wakeups from this update into one enqueue per agent to avoid duplicate runs.
1063
1196
  void (async () => {
1064
1197
  const wakeups = new Map();
@@ -1108,16 +1241,33 @@ export function issueRoutes(db, storage) {
1108
1241
  },
1109
1242
  });
1110
1243
  }
1111
- if (!assigneeChanged && statusChangedFromReviewToInProgress && issue.assigneeAgentId) {
1244
+ if (!assigneeChanged && statusReturnedFromReviewToAssignee && issue.assigneeAgentId) {
1245
+ const commentContext = comment
1246
+ ? {
1247
+ commentId: comment.id,
1248
+ wakeCommentId: comment.id,
1249
+ comment: {
1250
+ id: comment.id,
1251
+ body: comment.body,
1252
+ authorAgentId: comment.authorAgentId,
1253
+ authorUserId: comment.authorUserId,
1254
+ },
1255
+ }
1256
+ : {};
1112
1257
  wakeups.set(issue.assigneeAgentId, {
1113
1258
  source: "assignment",
1114
1259
  triggerDetail: "system",
1115
1260
  reason: "issue_changes_requested",
1116
- payload: { issueId: issue.id, mutation: "review_changes_requested" },
1261
+ payload: {
1262
+ issueId: issue.id,
1263
+ mutation: "review_changes_requested",
1264
+ ...(comment ? { commentId: comment.id } : {}),
1265
+ },
1117
1266
  requestedByActorType: actor.actorType,
1118
1267
  requestedByActorId: actor.actorId,
1119
1268
  contextSnapshot: {
1120
1269
  issueId: issue.id,
1270
+ taskId: issue.id,
1121
1271
  source: "issue.review_changes_requested",
1122
1272
  wakeSource: "assignment",
1123
1273
  wakeReason: "issue_changes_requested",
@@ -1128,19 +1278,26 @@ export function issueRoutes(db, storage) {
1128
1278
  status: issue.status,
1129
1279
  priority: issue.priority,
1130
1280
  },
1281
+ ...commentContext,
1131
1282
  },
1132
1283
  });
1133
1284
  }
1134
- if ((statusChangedToInReview || reviewerChangedInReview) && issue.reviewerAgentId) {
1135
- const mutation = statusChangedToInReview ? "status_to_in_review" : "reviewer_changed_in_review";
1285
+ if ((statusChangedToInReview || statusChangedToBlocked || reviewerChangedInReviewableStatus) && issue.reviewerAgentId) {
1286
+ const mutation = statusChangedToInReview
1287
+ ? "status_to_in_review"
1288
+ : statusChangedToBlocked
1289
+ ? "status_to_blocked"
1290
+ : issue.status === "blocked"
1291
+ ? "reviewer_changed_blocked"
1292
+ : "reviewer_changed_in_review";
1136
1293
  const actorIsReviewerAgent = actor.actorType === "agent" && actor.actorId === issue.reviewerAgentId;
1137
1294
  const actorIsAssigneeAgent = actor.actorType === "agent" && actor.actorId === issue.assigneeAgentId;
1138
- const assigneeHandoffToReview = statusChangedToInReview && actorIsAssigneeAgent;
1295
+ const assigneeHandoffToReview = (statusChangedToInReview || statusChangedToBlocked) && actorIsAssigneeAgent;
1139
1296
  if (!actorIsReviewerAgent || assigneeHandoffToReview) {
1140
1297
  wakeups.set(issue.reviewerAgentId, buildIssueReviewWakeupOptions({
1141
1298
  issue,
1142
1299
  mutation,
1143
- contextSource: statusChangedToInReview ? "issue.status_change" : "issue.reviewer_change",
1300
+ contextSource: statusChangedToInReview || statusChangedToBlocked ? "issue.status_change" : "issue.reviewer_change",
1144
1301
  requestedByActorType: actor.actorType,
1145
1302
  requestedByActorId: actor.actorId,
1146
1303
  }));
@@ -1324,6 +1481,62 @@ export function issueRoutes(db, storage) {
1324
1481
  });
1325
1482
  res.json(released);
1326
1483
  });
1484
+ router.post("/issues/:id/commit", validate(reportIssueCommitSchema), async (req, res) => {
1485
+ const id = req.params.id;
1486
+ const issue = await svc.getById(id);
1487
+ if (!issue) {
1488
+ res.status(404).json({ error: "Issue not found" });
1489
+ return;
1490
+ }
1491
+ assertCompanyAccess(req, issue.orgId);
1492
+ if (req.actor.type !== "agent") {
1493
+ res.status(403).json({ error: "Agent authentication required" });
1494
+ return;
1495
+ }
1496
+ if (!(await assertAgentRunCheckoutOwnership(req, res, issue)))
1497
+ return;
1498
+ const actor = getActorInfo(req);
1499
+ const commitRun = await resolveAgentCommitRunId(req, res, issue);
1500
+ if (!commitRun.ok)
1501
+ return;
1502
+ const sha = req.body.sha.trim().toLowerCase();
1503
+ const subject = commitSubject(req.body.message);
1504
+ const shortSha = sha.slice(0, 7);
1505
+ if (commitRun.runId) {
1506
+ await heartbeat.reportRunActivity(commitRun.runId).catch((err) => logger.warn({ err, runId: commitRun.runId }, "failed to clear detached run warning after issue commit activity"));
1507
+ }
1508
+ await logActivity(db, {
1509
+ orgId: issue.orgId,
1510
+ actorType: actor.actorType,
1511
+ actorId: actor.actorId,
1512
+ agentId: actor.agentId,
1513
+ runId: commitRun.runId,
1514
+ action: "issue.code_committed",
1515
+ entityType: "issue",
1516
+ entityId: issue.id,
1517
+ details: {
1518
+ sha,
1519
+ shortSha,
1520
+ message: req.body.message,
1521
+ subject,
1522
+ identifier: issue.identifier,
1523
+ issueTitle: issue.title,
1524
+ branch: req.body.branch ?? null,
1525
+ repoPath: req.body.repoPath ?? null,
1526
+ workspacePath: req.body.workspacePath ?? null,
1527
+ commitCount: req.body.commitCount ?? 1,
1528
+ },
1529
+ });
1530
+ res.status(201).json({
1531
+ ok: true,
1532
+ issueId: issue.id,
1533
+ sha,
1534
+ shortSha,
1535
+ message: req.body.message,
1536
+ subject,
1537
+ runId: commitRun.runId,
1538
+ });
1539
+ });
1327
1540
  router.get("/issues/:id/comments", async (req, res) => {
1328
1541
  const id = req.params.id;
1329
1542
  const issue = await svc.getById(id);
@@ -1484,7 +1697,8 @@ export function issueRoutes(db, storage) {
1484
1697
  const assigneeId = currentIssue.assigneeAgentId;
1485
1698
  const actorIsAgent = actor.actorType === "agent";
1486
1699
  const selfComment = actorIsAgent && actor.actorId === assigneeId;
1487
- const skipWake = selfComment || isClosed;
1700
+ const backlogComment = currentIssue.status === "backlog";
1701
+ const skipWake = selfComment || isClosed || backlogComment;
1488
1702
  if (assigneeId && (reopened || !skipWake)) {
1489
1703
  if (reopened) {
1490
1704
  wakeups.set(assigneeId, {