@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/services/issues.js
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import { Buffer } from "node:buffer";
|
|
2
|
-
import { and, asc, desc, eq, inArray, isNull, ne, or, sql } from "drizzle-orm";
|
|
3
|
-
import { activityLog, agentWakeupRequests, agents, assets, companies, companyMemberships, documents, goals, heartbeatRuns, executionWorkspaces, issueAttachments, issueInboxArchives, issueLabels, issueRelations, issueComments, issueDocuments, issueReadStates, issues, labels, projectWorkspaces, projects, } from "@penclipai/db";
|
|
4
|
-
import { extractAgentMentionIds, extractProjectMentionIds, isUuidLike } from "@penclipai/shared";
|
|
2
|
+
import { and, asc, desc, eq, gt, inArray, isNull, lt, ne, notInArray, or, sql } from "drizzle-orm";
|
|
3
|
+
import { activityLog, agentWakeupRequests, agents, approvals, assets, companies, companyMemberships, documents, goals, heartbeatRuns, executionWorkspaces, issueApprovals, issueAttachments, issueInboxArchives, issueLabels, issueRelations, issueComments, issueDocuments, issueReadStates, issueThreadInteractions, issues, labels, projectWorkspaces, projects, } from "@penclipai/db";
|
|
4
|
+
import { clampIssueRequestDepth, extractAgentMentionIds, extractProjectMentionIds, isUuidLike, normalizeIssueIdentifier as normalizeIssueReferenceIdentifier, } from "@penclipai/shared";
|
|
5
5
|
import { conflict, notFound, unprocessable } from "../errors.js";
|
|
6
|
+
import { parseObject } from "../adapters/utils.js";
|
|
6
7
|
import { defaultIssueExecutionWorkspaceSettingsForProject, gateProjectExecutionWorkspacePolicy, issueExecutionWorkspaceModeForPersistedWorkspace, parseIssueExecutionWorkspaceSettings, parseProjectExecutionWorkspacePolicy, } from "./execution-workspace-policy.js";
|
|
8
|
+
import { mergeExecutionWorkspaceConfig } from "./execution-workspaces.js";
|
|
9
|
+
import { buildInitialIssueMonitorFields, normalizeIssueExecutionPolicy } from "./issue-execution-policy.js";
|
|
7
10
|
import { instanceSettingsService } from "./instance-settings.js";
|
|
8
11
|
import { redactCurrentUserText } from "../log-redaction.js";
|
|
9
12
|
import { resolveIssueGoalId, resolveNextIssueGoalId } from "./issue-goal-fallback.js";
|
|
10
13
|
import { getDefaultCompanyGoal } from "./goals.js";
|
|
11
14
|
import { isVerifiedIssueTreeControlInteractionWake, issueTreeControlService, } from "./issue-tree-control.js";
|
|
15
|
+
import { parseIssueGraphLivenessIncidentKey } from "./recovery/origins.js";
|
|
12
16
|
const ALL_ISSUE_STATUSES = ["backlog", "todo", "in_progress", "in_review", "blocked", "done", "cancelled"];
|
|
13
17
|
const MAX_ISSUE_COMMENT_PAGE_LIMIT = 500;
|
|
14
18
|
export const ISSUE_LIST_DEFAULT_LIMIT = 500;
|
|
@@ -44,6 +48,14 @@ function readStringFromRecord(record, key) {
|
|
|
44
48
|
const value = record[key];
|
|
45
49
|
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
46
50
|
}
|
|
51
|
+
function buildReusedExecutionWorkspaceConfigPatchFromIssueSettings(settings) {
|
|
52
|
+
return {
|
|
53
|
+
environmentId: settings?.environmentId ?? null,
|
|
54
|
+
provisionCommand: settings?.workspaceStrategy?.provisionCommand ?? null,
|
|
55
|
+
teardownCommand: settings?.workspaceStrategy?.teardownCommand ?? null,
|
|
56
|
+
workspaceRuntime: settings?.workspaceRuntime ?? null,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
47
59
|
function sameRunLock(checkoutRunId, actorRunId) {
|
|
48
60
|
if (actorRunId)
|
|
49
61
|
return checkoutRunId === actorRunId;
|
|
@@ -463,6 +475,21 @@ async function withIssueLabels(dbOrTx, rows) {
|
|
|
463
475
|
const ACTIVE_RUN_STATUSES = ["queued", "running"];
|
|
464
476
|
const BLOCKER_ATTENTION_ACTIVE_RUN_STATUSES = ["queued", "running"];
|
|
465
477
|
const BLOCKER_ATTENTION_ACTIVE_WAKE_STATUSES = ["queued", "deferred_issue_execution"];
|
|
478
|
+
const BLOCKER_ATTENTION_PENDING_INTERACTION_STATUSES = ["pending"];
|
|
479
|
+
const BLOCKER_ATTENTION_PENDING_APPROVAL_STATUSES = ["pending", "revision_requested"];
|
|
480
|
+
const BLOCKER_ATTENTION_OPEN_RECOVERY_ORIGIN_KIND = "harness_liveness_escalation";
|
|
481
|
+
const PRODUCTIVITY_REVIEW_ORIGIN_KIND = "issue_productivity_review";
|
|
482
|
+
const PRODUCTIVITY_REVIEW_TERMINAL_STATUSES = ["done", "cancelled"];
|
|
483
|
+
const PRODUCTIVITY_REVIEW_ACTIVITY_ACTIONS = [
|
|
484
|
+
"issue.productivity_review_created",
|
|
485
|
+
"issue.productivity_review_updated",
|
|
486
|
+
];
|
|
487
|
+
const PRODUCTIVITY_REVIEW_TRIGGERS = [
|
|
488
|
+
"no_comment_streak",
|
|
489
|
+
"long_active_duration",
|
|
490
|
+
"high_churn",
|
|
491
|
+
];
|
|
492
|
+
const BLOCKER_ATTENTION_OPEN_RECOVERY_TERMINAL_STATUSES = ["done", "cancelled"];
|
|
466
493
|
const BLOCKER_ATTENTION_MAX_DEPTH = 8;
|
|
467
494
|
const BLOCKER_ATTENTION_MAX_NODES = 2000;
|
|
468
495
|
const BLOCKER_ATTENTION_INVOKABLE_AGENT_STATUSES = new Set(["active", "idle", "running", "error"]);
|
|
@@ -499,8 +526,10 @@ function createIssueBlockerAttention(input = {}) {
|
|
|
499
526
|
reason: input.reason ?? null,
|
|
500
527
|
unresolvedBlockerCount: input.unresolvedBlockerCount ?? 0,
|
|
501
528
|
coveredBlockerCount: input.coveredBlockerCount ?? 0,
|
|
529
|
+
stalledBlockerCount: input.stalledBlockerCount ?? 0,
|
|
502
530
|
attentionBlockerCount: input.attentionBlockerCount ?? 0,
|
|
503
531
|
sampleBlockerIdentifier: input.sampleBlockerIdentifier ?? null,
|
|
532
|
+
sampleStalledBlockerIdentifier: input.sampleStalledBlockerIdentifier ?? null,
|
|
504
533
|
};
|
|
505
534
|
}
|
|
506
535
|
function blockerSampleIdentifier(node) {
|
|
@@ -594,6 +623,81 @@ async function terminalExplicitBlockersByRoot(companyId, roots, dbOrTx) {
|
|
|
594
623
|
}
|
|
595
624
|
return terminalByRoot;
|
|
596
625
|
}
|
|
626
|
+
function readProductivityReviewTrigger(value) {
|
|
627
|
+
if (typeof value !== "string")
|
|
628
|
+
return null;
|
|
629
|
+
return PRODUCTIVITY_REVIEW_TRIGGERS.includes(value)
|
|
630
|
+
? value
|
|
631
|
+
: null;
|
|
632
|
+
}
|
|
633
|
+
function readProductivityReviewStreak(value) {
|
|
634
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0)
|
|
635
|
+
return null;
|
|
636
|
+
return Math.floor(value);
|
|
637
|
+
}
|
|
638
|
+
async function listIssueProductivityReviewMap(dbOrTx, companyId, sourceIssueIds) {
|
|
639
|
+
const map = new Map();
|
|
640
|
+
if (sourceIssueIds.length === 0)
|
|
641
|
+
return map;
|
|
642
|
+
const reviewRows = [];
|
|
643
|
+
for (const chunk of chunkList([...new Set(sourceIssueIds)], ISSUE_LIST_RELATED_QUERY_CHUNK_SIZE)) {
|
|
644
|
+
const rows = await dbOrTx
|
|
645
|
+
.select({
|
|
646
|
+
sourceIssueId: issues.originId,
|
|
647
|
+
reviewIssueId: issues.id,
|
|
648
|
+
reviewIdentifier: issues.identifier,
|
|
649
|
+
status: issues.status,
|
|
650
|
+
priority: issues.priority,
|
|
651
|
+
createdAt: issues.createdAt,
|
|
652
|
+
updatedAt: issues.updatedAt,
|
|
653
|
+
})
|
|
654
|
+
.from(issues)
|
|
655
|
+
.where(and(eq(issues.companyId, companyId), eq(issues.originKind, PRODUCTIVITY_REVIEW_ORIGIN_KIND), inArray(issues.originId, chunk), isNull(issues.hiddenAt), notInArray(issues.status, PRODUCTIVITY_REVIEW_TERMINAL_STATUSES)))
|
|
656
|
+
.orderBy(desc(issues.createdAt), desc(issues.id));
|
|
657
|
+
reviewRows.push(...rows);
|
|
658
|
+
}
|
|
659
|
+
if (reviewRows.length === 0)
|
|
660
|
+
return map;
|
|
661
|
+
const reviewIssueIds = reviewRows.map((row) => row.reviewIssueId);
|
|
662
|
+
const triggerByReviewIssueId = new Map();
|
|
663
|
+
for (const chunk of chunkList(reviewIssueIds, ISSUE_LIST_RELATED_QUERY_CHUNK_SIZE)) {
|
|
664
|
+
const detailRows = await dbOrTx
|
|
665
|
+
.select({
|
|
666
|
+
entityId: activityLog.entityId,
|
|
667
|
+
details: activityLog.details,
|
|
668
|
+
createdAt: activityLog.createdAt,
|
|
669
|
+
})
|
|
670
|
+
.from(activityLog)
|
|
671
|
+
.where(and(eq(activityLog.companyId, companyId), eq(activityLog.entityType, "issue"), inArray(activityLog.entityId, chunk), inArray(activityLog.action, PRODUCTIVITY_REVIEW_ACTIVITY_ACTIONS)))
|
|
672
|
+
.orderBy(desc(activityLog.createdAt));
|
|
673
|
+
for (const row of detailRows) {
|
|
674
|
+
if (triggerByReviewIssueId.has(row.entityId))
|
|
675
|
+
continue;
|
|
676
|
+
triggerByReviewIssueId.set(row.entityId, {
|
|
677
|
+
trigger: readProductivityReviewTrigger(row.details?.trigger),
|
|
678
|
+
noCommentStreak: readProductivityReviewStreak(row.details?.noCommentStreak),
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
for (const row of reviewRows) {
|
|
683
|
+
if (!row.sourceIssueId)
|
|
684
|
+
continue;
|
|
685
|
+
if (map.has(row.sourceIssueId))
|
|
686
|
+
continue;
|
|
687
|
+
const detail = triggerByReviewIssueId.get(row.reviewIssueId);
|
|
688
|
+
map.set(row.sourceIssueId, {
|
|
689
|
+
reviewIssueId: row.reviewIssueId,
|
|
690
|
+
reviewIdentifier: row.reviewIdentifier,
|
|
691
|
+
status: row.status,
|
|
692
|
+
priority: row.priority,
|
|
693
|
+
trigger: detail?.trigger ?? null,
|
|
694
|
+
noCommentStreak: detail?.noCommentStreak ?? null,
|
|
695
|
+
createdAt: row.createdAt,
|
|
696
|
+
updatedAt: row.updatedAt,
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
return map;
|
|
700
|
+
}
|
|
597
701
|
async function listIssueBlockerAttentionMap(dbOrTx, companyId, issueRows) {
|
|
598
702
|
const roots = issueRows.filter((row) => row.companyId === companyId && row.status === "blocked");
|
|
599
703
|
const attentionMap = new Map();
|
|
@@ -719,6 +823,42 @@ async function listIssueBlockerAttentionMap(dbOrTx, companyId, issueRows) {
|
|
|
719
823
|
activeIssueIds.add(row.issueId);
|
|
720
824
|
}
|
|
721
825
|
}
|
|
826
|
+
const explicitWaitCandidateIds = [...nodesById.values()]
|
|
827
|
+
.filter((node) => node.status !== "done")
|
|
828
|
+
.map((node) => node.id);
|
|
829
|
+
const explicitWaitingIssueIds = new Set();
|
|
830
|
+
if (explicitWaitCandidateIds.length > 0) {
|
|
831
|
+
for (const chunk of chunkList(explicitWaitCandidateIds, ISSUE_LIST_RELATED_QUERY_CHUNK_SIZE)) {
|
|
832
|
+
const interactionRows = await dbOrTx
|
|
833
|
+
.select({ issueId: issueThreadInteractions.issueId })
|
|
834
|
+
.from(issueThreadInteractions)
|
|
835
|
+
.where(and(eq(issueThreadInteractions.companyId, companyId), inArray(issueThreadInteractions.status, BLOCKER_ATTENTION_PENDING_INTERACTION_STATUSES), inArray(issueThreadInteractions.issueId, chunk)));
|
|
836
|
+
for (const row of interactionRows)
|
|
837
|
+
explicitWaitingIssueIds.add(row.issueId);
|
|
838
|
+
const approvalRows = await dbOrTx
|
|
839
|
+
.select({ issueId: issueApprovals.issueId })
|
|
840
|
+
.from(issueApprovals)
|
|
841
|
+
.innerJoin(approvals, eq(issueApprovals.approvalId, approvals.id))
|
|
842
|
+
.where(and(eq(issueApprovals.companyId, companyId), inArray(approvals.status, BLOCKER_ATTENTION_PENDING_APPROVAL_STATUSES), inArray(issueApprovals.issueId, chunk)));
|
|
843
|
+
for (const row of approvalRows)
|
|
844
|
+
explicitWaitingIssueIds.add(row.issueId);
|
|
845
|
+
}
|
|
846
|
+
// Recovery rows are intentionally company-wide: a liveness escalation for
|
|
847
|
+
// the same leaf blocker represents an active waiting path even when that
|
|
848
|
+
// blocker is reached through another blocked graph.
|
|
849
|
+
const recoveryRows = await dbOrTx
|
|
850
|
+
.select({ id: issues.id, originId: issues.originId })
|
|
851
|
+
.from(issues)
|
|
852
|
+
.where(and(eq(issues.companyId, companyId), eq(issues.originKind, BLOCKER_ATTENTION_OPEN_RECOVERY_ORIGIN_KIND), isNull(issues.hiddenAt), notInArray(issues.status, BLOCKER_ATTENTION_OPEN_RECOVERY_TERMINAL_STATUSES)));
|
|
853
|
+
for (const row of recoveryRows) {
|
|
854
|
+
const parsed = parseIssueGraphLivenessIncidentKey(row.originId);
|
|
855
|
+
if (!parsed || parsed.companyId !== companyId)
|
|
856
|
+
continue;
|
|
857
|
+
explicitWaitingIssueIds.add(row.id);
|
|
858
|
+
explicitWaitingIssueIds.add(parsed.issueId);
|
|
859
|
+
explicitWaitingIssueIds.add(parsed.leafIssueId);
|
|
860
|
+
}
|
|
861
|
+
}
|
|
722
862
|
const agentRows = agentIds.size > 0
|
|
723
863
|
? await dbOrTx
|
|
724
864
|
.select({
|
|
@@ -731,37 +871,73 @@ async function listIssueBlockerAttentionMap(dbOrTx, companyId, issueRows) {
|
|
|
731
871
|
: [];
|
|
732
872
|
const agentsById = new Map(agentRows.map((agent) => [agent.id, agent]));
|
|
733
873
|
const classifyPath = (nodeId, seen) => {
|
|
734
|
-
|
|
735
|
-
|
|
874
|
+
const sample = blockerSampleIdentifier(nodesById.get(nodeId));
|
|
875
|
+
if (truncated || seen.has(nodeId)) {
|
|
876
|
+
return { covered: false, stalled: false, sampleBlockerIdentifier: sample, sampleStalledBlockerIdentifier: null };
|
|
877
|
+
}
|
|
736
878
|
const node = nodesById.get(nodeId);
|
|
737
|
-
if (!node || node.companyId !== companyId)
|
|
738
|
-
return { covered: false, sampleBlockerIdentifier: nodeId };
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
if (
|
|
742
|
-
return { covered: true, sampleBlockerIdentifier:
|
|
743
|
-
|
|
744
|
-
|
|
879
|
+
if (!node || node.companyId !== companyId) {
|
|
880
|
+
return { covered: false, stalled: false, sampleBlockerIdentifier: nodeId, sampleStalledBlockerIdentifier: null };
|
|
881
|
+
}
|
|
882
|
+
const nodeSample = blockerSampleIdentifier(node);
|
|
883
|
+
if (node.status === "done") {
|
|
884
|
+
return { covered: true, stalled: false, sampleBlockerIdentifier: nodeSample, sampleStalledBlockerIdentifier: null };
|
|
885
|
+
}
|
|
886
|
+
if (explicitWaitingIssueIds.has(node.id)) {
|
|
887
|
+
return { covered: true, stalled: false, sampleBlockerIdentifier: nodeSample, sampleStalledBlockerIdentifier: null };
|
|
888
|
+
}
|
|
889
|
+
if (node.status === "in_review") {
|
|
890
|
+
const hasWaitingPath = activeIssueIds.has(node.id) || Boolean(node.assigneeUserId);
|
|
891
|
+
if (hasWaitingPath) {
|
|
892
|
+
return { covered: true, stalled: false, sampleBlockerIdentifier: nodeSample, sampleStalledBlockerIdentifier: null };
|
|
893
|
+
}
|
|
894
|
+
return { covered: false, stalled: true, sampleBlockerIdentifier: nodeSample, sampleStalledBlockerIdentifier: nodeSample };
|
|
895
|
+
}
|
|
896
|
+
if (activeIssueIds.has(node.id)) {
|
|
897
|
+
return { covered: true, stalled: false, sampleBlockerIdentifier: nodeSample, sampleStalledBlockerIdentifier: null };
|
|
898
|
+
}
|
|
899
|
+
if (node.status === "cancelled") {
|
|
900
|
+
return { covered: false, stalled: false, sampleBlockerIdentifier: nodeSample, sampleStalledBlockerIdentifier: null };
|
|
901
|
+
}
|
|
745
902
|
const downstream = (edgesByIssueId.get(node.id) ?? []).filter((edge) => nodesById.get(edge.blockerIssueId)?.status !== "done");
|
|
746
903
|
if (downstream.length > 0) {
|
|
747
904
|
const nextSeen = new Set(seen);
|
|
748
905
|
nextSeen.add(nodeId);
|
|
749
906
|
const classified = downstream.map((edge) => classifyPath(edge.blockerIssueId, nextSeen));
|
|
750
|
-
const
|
|
751
|
-
|
|
752
|
-
|
|
907
|
+
const stalledChild = classified.find((result) => result.stalled || result.sampleStalledBlockerIdentifier);
|
|
908
|
+
const sampleStalled = stalledChild?.sampleStalledBlockerIdentifier ?? null;
|
|
909
|
+
const hardAttention = classified.find((result) => !result.covered && !result.stalled);
|
|
910
|
+
if (hardAttention) {
|
|
911
|
+
return {
|
|
912
|
+
covered: false,
|
|
913
|
+
stalled: false,
|
|
914
|
+
sampleBlockerIdentifier: hardAttention.sampleBlockerIdentifier,
|
|
915
|
+
sampleStalledBlockerIdentifier: sampleStalled,
|
|
916
|
+
};
|
|
917
|
+
}
|
|
918
|
+
const stalledEntry = classified.find((result) => result.stalled);
|
|
919
|
+
if (stalledEntry) {
|
|
920
|
+
return {
|
|
921
|
+
covered: false,
|
|
922
|
+
stalled: true,
|
|
923
|
+
sampleBlockerIdentifier: stalledEntry.sampleBlockerIdentifier,
|
|
924
|
+
sampleStalledBlockerIdentifier: sampleStalled,
|
|
925
|
+
};
|
|
926
|
+
}
|
|
753
927
|
return {
|
|
754
928
|
covered: true,
|
|
755
|
-
|
|
929
|
+
stalled: false,
|
|
930
|
+
sampleBlockerIdentifier: classified[0]?.sampleBlockerIdentifier ?? nodeSample,
|
|
931
|
+
sampleStalledBlockerIdentifier: null,
|
|
756
932
|
};
|
|
757
933
|
}
|
|
758
934
|
if (node.assigneeAgentId) {
|
|
759
935
|
const assignee = agentsById.get(node.assigneeAgentId);
|
|
760
936
|
if (!assignee || assignee.companyId !== companyId || !BLOCKER_ATTENTION_INVOKABLE_AGENT_STATUSES.has(assignee.status)) {
|
|
761
|
-
return { covered: false, sampleBlockerIdentifier:
|
|
937
|
+
return { covered: false, stalled: false, sampleBlockerIdentifier: nodeSample, sampleStalledBlockerIdentifier: null };
|
|
762
938
|
}
|
|
763
939
|
}
|
|
764
|
-
return { covered: false, sampleBlockerIdentifier:
|
|
940
|
+
return { covered: false, stalled: false, sampleBlockerIdentifier: nodeSample, sampleStalledBlockerIdentifier: null };
|
|
765
941
|
};
|
|
766
942
|
for (const root of roots) {
|
|
767
943
|
const topLevelEdges = (edgesByIssueId.get(root.id) ?? []).filter((edge) => nodesById.get(edge.blockerIssueId)?.status !== "done");
|
|
@@ -777,21 +953,40 @@ async function listIssueBlockerAttentionMap(dbOrTx, companyId, issueRows) {
|
|
|
777
953
|
result: classifyPath(edge.blockerIssueId, new Set([root.id])),
|
|
778
954
|
}));
|
|
779
955
|
const coveredBlockerCount = classified.filter((entry) => entry.result.covered).length;
|
|
780
|
-
const
|
|
781
|
-
const
|
|
782
|
-
const
|
|
956
|
+
const stalledBlockerCount = classified.filter((entry) => entry.result.stalled).length;
|
|
957
|
+
const attentionBlockerCount = classified.length - coveredBlockerCount - stalledBlockerCount;
|
|
958
|
+
const hardAttentionEntry = classified.find((entry) => !entry.result.covered && !entry.result.stalled);
|
|
959
|
+
const stalledEntry = classified.find((entry) => entry.result.stalled);
|
|
960
|
+
const sampleEntry = hardAttentionEntry ?? stalledEntry ?? classified[0] ?? null;
|
|
783
961
|
const sampleNode = sampleEntry ? nodesById.get(sampleEntry.edge.blockerIssueId) : null;
|
|
962
|
+
const sampleStalledFromChain = classified
|
|
963
|
+
.map((entry) => entry.result.sampleStalledBlockerIdentifier)
|
|
964
|
+
.find((value) => value);
|
|
965
|
+
let state;
|
|
966
|
+
let reason;
|
|
967
|
+
if (attentionBlockerCount > 0) {
|
|
968
|
+
state = "needs_attention";
|
|
969
|
+
reason = "attention_required";
|
|
970
|
+
}
|
|
971
|
+
else if (stalledBlockerCount > 0) {
|
|
972
|
+
state = "stalled";
|
|
973
|
+
reason = "stalled_review";
|
|
974
|
+
}
|
|
975
|
+
else {
|
|
976
|
+
state = "covered";
|
|
977
|
+
reason = topLevelEdges.every((edge) => nodesById.get(edge.blockerIssueId)?.parentId === root.id)
|
|
978
|
+
? "active_child"
|
|
979
|
+
: "active_dependency";
|
|
980
|
+
}
|
|
784
981
|
attentionMap.set(root.id, createIssueBlockerAttention({
|
|
785
|
-
state
|
|
786
|
-
reason
|
|
787
|
-
? topLevelEdges.every((edge) => nodesById.get(edge.blockerIssueId)?.parentId === root.id)
|
|
788
|
-
? "active_child"
|
|
789
|
-
: "active_dependency"
|
|
790
|
-
: "attention_required",
|
|
982
|
+
state,
|
|
983
|
+
reason,
|
|
791
984
|
unresolvedBlockerCount: topLevelEdges.length,
|
|
792
985
|
coveredBlockerCount,
|
|
986
|
+
stalledBlockerCount,
|
|
793
987
|
attentionBlockerCount,
|
|
794
988
|
sampleBlockerIdentifier: sampleEntry?.result.sampleBlockerIdentifier ?? blockerSampleIdentifier(sampleNode),
|
|
989
|
+
sampleStalledBlockerIdentifier: stalledEntry?.result.sampleStalledBlockerIdentifier ?? sampleStalledFromChain ?? null,
|
|
795
990
|
}));
|
|
796
991
|
}
|
|
797
992
|
return attentionMap;
|
|
@@ -837,6 +1032,12 @@ const issueListSelect = {
|
|
|
837
1032
|
assigneeAdapterOverrides: issues.assigneeAdapterOverrides,
|
|
838
1033
|
executionPolicy: sql `null`,
|
|
839
1034
|
executionState: sql `null`,
|
|
1035
|
+
monitorNextCheckAt: issues.monitorNextCheckAt,
|
|
1036
|
+
monitorWakeRequestedAt: issues.monitorWakeRequestedAt,
|
|
1037
|
+
monitorLastTriggeredAt: issues.monitorLastTriggeredAt,
|
|
1038
|
+
monitorAttemptCount: issues.monitorAttemptCount,
|
|
1039
|
+
monitorNotes: issues.monitorNotes,
|
|
1040
|
+
monitorScheduledBy: issues.monitorScheduledBy,
|
|
840
1041
|
executionWorkspaceId: issues.executionWorkspaceId,
|
|
841
1042
|
executionWorkspacePreference: issues.executionWorkspacePreference,
|
|
842
1043
|
executionWorkspaceSettings: sql `null`,
|
|
@@ -935,6 +1136,49 @@ async function lastActivityStatsForIssues(dbOrTx, companyId, issueIds) {
|
|
|
935
1136
|
}
|
|
936
1137
|
return [...byIssueId.values()];
|
|
937
1138
|
}
|
|
1139
|
+
async function blockedByMapForIssues(dbOrTx, companyId, issueIds) {
|
|
1140
|
+
const map = new Map();
|
|
1141
|
+
const uniqueIssueIds = [...new Set(issueIds)];
|
|
1142
|
+
if (uniqueIssueIds.length === 0)
|
|
1143
|
+
return map;
|
|
1144
|
+
for (const issueId of uniqueIssueIds) {
|
|
1145
|
+
map.set(issueId, []);
|
|
1146
|
+
}
|
|
1147
|
+
for (const issueIdChunk of chunkList(uniqueIssueIds, ISSUE_LIST_RELATED_QUERY_CHUNK_SIZE)) {
|
|
1148
|
+
const rows = await dbOrTx
|
|
1149
|
+
.select({
|
|
1150
|
+
currentIssueId: issueRelations.relatedIssueId,
|
|
1151
|
+
relatedId: issues.id,
|
|
1152
|
+
identifier: issues.identifier,
|
|
1153
|
+
title: issues.title,
|
|
1154
|
+
status: issues.status,
|
|
1155
|
+
priority: issues.priority,
|
|
1156
|
+
assigneeAgentId: issues.assigneeAgentId,
|
|
1157
|
+
assigneeUserId: issues.assigneeUserId,
|
|
1158
|
+
})
|
|
1159
|
+
.from(issueRelations)
|
|
1160
|
+
.innerJoin(issues, eq(issueRelations.issueId, issues.id))
|
|
1161
|
+
.where(and(eq(issueRelations.companyId, companyId), eq(issueRelations.type, "blocks"), inArray(issueRelations.relatedIssueId, issueIdChunk)));
|
|
1162
|
+
for (const row of rows) {
|
|
1163
|
+
const blockedBy = map.get(row.currentIssueId);
|
|
1164
|
+
if (!blockedBy)
|
|
1165
|
+
continue;
|
|
1166
|
+
blockedBy.push({
|
|
1167
|
+
id: row.relatedId,
|
|
1168
|
+
identifier: row.identifier,
|
|
1169
|
+
title: row.title,
|
|
1170
|
+
status: row.status,
|
|
1171
|
+
priority: row.priority,
|
|
1172
|
+
assigneeAgentId: row.assigneeAgentId,
|
|
1173
|
+
assigneeUserId: row.assigneeUserId,
|
|
1174
|
+
});
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
for (const blockedBy of map.values()) {
|
|
1178
|
+
blockedBy.sort((a, b) => a.title.localeCompare(b.title));
|
|
1179
|
+
}
|
|
1180
|
+
return map;
|
|
1181
|
+
}
|
|
938
1182
|
export function issueService(db) {
|
|
939
1183
|
const instanceSettings = instanceSettingsService(db);
|
|
940
1184
|
const treeControlSvc = issueTreeControlService(db);
|
|
@@ -1298,10 +1542,14 @@ export function issueService(db) {
|
|
|
1298
1542
|
const limit = typeof filters?.limit === "number" && Number.isFinite(filters.limit)
|
|
1299
1543
|
? Math.max(1, Math.floor(filters.limit))
|
|
1300
1544
|
: undefined;
|
|
1545
|
+
const offset = typeof filters?.offset === "number" && Number.isFinite(filters.offset)
|
|
1546
|
+
? Math.max(0, Math.floor(filters.offset))
|
|
1547
|
+
: 0;
|
|
1301
1548
|
const touchedByUserId = filters?.touchedByUserId?.trim() || undefined;
|
|
1302
1549
|
const inboxArchivedByUserId = filters?.inboxArchivedByUserId?.trim() || undefined;
|
|
1303
1550
|
const unreadForUserId = filters?.unreadForUserId?.trim() || undefined;
|
|
1304
1551
|
const contextUserId = unreadForUserId ?? touchedByUserId ?? inboxArchivedByUserId;
|
|
1552
|
+
const includeBlockedBy = filters?.includeBlockedBy === true;
|
|
1305
1553
|
const rawSearch = filters?.q?.trim() ?? "";
|
|
1306
1554
|
const hasSearch = rawSearch.length > 0;
|
|
1307
1555
|
const escapedSearch = hasSearch ? escapeLikePattern(rawSearch) : "";
|
|
@@ -1408,8 +1656,11 @@ export function issueService(db) {
|
|
|
1408
1656
|
.select(issueListSelect)
|
|
1409
1657
|
.from(issues)
|
|
1410
1658
|
.where(and(...conditions))
|
|
1411
|
-
.orderBy(hasSearch ? asc(searchOrder) : asc(priorityOrder), asc(priorityOrder), desc(canonicalLastActivityAt), desc(issues.updatedAt));
|
|
1412
|
-
const
|
|
1659
|
+
.orderBy(hasSearch ? asc(searchOrder) : asc(priorityOrder), asc(priorityOrder), desc(canonicalLastActivityAt), desc(issues.updatedAt), desc(issues.id));
|
|
1660
|
+
const pageQuery = offset > 0
|
|
1661
|
+
? (limit === undefined ? baseQuery.offset(offset) : baseQuery.limit(limit).offset(offset))
|
|
1662
|
+
: (limit === undefined ? baseQuery : baseQuery.limit(limit));
|
|
1663
|
+
const rows = (await pageQuery).map((row) => ({
|
|
1413
1664
|
...row,
|
|
1414
1665
|
description: decodeDatabaseTextPreview(row.description, ISSUE_LIST_DESCRIPTION_MAX_CHARS),
|
|
1415
1666
|
}));
|
|
@@ -1420,7 +1671,7 @@ export function issueService(db) {
|
|
|
1420
1671
|
return withRuns;
|
|
1421
1672
|
}
|
|
1422
1673
|
const issueIds = withRuns.map((row) => row.id);
|
|
1423
|
-
const [statsRows, readRows, lastActivityRows] = await Promise.all([
|
|
1674
|
+
const [statsRows, readRows, lastActivityRows, blockedByMap] = await Promise.all([
|
|
1424
1675
|
contextUserId
|
|
1425
1676
|
? userCommentStatsForIssues(db, companyId, contextUserId, issueIds)
|
|
1426
1677
|
: Promise.resolve([]),
|
|
@@ -1428,18 +1679,28 @@ export function issueService(db) {
|
|
|
1428
1679
|
? userReadStatsForIssues(db, companyId, contextUserId, issueIds)
|
|
1429
1680
|
: Promise.resolve([]),
|
|
1430
1681
|
lastActivityStatsForIssues(db, companyId, issueIds),
|
|
1682
|
+
includeBlockedBy
|
|
1683
|
+
? blockedByMapForIssues(db, companyId, issueIds)
|
|
1684
|
+
: Promise.resolve(new Map()),
|
|
1431
1685
|
]);
|
|
1432
1686
|
const statsByIssueId = new Map(statsRows.map((row) => [row.issueId, row]));
|
|
1433
1687
|
const lastActivityByIssueId = new Map(lastActivityRows.map((row) => [row.issueId, row]));
|
|
1434
|
-
const blockerAttentionByIssueId = await
|
|
1688
|
+
const [blockerAttentionByIssueId, productivityReviewByIssueId] = await Promise.all([
|
|
1689
|
+
listIssueBlockerAttentionMap(db, companyId, withRuns),
|
|
1690
|
+
listIssueProductivityReviewMap(db, companyId, issueIds),
|
|
1691
|
+
]);
|
|
1435
1692
|
if (!contextUserId) {
|
|
1436
1693
|
return withRuns.map((row) => {
|
|
1437
1694
|
const activity = lastActivityByIssueId.get(row.id);
|
|
1438
1695
|
const lastActivityAt = latestIssueActivityAt(row.updatedAt, activity?.latestCommentAt ?? null, activity?.latestLogAt ?? null) ?? row.updatedAt;
|
|
1439
1696
|
return {
|
|
1440
1697
|
...row,
|
|
1698
|
+
...(includeBlockedBy ? { blockedBy: blockedByMap.get(row.id) ?? [] } : {}),
|
|
1441
1699
|
lastActivityAt,
|
|
1442
1700
|
...(blockerAttentionByIssueId.has(row.id) ? { blockerAttention: blockerAttentionByIssueId.get(row.id) } : {}),
|
|
1701
|
+
...(productivityReviewByIssueId.has(row.id)
|
|
1702
|
+
? { productivityReview: productivityReviewByIssueId.get(row.id) }
|
|
1703
|
+
: {}),
|
|
1443
1704
|
};
|
|
1444
1705
|
});
|
|
1445
1706
|
}
|
|
@@ -1449,8 +1710,12 @@ export function issueService(db) {
|
|
|
1449
1710
|
const lastActivityAt = latestIssueActivityAt(row.updatedAt, activity?.latestCommentAt ?? null, activity?.latestLogAt ?? null) ?? row.updatedAt;
|
|
1450
1711
|
return {
|
|
1451
1712
|
...row,
|
|
1713
|
+
...(includeBlockedBy ? { blockedBy: blockedByMap.get(row.id) ?? [] } : {}),
|
|
1452
1714
|
lastActivityAt,
|
|
1453
1715
|
...(blockerAttentionByIssueId.has(row.id) ? { blockerAttention: blockerAttentionByIssueId.get(row.id) } : {}),
|
|
1716
|
+
...(productivityReviewByIssueId.has(row.id)
|
|
1717
|
+
? { productivityReview: productivityReviewByIssueId.get(row.id) }
|
|
1718
|
+
: {}),
|
|
1454
1719
|
...deriveIssueUserContext(row, contextUserId, {
|
|
1455
1720
|
myLastCommentAt: statsByIssueId.get(row.id)?.myLastCommentAt ?? null,
|
|
1456
1721
|
myLastReadAt: readByIssueId.get(row.id) ?? null,
|
|
@@ -1538,8 +1803,9 @@ export function issueService(db) {
|
|
|
1538
1803
|
},
|
|
1539
1804
|
getById: async (raw) => {
|
|
1540
1805
|
const id = raw.trim();
|
|
1541
|
-
|
|
1542
|
-
|
|
1806
|
+
const identifier = normalizeIssueReferenceIdentifier(id);
|
|
1807
|
+
if (identifier) {
|
|
1808
|
+
return getIssueByIdentifier(identifier);
|
|
1543
1809
|
}
|
|
1544
1810
|
if (!isUuidLike(id)) {
|
|
1545
1811
|
return null;
|
|
@@ -1577,6 +1843,9 @@ export function issueService(db) {
|
|
|
1577
1843
|
listBlockerAttention: async (companyId, issueRows, dbOrTx = db) => {
|
|
1578
1844
|
return listIssueBlockerAttentionMap(dbOrTx, companyId, issueRows);
|
|
1579
1845
|
},
|
|
1846
|
+
listProductivityReviews: async (companyId, sourceIssueIds, dbOrTx = db) => {
|
|
1847
|
+
return listIssueProductivityReviewMap(dbOrTx, companyId, sourceIssueIds);
|
|
1848
|
+
},
|
|
1580
1849
|
listWakeableBlockedDependents: async (blockerIssueId) => {
|
|
1581
1850
|
const blockerIssue = await db
|
|
1582
1851
|
.select({ id: issues.id, companyId: issues.companyId })
|
|
@@ -1715,7 +1984,7 @@ export function issueService(db) {
|
|
|
1715
1984
|
parentId: parent.id,
|
|
1716
1985
|
projectId: issueData.projectId ?? parent.projectId,
|
|
1717
1986
|
goalId: issueData.goalId ?? parent.goalId,
|
|
1718
|
-
requestDepth: Math.max(parent.requestDepth + 1, issueData.requestDepth ?? 0),
|
|
1987
|
+
requestDepth: clampIssueRequestDepth(Math.max(clampIssueRequestDepth(parent.requestDepth) + 1, issueData.requestDepth ?? 0)),
|
|
1719
1988
|
description: appendAcceptanceCriteriaToDescription(issueData.description, acceptanceCriteria),
|
|
1720
1989
|
inheritExecutionWorkspaceFromIssueId: parent.id,
|
|
1721
1990
|
});
|
|
@@ -1788,16 +2057,60 @@ export function issueService(db) {
|
|
|
1788
2057
|
}
|
|
1789
2058
|
}
|
|
1790
2059
|
}
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
2060
|
+
// Cache the project policy lookup for this insert. Both the
|
|
2061
|
+
// default-settings block and the assignee-environment-promotion block
|
|
2062
|
+
// need the same row; without caching they'd issue two round-trips.
|
|
2063
|
+
let projectPolicyCached = null;
|
|
2064
|
+
let projectPolicyLoaded = false;
|
|
2065
|
+
const loadProjectPolicyOnce = async () => {
|
|
2066
|
+
if (projectPolicyLoaded)
|
|
2067
|
+
return projectPolicyCached;
|
|
2068
|
+
projectPolicyLoaded = true;
|
|
2069
|
+
if (!issueData.projectId)
|
|
2070
|
+
return null;
|
|
2071
|
+
const projectRow = await tx
|
|
1795
2072
|
.select({ executionWorkspacePolicy: projects.executionWorkspacePolicy })
|
|
1796
2073
|
.from(projects)
|
|
1797
2074
|
.where(and(eq(projects.id, issueData.projectId), eq(projects.companyId, companyId)))
|
|
1798
2075
|
.then((rows) => rows[0] ?? null);
|
|
2076
|
+
projectPolicyCached = parseProjectExecutionWorkspacePolicy(projectRow?.executionWorkspacePolicy);
|
|
2077
|
+
return projectPolicyCached;
|
|
2078
|
+
};
|
|
2079
|
+
if (executionWorkspaceSettings == null &&
|
|
2080
|
+
executionWorkspaceId == null &&
|
|
2081
|
+
issueData.projectId) {
|
|
1799
2082
|
executionWorkspaceSettings =
|
|
1800
|
-
defaultIssueExecutionWorkspaceSettingsForProject(gateProjectExecutionWorkspacePolicy(
|
|
2083
|
+
defaultIssueExecutionWorkspaceSettingsForProject(gateProjectExecutionWorkspacePolicy(await loadProjectPolicyOnce(), isolatedWorkspacesEnabled));
|
|
2084
|
+
}
|
|
2085
|
+
if (data.assigneeAgentId && isolatedWorkspacesEnabled) {
|
|
2086
|
+
const currentWorkspaceSettings = executionWorkspaceSettings == null
|
|
2087
|
+
? {}
|
|
2088
|
+
: parseObject(executionWorkspaceSettings);
|
|
2089
|
+
const issueHasEnvironmentSelection = Object.prototype.hasOwnProperty.call(currentWorkspaceSettings, "environmentId");
|
|
2090
|
+
// Don't promote the assignee agent's defaultEnvironmentId if either
|
|
2091
|
+
// the issue or the project policy already specifies an environment.
|
|
2092
|
+
// resolveExecutionWorkspaceEnvironmentId treats issue settings as
|
|
2093
|
+
// higher priority than project policy, so promoting the agent's
|
|
2094
|
+
// default to issue settings would invert the documented priority
|
|
2095
|
+
// (project policy must win over agent default when explicitly set).
|
|
2096
|
+
let projectHasEnvironmentSelection = false;
|
|
2097
|
+
if (!issueHasEnvironmentSelection && issueData.projectId) {
|
|
2098
|
+
const projectPolicy = await loadProjectPolicyOnce();
|
|
2099
|
+
projectHasEnvironmentSelection = projectPolicy?.environmentId !== undefined;
|
|
2100
|
+
}
|
|
2101
|
+
if (!issueHasEnvironmentSelection && !projectHasEnvironmentSelection) {
|
|
2102
|
+
const assigneeAgent = await tx
|
|
2103
|
+
.select({ defaultEnvironmentId: agents.defaultEnvironmentId })
|
|
2104
|
+
.from(agents)
|
|
2105
|
+
.where(and(eq(agents.id, data.assigneeAgentId), eq(agents.companyId, companyId)))
|
|
2106
|
+
.then((rows) => rows[0] ?? null);
|
|
2107
|
+
if (typeof assigneeAgent?.defaultEnvironmentId === "string" && assigneeAgent.defaultEnvironmentId.length > 0) {
|
|
2108
|
+
executionWorkspaceSettings = {
|
|
2109
|
+
...currentWorkspaceSettings,
|
|
2110
|
+
environmentId: assigneeAgent.defaultEnvironmentId,
|
|
2111
|
+
};
|
|
2112
|
+
}
|
|
2113
|
+
}
|
|
1801
2114
|
}
|
|
1802
2115
|
if (!projectWorkspaceId && issueData.projectId) {
|
|
1803
2116
|
const project = await tx
|
|
@@ -1842,6 +2155,7 @@ export function issueService(db) {
|
|
|
1842
2155
|
const identifier = `${company.issuePrefix}-${issueNumber}`;
|
|
1843
2156
|
const values = {
|
|
1844
2157
|
...issueData,
|
|
2158
|
+
requestDepth: clampIssueRequestDepth(issueData.requestDepth),
|
|
1845
2159
|
originKind: issueData.originKind ?? "manual",
|
|
1846
2160
|
goalId: resolveIssueGoalId({
|
|
1847
2161
|
projectId: issueData.projectId,
|
|
@@ -1866,6 +2180,12 @@ export function issueService(db) {
|
|
|
1866
2180
|
if (values.status === "cancelled") {
|
|
1867
2181
|
values.cancelledAt = new Date();
|
|
1868
2182
|
}
|
|
2183
|
+
Object.assign(values, buildInitialIssueMonitorFields({
|
|
2184
|
+
policy: normalizeIssueExecutionPolicy(issueData.executionPolicy ?? null),
|
|
2185
|
+
status: values.status ?? "backlog",
|
|
2186
|
+
assigneeAgentId: values.assigneeAgentId ?? null,
|
|
2187
|
+
assigneeUserId: values.assigneeUserId ?? null,
|
|
2188
|
+
}));
|
|
1869
2189
|
const [issue] = await tx.insert(issues).values(values).returning();
|
|
1870
2190
|
if (inputLabelIds) {
|
|
1871
2191
|
await syncIssueLabels(issue.id, companyId, inputLabelIds, tx);
|
|
@@ -1902,6 +2222,9 @@ export function issueService(db) {
|
|
|
1902
2222
|
...issueData,
|
|
1903
2223
|
updatedAt: new Date(),
|
|
1904
2224
|
};
|
|
2225
|
+
if (issueData.requestDepth !== undefined) {
|
|
2226
|
+
patch.requestDepth = clampIssueRequestDepth(issueData.requestDepth);
|
|
2227
|
+
}
|
|
1905
2228
|
const nextAssigneeAgentId = issueData.assigneeAgentId !== undefined ? issueData.assigneeAgentId : existing.assigneeAgentId;
|
|
1906
2229
|
const nextAssigneeUserId = issueData.assigneeUserId !== undefined ? issueData.assigneeUserId : existing.assigneeUserId;
|
|
1907
2230
|
if (nextAssigneeAgentId && nextAssigneeUserId) {
|
|
@@ -1927,6 +2250,12 @@ export function issueService(db) {
|
|
|
1927
2250
|
const nextProjectId = issueData.projectId !== undefined ? issueData.projectId : existing.projectId;
|
|
1928
2251
|
const nextProjectWorkspaceId = issueData.projectWorkspaceId !== undefined ? issueData.projectWorkspaceId : existing.projectWorkspaceId;
|
|
1929
2252
|
const nextExecutionWorkspaceId = issueData.executionWorkspaceId !== undefined ? issueData.executionWorkspaceId : existing.executionWorkspaceId;
|
|
2253
|
+
const nextExecutionWorkspacePreference = issueData.executionWorkspacePreference !== undefined
|
|
2254
|
+
? issueData.executionWorkspacePreference
|
|
2255
|
+
: existing.executionWorkspacePreference;
|
|
2256
|
+
const nextExecutionWorkspaceSettings = issueData.executionWorkspaceSettings !== undefined
|
|
2257
|
+
? parseIssueExecutionWorkspaceSettings(issueData.executionWorkspaceSettings)
|
|
2258
|
+
: parseIssueExecutionWorkspaceSettings(existing.executionWorkspaceSettings);
|
|
1930
2259
|
if (nextProjectWorkspaceId) {
|
|
1931
2260
|
await assertValidProjectWorkspace(existing.companyId, nextProjectId, nextProjectWorkspaceId);
|
|
1932
2261
|
}
|
|
@@ -1961,6 +2290,66 @@ export function issueService(db) {
|
|
|
1961
2290
|
getProjectDefaultGoalId(tx, existing.companyId, existing.projectId),
|
|
1962
2291
|
getProjectDefaultGoalId(tx, existing.companyId, issueData.projectId !== undefined ? issueData.projectId : existing.projectId),
|
|
1963
2292
|
]);
|
|
2293
|
+
// Mirror the create() path: when the assignee changes to a non-null
|
|
2294
|
+
// agent, default the issue's executionWorkspaceSettings.environmentId
|
|
2295
|
+
// to the new agent's defaultEnvironmentId. Skip when:
|
|
2296
|
+
// - this update explicitly sets executionWorkspaceSettings.environmentId
|
|
2297
|
+
// (caller is making a deliberate override; respect it), OR
|
|
2298
|
+
// - the project policy already specifies an environmentId (project
|
|
2299
|
+
// policy must win over agent default per the documented priority
|
|
2300
|
+
// order in resolveExecutionWorkspaceEnvironmentId), OR
|
|
2301
|
+
// - the issue already has an environmentId that was *not* the prior
|
|
2302
|
+
// assignee's default (i.e., the operator set it explicitly in an
|
|
2303
|
+
// earlier update; preserve their choice). When the existing
|
|
2304
|
+
// environmentId matches the prior assignee's default, treat it as
|
|
2305
|
+
// auto-promoted and refresh it to the new assignee's default.
|
|
2306
|
+
const assigneeChanged = issueData.assigneeAgentId !== undefined &&
|
|
2307
|
+
issueData.assigneeAgentId !== null &&
|
|
2308
|
+
issueData.assigneeAgentId !== existing.assigneeAgentId;
|
|
2309
|
+
const explicitEnvInThisUpdate = issueData.executionWorkspaceSettings !== undefined &&
|
|
2310
|
+
Object.prototype.hasOwnProperty.call(parseObject(issueData.executionWorkspaceSettings), "environmentId");
|
|
2311
|
+
if (assigneeChanged && isolatedWorkspacesEnabled && !explicitEnvInThisUpdate) {
|
|
2312
|
+
let projectHasEnvironmentSelection = false;
|
|
2313
|
+
if (nextProjectId) {
|
|
2314
|
+
const projectRow = await tx
|
|
2315
|
+
.select({ executionWorkspacePolicy: projects.executionWorkspacePolicy })
|
|
2316
|
+
.from(projects)
|
|
2317
|
+
.where(and(eq(projects.id, nextProjectId), eq(projects.companyId, existing.companyId)))
|
|
2318
|
+
.then((rows) => rows[0] ?? null);
|
|
2319
|
+
const projectPolicy = parseProjectExecutionWorkspacePolicy(projectRow?.executionWorkspacePolicy);
|
|
2320
|
+
projectHasEnvironmentSelection = projectPolicy?.environmentId !== undefined;
|
|
2321
|
+
}
|
|
2322
|
+
if (!projectHasEnvironmentSelection) {
|
|
2323
|
+
const baseSettings = nextExecutionWorkspaceSettings == null
|
|
2324
|
+
? {}
|
|
2325
|
+
: parseObject(nextExecutionWorkspaceSettings);
|
|
2326
|
+
const existingEnvId = typeof baseSettings.environmentId === "string"
|
|
2327
|
+
? baseSettings.environmentId
|
|
2328
|
+
: null;
|
|
2329
|
+
const agentRows = await tx
|
|
2330
|
+
.select({ id: agents.id, defaultEnvironmentId: agents.defaultEnvironmentId })
|
|
2331
|
+
.from(agents)
|
|
2332
|
+
.where(and(eq(agents.companyId, existing.companyId), inArray(agents.id, [issueData.assigneeAgentId, existing.assigneeAgentId].filter((value) => typeof value === "string"))));
|
|
2333
|
+
const newAssignee = agentRows.find((row) => row.id === issueData.assigneeAgentId);
|
|
2334
|
+
const previousAssignee = existing.assigneeAgentId
|
|
2335
|
+
? agentRows.find((row) => row.id === existing.assigneeAgentId)
|
|
2336
|
+
: null;
|
|
2337
|
+
const newDefaultEnvId = typeof newAssignee?.defaultEnvironmentId === "string" && newAssignee.defaultEnvironmentId.length > 0
|
|
2338
|
+
? newAssignee.defaultEnvironmentId
|
|
2339
|
+
: null;
|
|
2340
|
+
const previousDefaultEnvId = typeof previousAssignee?.defaultEnvironmentId === "string" && previousAssignee.defaultEnvironmentId.length > 0
|
|
2341
|
+
? previousAssignee.defaultEnvironmentId
|
|
2342
|
+
: null;
|
|
2343
|
+
const existingEnvWasAutoPromoted = existingEnvId === null ||
|
|
2344
|
+
(previousDefaultEnvId !== null && existingEnvId === previousDefaultEnvId);
|
|
2345
|
+
if (newDefaultEnvId && existingEnvWasAutoPromoted) {
|
|
2346
|
+
patch.executionWorkspaceSettings = {
|
|
2347
|
+
...baseSettings,
|
|
2348
|
+
environmentId: newDefaultEnvId,
|
|
2349
|
+
};
|
|
2350
|
+
}
|
|
2351
|
+
}
|
|
2352
|
+
}
|
|
1964
2353
|
patch.goalId = resolveNextIssueGoalId({
|
|
1965
2354
|
currentProjectId: existing.projectId,
|
|
1966
2355
|
currentGoalId: existing.goalId,
|
|
@@ -1987,6 +2376,27 @@ export function issueService(db) {
|
|
|
1987
2376
|
userId: actorUserId ?? null,
|
|
1988
2377
|
}, tx);
|
|
1989
2378
|
}
|
|
2379
|
+
if (issueData.executionWorkspaceSettings !== undefined &&
|
|
2380
|
+
nextExecutionWorkspaceId &&
|
|
2381
|
+
nextExecutionWorkspacePreference === "reuse_existing") {
|
|
2382
|
+
const workspace = await tx
|
|
2383
|
+
.select({
|
|
2384
|
+
id: executionWorkspaces.id,
|
|
2385
|
+
metadata: executionWorkspaces.metadata,
|
|
2386
|
+
})
|
|
2387
|
+
.from(executionWorkspaces)
|
|
2388
|
+
.where(and(eq(executionWorkspaces.id, nextExecutionWorkspaceId), eq(executionWorkspaces.companyId, existing.companyId)))
|
|
2389
|
+
.then((rows) => rows[0] ?? null);
|
|
2390
|
+
if (workspace) {
|
|
2391
|
+
await tx
|
|
2392
|
+
.update(executionWorkspaces)
|
|
2393
|
+
.set({
|
|
2394
|
+
metadata: mergeExecutionWorkspaceConfig(workspace.metadata ?? null, buildReusedExecutionWorkspaceConfigPatchFromIssueSettings(nextExecutionWorkspaceSettings)),
|
|
2395
|
+
updatedAt: new Date(),
|
|
2396
|
+
})
|
|
2397
|
+
.where(eq(executionWorkspaces.id, workspace.id));
|
|
2398
|
+
}
|
|
2399
|
+
}
|
|
1990
2400
|
const [enriched] = await withIssueLabels(tx, [updated]);
|
|
1991
2401
|
return enriched;
|
|
1992
2402
|
};
|
|
@@ -2359,14 +2769,8 @@ export function issueService(db) {
|
|
|
2359
2769
|
if (!anchor)
|
|
2360
2770
|
return [];
|
|
2361
2771
|
conditions.push(order === "asc"
|
|
2362
|
-
?
|
|
2363
|
-
|
|
2364
|
-
OR (${issueComments.createdAt} = ${anchor.createdAt} AND ${issueComments.id} > ${anchor.id})
|
|
2365
|
-
)`
|
|
2366
|
-
: sql `(
|
|
2367
|
-
${issueComments.createdAt} < ${anchor.createdAt}
|
|
2368
|
-
OR (${issueComments.createdAt} = ${anchor.createdAt} AND ${issueComments.id} < ${anchor.id})
|
|
2369
|
-
)`);
|
|
2772
|
+
? or(gt(issueComments.createdAt, anchor.createdAt), and(eq(issueComments.createdAt, anchor.createdAt), gt(issueComments.id, anchor.id)))
|
|
2773
|
+
: or(lt(issueComments.createdAt, anchor.createdAt), and(eq(issueComments.createdAt, anchor.createdAt), lt(issueComments.id, anchor.id))));
|
|
2370
2774
|
}
|
|
2371
2775
|
const query = db
|
|
2372
2776
|
.select()
|