@rudderhq/server 0.2.0-canary.9 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (192) 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 +17 -3
  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 +62 -3
  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 +30 -1
  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 +285 -46
  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 +229 -19
  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 +10 -1
  57. package/dist/services/chat-assistant.d.ts.map +1 -1
  58. package/dist/services/chat-assistant.js +102 -3
  59. package/dist/services/chat-assistant.js.map +1 -1
  60. package/dist/services/chats.d.ts +87 -12
  61. package/dist/services/chats.d.ts.map +1 -1
  62. package/dist/services/chats.js +185 -10
  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 +76 -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 +25 -16
  72. package/dist/services/instance-settings.js.map +1 -1
  73. package/dist/services/issue-review-wakeup.d.ts +49 -1
  74. package/dist/services/issue-review-wakeup.d.ts.map +1 -1
  75. package/dist/services/issue-review-wakeup.js +39 -2
  76. package/dist/services/issue-review-wakeup.js.map +1 -1
  77. package/dist/services/issues.d.ts +2 -1
  78. package/dist/services/issues.d.ts.map +1 -1
  79. package/dist/services/issues.js +126 -5
  80. package/dist/services/issues.js.map +1 -1
  81. package/dist/services/knowledge-portability/organization-skills.d.ts +1 -0
  82. package/dist/services/knowledge-portability/organization-skills.d.ts.map +1 -1
  83. package/dist/services/knowledge-portability/organization-skills.js +3 -2
  84. package/dist/services/knowledge-portability/organization-skills.js.map +1 -1
  85. package/dist/services/messenger.d.ts +5 -0
  86. package/dist/services/messenger.d.ts.map +1 -1
  87. package/dist/services/messenger.js +147 -9
  88. package/dist/services/messenger.js.map +1 -1
  89. package/dist/services/organization-workspace-browser.d.ts.map +1 -1
  90. package/dist/services/organization-workspace-browser.js +64 -9
  91. package/dist/services/organization-workspace-browser.js.map +1 -1
  92. package/dist/services/orgs.d.ts +1 -1
  93. package/dist/services/plugin-registry.d.ts +4 -4
  94. package/dist/services/projects.d.ts +1 -1
  95. package/dist/services/runtime-kernel/heartbeat.d.ts.map +1 -1
  96. package/dist/services/runtime-kernel/heartbeat.js +567 -29
  97. package/dist/services/runtime-kernel/heartbeat.js.map +1 -1
  98. package/dist/services/secrets.d.ts +5 -5
  99. package/dist/services/workspace-backups.d.ts.map +1 -1
  100. package/dist/services/workspace-backups.js +6 -0
  101. package/dist/services/workspace-backups.js.map +1 -1
  102. package/dist/services/workspace-runtime.d.ts.map +1 -1
  103. package/dist/services/workspace-runtime.js +2 -0
  104. package/dist/services/workspace-runtime.js.map +1 -1
  105. package/package.json +13 -13
  106. package/resources/bundled-skills/rudder/SKILL.md +72 -7
  107. package/resources/bundled-skills/rudder/references/cli-reference.md +34 -9
  108. package/resources/bundled-skills/rudder/references/organization-skills.md +12 -7
  109. package/resources/bundled-skills/rudder-create-agent/references/cli-reference.md +1 -0
  110. package/skills/rudder/SKILL.md +72 -7
  111. package/skills/rudder/references/cli-reference.md +34 -9
  112. package/skills/rudder/references/organization-skills.md +12 -7
  113. package/skills/rudder-create-agent/references/cli-reference.md +1 -0
  114. package/ui-dist/assets/{_basePickBy-aX2f6dVl.js → _basePickBy-3Hg7N37c.js} +1 -1
  115. package/ui-dist/assets/{_baseUniq-BYwL7heN.js → _baseUniq-Bvy8WJh0.js} +1 -1
  116. package/ui-dist/assets/{arc-BG9f5pwY.js → arc-DrmvGX4U.js} +1 -1
  117. package/ui-dist/assets/{architectureDiagram-2XIMDMQ5-BFFQoJJ1.js → architectureDiagram-2XIMDMQ5-vbevcV-8.js} +1 -1
  118. package/ui-dist/assets/{blockDiagram-WCTKOSBZ-Bvx1IB1z.js → blockDiagram-WCTKOSBZ-DvupMRN9.js} +1 -1
  119. package/ui-dist/assets/{c4Diagram-IC4MRINW-DJbCE4sh.js → c4Diagram-IC4MRINW-CbsNVA8e.js} +1 -1
  120. package/ui-dist/assets/channel-DhW0A-FV.js +1 -0
  121. package/ui-dist/assets/{chunk-4BX2VUAB-BOVbLFsN.js → chunk-4BX2VUAB-BL4OUqNV.js} +1 -1
  122. package/ui-dist/assets/{chunk-55IACEB6-D5pKj6S9.js → chunk-55IACEB6-DFwq2ebc.js} +1 -1
  123. package/ui-dist/assets/{chunk-FMBD7UC4-OY5xuJeR.js → chunk-FMBD7UC4-Cyl6kF9G.js} +1 -1
  124. package/ui-dist/assets/{chunk-JSJVCQXG-C5X67KZg.js → chunk-JSJVCQXG-v4mfLtsY.js} +1 -1
  125. package/ui-dist/assets/{chunk-KX2RTZJC-C-4PZ9Q_.js → chunk-KX2RTZJC-Bfg48g5k.js} +1 -1
  126. package/ui-dist/assets/{chunk-NQ4KR5QH-XysPlqPj.js → chunk-NQ4KR5QH-BcSdbequ.js} +1 -1
  127. package/ui-dist/assets/{chunk-QZHKN3VN-B5wEbFHo.js → chunk-QZHKN3VN-BT8QI712.js} +1 -1
  128. package/ui-dist/assets/{chunk-WL4C6EOR-BanwYFa2.js → chunk-WL4C6EOR-CqH2or9g.js} +1 -1
  129. package/ui-dist/assets/classDiagram-VBA2DB6C-Bw6kzUsz.js +1 -0
  130. package/ui-dist/assets/classDiagram-v2-RAHNMMFH-Bw6kzUsz.js +1 -0
  131. package/ui-dist/assets/clone-Luak8Fsn.js +1 -0
  132. package/ui-dist/assets/{cose-bilkent-S5V4N54A-Cd4q2swD.js → cose-bilkent-S5V4N54A-CLH06Lnz.js} +1 -1
  133. package/ui-dist/assets/{dagre-KLK3FWXG-B_VyOhf3.js → dagre-KLK3FWXG-DxNQPDBj.js} +1 -1
  134. package/ui-dist/assets/{diagram-E7M64L7V-BRoG4Mz6.js → diagram-E7M64L7V-BOcSeWh0.js} +1 -1
  135. package/ui-dist/assets/{diagram-IFDJBPK2-CRU_A9p9.js → diagram-IFDJBPK2-DXyaFKVr.js} +1 -1
  136. package/ui-dist/assets/{diagram-P4PSJMXO-BYSQDbfb.js → diagram-P4PSJMXO-DhY_ls3C.js} +1 -1
  137. package/ui-dist/assets/{erDiagram-INFDFZHY-v5j1kyWr.js → erDiagram-INFDFZHY-QtL5Yt_b.js} +1 -1
  138. package/ui-dist/assets/{flowDiagram-PKNHOUZH-C06ZQgTj.js → flowDiagram-PKNHOUZH-BYqyaowc.js} +1 -1
  139. package/ui-dist/assets/{ganttDiagram-A5KZAMGK-Dw9p5nQ1.js → ganttDiagram-A5KZAMGK-D4xd7J_z.js} +1 -1
  140. package/ui-dist/assets/{gitGraphDiagram-K3NZZRJ6-CrpXRIaP.js → gitGraphDiagram-K3NZZRJ6-Co9xqKNH.js} +1 -1
  141. package/ui-dist/assets/{graph-ClTUmULf.js → graph-DEC7S98H.js} +1 -1
  142. package/ui-dist/assets/{index-sLGLHxIu.js → index-4_gJOU3u.js} +1 -1
  143. package/ui-dist/assets/{index-Cr7n11UG.js → index-B8QjK4Xd.js} +1 -1
  144. package/ui-dist/assets/index-BLDnKx7N.js +1478 -0
  145. package/ui-dist/assets/{index-DxzAgTWd.js → index-BX6QyxsL.js} +1 -1
  146. package/ui-dist/assets/{index-CIr7H9OI.js → index-BZGiyL9p.js} +1 -1
  147. package/ui-dist/assets/{index-DK13xhRv.js → index-BelfAyHh.js} +1 -1
  148. package/ui-dist/assets/index-BisI78wU.css +1 -0
  149. package/ui-dist/assets/{index-D-6z8wxx.js → index-Bm86s0IY.js} +1 -1
  150. package/ui-dist/assets/{index-T81awgzh.js → index-Bz0jEwWG.js} +1 -1
  151. package/ui-dist/assets/{index-CqYInp-c.js → index-CFANc8oH.js} +1 -1
  152. package/ui-dist/assets/{index-Btwy7Cp-.js → index-CIAMqUzr.js} +1 -1
  153. package/ui-dist/assets/{index-L6M3nVxh.js → index-ClrueuiI.js} +1 -1
  154. package/ui-dist/assets/{index-C_BTFRTX.js → index-CpxwEuIg.js} +1 -1
  155. package/ui-dist/assets/{index-CQWmziMF.js → index-D1ZkASZY.js} +1 -1
  156. package/ui-dist/assets/{index-DWFMs9uk.js → index-DUP0i_Iv.js} +1 -1
  157. package/ui-dist/assets/{index-BVfM9ax8.js → index-DawkXomB.js} +1 -1
  158. package/ui-dist/assets/{index-DNlWBtHa.js → index-DxchV0Z7.js} +1 -1
  159. package/ui-dist/assets/{index-DkDkjZ-D.js → index-Dzd88G_H.js} +1 -1
  160. package/ui-dist/assets/{index-BvGogi9q.js → index-SklGX83C.js} +1 -1
  161. package/ui-dist/assets/{index-Bpc2gRVo.js → index-_xX3B4n0.js} +1 -1
  162. package/ui-dist/assets/{index-_x9smX4T.js → index-bVqVfFu5.js} +1 -1
  163. package/ui-dist/assets/{index-DAhPD1Ss.js → index-eIjkqSkc.js} +1 -1
  164. package/ui-dist/assets/{index-4uxadHo5.js → index-mIrYeZR2.js} +1 -1
  165. package/ui-dist/assets/{index-BYC_xlrx.js → index-xg2FQeSA.js} +1 -1
  166. package/ui-dist/assets/{infoDiagram-LFFYTUFH-BA3VxOIU.js → infoDiagram-LFFYTUFH-BQ0qsBJ6.js} +1 -1
  167. package/ui-dist/assets/{ishikawaDiagram-PHBUUO56-DGrizi0S.js → ishikawaDiagram-PHBUUO56-B1u2RAnY.js} +1 -1
  168. package/ui-dist/assets/{journeyDiagram-4ABVD52K-6ey34a7e.js → journeyDiagram-4ABVD52K-Dv5wJGwT.js} +1 -1
  169. package/ui-dist/assets/{kanban-definition-K7BYSVSG-CwNnmsam.js → kanban-definition-K7BYSVSG-CJOykCsT.js} +1 -1
  170. package/ui-dist/assets/{layout-buNxvllr.js → layout-BDcM6t-f.js} +1 -1
  171. package/ui-dist/assets/{linear-BPWhxaRl.js → linear-B9Sm5Y96.js} +1 -1
  172. package/ui-dist/assets/{mermaid.core-Cajx0s-z.js → mermaid.core-lZPaf_Ix.js} +4 -4
  173. package/ui-dist/assets/{mindmap-definition-YRQLILUH-Bf5InEp-.js → mindmap-definition-YRQLILUH-Cu4HfP8K.js} +1 -1
  174. package/ui-dist/assets/{pieDiagram-SKSYHLDU-CZFz7NWC.js → pieDiagram-SKSYHLDU-B_v-Vluc.js} +1 -1
  175. package/ui-dist/assets/{quadrantDiagram-337W2JSQ-XBmKVoc9.js → quadrantDiagram-337W2JSQ-BU1ZwGcS.js} +1 -1
  176. package/ui-dist/assets/{requirementDiagram-Z7DCOOCP-BkgdDv0H.js → requirementDiagram-Z7DCOOCP-DBOqB50G.js} +1 -1
  177. package/ui-dist/assets/{sankeyDiagram-WA2Y5GQK-CASFR28i.js → sankeyDiagram-WA2Y5GQK-CsXDIOlq.js} +1 -1
  178. package/ui-dist/assets/{sequenceDiagram-2WXFIKYE-BzY7LMRv.js → sequenceDiagram-2WXFIKYE-Cmgr7vKy.js} +1 -1
  179. package/ui-dist/assets/{stateDiagram-RAJIS63D-C9UMSk36.js → stateDiagram-RAJIS63D-Bd0uRbWd.js} +1 -1
  180. package/ui-dist/assets/stateDiagram-v2-FVOUBMTO-qGaY7iN1.js +1 -0
  181. package/ui-dist/assets/{timeline-definition-YZTLITO2-D6m4R4xe.js → timeline-definition-YZTLITO2-B9OfCgYQ.js} +1 -1
  182. package/ui-dist/assets/{treemap-KZPCXAKY-7V9mnT8T.js → treemap-KZPCXAKY-FWWMNo03.js} +1 -1
  183. package/ui-dist/assets/{vennDiagram-LZ73GAT5-Ci-sfAyq.js → vennDiagram-LZ73GAT5-CGs3T7cn.js} +1 -1
  184. package/ui-dist/assets/{xychartDiagram-JWTSCODW-BayXhRSu.js → xychartDiagram-JWTSCODW-BJ6DrP1k.js} +1 -1
  185. package/ui-dist/index.html +2 -2
  186. package/ui-dist/assets/channel-ClX7n84B.js +0 -1
  187. package/ui-dist/assets/classDiagram-VBA2DB6C-DvWbsnVz.js +0 -1
  188. package/ui-dist/assets/classDiagram-v2-RAHNMMFH-DvWbsnVz.js +0 -1
  189. package/ui-dist/assets/clone-Dla3A8ZA.js +0 -1
  190. package/ui-dist/assets/index-CSANx6ee.css +0 -1
  191. package/ui-dist/assets/index-DCa9-Sy-.js +0 -1439
  192. 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 {
@@ -866,10 +924,18 @@ export function issueRoutes(db, storage) {
866
924
  await assertCanAssignTasks(req, orgId);
867
925
  }
868
926
  const actor = getActorInfo(req);
869
- const issue = await svc.create(orgId, {
927
+ const createInput = {
870
928
  ...req.body,
871
929
  createdByAgentId: actor.agentId,
872
930
  createdByUserId: actor.actorType === "user" ? actor.actorId : null,
931
+ };
932
+ const hasExplicitAssignee = Object.prototype.hasOwnProperty.call(req.body, "assigneeAgentId") ||
933
+ Object.prototype.hasOwnProperty.call(req.body, "assigneeUserId");
934
+ if (actor.actorType === "agent" && actor.agentId && !hasExplicitAssignee) {
935
+ createInput.assigneeAgentId = actor.agentId;
936
+ }
937
+ const issue = await svc.create(orgId, {
938
+ ...createInput,
873
939
  });
874
940
  await logActivity(db, {
875
941
  orgId,
@@ -933,18 +999,36 @@ export function issueRoutes(db, storage) {
933
999
  return;
934
1000
  const actor = getActorInfo(req);
935
1001
  const isClosed = existing.status === "done" || existing.status === "cancelled";
936
- const { comment: commentBody, reopen: reopenRequested, hiddenAt: hiddenAtRaw, ...updateFields } = req.body;
1002
+ const { comment: commentBody, reopen: reopenRequested, hiddenAt: hiddenAtRaw, reviewDecision, ...updateFields } = req.body;
937
1003
  if (hiddenAtRaw !== undefined) {
938
1004
  updateFields.hiddenAt = hiddenAtRaw ? new Date(hiddenAtRaw) : null;
939
1005
  }
940
1006
  if (commentBody && reopenRequested === true && isClosed && updateFields.status === undefined) {
941
1007
  updateFields.status = "todo";
942
1008
  }
1009
+ if (reviewDecision !== undefined) {
1010
+ if (!commentBody) {
1011
+ throw unprocessable("Reviewer decisions require a comment");
1012
+ }
1013
+ if (!statusAcceptsReviewerDecision(existing.status)) {
1014
+ throw unprocessable("Reviewer decisions can only be recorded while the issue is in_review or blocked");
1015
+ }
1016
+ if (actor.actorType === "agent" && !isReviewerAgentForIssue(actor, existing)) {
1017
+ throw forbidden("Only the reviewer agent can record a reviewer decision");
1018
+ }
1019
+ const decisionStatus = statusForReviewDecision(reviewDecision);
1020
+ if (decisionStatus) {
1021
+ updateFields.status = decisionStatus;
1022
+ }
1023
+ else {
1024
+ delete updateFields.status;
1025
+ }
1026
+ }
943
1027
  let reviewedCompletionNormalized = false;
944
1028
  if (updateFields.status === "done" &&
945
1029
  issueHasReviewer(existing) &&
946
1030
  actor.actorType === "agent" &&
947
- !(existing.status === "in_review" && isReviewerAgentForIssue(actor, existing))) {
1031
+ !(statusAcceptsReviewerDecision(existing.status) && isReviewerAgentForIssue(actor, existing))) {
948
1032
  updateFields.status = "in_review";
949
1033
  reviewedCompletionNormalized = true;
950
1034
  }
@@ -1045,20 +1129,66 @@ export function issueRoutes(db, storage) {
1045
1129
  },
1046
1130
  });
1047
1131
  }
1132
+ if (reviewDecision !== undefined) {
1133
+ const reviewOutcome = reviewDecision === "blocked" ? "human_handoff" : "review_closed";
1134
+ await logActivity(db, {
1135
+ orgId: issue.orgId,
1136
+ actorType: actor.actorType,
1137
+ actorId: actor.actorId,
1138
+ agentId: actor.agentId,
1139
+ runId: actor.runId,
1140
+ action: "issue.review_decision_recorded",
1141
+ entityType: "issue",
1142
+ entityId: issue.id,
1143
+ details: {
1144
+ decision: reviewDecision,
1145
+ outcome: reviewOutcome,
1146
+ operatorActionRequired: reviewOutcome === "human_handoff",
1147
+ status: issue.status,
1148
+ identifier: issue.identifier,
1149
+ commentId: comment?.id ?? null,
1150
+ },
1151
+ });
1152
+ if (reviewerDecisionRequiresHumanHandoff(reviewDecision)) {
1153
+ await logActivity(db, {
1154
+ orgId: issue.orgId,
1155
+ actorType: actor.actorType,
1156
+ actorId: actor.actorId,
1157
+ agentId: actor.agentId,
1158
+ runId: actor.runId,
1159
+ action: "issue.human_intervention_required",
1160
+ entityType: "issue",
1161
+ entityId: issue.id,
1162
+ details: {
1163
+ decision: reviewDecision,
1164
+ status: issue.status,
1165
+ identifier: issue.identifier,
1166
+ issueTitle: issue.title,
1167
+ commentId: comment?.id ?? null,
1168
+ previousReviewerAgentId: existing.reviewerAgentId,
1169
+ previousReviewerUserId: existing.reviewerUserId,
1170
+ nextAction: "Human/operator intervention is required before agent review can continue.",
1171
+ },
1172
+ });
1173
+ }
1174
+ }
1048
1175
  const assigneeChanged = assigneeWillChange;
1049
1176
  const reviewerChanged = reviewerWillChange;
1050
1177
  const statusChangedFromBacklog = existing.status === "backlog" &&
1051
1178
  issue.status !== "backlog" &&
1052
- req.body.status !== undefined;
1179
+ updateFields.status !== undefined;
1053
1180
  const statusChangedToInReview = existing.status !== "in_review" &&
1054
1181
  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";
1182
+ updateFields.status !== undefined;
1183
+ const statusChangedToBlocked = existing.status !== "blocked" &&
1184
+ issue.status === "blocked" &&
1185
+ updateFields.status !== undefined;
1186
+ const statusReturnedFromReviewToAssignee = statusAcceptsReviewerDecision(existing.status) &&
1187
+ (issue.status === "in_progress" || issue.status === "todo") &&
1188
+ updateFields.status !== undefined;
1189
+ const reviewerChangedInReviewableStatus = reviewerChanged &&
1190
+ statusAcceptsReviewerDecision(existing.status) &&
1191
+ statusAcceptsReviewerDecision(issue.status);
1062
1192
  // Merge all wakeups from this update into one enqueue per agent to avoid duplicate runs.
1063
1193
  void (async () => {
1064
1194
  const wakeups = new Map();
@@ -1108,16 +1238,33 @@ export function issueRoutes(db, storage) {
1108
1238
  },
1109
1239
  });
1110
1240
  }
1111
- if (!assigneeChanged && statusChangedFromReviewToInProgress && issue.assigneeAgentId) {
1241
+ if (!assigneeChanged && statusReturnedFromReviewToAssignee && issue.assigneeAgentId) {
1242
+ const commentContext = comment
1243
+ ? {
1244
+ commentId: comment.id,
1245
+ wakeCommentId: comment.id,
1246
+ comment: {
1247
+ id: comment.id,
1248
+ body: comment.body,
1249
+ authorAgentId: comment.authorAgentId,
1250
+ authorUserId: comment.authorUserId,
1251
+ },
1252
+ }
1253
+ : {};
1112
1254
  wakeups.set(issue.assigneeAgentId, {
1113
1255
  source: "assignment",
1114
1256
  triggerDetail: "system",
1115
1257
  reason: "issue_changes_requested",
1116
- payload: { issueId: issue.id, mutation: "review_changes_requested" },
1258
+ payload: {
1259
+ issueId: issue.id,
1260
+ mutation: "review_changes_requested",
1261
+ ...(comment ? { commentId: comment.id } : {}),
1262
+ },
1117
1263
  requestedByActorType: actor.actorType,
1118
1264
  requestedByActorId: actor.actorId,
1119
1265
  contextSnapshot: {
1120
1266
  issueId: issue.id,
1267
+ taskId: issue.id,
1121
1268
  source: "issue.review_changes_requested",
1122
1269
  wakeSource: "assignment",
1123
1270
  wakeReason: "issue_changes_requested",
@@ -1128,19 +1275,26 @@ export function issueRoutes(db, storage) {
1128
1275
  status: issue.status,
1129
1276
  priority: issue.priority,
1130
1277
  },
1278
+ ...commentContext,
1131
1279
  },
1132
1280
  });
1133
1281
  }
1134
- if ((statusChangedToInReview || reviewerChangedInReview) && issue.reviewerAgentId) {
1135
- const mutation = statusChangedToInReview ? "status_to_in_review" : "reviewer_changed_in_review";
1282
+ if ((statusChangedToInReview || statusChangedToBlocked || reviewerChangedInReviewableStatus) && issue.reviewerAgentId) {
1283
+ const mutation = statusChangedToInReview
1284
+ ? "status_to_in_review"
1285
+ : statusChangedToBlocked
1286
+ ? "status_to_blocked"
1287
+ : issue.status === "blocked"
1288
+ ? "reviewer_changed_blocked"
1289
+ : "reviewer_changed_in_review";
1136
1290
  const actorIsReviewerAgent = actor.actorType === "agent" && actor.actorId === issue.reviewerAgentId;
1137
1291
  const actorIsAssigneeAgent = actor.actorType === "agent" && actor.actorId === issue.assigneeAgentId;
1138
- const assigneeHandoffToReview = statusChangedToInReview && actorIsAssigneeAgent;
1292
+ const assigneeHandoffToReview = (statusChangedToInReview || statusChangedToBlocked) && actorIsAssigneeAgent;
1139
1293
  if (!actorIsReviewerAgent || assigneeHandoffToReview) {
1140
1294
  wakeups.set(issue.reviewerAgentId, buildIssueReviewWakeupOptions({
1141
1295
  issue,
1142
1296
  mutation,
1143
- contextSource: statusChangedToInReview ? "issue.status_change" : "issue.reviewer_change",
1297
+ contextSource: statusChangedToInReview || statusChangedToBlocked ? "issue.status_change" : "issue.reviewer_change",
1144
1298
  requestedByActorType: actor.actorType,
1145
1299
  requestedByActorId: actor.actorId,
1146
1300
  }));
@@ -1324,6 +1478,62 @@ export function issueRoutes(db, storage) {
1324
1478
  });
1325
1479
  res.json(released);
1326
1480
  });
1481
+ router.post("/issues/:id/commit", validate(reportIssueCommitSchema), async (req, res) => {
1482
+ const id = req.params.id;
1483
+ const issue = await svc.getById(id);
1484
+ if (!issue) {
1485
+ res.status(404).json({ error: "Issue not found" });
1486
+ return;
1487
+ }
1488
+ assertCompanyAccess(req, issue.orgId);
1489
+ if (req.actor.type !== "agent") {
1490
+ res.status(403).json({ error: "Agent authentication required" });
1491
+ return;
1492
+ }
1493
+ if (!(await assertAgentRunCheckoutOwnership(req, res, issue)))
1494
+ return;
1495
+ const actor = getActorInfo(req);
1496
+ const commitRun = await resolveAgentCommitRunId(req, res, issue);
1497
+ if (!commitRun.ok)
1498
+ return;
1499
+ const sha = req.body.sha.trim().toLowerCase();
1500
+ const subject = commitSubject(req.body.message);
1501
+ const shortSha = sha.slice(0, 7);
1502
+ if (commitRun.runId) {
1503
+ await heartbeat.reportRunActivity(commitRun.runId).catch((err) => logger.warn({ err, runId: commitRun.runId }, "failed to clear detached run warning after issue commit activity"));
1504
+ }
1505
+ await logActivity(db, {
1506
+ orgId: issue.orgId,
1507
+ actorType: actor.actorType,
1508
+ actorId: actor.actorId,
1509
+ agentId: actor.agentId,
1510
+ runId: commitRun.runId,
1511
+ action: "issue.code_committed",
1512
+ entityType: "issue",
1513
+ entityId: issue.id,
1514
+ details: {
1515
+ sha,
1516
+ shortSha,
1517
+ message: req.body.message,
1518
+ subject,
1519
+ identifier: issue.identifier,
1520
+ issueTitle: issue.title,
1521
+ branch: req.body.branch ?? null,
1522
+ repoPath: req.body.repoPath ?? null,
1523
+ workspacePath: req.body.workspacePath ?? null,
1524
+ commitCount: req.body.commitCount ?? 1,
1525
+ },
1526
+ });
1527
+ res.status(201).json({
1528
+ ok: true,
1529
+ issueId: issue.id,
1530
+ sha,
1531
+ shortSha,
1532
+ message: req.body.message,
1533
+ subject,
1534
+ runId: commitRun.runId,
1535
+ });
1536
+ });
1327
1537
  router.get("/issues/:id/comments", async (req, res) => {
1328
1538
  const id = req.params.id;
1329
1539
  const issue = await svc.getById(id);