@penclipai/server 2026.426.0 → 2026.505.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/builtin-adapter-types.d.ts.map +1 -1
- package/dist/adapters/builtin-adapter-types.js +3 -0
- package/dist/adapters/builtin-adapter-types.js.map +1 -1
- package/dist/adapters/index.d.ts +2 -2
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js +1 -1
- package/dist/adapters/index.js.map +1 -1
- package/dist/adapters/registry.d.ts +2 -1
- package/dist/adapters/registry.d.ts.map +1 -1
- package/dist/adapters/registry.js +76 -6
- package/dist/adapters/registry.js.map +1 -1
- package/dist/adapters/types.d.ts +1 -1
- package/dist/adapters/types.d.ts.map +1 -1
- package/dist/adapters/utils.d.ts.map +1 -1
- package/dist/adapters/utils.js +2 -1
- package/dist/adapters/utils.js.map +1 -1
- package/dist/attachment-types.d.ts +1 -16
- package/dist/attachment-types.d.ts.map +1 -1
- package/dist/attachment-types.js +7 -0
- package/dist/attachment-types.js.map +1 -1
- package/dist/auth/better-auth.d.ts +3 -1
- package/dist/auth/better-auth.d.ts.map +1 -1
- package/dist/auth/better-auth.js +8 -2
- package/dist/auth/better-auth.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +27 -13
- package/dist/index.js.map +1 -1
- package/dist/middleware/auth.d.ts.map +1 -1
- package/dist/middleware/auth.js +143 -2
- package/dist/middleware/auth.js.map +1 -1
- package/dist/onboarding-assets/ceo/AGENTS.md +1 -1
- package/dist/onboarding-assets/ceo/HEARTBEAT.md +5 -5
- package/dist/redaction.d.ts.map +1 -1
- package/dist/redaction.js +30 -12
- package/dist/redaction.js.map +1 -1
- package/dist/routes/access.d.ts.map +1 -1
- package/dist/routes/access.js +10 -0
- package/dist/routes/access.js.map +1 -1
- package/dist/routes/activity.d.ts.map +1 -1
- package/dist/routes/activity.js +4 -2
- package/dist/routes/activity.js.map +1 -1
- package/dist/routes/adapters.d.ts.map +1 -1
- package/dist/routes/adapters.js +1 -0
- package/dist/routes/adapters.js.map +1 -1
- package/dist/routes/agents.d.ts.map +1 -1
- package/dist/routes/agents.js +317 -56
- package/dist/routes/agents.js.map +1 -1
- package/dist/routes/costs.d.ts.map +1 -1
- package/dist/routes/costs.js +21 -2
- package/dist/routes/costs.js.map +1 -1
- package/dist/routes/instance-settings.d.ts.map +1 -1
- package/dist/routes/instance-settings.js +37 -2
- package/dist/routes/instance-settings.js.map +1 -1
- package/dist/routes/issue-tree-control.d.ts.map +1 -1
- package/dist/routes/issue-tree-control.js +3 -1
- package/dist/routes/issue-tree-control.js.map +1 -1
- package/dist/routes/issues.d.ts.map +1 -1
- package/dist/routes/issues.js +257 -32
- package/dist/routes/issues.js.map +1 -1
- package/dist/routes/projects.d.ts.map +1 -1
- package/dist/routes/projects.js +10 -3
- package/dist/routes/projects.js.map +1 -1
- package/dist/routes/routines.d.ts.map +1 -1
- package/dist/routes/routines.js +6 -1
- package/dist/routes/routines.js.map +1 -1
- package/dist/routes/workspace-command-authz.d.ts +1 -1
- package/dist/routes/workspace-command-authz.d.ts.map +1 -1
- package/dist/routes/workspace-command-authz.js +2 -2
- package/dist/routes/workspace-command-authz.js.map +1 -1
- package/dist/runtime-api.d.ts +4 -0
- package/dist/runtime-api.d.ts.map +1 -1
- package/dist/runtime-api.js +38 -10
- package/dist/runtime-api.js.map +1 -1
- package/dist/services/companies.d.ts +6 -0
- package/dist/services/companies.d.ts.map +1 -1
- package/dist/services/companies.js +1 -0
- package/dist/services/companies.js.map +1 -1
- package/dist/services/company-portability.d.ts.map +1 -1
- package/dist/services/company-portability.js +16 -15
- package/dist/services/company-portability.js.map +1 -1
- package/dist/services/costs.d.ts +9 -0
- package/dist/services/costs.d.ts.map +1 -1
- package/dist/services/costs.js +45 -1
- package/dist/services/costs.js.map +1 -1
- package/dist/services/environment-execution-target.d.ts.map +1 -1
- package/dist/services/environment-execution-target.js +7 -13
- package/dist/services/environment-execution-target.js.map +1 -1
- package/dist/services/environment-run-orchestrator.d.ts.map +1 -1
- package/dist/services/environment-run-orchestrator.js +56 -0
- package/dist/services/environment-run-orchestrator.js.map +1 -1
- package/dist/services/environment-runtime.d.ts +2 -0
- package/dist/services/environment-runtime.d.ts.map +1 -1
- package/dist/services/environment-runtime.js +80 -39
- package/dist/services/environment-runtime.js.map +1 -1
- package/dist/services/heartbeat-stop-metadata.d.ts +2 -1
- package/dist/services/heartbeat-stop-metadata.d.ts.map +1 -1
- package/dist/services/heartbeat-stop-metadata.js +10 -1
- package/dist/services/heartbeat-stop-metadata.js.map +1 -1
- package/dist/services/heartbeat-stop-metadata.test.js +24 -0
- package/dist/services/heartbeat-stop-metadata.test.js.map +1 -1
- package/dist/services/heartbeat.d.ts +156 -5
- package/dist/services/heartbeat.d.ts.map +1 -1
- package/dist/services/heartbeat.js +1384 -112
- package/dist/services/heartbeat.js.map +1 -1
- package/dist/services/index.d.ts +1 -0
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +1 -0
- package/dist/services/index.js.map +1 -1
- package/dist/services/instance-settings.d.ts.map +1 -1
- package/dist/services/instance-settings.js +4 -1
- package/dist/services/instance-settings.js.map +1 -1
- package/dist/services/issue-execution-policy.d.ts +56 -1
- package/dist/services/issue-execution-policy.d.ts.map +1 -1
- package/dist/services/issue-execution-policy.js +400 -2
- package/dist/services/issue-execution-policy.js.map +1 -1
- package/dist/services/issue-thread-interactions.d.ts +5 -1
- package/dist/services/issue-thread-interactions.d.ts.map +1 -1
- package/dist/services/issue-thread-interactions.js +44 -1
- package/dist/services/issue-thread-interactions.js.map +1 -1
- package/dist/services/issue-tree-control.d.ts +1 -0
- package/dist/services/issue-tree-control.d.ts.map +1 -1
- package/dist/services/issue-tree-control.js +84 -4
- package/dist/services/issue-tree-control.js.map +1 -1
- package/dist/services/issues.d.ts +10 -1
- package/dist/services/issues.d.ts.map +1 -1
- package/dist/services/issues.js +452 -48
- package/dist/services/issues.js.map +1 -1
- package/dist/services/plugin-environment-driver.d.ts +4 -0
- package/dist/services/plugin-environment-driver.d.ts.map +1 -1
- package/dist/services/plugin-environment-driver.js +18 -1
- package/dist/services/plugin-environment-driver.js.map +1 -1
- package/dist/services/productivity-review.d.ts +83 -0
- package/dist/services/productivity-review.d.ts.map +1 -0
- package/dist/services/productivity-review.js +650 -0
- package/dist/services/productivity-review.js.map +1 -0
- package/dist/services/recovery/index.d.ts +1 -1
- package/dist/services/recovery/index.d.ts.map +1 -1
- package/dist/services/recovery/index.js +1 -1
- package/dist/services/recovery/index.js.map +1 -1
- package/dist/services/recovery/issue-graph-liveness.d.ts +13 -1
- package/dist/services/recovery/issue-graph-liveness.d.ts.map +1 -1
- package/dist/services/recovery/issue-graph-liveness.js +212 -92
- package/dist/services/recovery/issue-graph-liveness.js.map +1 -1
- package/dist/services/recovery/origins.d.ts +2 -0
- package/dist/services/recovery/origins.d.ts.map +1 -1
- package/dist/services/recovery/origins.js +4 -0
- package/dist/services/recovery/origins.js.map +1 -1
- package/dist/services/recovery/run-liveness-continuations.d.ts.map +1 -1
- package/dist/services/recovery/run-liveness-continuations.js.map +1 -1
- package/dist/services/recovery/service.d.ts +20 -2
- package/dist/services/recovery/service.d.ts.map +1 -1
- package/dist/services/recovery/service.js +405 -63
- package/dist/services/recovery/service.js.map +1 -1
- package/dist/services/routines.d.ts +5 -2
- package/dist/services/routines.d.ts.map +1 -1
- package/dist/services/routines.js +47 -3
- package/dist/services/routines.js.map +1 -1
- package/dist/worktree-config.d.ts.map +1 -1
- package/dist/worktree-config.js +2 -5
- package/dist/worktree-config.js.map +1 -1
- package/package.json +16 -15
- package/skills/diagnose-why-work-stopped/SKILL.md +161 -0
- package/skills/paperclip/SKILL.md +37 -26
- package/skills/paperclip/references/api-reference.md +6 -2
- package/skills/paperclip-converting-plans-to-tasks/SKILL.md +42 -0
- package/skills/paperclip-create-agent/SKILL.md +3 -2
- package/skills/paperclip-create-agent/references/agent-instruction-templates.md +1 -1
- package/skills/paperclip-create-agent/references/api-reference.md +7 -2
- package/skills/paperclip-create-agent/references/baseline-role-guide.md +1 -1
- package/skills/paperclip-create-agent/references/draft-review-checklist.md +2 -2
- package/skills/paperclip-dev/SKILL.md +267 -0
- package/skills/terminal-bench-loop/SKILL.md +236 -0
- package/ui-dist/assets/{_basePickBy-BRqa7PJ5.js → _basePickBy-BS0Fg_DB.js} +1 -1
- package/ui-dist/assets/{_baseUniq-DhE2yrXC.js → _baseUniq-Dtnt_4SE.js} +1 -1
- package/ui-dist/assets/{arc-7qnikTQ3.js → arc-BCoOPxh5.js} +1 -1
- package/ui-dist/assets/{architectureDiagram-VXUJARFQ-CH0wVUOM.js → architectureDiagram-VXUJARFQ-C6eX2QUo.js} +1 -1
- package/ui-dist/assets/{blockDiagram-VD42YOAC-CeeRyJQX.js → blockDiagram-VD42YOAC-aUueUD4B.js} +1 -1
- package/ui-dist/assets/browser-ponyfill-BlAfsWm_.js +2 -0
- package/ui-dist/assets/{c4Diagram-YG6GDRKO-C_cV0CGo.js → c4Diagram-YG6GDRKO-CfPWRlOF.js} +1 -1
- package/ui-dist/assets/channel-ChNSCFJf.js +1 -0
- package/ui-dist/assets/{chunk-4BX2VUAB-DQ6pxPVT.js → chunk-4BX2VUAB-BTD1apA4.js} +1 -1
- package/ui-dist/assets/{chunk-55IACEB6-L8pS0IoX.js → chunk-55IACEB6-BXXF_ClN.js} +1 -1
- package/ui-dist/assets/{chunk-B4BG7PRW-BZKGE88E.js → chunk-B4BG7PRW-hAZeWGP8.js} +1 -1
- package/ui-dist/assets/{chunk-DI55MBZ5-CefSoZ_K.js → chunk-DI55MBZ5-cOH3UoEl.js} +1 -1
- package/ui-dist/assets/{chunk-FMBD7UC4-Bc3qTTHB.js → chunk-FMBD7UC4-Cu2yZOcl.js} +1 -1
- package/ui-dist/assets/{chunk-QN33PNHL-CjWBr5bI.js → chunk-QN33PNHL-0DNN5aRU.js} +1 -1
- package/ui-dist/assets/{chunk-QZHKN3VN-C0JUdmmz.js → chunk-QZHKN3VN-B9_bhK2n.js} +1 -1
- package/ui-dist/assets/{chunk-TZMSLE5B-D4d4I82z.js → chunk-TZMSLE5B-Cr5xwxio.js} +1 -1
- package/ui-dist/assets/classDiagram-2ON5EDUG-4aK1QZU3.js +1 -0
- package/ui-dist/assets/classDiagram-v2-WZHVMYZB-4aK1QZU3.js +1 -0
- package/ui-dist/assets/clone-C8lk5Qbc.js +1 -0
- package/ui-dist/assets/{cose-bilkent-S5V4N54A-B09h9XGZ.js → cose-bilkent-S5V4N54A-6_Dw6gpQ.js} +1 -1
- package/ui-dist/assets/{dagre-6UL2VRFP-CA02PXuX.js → dagre-6UL2VRFP-CFBhlh5H.js} +1 -1
- package/ui-dist/assets/{diagram-PSM6KHXK-DaT9cnrY.js → diagram-PSM6KHXK-C88ftcah.js} +1 -1
- package/ui-dist/assets/{diagram-QEK2KX5R-Drwc3gBw.js → diagram-QEK2KX5R-9EUupcuH.js} +1 -1
- package/ui-dist/assets/{diagram-S2PKOQOG-CpsGCaT6.js → diagram-S2PKOQOG-Dsml0wWh.js} +1 -1
- package/ui-dist/assets/{erDiagram-Q2GNP2WA-CVkBh9TY.js → erDiagram-Q2GNP2WA-sM-XdfHS.js} +1 -1
- package/ui-dist/assets/{flowDiagram-NV44I4VS-De9sXvPR.js → flowDiagram-NV44I4VS-qll7oaoW.js} +1 -1
- package/ui-dist/assets/{ganttDiagram-JELNMOA3-CSFa0gXS.js → ganttDiagram-JELNMOA3-VWnJMcjC.js} +1 -1
- package/ui-dist/assets/{gitGraphDiagram-V2S2FVAM-DEJaChxa.js → gitGraphDiagram-V2S2FVAM-DFnocrfl.js} +1 -1
- package/ui-dist/assets/{graph-D2R4DCtu.js → graph-nq3Qye4Z.js} +1 -1
- package/ui-dist/assets/{index-DEG-9CFs.js → index-3Owzaheh.js} +1 -1
- package/ui-dist/assets/{index-DHnKx9xX.js → index-B2A-a635.js} +1 -1
- package/ui-dist/assets/{index-C1I0SGDm.js → index-BGFrRiqa.js} +1 -1
- package/ui-dist/assets/{index-B44EtLRv.js → index-BVC5UhRK.js} +1 -1
- package/ui-dist/assets/{index-C_dAXwxT.js → index-BrP1U_Hy.js} +1 -1
- package/ui-dist/assets/{index-flZjKn_n.js → index-CXXHGqM8.js} +1 -1
- package/ui-dist/assets/{index-ssM_UKPW.js → index-CgyPAauR.js} +1 -1
- package/ui-dist/assets/{index-Ct1AraKR.js → index-CksQ4Ytv.js} +1 -1
- package/ui-dist/assets/{index-DQ6I_vpd.js → index-CrNzj2vZ.js} +1 -1
- package/ui-dist/assets/{index-DzZID5RY.js → index-CxbZBH3M.js} +1 -1
- package/ui-dist/assets/{index-Cn6_RRY5.js → index-D-dSSrf-.js} +1 -1
- package/ui-dist/assets/{index-CVa2OHgx.js → index-D6uZ_7Vh.js} +1 -1
- package/ui-dist/assets/{index-BzjWQd50.js → index-D7JGmxas.js} +1 -1
- package/ui-dist/assets/{index-CnT1_9UF.js → index-DDqO9GAq.js} +1 -1
- package/ui-dist/assets/index-DEUtmlPm.js +513 -0
- package/ui-dist/assets/{index-D2fEhyQg.js → index-DF5RDSoK.js} +1 -1
- package/ui-dist/assets/{index-CZGNe8K3.js → index-DfI92epU.js} +1 -1
- package/ui-dist/assets/{index-ByamXtyB.js → index-Dukb9MDQ.js} +1 -1
- package/ui-dist/assets/index-HP73_6Vr.css +1 -0
- package/ui-dist/assets/{index-BJS4rvUh.js → index-NXDTW2n4.js} +1 -1
- package/ui-dist/assets/{index-Bad5Hy7e.js → index-SxPPG9ig.js} +1 -1
- package/ui-dist/assets/{index-CC51mhhA.js → index-lC4Yz3Gw.js} +1 -1
- package/ui-dist/assets/{index-BFzkl36p.js → index-q2RXGI2V.js} +1 -1
- package/ui-dist/assets/{index-40icqWwg.js → index-qjfdrS96.js} +1 -1
- package/ui-dist/assets/{infoDiagram-HS3SLOUP-CJcjzWkM.js → infoDiagram-HS3SLOUP-CTrK5xoS.js} +1 -1
- package/ui-dist/assets/{journeyDiagram-XKPGCS4Q-ByITI00s.js → journeyDiagram-XKPGCS4Q-YFC7FykG.js} +1 -1
- package/ui-dist/assets/{kanban-definition-3W4ZIXB7-DvEjKke-.js → kanban-definition-3W4ZIXB7-B3dlyva0.js} +1 -1
- package/ui-dist/assets/{layout-CZcd66hi.js → layout-DefunPTK.js} +1 -1
- package/ui-dist/assets/{linear-jTUy3iHu.js → linear-CIPvzeMv.js} +1 -1
- package/ui-dist/assets/{mermaid.core-DECSZPbJ.js → mermaid.core-zKYhmnnR.js} +4 -4
- package/ui-dist/assets/{mindmap-definition-VGOIOE7T-Twtu17_c.js → mindmap-definition-VGOIOE7T-BlU-ebRa.js} +1 -1
- package/ui-dist/assets/{pieDiagram-ADFJNKIX-DlbgZ010.js → pieDiagram-ADFJNKIX-Ceto4LXH.js} +1 -1
- package/ui-dist/assets/{quadrantDiagram-AYHSOK5B-CMAa3qAT.js → quadrantDiagram-AYHSOK5B-C6M6hkuE.js} +1 -1
- package/ui-dist/assets/{requirementDiagram-UZGBJVZJ-CXRTfJOe.js → requirementDiagram-UZGBJVZJ-B-bcG938.js} +1 -1
- package/ui-dist/assets/{sankeyDiagram-TZEHDZUN-DeyO4fer.js → sankeyDiagram-TZEHDZUN-CIqty6Qi.js} +1 -1
- package/ui-dist/assets/{sequenceDiagram-WL72ISMW-Ch8wlJIL.js → sequenceDiagram-WL72ISMW-CIt2R5tk.js} +1 -1
- package/ui-dist/assets/{stateDiagram-FKZM4ZOC-BgL_AAl9.js → stateDiagram-FKZM4ZOC-BC1RFlfg.js} +1 -1
- package/ui-dist/assets/stateDiagram-v2-4FDKWEC3-Iy6tYSSw.js +1 -0
- package/ui-dist/assets/{timeline-definition-IT6M3QCI-D1QWd7TQ.js → timeline-definition-IT6M3QCI-DZqvoU94.js} +1 -1
- package/ui-dist/assets/{treemap-GDKQZRPO-B5RkmUv8.js → treemap-GDKQZRPO-CSeKauwA.js} +1 -1
- package/ui-dist/assets/{xychartDiagram-PRI3JC2R-WtDhjZfk.js → xychartDiagram-PRI3JC2R-Ut3mCiEd.js} +1 -1
- package/ui-dist/index.html +2 -2
- package/ui-dist/locales/en/common.json +137 -1
- package/ui-dist/locales/zh-CN/common.json +111 -1
- package/ui-dist/assets/browser-ponyfill-Ct3hGqsr.js +0 -2
- package/ui-dist/assets/channel-pHFjGZL-.js +0 -1
- package/ui-dist/assets/classDiagram-2ON5EDUG-X4ZksqXl.js +0 -1
- package/ui-dist/assets/classDiagram-v2-WZHVMYZB-X4ZksqXl.js +0 -1
- package/ui-dist/assets/clone-DZzimpfG.js +0 -1
- package/ui-dist/assets/index-C1oE3J7o.css +0 -1
- package/ui-dist/assets/index-fSIlEIHr.js +0 -510
- package/ui-dist/assets/stateDiagram-v2-4FDKWEC3-gnLzrhSv.js +0 -1
package/dist/routes/issues.js
CHANGED
|
@@ -3,18 +3,18 @@ import { Router } from "express";
|
|
|
3
3
|
import multer from "multer";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
import { issueExecutionDecisions } from "@penclipai/db";
|
|
6
|
-
import { addIssueCommentSchema, acceptIssueThreadInteractionSchema, createIssueAttachmentMetadataSchema, createIssueThreadInteractionSchema, createIssueWorkProductSchema, createIssueLabelSchema, checkoutIssueSchema, createChildIssueSchema, createIssueSchema, feedbackTargetTypeSchema, feedbackTraceStatusSchema, feedbackVoteValueSchema, upsertIssueFeedbackVoteSchema, linkIssueApprovalSchema, issueDocumentKeySchema, ISSUE_CONTINUATION_SUMMARY_DOCUMENT_KEY, rejectIssueThreadInteractionSchema, restoreIssueDocumentRevisionSchema, respondIssueThreadInteractionSchema, updateIssueWorkProductSchema, upsertIssueDocumentSchema, updateIssueSchema, getClosedIsolatedExecutionWorkspaceMessage, isClosedIsolatedExecutionWorkspace, } from "@penclipai/shared";
|
|
6
|
+
import { addIssueCommentSchema, acceptIssueThreadInteractionSchema, cancelIssueThreadInteractionSchema, createIssueAttachmentMetadataSchema, createIssueThreadInteractionSchema, createIssueWorkProductSchema, createIssueLabelSchema, checkoutIssueSchema, createChildIssueSchema, createIssueSchema, feedbackTargetTypeSchema, feedbackTraceStatusSchema, feedbackVoteValueSchema, upsertIssueFeedbackVoteSchema, linkIssueApprovalSchema, issueDocumentKeySchema, ISSUE_CONTINUATION_SUMMARY_DOCUMENT_KEY, rejectIssueThreadInteractionSchema, restoreIssueDocumentRevisionSchema, respondIssueThreadInteractionSchema, updateIssueWorkProductSchema, upsertIssueDocumentSchema, updateIssueSchema, getClosedIsolatedExecutionWorkspaceMessage, isClosedIsolatedExecutionWorkspace, normalizeIssueIdentifier as normalizeIssueReferenceIdentifier, } from "@penclipai/shared";
|
|
7
7
|
import { getTelemetryClient } from "../telemetry.js";
|
|
8
8
|
import { trackAgentTaskCompleted } from "@penclipai/shared/telemetry";
|
|
9
9
|
import { validate } from "../middleware/validate.js";
|
|
10
10
|
import * as serviceIndex from "../services/index.js";
|
|
11
|
-
import { accessService, agentService, goalService, heartbeatService, issueApprovalService, issueThreadInteractionService, ISSUE_LIST_DEFAULT_LIMIT, ISSUE_LIST_MAX_LIMIT, issueReferenceService, issueService, clampIssueListLimit, documentService, logActivity, projectService, routineService, workProductService, } from "../services/index.js";
|
|
11
|
+
import { accessService, agentService, companyService, goalService, heartbeatService, issueApprovalService, issueThreadInteractionService, ISSUE_LIST_DEFAULT_LIMIT, ISSUE_LIST_MAX_LIMIT, issueReferenceService, issueService, clampIssueListLimit, documentService, logActivity, projectService, routineService, workProductService, } from "../services/index.js";
|
|
12
12
|
import { logger } from "../middleware/logger.js";
|
|
13
13
|
import { conflict, forbidden, HttpError, notFound, unauthorized } from "../errors.js";
|
|
14
14
|
import { assertBoard, assertCompanyAccess, getActorInfo } from "./authz.js";
|
|
15
15
|
import { assertNoAgentHostWorkspaceCommandMutation, collectIssueWorkspaceCommandPaths, } from "./workspace-command-authz.js";
|
|
16
16
|
import { shouldWakeAssigneeOnCheckout } from "./issues-checkout-wakeup.js";
|
|
17
|
-
import { isInlineAttachmentContentType,
|
|
17
|
+
import { isInlineAttachmentContentType, normalizeIssueAttachmentMaxBytes, normalizeContentType, SVG_CONTENT_TYPE, } from "../attachment-types.js";
|
|
18
18
|
import { queueIssueAssignmentWakeup } from "../services/issue-assignment-wakeup.js";
|
|
19
19
|
import { resolveExplicitRequestUiLocale } from "../ui-locale.js";
|
|
20
20
|
import { assertEnvironmentSelectionForCompany } from "./environment-selection.js";
|
|
@@ -22,7 +22,7 @@ import { executionWorkspaceService as executionWorkspaceServiceDirect } from "..
|
|
|
22
22
|
import { feedbackService } from "../services/feedback.js";
|
|
23
23
|
import { instanceSettingsService } from "../services/instance-settings.js";
|
|
24
24
|
import { environmentService } from "../services/environments.js";
|
|
25
|
-
import { applyIssueExecutionPolicyTransition, normalizeIssueExecutionPolicy, parseIssueExecutionState, } from "../services/issue-execution-policy.js";
|
|
25
|
+
import { applyIssueExecutionPolicyTransition, normalizeIssueExecutionPolicy, parseIssueExecutionState, redactIssueMonitorExternalRef, setIssueExecutionPolicyMonitorScheduledBy, } from "../services/issue-execution-policy.js";
|
|
26
26
|
const MAX_ISSUE_COMMENT_LIMIT = 500;
|
|
27
27
|
const updateIssueRouteSchema = updateIssueSchema.extend({
|
|
28
28
|
interrupt: z.boolean().optional(),
|
|
@@ -73,6 +73,39 @@ function summarizeIssueReferenceActivityDetails(input) {
|
|
|
73
73
|
...(input.currentReferencedIssues.length > 0 ? { currentReferencedIssues: input.currentReferencedIssues } : {}),
|
|
74
74
|
};
|
|
75
75
|
}
|
|
76
|
+
function monitorPoliciesEqual(left, right) {
|
|
77
|
+
return JSON.stringify(left?.monitor ?? null) === JSON.stringify(right?.monitor ?? null);
|
|
78
|
+
}
|
|
79
|
+
function applyActorMonitorScheduledBy(policy, actorType) {
|
|
80
|
+
return setIssueExecutionPolicyMonitorScheduledBy(policy, actorType === "user" ? "board" : "assignee");
|
|
81
|
+
}
|
|
82
|
+
function assertCanManageIssueMonitor(req, assigneeAgentId, monitorChanged) {
|
|
83
|
+
if (!monitorChanged)
|
|
84
|
+
return;
|
|
85
|
+
if (req.actor.type === "board")
|
|
86
|
+
return;
|
|
87
|
+
if (req.actor.type === "agent" && req.actor.agentId && req.actor.agentId === assigneeAgentId)
|
|
88
|
+
return;
|
|
89
|
+
throw forbidden("Only the assignee agent or a board user can manage issue monitors");
|
|
90
|
+
}
|
|
91
|
+
function summarizeIssueMonitor(issue, policy) {
|
|
92
|
+
const state = parseIssueExecutionState(issue.executionState);
|
|
93
|
+
return {
|
|
94
|
+
nextCheckAt: issue.monitorNextCheckAt?.toISOString() ?? policy?.monitor?.nextCheckAt ?? null,
|
|
95
|
+
lastTriggeredAt: issue.monitorLastTriggeredAt?.toISOString() ?? state?.monitor?.lastTriggeredAt ?? null,
|
|
96
|
+
attemptCount: issue.monitorAttemptCount ?? state?.monitor?.attemptCount ?? 0,
|
|
97
|
+
notes: policy?.monitor?.notes ?? issue.monitorNotes ?? state?.monitor?.notes ?? null,
|
|
98
|
+
scheduledBy: issue.monitorScheduledBy ?? policy?.monitor?.scheduledBy ?? state?.monitor?.scheduledBy ?? null,
|
|
99
|
+
kind: policy?.monitor?.kind ?? state?.monitor?.kind ?? null,
|
|
100
|
+
serviceName: policy?.monitor?.serviceName ?? state?.monitor?.serviceName ?? null,
|
|
101
|
+
externalRef: redactIssueMonitorExternalRef(policy?.monitor?.externalRef ?? state?.monitor?.externalRef ?? null),
|
|
102
|
+
timeoutAt: policy?.monitor?.timeoutAt ?? state?.monitor?.timeoutAt ?? null,
|
|
103
|
+
maxAttempts: policy?.monitor?.maxAttempts ?? state?.monitor?.maxAttempts ?? null,
|
|
104
|
+
recoveryPolicy: policy?.monitor?.recoveryPolicy ?? state?.monitor?.recoveryPolicy ?? null,
|
|
105
|
+
status: state?.monitor?.status ?? (policy?.monitor ? "scheduled" : null),
|
|
106
|
+
clearReason: state?.monitor?.clearReason ?? null,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
76
109
|
function activityExecutionParticipantKey(participant) {
|
|
77
110
|
return participant.type === "agent" ? `agent:${participant.agentId}` : `user:${participant.userId}`;
|
|
78
111
|
}
|
|
@@ -251,6 +284,7 @@ export function issueRoutes(db, storage, opts = {}) {
|
|
|
251
284
|
pluginWorkerManager: opts.pluginWorkerManager,
|
|
252
285
|
});
|
|
253
286
|
const feedback = feedbackService(db);
|
|
287
|
+
const companiesSvc = companyService(db);
|
|
254
288
|
const instanceSettings = instanceSettingsService(db);
|
|
255
289
|
const agentsSvc = agentService(db);
|
|
256
290
|
const projectsSvc = projectService(db);
|
|
@@ -271,10 +305,6 @@ export function issueRoutes(db, storage, opts = {}) {
|
|
|
271
305
|
};
|
|
272
306
|
const feedbackExportService = opts?.feedbackExportService;
|
|
273
307
|
const environmentsSvc = environmentService(db);
|
|
274
|
-
const upload = multer({
|
|
275
|
-
storage: multer.memoryStorage(),
|
|
276
|
-
limits: { fileSize: MAX_ATTACHMENT_BYTES, files: 1 },
|
|
277
|
-
});
|
|
278
308
|
function withContentPath(attachment) {
|
|
279
309
|
return {
|
|
280
310
|
...attachment,
|
|
@@ -323,7 +353,11 @@ export function issueRoutes(db, storage, opts = {}) {
|
|
|
323
353
|
function translateMessage(req, key) {
|
|
324
354
|
return typeof req.t === "function" ? req.t(key) : key;
|
|
325
355
|
}
|
|
326
|
-
async function runSingleFileUpload(req, res) {
|
|
356
|
+
async function runSingleFileUpload(req, res, fileSizeLimit) {
|
|
357
|
+
const upload = multer({
|
|
358
|
+
storage: multer.memoryStorage(),
|
|
359
|
+
limits: { fileSize: fileSizeLimit, files: 1 },
|
|
360
|
+
});
|
|
327
361
|
await new Promise((resolve, reject) => {
|
|
328
362
|
upload.single("file")(req, res, (err) => {
|
|
329
363
|
if (err)
|
|
@@ -431,23 +465,40 @@ export function issueRoutes(db, storage, opts = {}) {
|
|
|
431
465
|
res.status(403).json({ error: "Agent authentication required" });
|
|
432
466
|
return false;
|
|
433
467
|
}
|
|
434
|
-
if (issue.
|
|
468
|
+
if (issue.assigneeAgentId === null) {
|
|
435
469
|
return true;
|
|
436
470
|
}
|
|
437
471
|
if (issue.assigneeAgentId !== actorAgentId) {
|
|
438
472
|
if (await hasActiveCheckoutManagementOverride(actorAgentId, issue.companyId, issue.assigneeAgentId)) {
|
|
439
473
|
return true;
|
|
440
474
|
}
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
475
|
+
if (issue.status === "in_progress") {
|
|
476
|
+
res.status(409).json({
|
|
477
|
+
error: "Issue is checked out by another agent",
|
|
478
|
+
details: {
|
|
479
|
+
issueId: issue.id,
|
|
480
|
+
assigneeAgentId: issue.assigneeAgentId,
|
|
481
|
+
actorAgentId,
|
|
482
|
+
},
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
else {
|
|
486
|
+
res.status(403).json({
|
|
487
|
+
error: "Agent cannot mutate another agent's issue",
|
|
488
|
+
details: {
|
|
489
|
+
issueId: issue.id,
|
|
490
|
+
assigneeAgentId: issue.assigneeAgentId,
|
|
491
|
+
actorAgentId,
|
|
492
|
+
status: issue.status,
|
|
493
|
+
securityPrinciples: ["Least Privilege", "Complete Mediation", "Fail Securely"],
|
|
494
|
+
},
|
|
495
|
+
});
|
|
496
|
+
}
|
|
449
497
|
return false;
|
|
450
498
|
}
|
|
499
|
+
if (issue.status !== "in_progress") {
|
|
500
|
+
return true;
|
|
501
|
+
}
|
|
451
502
|
const runId = requireAgentRunId(req, res);
|
|
452
503
|
if (!runId)
|
|
453
504
|
return false;
|
|
@@ -479,7 +530,6 @@ export function issueRoutes(db, storage, opts = {}) {
|
|
|
479
530
|
details: {
|
|
480
531
|
issueId: issue.id,
|
|
481
532
|
status: issue.status,
|
|
482
|
-
securityPrinciples: ["Complete Mediation", "Fail Securely"],
|
|
483
533
|
},
|
|
484
534
|
});
|
|
485
535
|
return false;
|
|
@@ -500,7 +550,6 @@ export function issueRoutes(db, storage, opts = {}) {
|
|
|
500
550
|
holdId: activePauseHold.holdId,
|
|
501
551
|
rootIssueId: activePauseHold.rootIssueId,
|
|
502
552
|
mode: activePauseHold.mode,
|
|
503
|
-
securityPrinciples: ["Complete Mediation", "Fail Securely", "Secure Defaults"],
|
|
504
553
|
},
|
|
505
554
|
});
|
|
506
555
|
return false;
|
|
@@ -609,9 +658,10 @@ export function issueRoutes(db, storage, opts = {}) {
|
|
|
609
658
|
executionWorkspace: workspace,
|
|
610
659
|
});
|
|
611
660
|
}
|
|
612
|
-
async function
|
|
613
|
-
|
|
614
|
-
|
|
661
|
+
async function resolveIssueRouteId(rawId) {
|
|
662
|
+
const identifier = normalizeIssueReferenceIdentifier(rawId);
|
|
663
|
+
if (identifier) {
|
|
664
|
+
const issue = await svc.getByIdentifier(identifier);
|
|
615
665
|
if (issue) {
|
|
616
666
|
return issue.id;
|
|
617
667
|
}
|
|
@@ -639,7 +689,7 @@ export function issueRoutes(db, storage, opts = {}) {
|
|
|
639
689
|
// Resolve issue identifiers (e.g. "PAP-39") to UUIDs for all /issues/:id routes
|
|
640
690
|
router.param("id", async (req, res, next, rawId) => {
|
|
641
691
|
try {
|
|
642
|
-
req.params.id = await
|
|
692
|
+
req.params.id = await resolveIssueRouteId(rawId);
|
|
643
693
|
next();
|
|
644
694
|
}
|
|
645
695
|
catch (err) {
|
|
@@ -649,7 +699,7 @@ export function issueRoutes(db, storage, opts = {}) {
|
|
|
649
699
|
// Resolve issue identifiers (e.g. "PAP-39") to UUIDs for company-scoped attachment routes.
|
|
650
700
|
router.param("issueId", async (req, res, next, rawId) => {
|
|
651
701
|
try {
|
|
652
|
-
req.params.issueId = await
|
|
702
|
+
req.params.issueId = await resolveIssueRouteId(rawId);
|
|
653
703
|
next();
|
|
654
704
|
}
|
|
655
705
|
catch (err) {
|
|
@@ -687,6 +737,10 @@ export function issueRoutes(db, storage, opts = {}) {
|
|
|
687
737
|
? Number.parseInt(rawLimit, 10)
|
|
688
738
|
: null;
|
|
689
739
|
const limit = parsedLimit === null ? ISSUE_LIST_DEFAULT_LIMIT : clampIssueListLimit(parsedLimit);
|
|
740
|
+
const rawOffset = req.query.offset;
|
|
741
|
+
const parsedOffset = rawOffset !== undefined && /^\d+$/.test(rawOffset)
|
|
742
|
+
? Number.parseInt(rawOffset, 10)
|
|
743
|
+
: null;
|
|
690
744
|
if (assigneeUserFilterRaw === "me" && (!assigneeUserId || req.actor.type !== "board")) {
|
|
691
745
|
res.status(403).json({ error: "assigneeUserId=me requires board authentication" });
|
|
692
746
|
return;
|
|
@@ -707,6 +761,11 @@ export function issueRoutes(db, storage, opts = {}) {
|
|
|
707
761
|
res.status(400).json({ error: `limit must be a positive integer up to ${ISSUE_LIST_MAX_LIMIT}` });
|
|
708
762
|
return;
|
|
709
763
|
}
|
|
764
|
+
if (rawOffset !== undefined && (parsedOffset === null || !Number.isInteger(parsedOffset) || parsedOffset < 0)) {
|
|
765
|
+
res.status(400).json({ error: "offset must be a non-negative integer" });
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
const offset = parsedOffset ?? 0;
|
|
710
769
|
const result = await svc.list(companyId, {
|
|
711
770
|
status: normalizedStatus,
|
|
712
771
|
assigneeAgentId: req.query.assigneeAgentId,
|
|
@@ -725,8 +784,10 @@ export function issueRoutes(db, storage, opts = {}) {
|
|
|
725
784
|
originId: req.query.originId,
|
|
726
785
|
includeRoutineExecutions: req.query.includeRoutineExecutions === "true" || req.query.includeRoutineExecutions === "1",
|
|
727
786
|
excludeRoutineExecutions: req.query.excludeRoutineExecutions === "true" || req.query.excludeRoutineExecutions === "1",
|
|
787
|
+
includeBlockedBy: req.query.includeBlockedBy === "true" || req.query.includeBlockedBy === "1",
|
|
728
788
|
q: req.query.q,
|
|
729
789
|
limit,
|
|
790
|
+
offset,
|
|
730
791
|
});
|
|
731
792
|
res.json(result);
|
|
732
793
|
});
|
|
@@ -795,13 +856,14 @@ export function issueRoutes(db, storage, opts = {}) {
|
|
|
795
856
|
const currentExecutionWorkspacePromise = issue.executionWorkspaceId
|
|
796
857
|
? executionWorkspacesSvc.getById(issue.executionWorkspaceId)
|
|
797
858
|
: Promise.resolve(null);
|
|
798
|
-
const [{ project, goal }, ancestors, commentCursor, wakeComment, relations, blockerAttention, attachments, continuationSummary, currentExecutionWorkspace,] = await Promise.all([
|
|
859
|
+
const [{ project, goal }, ancestors, commentCursor, wakeComment, relations, blockerAttention, productivityReview, attachments, continuationSummary, currentExecutionWorkspace,] = await Promise.all([
|
|
799
860
|
resolveIssueProjectAndGoal(issue),
|
|
800
861
|
svc.getAncestors(issue.id),
|
|
801
862
|
svc.getCommentCursor(issue.id),
|
|
802
863
|
wakeCommentId ? svc.getComment(wakeCommentId) : null,
|
|
803
864
|
svc.getRelationSummaries(issue.id),
|
|
804
865
|
svc.listBlockerAttention(issue.companyId, [issue]).then((map) => map.get(issue.id) ?? null),
|
|
866
|
+
svc.listProductivityReviews(issue.companyId, [issue.id]).then((map) => map.get(issue.id) ?? null),
|
|
805
867
|
svc.listAttachments(issue.id),
|
|
806
868
|
documentsSvc.getIssueDocumentByKey(issue.id, ISSUE_CONTINUATION_SUMMARY_DOCUMENT_KEY),
|
|
807
869
|
currentExecutionWorkspacePromise,
|
|
@@ -814,6 +876,7 @@ export function issueRoutes(db, storage, opts = {}) {
|
|
|
814
876
|
description: issue.description,
|
|
815
877
|
status: issue.status,
|
|
816
878
|
...(blockerAttention ? { blockerAttention } : {}),
|
|
879
|
+
productivityReview,
|
|
817
880
|
priority: issue.priority,
|
|
818
881
|
projectId: issue.projectId,
|
|
819
882
|
goalId: goal?.id ?? issue.goalId,
|
|
@@ -822,6 +885,8 @@ export function issueRoutes(db, storage, opts = {}) {
|
|
|
822
885
|
blocks: relations.blocks,
|
|
823
886
|
assigneeAgentId: issue.assigneeAgentId,
|
|
824
887
|
assigneeUserId: issue.assigneeUserId,
|
|
888
|
+
originKind: issue.originKind,
|
|
889
|
+
originId: issue.originId,
|
|
825
890
|
updatedAt: issue.updatedAt,
|
|
826
891
|
},
|
|
827
892
|
ancestors: ancestors.map((ancestor) => ({
|
|
@@ -881,13 +946,14 @@ export function issueRoutes(db, storage, opts = {}) {
|
|
|
881
946
|
return;
|
|
882
947
|
}
|
|
883
948
|
assertCompanyAccess(req, issue.companyId);
|
|
884
|
-
const [{ project, goal }, ancestors, mentionedProjectIds, documentPayload, relations, blockerAttention, referenceSummary] = await Promise.all([
|
|
949
|
+
const [{ project, goal }, ancestors, mentionedProjectIds, documentPayload, relations, blockerAttention, productivityReview, referenceSummary,] = await Promise.all([
|
|
885
950
|
resolveIssueProjectAndGoal(issue),
|
|
886
951
|
svc.getAncestors(issue.id),
|
|
887
952
|
svc.findMentionedProjectIds(issue.id, { includeCommentBodies: false }),
|
|
888
953
|
documentsSvc.getIssueDocumentPayload(issue),
|
|
889
954
|
svc.getRelationSummaries(issue.id),
|
|
890
955
|
svc.listBlockerAttention(issue.companyId, [issue]).then((map) => map.get(issue.id) ?? null),
|
|
956
|
+
svc.listProductivityReviews(issue.companyId, [issue.id]).then((map) => map.get(issue.id) ?? null),
|
|
891
957
|
issueReferencesSvc.listIssueReferenceSummary(issue.id),
|
|
892
958
|
]);
|
|
893
959
|
const mentionedProjects = mentionedProjectIds.length > 0
|
|
@@ -902,6 +968,7 @@ export function issueRoutes(db, storage, opts = {}) {
|
|
|
902
968
|
goalId: goal?.id ?? issue.goalId,
|
|
903
969
|
ancestors,
|
|
904
970
|
...(blockerAttention ? { blockerAttention } : {}),
|
|
971
|
+
productivityReview,
|
|
905
972
|
blockedBy: relations.blockedBy,
|
|
906
973
|
blocks: relations.blocks,
|
|
907
974
|
relatedWork: referenceSummary,
|
|
@@ -1485,7 +1552,8 @@ export function issueRoutes(db, storage, opts = {}) {
|
|
|
1485
1552
|
await assertIssueEnvironmentSelection(companyId, req.body.executionWorkspaceSettings?.environmentId);
|
|
1486
1553
|
const actor = getActorInfo(req);
|
|
1487
1554
|
const requestedUiLocale = resolveExplicitRequestUiLocale(req);
|
|
1488
|
-
const executionPolicy = normalizeIssueExecutionPolicy(req.body.executionPolicy);
|
|
1555
|
+
const executionPolicy = applyActorMonitorScheduledBy(normalizeIssueExecutionPolicy(req.body.executionPolicy), actor.actorType);
|
|
1556
|
+
assertCanManageIssueMonitor(req, req.body.assigneeAgentId ?? null, Boolean(executionPolicy?.monitor));
|
|
1489
1557
|
const issue = await svc.create(companyId, {
|
|
1490
1558
|
...req.body,
|
|
1491
1559
|
executionPolicy,
|
|
@@ -1515,6 +1583,28 @@ export function issueRoutes(db, storage, opts = {}) {
|
|
|
1515
1583
|
}),
|
|
1516
1584
|
},
|
|
1517
1585
|
});
|
|
1586
|
+
if (executionPolicy?.monitor) {
|
|
1587
|
+
await logActivity(db, {
|
|
1588
|
+
companyId,
|
|
1589
|
+
actorType: actor.actorType,
|
|
1590
|
+
actorId: actor.actorId,
|
|
1591
|
+
agentId: actor.agentId,
|
|
1592
|
+
runId: actor.runId,
|
|
1593
|
+
action: "issue.monitor_scheduled",
|
|
1594
|
+
entityType: "issue",
|
|
1595
|
+
entityId: issue.id,
|
|
1596
|
+
details: {
|
|
1597
|
+
identifier: issue.identifier,
|
|
1598
|
+
nextCheckAt: executionPolicy.monitor.nextCheckAt,
|
|
1599
|
+
notes: executionPolicy.monitor.notes,
|
|
1600
|
+
scheduledBy: executionPolicy.monitor.scheduledBy,
|
|
1601
|
+
serviceName: executionPolicy.monitor.serviceName ?? null,
|
|
1602
|
+
timeoutAt: executionPolicy.monitor.timeoutAt ?? null,
|
|
1603
|
+
maxAttempts: executionPolicy.monitor.maxAttempts ?? null,
|
|
1604
|
+
recoveryPolicy: executionPolicy.monitor.recoveryPolicy ?? null,
|
|
1605
|
+
},
|
|
1606
|
+
});
|
|
1607
|
+
}
|
|
1518
1608
|
void queueIssueAssignmentWakeup({
|
|
1519
1609
|
heartbeat,
|
|
1520
1610
|
issue,
|
|
@@ -1545,7 +1635,8 @@ export function issueRoutes(db, storage, opts = {}) {
|
|
|
1545
1635
|
}
|
|
1546
1636
|
await assertIssueEnvironmentSelection(parent.companyId, req.body.executionWorkspaceSettings?.environmentId);
|
|
1547
1637
|
const actor = getActorInfo(req);
|
|
1548
|
-
const executionPolicy = normalizeIssueExecutionPolicy(req.body.executionPolicy);
|
|
1638
|
+
const executionPolicy = applyActorMonitorScheduledBy(normalizeIssueExecutionPolicy(req.body.executionPolicy), actor.actorType);
|
|
1639
|
+
assertCanManageIssueMonitor(req, req.body.assigneeAgentId ?? null, Boolean(executionPolicy?.monitor));
|
|
1549
1640
|
const { issue, parentBlockerAdded } = await svc.createChild(parent.id, {
|
|
1550
1641
|
...req.body,
|
|
1551
1642
|
executionPolicy,
|
|
@@ -1572,6 +1663,29 @@ export function issueRoutes(db, storage, opts = {}) {
|
|
|
1572
1663
|
...(parentBlockerAdded ? { parentBlockerAdded: true } : {}),
|
|
1573
1664
|
},
|
|
1574
1665
|
});
|
|
1666
|
+
if (executionPolicy?.monitor) {
|
|
1667
|
+
await logActivity(db, {
|
|
1668
|
+
companyId: parent.companyId,
|
|
1669
|
+
actorType: actor.actorType,
|
|
1670
|
+
actorId: actor.actorId,
|
|
1671
|
+
agentId: actor.agentId,
|
|
1672
|
+
runId: actor.runId,
|
|
1673
|
+
action: "issue.monitor_scheduled",
|
|
1674
|
+
entityType: "issue",
|
|
1675
|
+
entityId: issue.id,
|
|
1676
|
+
details: {
|
|
1677
|
+
identifier: issue.identifier,
|
|
1678
|
+
parentId: parent.id,
|
|
1679
|
+
nextCheckAt: executionPolicy.monitor.nextCheckAt,
|
|
1680
|
+
notes: executionPolicy.monitor.notes,
|
|
1681
|
+
scheduledBy: executionPolicy.monitor.scheduledBy,
|
|
1682
|
+
serviceName: executionPolicy.monitor.serviceName ?? null,
|
|
1683
|
+
timeoutAt: executionPolicy.monitor.timeoutAt ?? null,
|
|
1684
|
+
maxAttempts: executionPolicy.monitor.maxAttempts ?? null,
|
|
1685
|
+
recoveryPolicy: executionPolicy.monitor.recoveryPolicy ?? null,
|
|
1686
|
+
},
|
|
1687
|
+
});
|
|
1688
|
+
}
|
|
1575
1689
|
void queueIssueAssignmentWakeup({
|
|
1576
1690
|
heartbeat,
|
|
1577
1691
|
issue,
|
|
@@ -1583,6 +1697,24 @@ export function issueRoutes(db, storage, opts = {}) {
|
|
|
1583
1697
|
});
|
|
1584
1698
|
res.status(201).json(issue);
|
|
1585
1699
|
});
|
|
1700
|
+
router.post("/issues/:id/monitor/check-now", async (req, res) => {
|
|
1701
|
+
const id = req.params.id;
|
|
1702
|
+
const issue = await svc.getById(id);
|
|
1703
|
+
if (!issue) {
|
|
1704
|
+
res.status(404).json({ error: "Issue not found" });
|
|
1705
|
+
return;
|
|
1706
|
+
}
|
|
1707
|
+
assertCompanyAccess(req, issue.companyId);
|
|
1708
|
+
assertCanManageIssueMonitor(req, issue.assigneeAgentId, true);
|
|
1709
|
+
const actor = getActorInfo(req);
|
|
1710
|
+
await heartbeat.triggerIssueMonitor(issue.id, {
|
|
1711
|
+
actorType: actor.actorType,
|
|
1712
|
+
actorId: actor.actorId,
|
|
1713
|
+
agentId: actor.agentId ?? null,
|
|
1714
|
+
runId: actor.runId ?? null,
|
|
1715
|
+
});
|
|
1716
|
+
res.json({ ok: true });
|
|
1717
|
+
});
|
|
1586
1718
|
router.patch("/issues/:id", validate(updateIssueRouteSchema), async (req, res) => {
|
|
1587
1719
|
const id = req.params.id;
|
|
1588
1720
|
const existing = await svc.getById(id);
|
|
@@ -1684,7 +1816,7 @@ export function issueRoutes(db, storage, opts = {}) {
|
|
|
1684
1816
|
updateFields.status = "todo";
|
|
1685
1817
|
}
|
|
1686
1818
|
if (req.body.executionPolicy !== undefined) {
|
|
1687
|
-
updateFields.executionPolicy = normalizeIssueExecutionPolicy(req.body.executionPolicy);
|
|
1819
|
+
updateFields.executionPolicy = applyActorMonitorScheduledBy(normalizeIssueExecutionPolicy(req.body.executionPolicy), actor.actorType);
|
|
1688
1820
|
}
|
|
1689
1821
|
const previousExecutionPolicy = normalizeIssueExecutionPolicy(existing.executionPolicy ?? null);
|
|
1690
1822
|
const nextExecutionPolicy = updateFields.executionPolicy !== undefined
|
|
@@ -1693,9 +1825,12 @@ export function issueRoutes(db, storage, opts = {}) {
|
|
|
1693
1825
|
if (normalizedAssigneeAgentId !== undefined) {
|
|
1694
1826
|
updateFields.assigneeAgentId = normalizedAssigneeAgentId;
|
|
1695
1827
|
}
|
|
1828
|
+
const monitorChanged = monitorPoliciesEqual(previousExecutionPolicy, nextExecutionPolicy) === false;
|
|
1829
|
+
assertCanManageIssueMonitor(req, existing.assigneeAgentId, req.body.executionPolicy !== undefined && monitorChanged);
|
|
1696
1830
|
const transition = applyIssueExecutionPolicyTransition({
|
|
1697
1831
|
issue: existing,
|
|
1698
1832
|
policy: nextExecutionPolicy,
|
|
1833
|
+
previousPolicy: previousExecutionPolicy,
|
|
1699
1834
|
requestedStatus: typeof updateFields.status === "string" ? updateFields.status : undefined,
|
|
1700
1835
|
requestedAssigneePatch: {
|
|
1701
1836
|
assigneeAgentId: normalizedAssigneeAgentId,
|
|
@@ -1707,6 +1842,7 @@ export function issueRoutes(db, storage, opts = {}) {
|
|
|
1707
1842
|
},
|
|
1708
1843
|
commentBody,
|
|
1709
1844
|
reviewRequest: reviewRequest === undefined ? undefined : reviewRequest,
|
|
1845
|
+
monitorExplicitlyUpdated: req.body.executionPolicy !== undefined && monitorChanged,
|
|
1710
1846
|
});
|
|
1711
1847
|
const decisionId = transition.decision ? randomUUID() : null;
|
|
1712
1848
|
if (decisionId) {
|
|
@@ -1980,6 +2116,51 @@ export function issueRoutes(db, storage, opts = {}) {
|
|
|
1980
2116
|
},
|
|
1981
2117
|
});
|
|
1982
2118
|
}
|
|
2119
|
+
const nextStoredExecutionPolicy = normalizeIssueExecutionPolicy(issue.executionPolicy ?? null);
|
|
2120
|
+
const previousMonitor = summarizeIssueMonitor(existing, previousExecutionPolicy);
|
|
2121
|
+
const nextMonitor = summarizeIssueMonitor(issue, nextStoredExecutionPolicy);
|
|
2122
|
+
const monitorScheduledChanged = previousMonitor.nextCheckAt !== nextMonitor.nextCheckAt;
|
|
2123
|
+
if (nextMonitor.nextCheckAt && (monitorScheduledChanged || previousMonitor.notes !== nextMonitor.notes)) {
|
|
2124
|
+
await logActivity(db, {
|
|
2125
|
+
companyId: issue.companyId,
|
|
2126
|
+
actorType: actor.actorType,
|
|
2127
|
+
actorId: actor.actorId,
|
|
2128
|
+
agentId: actor.agentId,
|
|
2129
|
+
runId: actor.runId,
|
|
2130
|
+
action: "issue.monitor_scheduled",
|
|
2131
|
+
entityType: "issue",
|
|
2132
|
+
entityId: issue.id,
|
|
2133
|
+
details: {
|
|
2134
|
+
identifier: issue.identifier,
|
|
2135
|
+
nextCheckAt: nextMonitor.nextCheckAt,
|
|
2136
|
+
previousNextCheckAt: previousMonitor.nextCheckAt,
|
|
2137
|
+
notes: nextMonitor.notes,
|
|
2138
|
+
scheduledBy: nextMonitor.scheduledBy,
|
|
2139
|
+
serviceName: nextMonitor.serviceName,
|
|
2140
|
+
timeoutAt: nextMonitor.timeoutAt,
|
|
2141
|
+
maxAttempts: nextMonitor.maxAttempts,
|
|
2142
|
+
recoveryPolicy: nextMonitor.recoveryPolicy,
|
|
2143
|
+
},
|
|
2144
|
+
});
|
|
2145
|
+
}
|
|
2146
|
+
else if (!nextMonitor.nextCheckAt && previousMonitor.nextCheckAt) {
|
|
2147
|
+
await logActivity(db, {
|
|
2148
|
+
companyId: issue.companyId,
|
|
2149
|
+
actorType: actor.actorType,
|
|
2150
|
+
actorId: actor.actorId,
|
|
2151
|
+
agentId: actor.agentId,
|
|
2152
|
+
runId: actor.runId,
|
|
2153
|
+
action: "issue.monitor_cleared",
|
|
2154
|
+
entityType: "issue",
|
|
2155
|
+
entityId: issue.id,
|
|
2156
|
+
details: {
|
|
2157
|
+
identifier: issue.identifier,
|
|
2158
|
+
previousNextCheckAt: previousMonitor.nextCheckAt,
|
|
2159
|
+
reason: nextMonitor.clearReason ?? "manual",
|
|
2160
|
+
notes: previousMonitor.notes,
|
|
2161
|
+
},
|
|
2162
|
+
});
|
|
2163
|
+
}
|
|
1983
2164
|
if (issue.status === "done" && existing.status !== "done") {
|
|
1984
2165
|
const tc = getTelemetryClient();
|
|
1985
2166
|
if (tc && actor.agentId) {
|
|
@@ -2701,6 +2882,48 @@ export function issueRoutes(db, storage, opts = {}) {
|
|
|
2701
2882
|
});
|
|
2702
2883
|
res.json(interaction);
|
|
2703
2884
|
});
|
|
2885
|
+
router.post("/issues/:id/interactions/:interactionId/cancel", validate(cancelIssueThreadInteractionSchema), async (req, res) => {
|
|
2886
|
+
const id = req.params.id;
|
|
2887
|
+
const interactionId = req.params.interactionId;
|
|
2888
|
+
const issue = await svc.getById(id);
|
|
2889
|
+
if (!issue) {
|
|
2890
|
+
res.status(404).json({ error: "Issue not found" });
|
|
2891
|
+
return;
|
|
2892
|
+
}
|
|
2893
|
+
assertCompanyAccess(req, issue.companyId);
|
|
2894
|
+
assertBoard(req);
|
|
2895
|
+
const actor = getActorInfo(req);
|
|
2896
|
+
const interaction = await issueThreadInteractionService(db).cancelQuestions(issue, interactionId, req.body, {
|
|
2897
|
+
agentId: actor.agentId,
|
|
2898
|
+
userId: actor.actorType === "user" ? actor.actorId : null,
|
|
2899
|
+
});
|
|
2900
|
+
await logActivity(db, {
|
|
2901
|
+
companyId: issue.companyId,
|
|
2902
|
+
actorType: actor.actorType,
|
|
2903
|
+
actorId: actor.actorId,
|
|
2904
|
+
agentId: actor.agentId,
|
|
2905
|
+
runId: actor.runId,
|
|
2906
|
+
action: "issue.thread_interaction_cancelled",
|
|
2907
|
+
entityType: "issue",
|
|
2908
|
+
entityId: issue.id,
|
|
2909
|
+
details: {
|
|
2910
|
+
interactionId: interaction.id,
|
|
2911
|
+
interactionKind: interaction.kind,
|
|
2912
|
+
interactionStatus: interaction.status,
|
|
2913
|
+
cancellationReason: interaction.kind === "ask_user_questions"
|
|
2914
|
+
? (interaction.result?.cancellationReason ?? null)
|
|
2915
|
+
: null,
|
|
2916
|
+
},
|
|
2917
|
+
});
|
|
2918
|
+
queueResolvedInteractionContinuationWakeup({
|
|
2919
|
+
heartbeat,
|
|
2920
|
+
issue,
|
|
2921
|
+
interaction,
|
|
2922
|
+
actor,
|
|
2923
|
+
source: "issue.interaction.cancel",
|
|
2924
|
+
});
|
|
2925
|
+
res.json(interaction);
|
|
2926
|
+
});
|
|
2704
2927
|
router.get("/issues/:id/comments/:commentId", async (req, res) => {
|
|
2705
2928
|
const id = req.params.id;
|
|
2706
2929
|
const commentId = req.params.commentId;
|
|
@@ -3211,13 +3434,15 @@ export function issueRoutes(db, storage, opts = {}) {
|
|
|
3211
3434
|
}
|
|
3212
3435
|
if (!(await assertAgentIssueMutationAllowed(req, res, issue)))
|
|
3213
3436
|
return;
|
|
3437
|
+
const company = await companiesSvc.getById(companyId);
|
|
3438
|
+
const attachmentMaxBytes = normalizeIssueAttachmentMaxBytes(company?.attachmentMaxBytes);
|
|
3214
3439
|
try {
|
|
3215
|
-
await runSingleFileUpload(req, res);
|
|
3440
|
+
await runSingleFileUpload(req, res, attachmentMaxBytes);
|
|
3216
3441
|
}
|
|
3217
3442
|
catch (err) {
|
|
3218
3443
|
if (err instanceof multer.MulterError) {
|
|
3219
3444
|
if (err.code === "LIMIT_FILE_SIZE") {
|
|
3220
|
-
res.status(422).json({ error: `Attachment exceeds ${
|
|
3445
|
+
res.status(422).json({ error: `Attachment exceeds ${attachmentMaxBytes} bytes` });
|
|
3221
3446
|
return;
|
|
3222
3447
|
}
|
|
3223
3448
|
res.status(400).json({ error: err.message });
|