@rudderhq/server 0.2.0-canary.9 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bootstrap/register-api-routes.d.ts.map +1 -1
- package/dist/bootstrap/register-api-routes.js +2 -0
- package/dist/bootstrap/register-api-routes.js.map +1 -1
- package/dist/bundled-plugins/plugin-linear/dist/ui/index.js +8 -1
- package/dist/bundled-plugins/plugin-linear/dist/ui/index.js.map +2 -2
- package/dist/bundled-plugins/plugin-linear/dist/worker.js +20 -5
- package/dist/bundled-plugins/plugin-linear/dist/worker.js.map +2 -2
- package/dist/home-paths.d.ts +2 -0
- package/dist/home-paths.d.ts.map +1 -1
- package/dist/home-paths.js +6 -1
- package/dist/home-paths.js.map +1 -1
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +55 -2
- package/dist/index.js.map +1 -1
- package/dist/langfuse-transcript.d.ts.map +1 -1
- package/dist/langfuse-transcript.js +16 -2
- package/dist/langfuse-transcript.js.map +1 -1
- package/dist/middleware/auth.d.ts.map +1 -1
- package/dist/middleware/auth.js +54 -1
- package/dist/middleware/auth.js.map +1 -1
- package/dist/onboarding-assets/ceo/HEARTBEAT.md +8 -4
- package/dist/onboarding-assets/default/HEARTBEAT.md +7 -4
- package/dist/routes/agents.d.ts.map +1 -1
- package/dist/routes/agents.js +79 -4
- package/dist/routes/agents.js.map +1 -1
- package/dist/routes/approvals.d.ts.map +1 -1
- package/dist/routes/approvals.js +47 -2
- package/dist/routes/approvals.js.map +1 -1
- package/dist/routes/chats.d.ts.map +1 -1
- package/dist/routes/chats.js +300 -92
- package/dist/routes/chats.js.map +1 -1
- package/dist/routes/costs.d.ts.map +1 -1
- package/dist/routes/costs.js +20 -0
- package/dist/routes/costs.js.map +1 -1
- package/dist/routes/issues.d.ts.map +1 -1
- package/dist/routes/issues.js +236 -22
- package/dist/routes/issues.js.map +1 -1
- package/dist/routes/onboarding.d.ts +3 -0
- package/dist/routes/onboarding.d.ts.map +1 -0
- package/dist/routes/onboarding.js +545 -0
- package/dist/routes/onboarding.js.map +1 -0
- package/dist/routes/orgs.d.ts.map +1 -1
- package/dist/routes/orgs.js +22 -0
- package/dist/routes/orgs.js.map +1 -1
- package/dist/services/activity.d.ts.map +1 -1
- package/dist/services/activity.js +32 -1
- package/dist/services/activity.js.map +1 -1
- package/dist/services/agent-run-context.d.ts +1 -0
- package/dist/services/agent-run-context.d.ts.map +1 -1
- package/dist/services/agent-run-context.js +1 -0
- package/dist/services/agent-run-context.js.map +1 -1
- package/dist/services/agents.d.ts +13 -13
- package/dist/services/automations.d.ts +2 -2
- package/dist/services/calendar.d.ts +4 -4
- package/dist/services/chat-assistant.d.ts +11 -2
- package/dist/services/chat-assistant.d.ts.map +1 -1
- package/dist/services/chat-assistant.js +143 -8
- package/dist/services/chat-assistant.js.map +1 -1
- package/dist/services/chats.d.ts +112 -13
- package/dist/services/chats.d.ts.map +1 -1
- package/dist/services/chats.js +218 -38
- package/dist/services/chats.js.map +1 -1
- package/dist/services/costs.d.ts +21 -0
- package/dist/services/costs.d.ts.map +1 -1
- package/dist/services/costs.js +102 -2
- package/dist/services/costs.js.map +1 -1
- package/dist/services/finance.d.ts +2 -2
- package/dist/services/goals.d.ts +12 -12
- package/dist/services/instance-settings.d.ts.map +1 -1
- package/dist/services/instance-settings.js +27 -16
- package/dist/services/instance-settings.js.map +1 -1
- package/dist/services/issue-approvals.d.ts +16 -2
- package/dist/services/issue-approvals.d.ts.map +1 -1
- package/dist/services/issue-approvals.js +27 -4
- package/dist/services/issue-approvals.js.map +1 -1
- package/dist/services/issue-review-wakeup.d.ts +49 -1
- package/dist/services/issue-review-wakeup.d.ts.map +1 -1
- package/dist/services/issue-review-wakeup.js +39 -2
- package/dist/services/issue-review-wakeup.js.map +1 -1
- package/dist/services/issues.d.ts +2 -1
- package/dist/services/issues.d.ts.map +1 -1
- package/dist/services/issues.js +126 -5
- package/dist/services/issues.js.map +1 -1
- package/dist/services/knowledge-portability/organization-skills.d.ts +1 -0
- package/dist/services/knowledge-portability/organization-skills.d.ts.map +1 -1
- package/dist/services/knowledge-portability/organization-skills.js +3 -2
- package/dist/services/knowledge-portability/organization-skills.js.map +1 -1
- package/dist/services/messenger.d.ts +5 -0
- package/dist/services/messenger.d.ts.map +1 -1
- package/dist/services/messenger.js +154 -15
- package/dist/services/messenger.js.map +1 -1
- package/dist/services/organization-workspace-browser.d.ts.map +1 -1
- package/dist/services/organization-workspace-browser.js +64 -9
- package/dist/services/organization-workspace-browser.js.map +1 -1
- package/dist/services/orgs.d.ts +1 -1
- package/dist/services/plugin-registry.d.ts +4 -4
- package/dist/services/projects.d.ts +1 -1
- package/dist/services/runtime-kernel/heartbeat.d.ts.map +1 -1
- package/dist/services/runtime-kernel/heartbeat.js +571 -31
- package/dist/services/runtime-kernel/heartbeat.js.map +1 -1
- package/dist/services/secrets.d.ts +5 -5
- package/dist/services/workspace-backups.d.ts.map +1 -1
- package/dist/services/workspace-backups.js +6 -0
- package/dist/services/workspace-backups.js.map +1 -1
- package/dist/services/workspace-runtime.d.ts.map +1 -1
- package/dist/services/workspace-runtime.js +2 -0
- package/dist/services/workspace-runtime.js.map +1 -1
- package/package.json +13 -13
- package/resources/bundled-skills/rudder/SKILL.md +72 -7
- package/resources/bundled-skills/rudder/references/cli-reference.md +34 -9
- package/resources/bundled-skills/rudder/references/organization-skills.md +12 -7
- package/resources/bundled-skills/rudder-create-agent/references/cli-reference.md +1 -0
- package/skills/rudder/SKILL.md +72 -7
- package/skills/rudder/references/cli-reference.md +34 -9
- package/skills/rudder/references/organization-skills.md +12 -7
- package/skills/rudder-create-agent/references/cli-reference.md +1 -0
- package/ui-dist/assets/{_basePickBy-aX2f6dVl.js → _basePickBy-EvWeCTRb.js} +1 -1
- package/ui-dist/assets/{_baseUniq-BYwL7heN.js → _baseUniq-C_DXAETg.js} +1 -1
- package/ui-dist/assets/{arc-BG9f5pwY.js → arc-BWTkVM-u.js} +1 -1
- package/ui-dist/assets/{architectureDiagram-2XIMDMQ5-BFFQoJJ1.js → architectureDiagram-2XIMDMQ5-yyX54Dgl.js} +1 -1
- package/ui-dist/assets/{blockDiagram-WCTKOSBZ-Bvx1IB1z.js → blockDiagram-WCTKOSBZ-DleWvS8P.js} +1 -1
- package/ui-dist/assets/{c4Diagram-IC4MRINW-DJbCE4sh.js → c4Diagram-IC4MRINW-CltWqWC_.js} +1 -1
- package/ui-dist/assets/channel-Gdzxe2a1.js +1 -0
- package/ui-dist/assets/{chunk-4BX2VUAB-BOVbLFsN.js → chunk-4BX2VUAB-CA6RvGN7.js} +1 -1
- package/ui-dist/assets/{chunk-55IACEB6-D5pKj6S9.js → chunk-55IACEB6-D_EpF39w.js} +1 -1
- package/ui-dist/assets/{chunk-FMBD7UC4-OY5xuJeR.js → chunk-FMBD7UC4-CYMkBnLy.js} +1 -1
- package/ui-dist/assets/{chunk-JSJVCQXG-C5X67KZg.js → chunk-JSJVCQXG-CIY2Cb1T.js} +1 -1
- package/ui-dist/assets/{chunk-KX2RTZJC-C-4PZ9Q_.js → chunk-KX2RTZJC-BUyGoIKj.js} +1 -1
- package/ui-dist/assets/{chunk-NQ4KR5QH-XysPlqPj.js → chunk-NQ4KR5QH-DkntSLtY.js} +1 -1
- package/ui-dist/assets/{chunk-QZHKN3VN-B5wEbFHo.js → chunk-QZHKN3VN-DeEs3yL0.js} +1 -1
- package/ui-dist/assets/{chunk-WL4C6EOR-BanwYFa2.js → chunk-WL4C6EOR-Va8TkdTb.js} +1 -1
- package/ui-dist/assets/classDiagram-VBA2DB6C-BN6WyuN3.js +1 -0
- package/ui-dist/assets/classDiagram-v2-RAHNMMFH-BN6WyuN3.js +1 -0
- package/ui-dist/assets/clone-DL9OCUyP.js +1 -0
- package/ui-dist/assets/{cose-bilkent-S5V4N54A-Cd4q2swD.js → cose-bilkent-S5V4N54A-Bb6NLaVm.js} +1 -1
- package/ui-dist/assets/{dagre-KLK3FWXG-B_VyOhf3.js → dagre-KLK3FWXG-DpqLnZ3A.js} +1 -1
- package/ui-dist/assets/{diagram-E7M64L7V-BRoG4Mz6.js → diagram-E7M64L7V-D7J8NbEW.js} +1 -1
- package/ui-dist/assets/{diagram-IFDJBPK2-CRU_A9p9.js → diagram-IFDJBPK2-Ds2u81Zi.js} +1 -1
- package/ui-dist/assets/{diagram-P4PSJMXO-BYSQDbfb.js → diagram-P4PSJMXO-BwBplO7L.js} +1 -1
- package/ui-dist/assets/{erDiagram-INFDFZHY-v5j1kyWr.js → erDiagram-INFDFZHY-Ba-Ynr8U.js} +1 -1
- package/ui-dist/assets/{flowDiagram-PKNHOUZH-C06ZQgTj.js → flowDiagram-PKNHOUZH-FnOXpXb_.js} +1 -1
- package/ui-dist/assets/{ganttDiagram-A5KZAMGK-Dw9p5nQ1.js → ganttDiagram-A5KZAMGK-B8-MpUjy.js} +1 -1
- package/ui-dist/assets/{gitGraphDiagram-K3NZZRJ6-CrpXRIaP.js → gitGraphDiagram-K3NZZRJ6-DvyBGQTF.js} +1 -1
- package/ui-dist/assets/{graph-ClTUmULf.js → graph-BdpIVR-I.js} +1 -1
- package/ui-dist/assets/{index-DK13xhRv.js → index-3CPMGfu4.js} +1 -1
- package/ui-dist/assets/index-44A3IjSd.css +1 -0
- package/ui-dist/assets/{index-L6M3nVxh.js → index-B4seykMn.js} +1 -1
- package/ui-dist/assets/{index-Bpc2gRVo.js → index-B5Lq7qho.js} +1 -1
- package/ui-dist/assets/{index-DkDkjZ-D.js → index-BKWZYXO6.js} +1 -1
- package/ui-dist/assets/{index-DxzAgTWd.js → index-BO-P9C91.js} +1 -1
- package/ui-dist/assets/{index-BvGogi9q.js → index-BO9KiNr0.js} +1 -1
- package/ui-dist/assets/{index-Btwy7Cp-.js → index-Bd_GitJ7.js} +1 -1
- package/ui-dist/assets/{index-DNlWBtHa.js → index-BeyQP4jc.js} +1 -1
- package/ui-dist/assets/{index-4uxadHo5.js → index-Bp3rYm9R.js} +1 -1
- package/ui-dist/assets/{index-DWFMs9uk.js → index-CBAKsDOH.js} +1 -1
- package/ui-dist/assets/{index-T81awgzh.js → index-CWPEuLky.js} +1 -1
- package/ui-dist/assets/{index-DAhPD1Ss.js → index-Ce0xbQ5p.js} +1 -1
- package/ui-dist/assets/{index-_x9smX4T.js → index-ChyWxMPa.js} +1 -1
- package/ui-dist/assets/{index-CIr7H9OI.js → index-CkEo4bIl.js} +1 -1
- package/ui-dist/assets/{index-sLGLHxIu.js → index-CvzsgQH3.js} +1 -1
- package/ui-dist/assets/{index-D-6z8wxx.js → index-DF0X3XZi.js} +1 -1
- package/ui-dist/assets/{index-BVfM9ax8.js → index-DNFqhIup.js} +1 -1
- package/ui-dist/assets/index-Dfi8PbGx.js +1484 -0
- package/ui-dist/assets/{index-C_BTFRTX.js → index-Dys_qAzR.js} +1 -1
- package/ui-dist/assets/{index-Cr7n11UG.js → index-DzKALBsQ.js} +1 -1
- package/ui-dist/assets/{index-CqYInp-c.js → index-Qe9bMaYk.js} +1 -1
- package/ui-dist/assets/{index-CQWmziMF.js → index-baeevrWz.js} +1 -1
- package/ui-dist/assets/{index-BYC_xlrx.js → index-bs5pLhnN.js} +1 -1
- package/ui-dist/assets/{infoDiagram-LFFYTUFH-BA3VxOIU.js → infoDiagram-LFFYTUFH-51Iz4iFI.js} +1 -1
- package/ui-dist/assets/{ishikawaDiagram-PHBUUO56-DGrizi0S.js → ishikawaDiagram-PHBUUO56-XMkPw0tW.js} +1 -1
- package/ui-dist/assets/{journeyDiagram-4ABVD52K-6ey34a7e.js → journeyDiagram-4ABVD52K-DAX0bTCG.js} +1 -1
- package/ui-dist/assets/{kanban-definition-K7BYSVSG-CwNnmsam.js → kanban-definition-K7BYSVSG-DndcgBkd.js} +1 -1
- package/ui-dist/assets/{layout-buNxvllr.js → layout-DE8DhR5g.js} +1 -1
- package/ui-dist/assets/{linear-BPWhxaRl.js → linear-B6lAW9Wb.js} +1 -1
- package/ui-dist/assets/{mermaid.core-Cajx0s-z.js → mermaid.core-BG--kYhA.js} +4 -4
- package/ui-dist/assets/{mindmap-definition-YRQLILUH-Bf5InEp-.js → mindmap-definition-YRQLILUH-DkjV0oE3.js} +1 -1
- package/ui-dist/assets/{pieDiagram-SKSYHLDU-CZFz7NWC.js → pieDiagram-SKSYHLDU-D03TjqYu.js} +1 -1
- package/ui-dist/assets/{quadrantDiagram-337W2JSQ-XBmKVoc9.js → quadrantDiagram-337W2JSQ-C0oqv-xU.js} +1 -1
- package/ui-dist/assets/{requirementDiagram-Z7DCOOCP-BkgdDv0H.js → requirementDiagram-Z7DCOOCP-okIS8feM.js} +1 -1
- package/ui-dist/assets/{sankeyDiagram-WA2Y5GQK-CASFR28i.js → sankeyDiagram-WA2Y5GQK-WOnxUdkO.js} +1 -1
- package/ui-dist/assets/{sequenceDiagram-2WXFIKYE-BzY7LMRv.js → sequenceDiagram-2WXFIKYE-RVCXfMRR.js} +1 -1
- package/ui-dist/assets/{stateDiagram-RAJIS63D-C9UMSk36.js → stateDiagram-RAJIS63D-CZFHvVtT.js} +1 -1
- package/ui-dist/assets/stateDiagram-v2-FVOUBMTO-DgYYudAJ.js +1 -0
- package/ui-dist/assets/{timeline-definition-YZTLITO2-D6m4R4xe.js → timeline-definition-YZTLITO2-S0uy5mlJ.js} +1 -1
- package/ui-dist/assets/{treemap-KZPCXAKY-7V9mnT8T.js → treemap-KZPCXAKY-Bhyg_yHs.js} +1 -1
- package/ui-dist/assets/{vennDiagram-LZ73GAT5-Ci-sfAyq.js → vennDiagram-LZ73GAT5-EnVupOQz.js} +1 -1
- package/ui-dist/assets/{xychartDiagram-JWTSCODW-BayXhRSu.js → xychartDiagram-JWTSCODW-BYpdJxGK.js} +1 -1
- package/ui-dist/index.html +2 -2
- package/ui-dist/assets/channel-ClX7n84B.js +0 -1
- package/ui-dist/assets/classDiagram-VBA2DB6C-DvWbsnVz.js +0 -1
- package/ui-dist/assets/classDiagram-v2-RAHNMMFH-DvWbsnVz.js +0 -1
- package/ui-dist/assets/clone-Dla3A8ZA.js +0 -1
- package/ui-dist/assets/index-CSANx6ee.css +0 -1
- package/ui-dist/assets/index-DCa9-Sy-.js +0 -1439
- package/ui-dist/assets/stateDiagram-v2-FVOUBMTO-DWVhbAdj.js +0 -1
package/dist/routes/chats.js
CHANGED
|
@@ -3,14 +3,14 @@ import { Router } from "express";
|
|
|
3
3
|
import multer from "multer";
|
|
4
4
|
import { addChatMessageSchema, updateChatConversationUserStateSchema, convertChatToIssueSchema, createChatAttachmentMetadataSchema, createChatContextLinkSchema, createChatConversationSchema, resolveChatOperationProposalSchema, setChatProjectContextSchema, updateChatConversationSchema, } from "@rudderhq/shared";
|
|
5
5
|
import { isAllowedContentType, MAX_ATTACHMENT_BYTES } from "../attachment-types.js";
|
|
6
|
-
import { HttpError } from "../errors.js";
|
|
6
|
+
import { forbidden, HttpError, unauthorized } from "../errors.js";
|
|
7
7
|
import { observeExecutionEvent, updateExecutionObservation, updateExecutionTraceIO, withExecutionObservation, } from "../langfuse.js";
|
|
8
8
|
import { emitExecutionTranscriptTree } from "../langfuse-transcript.js";
|
|
9
9
|
import { validate } from "../middleware/validate.js";
|
|
10
10
|
import { logger } from "../middleware/logger.js";
|
|
11
11
|
import { ChatAssistantStreamError, chatAssistantService, } from "../services/chat-assistant.js";
|
|
12
|
-
import { cancelActiveChatGeneration, claimChatGeneration } from "../services/chat-generation-locks.js";
|
|
13
|
-
import { agentService, chatService, operatorProfileService, organizationService, goalService, issueService, logActivity, projectService, } from "../services/index.js";
|
|
12
|
+
import { cancelActiveChatGeneration, claimChatGeneration, hasActiveChatGeneration } from "../services/chat-generation-locks.js";
|
|
13
|
+
import { accessService, agentService, chatService, operatorProfileService, organizationService, goalService, issueService, logActivity, projectService, } from "../services/index.js";
|
|
14
14
|
import { summarizeRuntimeSkillsForTrace } from "../services/runtime-trace-metadata.js";
|
|
15
15
|
import { assertBoard, assertCompanyAccess, getActorInfo } from "./authz.js";
|
|
16
16
|
export function chatRoutes(db, storage) {
|
|
@@ -21,6 +21,7 @@ export function chatRoutes(db, storage) {
|
|
|
21
21
|
const projectsSvc = projectService(db);
|
|
22
22
|
const agentsSvc = agentService(db);
|
|
23
23
|
const goalsSvc = goalService(db);
|
|
24
|
+
const access = accessService(db);
|
|
24
25
|
const assistantSvc = chatAssistantService(db);
|
|
25
26
|
const operatorProfiles = operatorProfileService(db);
|
|
26
27
|
const upload = multer({
|
|
@@ -84,6 +85,36 @@ export function chatRoutes(db, storage) {
|
|
|
84
85
|
assertBoard(req);
|
|
85
86
|
return req.actor.userId ?? "local-board";
|
|
86
87
|
}
|
|
88
|
+
function canCreateAgentsLegacy(agent) {
|
|
89
|
+
if (agent.role === "ceo")
|
|
90
|
+
return true;
|
|
91
|
+
if (!agent.permissions || typeof agent.permissions !== "object")
|
|
92
|
+
return false;
|
|
93
|
+
return Boolean(agent.permissions.canCreateAgents);
|
|
94
|
+
}
|
|
95
|
+
async function assertCanAssignTasks(req, orgId) {
|
|
96
|
+
assertCompanyAccess(req, orgId);
|
|
97
|
+
if (req.actor.type === "board") {
|
|
98
|
+
if (req.actor.source === "local_implicit" || req.actor.isInstanceAdmin)
|
|
99
|
+
return;
|
|
100
|
+
const allowed = await access.canUser(orgId, req.actor.userId, "tasks:assign");
|
|
101
|
+
if (!allowed)
|
|
102
|
+
throw forbidden("Missing permission: tasks:assign");
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (req.actor.type === "agent") {
|
|
106
|
+
if (!req.actor.agentId)
|
|
107
|
+
throw forbidden("Agent authentication required");
|
|
108
|
+
const allowedByGrant = await access.hasPermission(orgId, "agent", req.actor.agentId, "tasks:assign");
|
|
109
|
+
if (allowedByGrant)
|
|
110
|
+
return;
|
|
111
|
+
const actorAgent = await agentsSvc.getById(req.actor.agentId);
|
|
112
|
+
if (actorAgent && actorAgent.orgId === orgId && canCreateAgentsLegacy(actorAgent))
|
|
113
|
+
return;
|
|
114
|
+
throw forbidden("Missing permission: tasks:assign");
|
|
115
|
+
}
|
|
116
|
+
throw unauthorized();
|
|
117
|
+
}
|
|
87
118
|
function buildChatObservabilityContext(conversation, input) {
|
|
88
119
|
return {
|
|
89
120
|
surface: input.surface ?? "chat_action",
|
|
@@ -153,6 +184,11 @@ export function chatRoutes(db, storage) {
|
|
|
153
184
|
eventType: typeof systemPayload?.eventType === "string" ? systemPayload.eventType : null,
|
|
154
185
|
};
|
|
155
186
|
}
|
|
187
|
+
function modelTurnInputFromInvocationMeta(invocationMeta) {
|
|
188
|
+
return typeof invocationMeta.prompt === "string" && invocationMeta.prompt.trim().length > 0
|
|
189
|
+
? invocationMeta.prompt
|
|
190
|
+
: undefined;
|
|
191
|
+
}
|
|
156
192
|
function buildChatTraceInput(input, invocationMeta) {
|
|
157
193
|
return {
|
|
158
194
|
conversationId: input.conversationId,
|
|
@@ -313,50 +349,6 @@ export function chatRoutes(db, storage) {
|
|
|
313
349
|
function chatReplyingAgentId(conversation) {
|
|
314
350
|
return conversation?.chatRuntime?.runtimeAgentId ?? conversation?.preferredAgentId ?? null;
|
|
315
351
|
}
|
|
316
|
-
async function defaultIssueAssigneeAgentId(conversation) {
|
|
317
|
-
const candidateAgentIds = [conversation?.preferredAgentId, conversation?.routedAgentId]
|
|
318
|
-
.filter((id) => typeof id === "string" && id.trim().length > 0);
|
|
319
|
-
if (!conversation)
|
|
320
|
-
return null;
|
|
321
|
-
for (const candidateAgentId of candidateAgentIds) {
|
|
322
|
-
const agent = await agentsSvc.getById(candidateAgentId);
|
|
323
|
-
if (!agent || agent.orgId !== conversation.orgId)
|
|
324
|
-
continue;
|
|
325
|
-
if (agent.status === "pending_approval" || agent.status === "terminated")
|
|
326
|
-
continue;
|
|
327
|
-
return agent.id;
|
|
328
|
-
}
|
|
329
|
-
return null;
|
|
330
|
-
}
|
|
331
|
-
function hasIssueProposalAssignee(proposal) {
|
|
332
|
-
return Boolean((typeof proposal.assigneeAgentId === "string" && proposal.assigneeAgentId.trim().length > 0)
|
|
333
|
-
|| (typeof proposal.assigneeUserId === "string" && proposal.assigneeUserId.trim().length > 0));
|
|
334
|
-
}
|
|
335
|
-
function withDefaultIssueProposalAssignee(structuredPayload, assigneeAgentId) {
|
|
336
|
-
if (!structuredPayload || !assigneeAgentId)
|
|
337
|
-
return structuredPayload ?? null;
|
|
338
|
-
const nestedProposal = structuredPayload.issueProposal
|
|
339
|
-
&& typeof structuredPayload.issueProposal === "object"
|
|
340
|
-
&& !Array.isArray(structuredPayload.issueProposal)
|
|
341
|
-
? structuredPayload.issueProposal
|
|
342
|
-
: null;
|
|
343
|
-
const proposal = nestedProposal ?? structuredPayload;
|
|
344
|
-
if (hasIssueProposalAssignee(proposal))
|
|
345
|
-
return structuredPayload;
|
|
346
|
-
if (nestedProposal) {
|
|
347
|
-
return {
|
|
348
|
-
...structuredPayload,
|
|
349
|
-
issueProposal: {
|
|
350
|
-
...nestedProposal,
|
|
351
|
-
assigneeAgentId,
|
|
352
|
-
},
|
|
353
|
-
};
|
|
354
|
-
}
|
|
355
|
-
return {
|
|
356
|
-
...structuredPayload,
|
|
357
|
-
assigneeAgentId,
|
|
358
|
-
};
|
|
359
|
-
}
|
|
360
352
|
function proposedIssuePayload(structuredPayload) {
|
|
361
353
|
if (!structuredPayload)
|
|
362
354
|
return structuredPayload ?? null;
|
|
@@ -367,25 +359,121 @@ export function chatRoutes(db, storage) {
|
|
|
367
359
|
? structuredPayload.issueProposal
|
|
368
360
|
: structuredPayload;
|
|
369
361
|
}
|
|
370
|
-
|
|
362
|
+
function proposalAssignsOrReviewsIssue(proposal) {
|
|
363
|
+
if (!proposal)
|
|
364
|
+
return false;
|
|
365
|
+
return Boolean((typeof proposal.assigneeAgentId === "string" && proposal.assigneeAgentId.trim().length > 0)
|
|
366
|
+
|| (typeof proposal.assigneeUserId === "string" && proposal.assigneeUserId.trim().length > 0)
|
|
367
|
+
|| (typeof proposal.reviewerAgentId === "string" && proposal.reviewerAgentId.trim().length > 0)
|
|
368
|
+
|| (typeof proposal.reviewerUserId === "string" && proposal.reviewerUserId.trim().length > 0));
|
|
369
|
+
}
|
|
370
|
+
async function proposedIssuePayloadForConversion(conversationId, input) {
|
|
371
|
+
if (input.proposal)
|
|
372
|
+
return proposedIssuePayload(input.proposal);
|
|
373
|
+
if (input.messageId) {
|
|
374
|
+
const message = await svc.getMessage(conversationId, input.messageId);
|
|
375
|
+
return proposedIssuePayload(message?.structuredPayload ?? null);
|
|
376
|
+
}
|
|
377
|
+
const messages = await svc.listMessages(conversationId);
|
|
378
|
+
const message = [...messages].reverse().find((entry) => entry.kind === "issue_proposal");
|
|
379
|
+
return proposedIssuePayload(message?.structuredPayload ?? null);
|
|
380
|
+
}
|
|
381
|
+
async function assertCanConvertIssueProposal(req, conversation, input) {
|
|
382
|
+
const proposal = await proposedIssuePayloadForConversion(conversation.id, input);
|
|
383
|
+
if (proposalAssignsOrReviewsIssue(proposal)) {
|
|
384
|
+
await assertCanAssignTasks(req, conversation.orgId);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
function proposedPlanDocumentPayload(structuredPayload) {
|
|
388
|
+
if (!structuredPayload)
|
|
389
|
+
return null;
|
|
390
|
+
const rawDocument = structuredPayload.planDocument
|
|
391
|
+
&& typeof structuredPayload.planDocument === "object"
|
|
392
|
+
&& !Array.isArray(structuredPayload.planDocument)
|
|
393
|
+
? structuredPayload.planDocument
|
|
394
|
+
: structuredPayload.plan && typeof structuredPayload.plan === "object" && !Array.isArray(structuredPayload.plan)
|
|
395
|
+
? structuredPayload.plan
|
|
396
|
+
: null;
|
|
397
|
+
return rawDocument ? rawDocument : null;
|
|
398
|
+
}
|
|
399
|
+
async function persistAssistantReply(req, conversation, actor, assistantReply, turnContext, transcript = [], replyingAgentId = assistantReply.replyingAgentId ?? chatReplyingAgentId(conversation), existingMessageId) {
|
|
371
400
|
const createdMessages = [];
|
|
372
401
|
const { chatTurnId, turnVariant } = turnContext;
|
|
402
|
+
const attachGeneratedFiles = async (message, generatedAttachments) => {
|
|
403
|
+
if (!generatedAttachments || generatedAttachments.length === 0)
|
|
404
|
+
return message;
|
|
405
|
+
const attachments = [];
|
|
406
|
+
for (const generated of generatedAttachments) {
|
|
407
|
+
if (generated.body.length > MAX_ATTACHMENT_BYTES) {
|
|
408
|
+
throw new ChatAssistantStreamError(`Generated attachment exceeds ${MAX_ATTACHMENT_BYTES} bytes`, assistantReply.body, generatedAttachments);
|
|
409
|
+
}
|
|
410
|
+
const stored = await storage.putFile({
|
|
411
|
+
orgId: conversation.orgId,
|
|
412
|
+
namespace: `chats/${conversation.id}/generated`,
|
|
413
|
+
originalFilename: generated.originalFilename,
|
|
414
|
+
contentType: generated.contentType,
|
|
415
|
+
body: generated.body,
|
|
416
|
+
});
|
|
417
|
+
const attachment = await svc.createAttachment({
|
|
418
|
+
orgId: conversation.orgId,
|
|
419
|
+
conversationId: conversation.id,
|
|
420
|
+
messageId: message.id,
|
|
421
|
+
provider: stored.provider,
|
|
422
|
+
objectKey: stored.objectKey,
|
|
423
|
+
contentType: stored.contentType,
|
|
424
|
+
byteSize: stored.byteSize,
|
|
425
|
+
sha256: stored.sha256,
|
|
426
|
+
originalFilename: stored.originalFilename,
|
|
427
|
+
createdByAgentId: replyingAgentId,
|
|
428
|
+
createdByUserId: null,
|
|
429
|
+
});
|
|
430
|
+
attachments.push(attachment);
|
|
431
|
+
}
|
|
432
|
+
return {
|
|
433
|
+
...message,
|
|
434
|
+
attachments: [...(message.attachments ?? []), ...attachments],
|
|
435
|
+
};
|
|
436
|
+
};
|
|
437
|
+
const saveAssistantMessage = async (input) => {
|
|
438
|
+
if (existingMessageId) {
|
|
439
|
+
const updated = await svc.updateMessage(conversation.id, existingMessageId, {
|
|
440
|
+
kind: input.kind,
|
|
441
|
+
status: "completed",
|
|
442
|
+
body: input.body,
|
|
443
|
+
structuredPayload: input.structuredPayload ?? null,
|
|
444
|
+
transcript,
|
|
445
|
+
approvalId: input.approvalId ?? null,
|
|
446
|
+
replyingAgentId,
|
|
447
|
+
});
|
|
448
|
+
if (updated)
|
|
449
|
+
return updated;
|
|
450
|
+
}
|
|
451
|
+
return svc.addMessage(conversation.id, {
|
|
452
|
+
orgId: conversation.orgId,
|
|
453
|
+
role: "assistant",
|
|
454
|
+
kind: input.kind,
|
|
455
|
+
body: input.body,
|
|
456
|
+
structuredPayload: input.structuredPayload ?? null,
|
|
457
|
+
transcript,
|
|
458
|
+
approvalId: input.approvalId ?? null,
|
|
459
|
+
replyingAgentId,
|
|
460
|
+
chatTurnId,
|
|
461
|
+
turnVariant,
|
|
462
|
+
});
|
|
463
|
+
};
|
|
373
464
|
if (assistantReply.kind === "issue_proposal") {
|
|
374
|
-
const issueProposalStructuredPayload =
|
|
375
|
-
const shouldAutoCreateIssue = conversation.planMode
|
|
465
|
+
const issueProposalStructuredPayload = assistantReply.structuredPayload ?? null;
|
|
466
|
+
const shouldAutoCreateIssue = !conversation.planMode && conversation.issueCreationMode === "auto_create";
|
|
376
467
|
if (shouldAutoCreateIssue) {
|
|
377
|
-
const proposalMessage = await
|
|
378
|
-
orgId: conversation.orgId,
|
|
379
|
-
role: "assistant",
|
|
468
|
+
const proposalMessage = await saveAssistantMessage({
|
|
380
469
|
kind: "issue_proposal",
|
|
381
470
|
body: assistantReply.body,
|
|
382
471
|
structuredPayload: issueProposalStructuredPayload,
|
|
383
|
-
transcript,
|
|
384
|
-
replyingAgentId,
|
|
385
|
-
chatTurnId,
|
|
386
|
-
turnVariant,
|
|
387
472
|
});
|
|
388
|
-
createdMessages.push(proposalMessage);
|
|
473
|
+
createdMessages.push(await attachGeneratedFiles(proposalMessage, assistantReply.generatedAttachments));
|
|
474
|
+
await assertCanConvertIssueProposal(req, conversation, {
|
|
475
|
+
proposal: issueProposalStructuredPayload,
|
|
476
|
+
});
|
|
389
477
|
const issue = await svc.convertToIssue(conversation.id, {
|
|
390
478
|
actorUserId: actor.actorType === "user" ? actor.actorId : null,
|
|
391
479
|
messageId: proposalMessage.id,
|
|
@@ -414,32 +502,28 @@ export function chatRoutes(db, storage) {
|
|
|
414
502
|
details: {
|
|
415
503
|
issueId: issue.id,
|
|
416
504
|
issueIdentifier: issue.identifier,
|
|
417
|
-
source:
|
|
505
|
+
source: "auto_create",
|
|
418
506
|
},
|
|
419
507
|
});
|
|
420
508
|
return createdMessages;
|
|
421
509
|
}
|
|
510
|
+
const planDocument = proposedPlanDocumentPayload(issueProposalStructuredPayload);
|
|
422
511
|
const approval = await svc.createProposalApproval(conversation.orgId, {
|
|
423
512
|
type: "chat_issue_creation",
|
|
424
513
|
requestedByUserId: actor.actorType === "user" ? actor.actorId : null,
|
|
425
514
|
payload: {
|
|
426
515
|
chatConversationId: conversation.id,
|
|
427
516
|
proposedIssue: proposedIssuePayload(issueProposalStructuredPayload),
|
|
517
|
+
...(planDocument ? { planDocument } : {}),
|
|
428
518
|
},
|
|
429
519
|
});
|
|
430
|
-
const proposalMessage = await
|
|
431
|
-
orgId: conversation.orgId,
|
|
432
|
-
role: "assistant",
|
|
520
|
+
const proposalMessage = await saveAssistantMessage({
|
|
433
521
|
kind: "issue_proposal",
|
|
434
522
|
body: assistantReply.body,
|
|
435
523
|
structuredPayload: issueProposalStructuredPayload,
|
|
436
|
-
transcript,
|
|
437
524
|
approvalId: approval.id,
|
|
438
|
-
replyingAgentId,
|
|
439
|
-
chatTurnId,
|
|
440
|
-
turnVariant,
|
|
441
525
|
});
|
|
442
|
-
createdMessages.push(proposalMessage);
|
|
526
|
+
createdMessages.push(await attachGeneratedFiles(proposalMessage, assistantReply.generatedAttachments));
|
|
443
527
|
return createdMessages;
|
|
444
528
|
}
|
|
445
529
|
if (assistantReply.kind === "operation_proposal") {
|
|
@@ -455,9 +539,7 @@ export function chatRoutes(db, storage) {
|
|
|
455
539
|
: assistantReply.structuredPayload,
|
|
456
540
|
},
|
|
457
541
|
});
|
|
458
|
-
const proposalMessage = await
|
|
459
|
-
orgId: conversation.orgId,
|
|
460
|
-
role: "assistant",
|
|
542
|
+
const proposalMessage = await saveAssistantMessage({
|
|
461
543
|
kind: "operation_proposal",
|
|
462
544
|
body: assistantReply.body,
|
|
463
545
|
structuredPayload: {
|
|
@@ -469,41 +551,80 @@ export function chatRoutes(db, storage) {
|
|
|
469
551
|
decidedAt: null,
|
|
470
552
|
},
|
|
471
553
|
},
|
|
472
|
-
transcript,
|
|
473
554
|
approvalId: approval.id,
|
|
474
|
-
replyingAgentId,
|
|
475
|
-
chatTurnId,
|
|
476
|
-
turnVariant,
|
|
477
555
|
});
|
|
478
|
-
createdMessages.push(proposalMessage);
|
|
556
|
+
createdMessages.push(await attachGeneratedFiles(proposalMessage, assistantReply.generatedAttachments));
|
|
479
557
|
return createdMessages;
|
|
480
558
|
}
|
|
481
|
-
const assistantMessage = await
|
|
482
|
-
|
|
483
|
-
role: "assistant",
|
|
484
|
-
kind: assistantReply.kind === "routing_suggestion" ? "routing_suggestion" : "message",
|
|
559
|
+
const assistantMessage = await saveAssistantMessage({
|
|
560
|
+
kind: "message",
|
|
485
561
|
body: assistantReply.body,
|
|
486
562
|
structuredPayload: assistantReply.structuredPayload,
|
|
487
|
-
transcript,
|
|
488
|
-
replyingAgentId,
|
|
489
|
-
chatTurnId,
|
|
490
|
-
turnVariant,
|
|
491
563
|
});
|
|
492
|
-
createdMessages.push(assistantMessage);
|
|
564
|
+
createdMessages.push(await attachGeneratedFiles(assistantMessage, assistantReply.generatedAttachments));
|
|
493
565
|
return createdMessages;
|
|
494
566
|
}
|
|
495
|
-
async function
|
|
567
|
+
async function attachGeneratedFilesToPartialMessage(conversation, message, generatedAttachments, replyingAgentId) {
|
|
568
|
+
if (!message || !generatedAttachments || generatedAttachments.length === 0)
|
|
569
|
+
return message;
|
|
570
|
+
const attachments = [];
|
|
571
|
+
for (const generated of generatedAttachments) {
|
|
572
|
+
if (generated.body.length > MAX_ATTACHMENT_BYTES)
|
|
573
|
+
continue;
|
|
574
|
+
const stored = await storage.putFile({
|
|
575
|
+
orgId: conversation.orgId,
|
|
576
|
+
namespace: `chats/${conversation.id}/generated`,
|
|
577
|
+
originalFilename: generated.originalFilename,
|
|
578
|
+
contentType: generated.contentType,
|
|
579
|
+
body: generated.body,
|
|
580
|
+
});
|
|
581
|
+
const attachment = await svc.createAttachment({
|
|
582
|
+
orgId: conversation.orgId,
|
|
583
|
+
conversationId: conversation.id,
|
|
584
|
+
messageId: message.id,
|
|
585
|
+
provider: stored.provider,
|
|
586
|
+
objectKey: stored.objectKey,
|
|
587
|
+
contentType: stored.contentType,
|
|
588
|
+
byteSize: stored.byteSize,
|
|
589
|
+
sha256: stored.sha256,
|
|
590
|
+
originalFilename: stored.originalFilename,
|
|
591
|
+
createdByAgentId: replyingAgentId,
|
|
592
|
+
createdByUserId: null,
|
|
593
|
+
});
|
|
594
|
+
attachments.push(attachment);
|
|
595
|
+
}
|
|
596
|
+
return {
|
|
597
|
+
...message,
|
|
598
|
+
attachments: [...(message.attachments ?? []), ...attachments],
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
async function persistPartialAssistantMessage(conversation, body, status, turnContext, transcript = [], replyingAgentId = chatReplyingAgentId(conversation), existingMessageId) {
|
|
496
602
|
const trimmed = body.trim();
|
|
497
|
-
|
|
603
|
+
const fallbackBody = status === "stopped"
|
|
604
|
+
? "Chat run stopped before a final reply. Continue the conversation to resume from the preserved context."
|
|
605
|
+
: "Chat run failed before a final reply. Continue the conversation to resume from the preserved context.";
|
|
606
|
+
const durableBody = trimmed || (transcript.length > 0 ? fallbackBody : "");
|
|
607
|
+
if (!durableBody)
|
|
498
608
|
return null;
|
|
499
609
|
const chatTurnId = turnContext?.chatTurnId ?? randomUUID();
|
|
500
610
|
const turnVariant = turnContext?.turnVariant ?? 0;
|
|
611
|
+
if (existingMessageId) {
|
|
612
|
+
const updated = await svc.updateMessage(conversation.id, existingMessageId, {
|
|
613
|
+
kind: "message",
|
|
614
|
+
status,
|
|
615
|
+
body: durableBody,
|
|
616
|
+
transcript,
|
|
617
|
+
replyingAgentId,
|
|
618
|
+
});
|
|
619
|
+
if (updated)
|
|
620
|
+
return updated;
|
|
621
|
+
}
|
|
501
622
|
const message = await svc.addMessage(conversation.id, {
|
|
502
623
|
orgId: conversation.orgId,
|
|
503
624
|
role: "assistant",
|
|
504
625
|
kind: "message",
|
|
505
626
|
status,
|
|
506
|
-
body:
|
|
627
|
+
body: durableBody,
|
|
507
628
|
transcript,
|
|
508
629
|
replyingAgentId,
|
|
509
630
|
chatTurnId,
|
|
@@ -524,8 +645,9 @@ export function chatRoutes(db, storage) {
|
|
|
524
645
|
const status = statusParam === "resolved" || statusParam === "archived" || statusParam === "all"
|
|
525
646
|
? statusParam
|
|
526
647
|
: "active";
|
|
648
|
+
const q = typeof req.query.q === "string" ? req.query.q : undefined;
|
|
527
649
|
const userId = req.actor.type === "board" ? (req.actor.userId ?? "local-board") : null;
|
|
528
|
-
const conversations = await svc.list(orgId, { status }, userId);
|
|
650
|
+
const conversations = await svc.list(orgId, { status, q }, userId);
|
|
529
651
|
res.json(await assistantSvc.enrichConversations(conversations));
|
|
530
652
|
});
|
|
531
653
|
router.post("/orgs/:orgId/chats", validate(createChatConversationSchema), async (req, res) => {
|
|
@@ -538,6 +660,13 @@ export function chatRoutes(db, storage) {
|
|
|
538
660
|
}
|
|
539
661
|
const contextLinks = req.body.contextLinks ?? [];
|
|
540
662
|
await assertContextLinksBelongToCompany(orgId, contextLinks);
|
|
663
|
+
if (req.body.preferredAgentId) {
|
|
664
|
+
const agent = await agentsSvc.getById(req.body.preferredAgentId);
|
|
665
|
+
if (!agent || agent.orgId !== orgId) {
|
|
666
|
+
res.status(422).json({ error: "Preferred agent must belong to the same organization" });
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
541
670
|
const actor = getActorInfo(req);
|
|
542
671
|
const conversation = await svc.create(orgId, {
|
|
543
672
|
title: req.body.title,
|
|
@@ -629,6 +758,9 @@ export function chatRoutes(db, storage) {
|
|
|
629
758
|
res.status(404).json({ error: "Chat conversation not found" });
|
|
630
759
|
return;
|
|
631
760
|
}
|
|
761
|
+
if (!hasActiveChatGeneration(conversation.id)) {
|
|
762
|
+
await svc.markInterruptedStreamingMessages(conversation.id);
|
|
763
|
+
}
|
|
632
764
|
const messages = await svc.listMessages(conversation.id);
|
|
633
765
|
res.json(messages);
|
|
634
766
|
});
|
|
@@ -678,6 +810,7 @@ export function chatRoutes(db, storage) {
|
|
|
678
810
|
const assistantInput = await loadAssistantInput(conversation, actor);
|
|
679
811
|
const transcript = [];
|
|
680
812
|
const observedTranscript = [];
|
|
813
|
+
let modelTurnInput;
|
|
681
814
|
let fallbackOutput = null;
|
|
682
815
|
let finalChatOutput = null;
|
|
683
816
|
let finalChatStatus = "completed";
|
|
@@ -685,6 +818,7 @@ export function chatRoutes(db, storage) {
|
|
|
685
818
|
const streamed = await assistantSvc.streamChatAssistantReply({
|
|
686
819
|
...assistantInput,
|
|
687
820
|
onInvocationMeta: async (meta) => {
|
|
821
|
+
modelTurnInput = modelTurnInputFromInvocationMeta(meta);
|
|
688
822
|
currentChatTraceInput = buildChatTraceInput(traceInputBase, meta);
|
|
689
823
|
mergeChatInvocationTraceMetadata(chatObservation, meta);
|
|
690
824
|
updateExecutionObservation(observation, chatObservation, {
|
|
@@ -704,7 +838,7 @@ export function chatRoutes(db, storage) {
|
|
|
704
838
|
finalChatStatus = "failed";
|
|
705
839
|
throw new Error("Chat assistant reply was stopped before completion");
|
|
706
840
|
}
|
|
707
|
-
const created = await persistAssistantReply(assistantInput.conversation, actor, streamed.reply, turnContext, transcript, streamed.replyingAgentId);
|
|
841
|
+
const created = await persistAssistantReply(req, assistantInput.conversation, actor, streamed.reply, turnContext, transcript, streamed.replyingAgentId);
|
|
708
842
|
finalChatOutput = streamed.reply.body;
|
|
709
843
|
await logChatMessagesAdded(assistantInput.conversation, created, {
|
|
710
844
|
actorType: "system",
|
|
@@ -735,6 +869,7 @@ export function chatRoutes(db, storage) {
|
|
|
735
869
|
context: chatObservation,
|
|
736
870
|
parentObservation: observation,
|
|
737
871
|
transcript: observedTranscript,
|
|
872
|
+
initialTurnInput: modelTurnInput,
|
|
738
873
|
fallbackResult: fallbackOutput
|
|
739
874
|
? {
|
|
740
875
|
output: fallbackOutput,
|
|
@@ -779,6 +914,9 @@ export function chatRoutes(db, storage) {
|
|
|
779
914
|
});
|
|
780
915
|
}
|
|
781
916
|
logger.warn({ err, conversationId: conversation.id }, "chat assistant reply failed");
|
|
917
|
+
if (err instanceof HttpError) {
|
|
918
|
+
throw err;
|
|
919
|
+
}
|
|
782
920
|
res.status(502).json({
|
|
783
921
|
error: err instanceof Error ? err.message : "Chat assistant failed to respond",
|
|
784
922
|
});
|
|
@@ -837,6 +975,42 @@ export function chatRoutes(db, storage) {
|
|
|
837
975
|
let chatObservation = null;
|
|
838
976
|
const transcript = [];
|
|
839
977
|
const observedTranscript = [];
|
|
978
|
+
let modelTurnInput;
|
|
979
|
+
let assistantProgressMessage = null;
|
|
980
|
+
let assistantProgressMessageId = null;
|
|
981
|
+
let assistantDraftBody = "";
|
|
982
|
+
const persistStreamProgress = async (progressConversation, replyingAgentId = chatReplyingAgentId(progressConversation)) => {
|
|
983
|
+
if (!turnContextForPartial)
|
|
984
|
+
return null;
|
|
985
|
+
const input = {
|
|
986
|
+
kind: "message",
|
|
987
|
+
status: "streaming",
|
|
988
|
+
body: assistantDraftBody,
|
|
989
|
+
transcript,
|
|
990
|
+
replyingAgentId,
|
|
991
|
+
};
|
|
992
|
+
if (assistantProgressMessage) {
|
|
993
|
+
const updated = await svc.updateMessage(progressConversation.id, assistantProgressMessage.id, input);
|
|
994
|
+
if (updated) {
|
|
995
|
+
assistantProgressMessage = updated;
|
|
996
|
+
assistantProgressMessageId = assistantProgressMessage.id;
|
|
997
|
+
return assistantProgressMessage;
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
assistantProgressMessage = await svc.addMessage(progressConversation.id, {
|
|
1001
|
+
orgId: progressConversation.orgId,
|
|
1002
|
+
role: "assistant",
|
|
1003
|
+
kind: "message",
|
|
1004
|
+
status: "streaming",
|
|
1005
|
+
body: assistantDraftBody,
|
|
1006
|
+
transcript,
|
|
1007
|
+
replyingAgentId,
|
|
1008
|
+
chatTurnId: turnContextForPartial.chatTurnId,
|
|
1009
|
+
turnVariant: turnContextForPartial.turnVariant,
|
|
1010
|
+
});
|
|
1011
|
+
assistantProgressMessageId = assistantProgressMessage.id;
|
|
1012
|
+
return assistantProgressMessage;
|
|
1013
|
+
};
|
|
840
1014
|
let clientClosed = false;
|
|
841
1015
|
const handleClosed = () => {
|
|
842
1016
|
if (clientClosed || res.writableEnded)
|
|
@@ -894,6 +1068,7 @@ export function chatRoutes(db, storage) {
|
|
|
894
1068
|
...assistantInput,
|
|
895
1069
|
abortSignal: abortController.signal,
|
|
896
1070
|
onInvocationMeta: async (meta) => {
|
|
1071
|
+
modelTurnInput = modelTurnInputFromInvocationMeta(meta);
|
|
897
1072
|
currentChatTraceInput = buildChatTraceInput(traceInputBase, meta);
|
|
898
1073
|
mergeChatInvocationTraceMetadata(chatObservation, meta);
|
|
899
1074
|
updateExecutionObservation(observation, chatObservation, {
|
|
@@ -902,12 +1077,17 @@ export function chatRoutes(db, storage) {
|
|
|
902
1077
|
updateExecutionTraceIO(observation, { input: currentChatTraceInput });
|
|
903
1078
|
},
|
|
904
1079
|
onAssistantDelta: async (delta) => {
|
|
1080
|
+
assistantDraftBody = `${assistantDraftBody}${delta}`;
|
|
1081
|
+
await persistStreamProgress(assistantInput.conversation);
|
|
1082
|
+
if (clientClosed)
|
|
1083
|
+
return;
|
|
905
1084
|
writeStreamEvent(res, {
|
|
906
1085
|
type: "assistant_delta",
|
|
907
1086
|
delta,
|
|
908
1087
|
});
|
|
909
1088
|
},
|
|
910
1089
|
onAssistantState: async (state) => {
|
|
1090
|
+
await persistStreamProgress(assistantInput.conversation);
|
|
911
1091
|
if (clientClosed)
|
|
912
1092
|
return;
|
|
913
1093
|
writeStreamEvent(res, {
|
|
@@ -917,6 +1097,7 @@ export function chatRoutes(db, storage) {
|
|
|
917
1097
|
},
|
|
918
1098
|
onTranscriptEntry: async (entry) => {
|
|
919
1099
|
transcript.push(entry);
|
|
1100
|
+
await persistStreamProgress(assistantInput.conversation);
|
|
920
1101
|
if (clientClosed)
|
|
921
1102
|
return;
|
|
922
1103
|
writeStreamEvent(res, {
|
|
@@ -931,7 +1112,7 @@ export function chatRoutes(db, storage) {
|
|
|
931
1112
|
if (streamed.outcome === "stopped") {
|
|
932
1113
|
finalChatStatus = "stopped";
|
|
933
1114
|
finalChatOutput = streamed.partialBody;
|
|
934
|
-
const stoppedMessage = await persistPartialAssistantMessage(assistantInput.conversation, streamed.partialBody, "stopped", turnContextForPartial, transcript, streamed.replyingAgentId);
|
|
1115
|
+
const stoppedMessage = await persistPartialAssistantMessage(assistantInput.conversation, streamed.partialBody, "stopped", turnContextForPartial, transcript, streamed.replyingAgentId, assistantProgressMessageId);
|
|
935
1116
|
if (stoppedMessage) {
|
|
936
1117
|
await logChatMessagesAdded(assistantInput.conversation, [stoppedMessage], {
|
|
937
1118
|
actorType: "system",
|
|
@@ -957,7 +1138,7 @@ export function chatRoutes(db, storage) {
|
|
|
957
1138
|
}
|
|
958
1139
|
return;
|
|
959
1140
|
}
|
|
960
|
-
const createdMessages = await persistAssistantReply(assistantInput.conversation, actor, streamed.reply, turnContextForPartial, transcript, streamed.replyingAgentId);
|
|
1141
|
+
const createdMessages = await persistAssistantReply(req, assistantInput.conversation, actor, streamed.reply, turnContextForPartial, transcript, streamed.replyingAgentId, assistantProgressMessageId);
|
|
961
1142
|
finalChatOutput = streamed.reply.body;
|
|
962
1143
|
await logChatMessagesAdded(assistantInput.conversation, createdMessages, {
|
|
963
1144
|
actorType: "system",
|
|
@@ -993,6 +1174,14 @@ export function chatRoutes(db, storage) {
|
|
|
993
1174
|
context: chatObservation,
|
|
994
1175
|
parentObservation: observation,
|
|
995
1176
|
transcript: observedTranscript,
|
|
1177
|
+
initialTurnInput: modelTurnInput,
|
|
1178
|
+
fallbackResult: finalChatOutput
|
|
1179
|
+
? {
|
|
1180
|
+
output: finalChatOutput,
|
|
1181
|
+
subtype: finalChatStatus,
|
|
1182
|
+
isError: finalChatStatus === "failed",
|
|
1183
|
+
}
|
|
1184
|
+
: null,
|
|
996
1185
|
});
|
|
997
1186
|
finalChatOutput = finalChatOutput ?? transcriptStats.finalOutput ?? null;
|
|
998
1187
|
}
|
|
@@ -1020,8 +1209,10 @@ export function chatRoutes(db, storage) {
|
|
|
1020
1209
|
}
|
|
1021
1210
|
catch (err) {
|
|
1022
1211
|
const partialBody = err instanceof ChatAssistantStreamError ? err.partialBody : "";
|
|
1212
|
+
const generatedAttachments = err instanceof ChatAssistantStreamError ? err.generatedAttachments : [];
|
|
1023
1213
|
const failedReplyingAgentId = chatReplyingAgentId(assistantConversationForPartial);
|
|
1024
|
-
|
|
1214
|
+
let failedMessage = await persistPartialAssistantMessage(assistantConversationForPartial ?? conversation, partialBody, "failed", turnContextForPartial, transcript, failedReplyingAgentId, assistantProgressMessageId).catch(() => null);
|
|
1215
|
+
failedMessage = await attachGeneratedFilesToPartialMessage(assistantConversationForPartial ?? conversation, failedMessage, generatedAttachments, failedReplyingAgentId).catch(() => failedMessage);
|
|
1025
1216
|
if (failedMessage && assistantConversationForPartial) {
|
|
1026
1217
|
await logChatMessagesAdded(assistantConversationForPartial, [failedMessage], {
|
|
1027
1218
|
actorType: "system",
|
|
@@ -1186,6 +1377,11 @@ export function chatRoutes(db, storage) {
|
|
|
1186
1377
|
entityId: projectId,
|
|
1187
1378
|
}]);
|
|
1188
1379
|
}
|
|
1380
|
+
const messages = await svc.listMessages(conversation.id);
|
|
1381
|
+
if (messages.length > 0) {
|
|
1382
|
+
res.status(409).json({ error: "Project context is locked after conversation starts" });
|
|
1383
|
+
return;
|
|
1384
|
+
}
|
|
1189
1385
|
const updated = await svc.setProjectContextLink(conversation.id, conversation.orgId, projectId);
|
|
1190
1386
|
if (!updated) {
|
|
1191
1387
|
res.status(404).json({ error: "Chat conversation not found" });
|
|
@@ -1219,6 +1415,10 @@ export function chatRoutes(db, storage) {
|
|
|
1219
1415
|
return;
|
|
1220
1416
|
}
|
|
1221
1417
|
}
|
|
1418
|
+
await assertCanConvertIssueProposal(req, conversation, {
|
|
1419
|
+
messageId: req.body.messageId ?? null,
|
|
1420
|
+
proposal: req.body.proposal ?? null,
|
|
1421
|
+
});
|
|
1222
1422
|
const chatObservation = buildChatObservabilityContext(conversation, {
|
|
1223
1423
|
rootExecutionId: req.body.messageId ?? `chat-convert:${conversation.id}`,
|
|
1224
1424
|
trigger: "convert_to_issue",
|
|
@@ -1365,6 +1565,14 @@ export function chatRoutes(db, storage) {
|
|
|
1365
1565
|
if (typeof req.body.pinned === "boolean") {
|
|
1366
1566
|
await svc.setPinned(conversation.id, conversation.orgId, userId, req.body.pinned);
|
|
1367
1567
|
}
|
|
1568
|
+
if (typeof req.body.unread === "boolean") {
|
|
1569
|
+
if (req.body.unread) {
|
|
1570
|
+
await svc.markUnread(conversation.id, conversation.orgId, userId);
|
|
1571
|
+
}
|
|
1572
|
+
else {
|
|
1573
|
+
await svc.markRead(conversation.id, conversation.orgId, userId);
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1368
1576
|
const refreshed = await svc.getById(conversation.id, userId);
|
|
1369
1577
|
res.json(await assistantSvc.enrichConversation(refreshed));
|
|
1370
1578
|
});
|