@rudderhq/server 0.2.0-canary.8 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +124 -117
- package/dist/bundled-plugins/plugin-linear/dist/worker.js.map +3 -3
- 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 +62 -3
- package/dist/routes/agents.js.map +1 -1
- package/dist/routes/approvals.d.ts.map +1 -1
- package/dist/routes/approvals.js +30 -1
- package/dist/routes/approvals.js.map +1 -1
- package/dist/routes/chats.d.ts.map +1 -1
- package/dist/routes/chats.js +343 -53
- 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 +229 -19
- 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 -32
- package/dist/services/agent-run-context.d.ts.map +1 -1
- package/dist/services/agent-run-context.js +26 -128
- package/dist/services/agent-run-context.js.map +1 -1
- package/dist/services/agents.d.ts +26 -26
- package/dist/services/agents.d.ts.map +1 -1
- package/dist/services/agents.js +1 -42
- package/dist/services/agents.js.map +1 -1
- package/dist/services/assets.d.ts +2 -2
- package/dist/services/automations.d.ts +2 -2
- package/dist/services/calendar.d.ts +4 -4
- package/dist/services/chat-assistant.d.ts +12 -3
- package/dist/services/chat-assistant.d.ts.map +1 -1
- package/dist/services/chat-assistant.js +126 -99
- package/dist/services/chat-assistant.js.map +1 -1
- package/dist/services/chats.d.ts +88 -15
- package/dist/services/chats.d.ts.map +1 -1
- package/dist/services/chats.js +217 -14
- 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 +76 -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 +25 -16
- package/dist/services/instance-settings.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 +165 -11
- 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 -13
- package/dist/services/orgs.d.ts.map +1 -1
- package/dist/services/orgs.js +0 -2
- package/dist/services/orgs.js.map +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 +567 -29
- 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-DfISC403.js → _basePickBy-3Hg7N37c.js} +1 -1
- package/ui-dist/assets/{_baseUniq-s98gUpgR.js → _baseUniq-Bvy8WJh0.js} +1 -1
- package/ui-dist/assets/{arc-CR17323Q.js → arc-DrmvGX4U.js} +1 -1
- package/ui-dist/assets/{architectureDiagram-2XIMDMQ5-BzXAflU_.js → architectureDiagram-2XIMDMQ5-vbevcV-8.js} +1 -1
- package/ui-dist/assets/{blockDiagram-WCTKOSBZ-8TtjJ3eI.js → blockDiagram-WCTKOSBZ-DvupMRN9.js} +1 -1
- package/ui-dist/assets/{c4Diagram-IC4MRINW-CpDAvPEk.js → c4Diagram-IC4MRINW-CbsNVA8e.js} +1 -1
- package/ui-dist/assets/channel-DhW0A-FV.js +1 -0
- package/ui-dist/assets/{chunk-4BX2VUAB-DH5y5IcG.js → chunk-4BX2VUAB-BL4OUqNV.js} +1 -1
- package/ui-dist/assets/{chunk-55IACEB6-HUQ0oHmD.js → chunk-55IACEB6-DFwq2ebc.js} +1 -1
- package/ui-dist/assets/{chunk-FMBD7UC4-B_Tmn_aT.js → chunk-FMBD7UC4-Cyl6kF9G.js} +1 -1
- package/ui-dist/assets/{chunk-JSJVCQXG-COsiBBS-.js → chunk-JSJVCQXG-v4mfLtsY.js} +1 -1
- package/ui-dist/assets/{chunk-KX2RTZJC-Cvg_71ig.js → chunk-KX2RTZJC-Bfg48g5k.js} +1 -1
- package/ui-dist/assets/{chunk-NQ4KR5QH-BEW2kuUb.js → chunk-NQ4KR5QH-BcSdbequ.js} +1 -1
- package/ui-dist/assets/{chunk-QZHKN3VN-Bna2V6NY.js → chunk-QZHKN3VN-BT8QI712.js} +1 -1
- package/ui-dist/assets/{chunk-WL4C6EOR-DZ8gtX-v.js → chunk-WL4C6EOR-CqH2or9g.js} +1 -1
- package/ui-dist/assets/classDiagram-VBA2DB6C-Bw6kzUsz.js +1 -0
- package/ui-dist/assets/classDiagram-v2-RAHNMMFH-Bw6kzUsz.js +1 -0
- package/ui-dist/assets/clone-Luak8Fsn.js +1 -0
- package/ui-dist/assets/{cose-bilkent-S5V4N54A-kr92BRef.js → cose-bilkent-S5V4N54A-CLH06Lnz.js} +1 -1
- package/ui-dist/assets/{dagre-KLK3FWXG-D_g61aoF.js → dagre-KLK3FWXG-DxNQPDBj.js} +1 -1
- package/ui-dist/assets/{diagram-E7M64L7V-1LfSj6n8.js → diagram-E7M64L7V-BOcSeWh0.js} +1 -1
- package/ui-dist/assets/{diagram-IFDJBPK2-DK364c5M.js → diagram-IFDJBPK2-DXyaFKVr.js} +1 -1
- package/ui-dist/assets/{diagram-P4PSJMXO-CIq22L7l.js → diagram-P4PSJMXO-DhY_ls3C.js} +1 -1
- package/ui-dist/assets/{erDiagram-INFDFZHY-C2QX1WzN.js → erDiagram-INFDFZHY-QtL5Yt_b.js} +1 -1
- package/ui-dist/assets/{flowDiagram-PKNHOUZH-BXk0KTTB.js → flowDiagram-PKNHOUZH-BYqyaowc.js} +1 -1
- package/ui-dist/assets/{ganttDiagram-A5KZAMGK-D75CuaPG.js → ganttDiagram-A5KZAMGK-D4xd7J_z.js} +1 -1
- package/ui-dist/assets/{gitGraphDiagram-K3NZZRJ6-C_SvdzO5.js → gitGraphDiagram-K3NZZRJ6-Co9xqKNH.js} +1 -1
- package/ui-dist/assets/{graph-DTC5egc-.js → graph-DEC7S98H.js} +1 -1
- package/ui-dist/assets/{index-CEKv8ksn.js → index-4_gJOU3u.js} +1 -1
- package/ui-dist/assets/{index-CSzr8t_E.js → index-B8QjK4Xd.js} +1 -1
- package/ui-dist/assets/index-BLDnKx7N.js +1478 -0
- package/ui-dist/assets/{index-CqKwXFnI.js → index-BX6QyxsL.js} +1 -1
- package/ui-dist/assets/{index-DmSssfNa.js → index-BZGiyL9p.js} +1 -1
- package/ui-dist/assets/{index-BLvouPW7.js → index-BelfAyHh.js} +1 -1
- package/ui-dist/assets/index-BisI78wU.css +1 -0
- package/ui-dist/assets/{index-BUyOio4T.js → index-Bm86s0IY.js} +1 -1
- package/ui-dist/assets/{index-DLL28Pwh.js → index-Bz0jEwWG.js} +1 -1
- package/ui-dist/assets/{index--j9y8Fm1.js → index-CFANc8oH.js} +1 -1
- package/ui-dist/assets/{index-CjmpD1gl.js → index-CIAMqUzr.js} +1 -1
- package/ui-dist/assets/{index-lXemkB72.js → index-ClrueuiI.js} +1 -1
- package/ui-dist/assets/{index-D9UMKH7j.js → index-CpxwEuIg.js} +1 -1
- package/ui-dist/assets/{index-BI5DxFdZ.js → index-D1ZkASZY.js} +1 -1
- package/ui-dist/assets/{index-BkXN5tcA.js → index-DUP0i_Iv.js} +1 -1
- package/ui-dist/assets/{index-Be2KDBoW.js → index-DawkXomB.js} +1 -1
- package/ui-dist/assets/{index-CO0DiQaO.js → index-DxchV0Z7.js} +1 -1
- package/ui-dist/assets/{index-Bk9vEAxA.js → index-Dzd88G_H.js} +1 -1
- package/ui-dist/assets/{index-CyIEQyck.js → index-SklGX83C.js} +1 -1
- package/ui-dist/assets/{index-DL9Ygef7.js → index-_xX3B4n0.js} +1 -1
- package/ui-dist/assets/{index-8HHVhW-a.js → index-bVqVfFu5.js} +1 -1
- package/ui-dist/assets/{index-Dl-MnVEo.js → index-eIjkqSkc.js} +1 -1
- package/ui-dist/assets/{index-CNkqdDmO.js → index-mIrYeZR2.js} +1 -1
- package/ui-dist/assets/{index-CXBstvbt.js → index-xg2FQeSA.js} +1 -1
- package/ui-dist/assets/{infoDiagram-LFFYTUFH-DN2iczkC.js → infoDiagram-LFFYTUFH-BQ0qsBJ6.js} +1 -1
- package/ui-dist/assets/{ishikawaDiagram-PHBUUO56--Ku5ltXY.js → ishikawaDiagram-PHBUUO56-B1u2RAnY.js} +1 -1
- package/ui-dist/assets/{journeyDiagram-4ABVD52K-9wOJKEaq.js → journeyDiagram-4ABVD52K-Dv5wJGwT.js} +1 -1
- package/ui-dist/assets/{kanban-definition-K7BYSVSG-joaPa9x3.js → kanban-definition-K7BYSVSG-CJOykCsT.js} +1 -1
- package/ui-dist/assets/{layout-bH9QuYBa.js → layout-BDcM6t-f.js} +1 -1
- package/ui-dist/assets/{linear-CnfJ09Re.js → linear-B9Sm5Y96.js} +1 -1
- package/ui-dist/assets/{mermaid.core-CYkJAFnD.js → mermaid.core-lZPaf_Ix.js} +4 -4
- package/ui-dist/assets/{mindmap-definition-YRQLILUH-BQyC_8UY.js → mindmap-definition-YRQLILUH-Cu4HfP8K.js} +1 -1
- package/ui-dist/assets/{pieDiagram-SKSYHLDU-ev5uLtwW.js → pieDiagram-SKSYHLDU-B_v-Vluc.js} +1 -1
- package/ui-dist/assets/{quadrantDiagram-337W2JSQ-CQoe3akb.js → quadrantDiagram-337W2JSQ-BU1ZwGcS.js} +1 -1
- package/ui-dist/assets/{requirementDiagram-Z7DCOOCP-C9fd5-6o.js → requirementDiagram-Z7DCOOCP-DBOqB50G.js} +1 -1
- package/ui-dist/assets/{sankeyDiagram-WA2Y5GQK-Dznsr9L_.js → sankeyDiagram-WA2Y5GQK-CsXDIOlq.js} +1 -1
- package/ui-dist/assets/{sequenceDiagram-2WXFIKYE-jL7GufkU.js → sequenceDiagram-2WXFIKYE-Cmgr7vKy.js} +1 -1
- package/ui-dist/assets/{stateDiagram-RAJIS63D-C_4YyDYV.js → stateDiagram-RAJIS63D-Bd0uRbWd.js} +1 -1
- package/ui-dist/assets/stateDiagram-v2-FVOUBMTO-qGaY7iN1.js +1 -0
- package/ui-dist/assets/{timeline-definition-YZTLITO2-D8jKeAEo.js → timeline-definition-YZTLITO2-B9OfCgYQ.js} +1 -1
- package/ui-dist/assets/{treemap-KZPCXAKY-CV4vBepp.js → treemap-KZPCXAKY-FWWMNo03.js} +1 -1
- package/ui-dist/assets/{vennDiagram-LZ73GAT5-DAk_ThVs.js → vennDiagram-LZ73GAT5-CGs3T7cn.js} +1 -1
- package/ui-dist/assets/{xychartDiagram-JWTSCODW-D8wg1dlm.js → xychartDiagram-JWTSCODW-BJ6DrP1k.js} +1 -1
- package/ui-dist/index.html +2 -2
- package/ui-dist/assets/channel-BDMh8XFu.js +0 -1
- package/ui-dist/assets/classDiagram-VBA2DB6C-B6kvnvP_.js +0 -1
- package/ui-dist/assets/classDiagram-v2-RAHNMMFH-B6kvnvP_.js +0 -1
- package/ui-dist/assets/clone-B5CU-MlO.js +0 -1
- package/ui-dist/assets/index-CLXVCM5X.js +0 -1434
- package/ui-dist/assets/index-DoUUx1qN.css +0 -1
- package/ui-dist/assets/stateDiagram-v2-FVOUBMTO-BAU1MhTJ.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,24 +349,175 @@ export function chatRoutes(db, storage) {
|
|
|
313
349
|
function chatReplyingAgentId(conversation) {
|
|
314
350
|
return conversation?.chatRuntime?.runtimeAgentId ?? conversation?.preferredAgentId ?? null;
|
|
315
351
|
}
|
|
316
|
-
async function
|
|
352
|
+
async function defaultIssueAssigneeAgentId(conversation) {
|
|
353
|
+
const candidateAgentIds = [conversation?.preferredAgentId, conversation?.routedAgentId]
|
|
354
|
+
.filter((id) => typeof id === "string" && id.trim().length > 0);
|
|
355
|
+
if (!conversation)
|
|
356
|
+
return null;
|
|
357
|
+
for (const candidateAgentId of candidateAgentIds) {
|
|
358
|
+
const agent = await agentsSvc.getById(candidateAgentId);
|
|
359
|
+
if (!agent || agent.orgId !== conversation.orgId)
|
|
360
|
+
continue;
|
|
361
|
+
if (agent.status === "pending_approval" || agent.status === "terminated")
|
|
362
|
+
continue;
|
|
363
|
+
return agent.id;
|
|
364
|
+
}
|
|
365
|
+
return null;
|
|
366
|
+
}
|
|
367
|
+
function hasIssueProposalAssignee(proposal) {
|
|
368
|
+
return Boolean((typeof proposal.assigneeAgentId === "string" && proposal.assigneeAgentId.trim().length > 0)
|
|
369
|
+
|| (typeof proposal.assigneeUserId === "string" && proposal.assigneeUserId.trim().length > 0));
|
|
370
|
+
}
|
|
371
|
+
function withDefaultIssueProposalAssignee(structuredPayload, assigneeAgentId) {
|
|
372
|
+
if (!structuredPayload || !assigneeAgentId)
|
|
373
|
+
return structuredPayload ?? null;
|
|
374
|
+
const nestedProposal = structuredPayload.issueProposal
|
|
375
|
+
&& typeof structuredPayload.issueProposal === "object"
|
|
376
|
+
&& !Array.isArray(structuredPayload.issueProposal)
|
|
377
|
+
? structuredPayload.issueProposal
|
|
378
|
+
: null;
|
|
379
|
+
const proposal = nestedProposal ?? structuredPayload;
|
|
380
|
+
if (hasIssueProposalAssignee(proposal))
|
|
381
|
+
return structuredPayload;
|
|
382
|
+
if (nestedProposal) {
|
|
383
|
+
return {
|
|
384
|
+
...structuredPayload,
|
|
385
|
+
issueProposal: {
|
|
386
|
+
...nestedProposal,
|
|
387
|
+
assigneeAgentId,
|
|
388
|
+
},
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
return {
|
|
392
|
+
...structuredPayload,
|
|
393
|
+
assigneeAgentId,
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
function proposedIssuePayload(structuredPayload) {
|
|
397
|
+
if (!structuredPayload)
|
|
398
|
+
return structuredPayload ?? null;
|
|
399
|
+
return structuredPayload.issueProposal
|
|
400
|
+
&& typeof structuredPayload.issueProposal === "object"
|
|
401
|
+
&& !Array.isArray(structuredPayload.issueProposal)
|
|
402
|
+
&& structuredPayload.issueProposal !== null
|
|
403
|
+
? structuredPayload.issueProposal
|
|
404
|
+
: structuredPayload;
|
|
405
|
+
}
|
|
406
|
+
function proposalAssignsOrReviewsIssue(proposal) {
|
|
407
|
+
if (!proposal)
|
|
408
|
+
return false;
|
|
409
|
+
return Boolean((typeof proposal.assigneeAgentId === "string" && proposal.assigneeAgentId.trim().length > 0)
|
|
410
|
+
|| (typeof proposal.assigneeUserId === "string" && proposal.assigneeUserId.trim().length > 0)
|
|
411
|
+
|| (typeof proposal.reviewerAgentId === "string" && proposal.reviewerAgentId.trim().length > 0)
|
|
412
|
+
|| (typeof proposal.reviewerUserId === "string" && proposal.reviewerUserId.trim().length > 0));
|
|
413
|
+
}
|
|
414
|
+
async function proposedIssuePayloadForConversion(conversationId, input) {
|
|
415
|
+
if (input.proposal)
|
|
416
|
+
return proposedIssuePayload(input.proposal);
|
|
417
|
+
if (input.messageId) {
|
|
418
|
+
const message = await svc.getMessage(conversationId, input.messageId);
|
|
419
|
+
return proposedIssuePayload(message?.structuredPayload ?? null);
|
|
420
|
+
}
|
|
421
|
+
const messages = await svc.listMessages(conversationId);
|
|
422
|
+
const message = [...messages].reverse().find((entry) => entry.kind === "issue_proposal");
|
|
423
|
+
return proposedIssuePayload(message?.structuredPayload ?? null);
|
|
424
|
+
}
|
|
425
|
+
async function assertCanConvertIssueProposal(req, conversation, input) {
|
|
426
|
+
const proposal = await proposedIssuePayloadForConversion(conversation.id, input);
|
|
427
|
+
if (proposalAssignsOrReviewsIssue(proposal)) {
|
|
428
|
+
await assertCanAssignTasks(req, conversation.orgId);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
function proposedPlanDocumentPayload(structuredPayload) {
|
|
432
|
+
if (!structuredPayload)
|
|
433
|
+
return null;
|
|
434
|
+
const rawDocument = structuredPayload.planDocument
|
|
435
|
+
&& typeof structuredPayload.planDocument === "object"
|
|
436
|
+
&& !Array.isArray(structuredPayload.planDocument)
|
|
437
|
+
? structuredPayload.planDocument
|
|
438
|
+
: structuredPayload.plan && typeof structuredPayload.plan === "object" && !Array.isArray(structuredPayload.plan)
|
|
439
|
+
? structuredPayload.plan
|
|
440
|
+
: null;
|
|
441
|
+
return rawDocument ? rawDocument : null;
|
|
442
|
+
}
|
|
443
|
+
async function persistAssistantReply(req, conversation, actor, assistantReply, turnContext, transcript = [], replyingAgentId = assistantReply.replyingAgentId ?? chatReplyingAgentId(conversation), existingMessageId) {
|
|
317
444
|
const createdMessages = [];
|
|
318
445
|
const { chatTurnId, turnVariant } = turnContext;
|
|
446
|
+
const attachGeneratedFiles = async (message, generatedAttachments) => {
|
|
447
|
+
if (!generatedAttachments || generatedAttachments.length === 0)
|
|
448
|
+
return message;
|
|
449
|
+
const attachments = [];
|
|
450
|
+
for (const generated of generatedAttachments) {
|
|
451
|
+
if (generated.body.length > MAX_ATTACHMENT_BYTES) {
|
|
452
|
+
throw new ChatAssistantStreamError(`Generated attachment exceeds ${MAX_ATTACHMENT_BYTES} bytes`, assistantReply.body, generatedAttachments);
|
|
453
|
+
}
|
|
454
|
+
const stored = await storage.putFile({
|
|
455
|
+
orgId: conversation.orgId,
|
|
456
|
+
namespace: `chats/${conversation.id}/generated`,
|
|
457
|
+
originalFilename: generated.originalFilename,
|
|
458
|
+
contentType: generated.contentType,
|
|
459
|
+
body: generated.body,
|
|
460
|
+
});
|
|
461
|
+
const attachment = await svc.createAttachment({
|
|
462
|
+
orgId: conversation.orgId,
|
|
463
|
+
conversationId: conversation.id,
|
|
464
|
+
messageId: message.id,
|
|
465
|
+
provider: stored.provider,
|
|
466
|
+
objectKey: stored.objectKey,
|
|
467
|
+
contentType: stored.contentType,
|
|
468
|
+
byteSize: stored.byteSize,
|
|
469
|
+
sha256: stored.sha256,
|
|
470
|
+
originalFilename: stored.originalFilename,
|
|
471
|
+
createdByAgentId: replyingAgentId,
|
|
472
|
+
createdByUserId: null,
|
|
473
|
+
});
|
|
474
|
+
attachments.push(attachment);
|
|
475
|
+
}
|
|
476
|
+
return {
|
|
477
|
+
...message,
|
|
478
|
+
attachments: [...(message.attachments ?? []), ...attachments],
|
|
479
|
+
};
|
|
480
|
+
};
|
|
481
|
+
const saveAssistantMessage = async (input) => {
|
|
482
|
+
if (existingMessageId) {
|
|
483
|
+
const updated = await svc.updateMessage(conversation.id, existingMessageId, {
|
|
484
|
+
kind: input.kind,
|
|
485
|
+
status: "completed",
|
|
486
|
+
body: input.body,
|
|
487
|
+
structuredPayload: input.structuredPayload ?? null,
|
|
488
|
+
transcript,
|
|
489
|
+
approvalId: input.approvalId ?? null,
|
|
490
|
+
replyingAgentId,
|
|
491
|
+
});
|
|
492
|
+
if (updated)
|
|
493
|
+
return updated;
|
|
494
|
+
}
|
|
495
|
+
return svc.addMessage(conversation.id, {
|
|
496
|
+
orgId: conversation.orgId,
|
|
497
|
+
role: "assistant",
|
|
498
|
+
kind: input.kind,
|
|
499
|
+
body: input.body,
|
|
500
|
+
structuredPayload: input.structuredPayload ?? null,
|
|
501
|
+
transcript,
|
|
502
|
+
approvalId: input.approvalId ?? null,
|
|
503
|
+
replyingAgentId,
|
|
504
|
+
chatTurnId,
|
|
505
|
+
turnVariant,
|
|
506
|
+
});
|
|
507
|
+
};
|
|
319
508
|
if (assistantReply.kind === "issue_proposal") {
|
|
320
|
-
const
|
|
509
|
+
const issueProposalStructuredPayload = withDefaultIssueProposalAssignee(assistantReply.structuredPayload, await defaultIssueAssigneeAgentId(conversation));
|
|
510
|
+
const shouldAutoCreateIssue = !conversation.planMode && conversation.issueCreationMode === "auto_create";
|
|
321
511
|
if (shouldAutoCreateIssue) {
|
|
322
|
-
const proposalMessage = await
|
|
323
|
-
orgId: conversation.orgId,
|
|
324
|
-
role: "assistant",
|
|
512
|
+
const proposalMessage = await saveAssistantMessage({
|
|
325
513
|
kind: "issue_proposal",
|
|
326
514
|
body: assistantReply.body,
|
|
327
|
-
structuredPayload:
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
515
|
+
structuredPayload: issueProposalStructuredPayload,
|
|
516
|
+
});
|
|
517
|
+
createdMessages.push(await attachGeneratedFiles(proposalMessage, assistantReply.generatedAttachments));
|
|
518
|
+
await assertCanConvertIssueProposal(req, conversation, {
|
|
519
|
+
proposal: issueProposalStructuredPayload,
|
|
332
520
|
});
|
|
333
|
-
createdMessages.push(proposalMessage);
|
|
334
521
|
const issue = await svc.convertToIssue(conversation.id, {
|
|
335
522
|
actorUserId: actor.actorType === "user" ? actor.actorId : null,
|
|
336
523
|
messageId: proposalMessage.id,
|
|
@@ -359,36 +546,28 @@ export function chatRoutes(db, storage) {
|
|
|
359
546
|
details: {
|
|
360
547
|
issueId: issue.id,
|
|
361
548
|
issueIdentifier: issue.identifier,
|
|
362
|
-
source:
|
|
549
|
+
source: "auto_create",
|
|
363
550
|
},
|
|
364
551
|
});
|
|
365
552
|
return createdMessages;
|
|
366
553
|
}
|
|
554
|
+
const planDocument = proposedPlanDocumentPayload(issueProposalStructuredPayload);
|
|
367
555
|
const approval = await svc.createProposalApproval(conversation.orgId, {
|
|
368
556
|
type: "chat_issue_creation",
|
|
369
557
|
requestedByUserId: actor.actorType === "user" ? actor.actorId : null,
|
|
370
558
|
payload: {
|
|
371
559
|
chatConversationId: conversation.id,
|
|
372
|
-
proposedIssue:
|
|
373
|
-
|
|
374
|
-
assistantReply.structuredPayload.issueProposal !== null
|
|
375
|
-
? assistantReply.structuredPayload.issueProposal
|
|
376
|
-
: assistantReply.structuredPayload,
|
|
560
|
+
proposedIssue: proposedIssuePayload(issueProposalStructuredPayload),
|
|
561
|
+
...(planDocument ? { planDocument } : {}),
|
|
377
562
|
},
|
|
378
563
|
});
|
|
379
|
-
const proposalMessage = await
|
|
380
|
-
orgId: conversation.orgId,
|
|
381
|
-
role: "assistant",
|
|
564
|
+
const proposalMessage = await saveAssistantMessage({
|
|
382
565
|
kind: "issue_proposal",
|
|
383
566
|
body: assistantReply.body,
|
|
384
|
-
structuredPayload:
|
|
385
|
-
transcript,
|
|
567
|
+
structuredPayload: issueProposalStructuredPayload,
|
|
386
568
|
approvalId: approval.id,
|
|
387
|
-
replyingAgentId,
|
|
388
|
-
chatTurnId,
|
|
389
|
-
turnVariant,
|
|
390
569
|
});
|
|
391
|
-
createdMessages.push(proposalMessage);
|
|
570
|
+
createdMessages.push(await attachGeneratedFiles(proposalMessage, assistantReply.generatedAttachments));
|
|
392
571
|
return createdMessages;
|
|
393
572
|
}
|
|
394
573
|
if (assistantReply.kind === "operation_proposal") {
|
|
@@ -404,9 +583,7 @@ export function chatRoutes(db, storage) {
|
|
|
404
583
|
: assistantReply.structuredPayload,
|
|
405
584
|
},
|
|
406
585
|
});
|
|
407
|
-
const proposalMessage = await
|
|
408
|
-
orgId: conversation.orgId,
|
|
409
|
-
role: "assistant",
|
|
586
|
+
const proposalMessage = await saveAssistantMessage({
|
|
410
587
|
kind: "operation_proposal",
|
|
411
588
|
body: assistantReply.body,
|
|
412
589
|
structuredPayload: {
|
|
@@ -418,41 +595,80 @@ export function chatRoutes(db, storage) {
|
|
|
418
595
|
decidedAt: null,
|
|
419
596
|
},
|
|
420
597
|
},
|
|
421
|
-
transcript,
|
|
422
598
|
approvalId: approval.id,
|
|
423
|
-
replyingAgentId,
|
|
424
|
-
chatTurnId,
|
|
425
|
-
turnVariant,
|
|
426
599
|
});
|
|
427
|
-
createdMessages.push(proposalMessage);
|
|
600
|
+
createdMessages.push(await attachGeneratedFiles(proposalMessage, assistantReply.generatedAttachments));
|
|
428
601
|
return createdMessages;
|
|
429
602
|
}
|
|
430
|
-
const assistantMessage = await
|
|
431
|
-
orgId: conversation.orgId,
|
|
432
|
-
role: "assistant",
|
|
603
|
+
const assistantMessage = await saveAssistantMessage({
|
|
433
604
|
kind: assistantReply.kind === "routing_suggestion" ? "routing_suggestion" : "message",
|
|
434
605
|
body: assistantReply.body,
|
|
435
606
|
structuredPayload: assistantReply.structuredPayload,
|
|
436
|
-
transcript,
|
|
437
|
-
replyingAgentId,
|
|
438
|
-
chatTurnId,
|
|
439
|
-
turnVariant,
|
|
440
607
|
});
|
|
441
|
-
createdMessages.push(assistantMessage);
|
|
608
|
+
createdMessages.push(await attachGeneratedFiles(assistantMessage, assistantReply.generatedAttachments));
|
|
442
609
|
return createdMessages;
|
|
443
610
|
}
|
|
444
|
-
async function
|
|
611
|
+
async function attachGeneratedFilesToPartialMessage(conversation, message, generatedAttachments, replyingAgentId) {
|
|
612
|
+
if (!message || !generatedAttachments || generatedAttachments.length === 0)
|
|
613
|
+
return message;
|
|
614
|
+
const attachments = [];
|
|
615
|
+
for (const generated of generatedAttachments) {
|
|
616
|
+
if (generated.body.length > MAX_ATTACHMENT_BYTES)
|
|
617
|
+
continue;
|
|
618
|
+
const stored = await storage.putFile({
|
|
619
|
+
orgId: conversation.orgId,
|
|
620
|
+
namespace: `chats/${conversation.id}/generated`,
|
|
621
|
+
originalFilename: generated.originalFilename,
|
|
622
|
+
contentType: generated.contentType,
|
|
623
|
+
body: generated.body,
|
|
624
|
+
});
|
|
625
|
+
const attachment = await svc.createAttachment({
|
|
626
|
+
orgId: conversation.orgId,
|
|
627
|
+
conversationId: conversation.id,
|
|
628
|
+
messageId: message.id,
|
|
629
|
+
provider: stored.provider,
|
|
630
|
+
objectKey: stored.objectKey,
|
|
631
|
+
contentType: stored.contentType,
|
|
632
|
+
byteSize: stored.byteSize,
|
|
633
|
+
sha256: stored.sha256,
|
|
634
|
+
originalFilename: stored.originalFilename,
|
|
635
|
+
createdByAgentId: replyingAgentId,
|
|
636
|
+
createdByUserId: null,
|
|
637
|
+
});
|
|
638
|
+
attachments.push(attachment);
|
|
639
|
+
}
|
|
640
|
+
return {
|
|
641
|
+
...message,
|
|
642
|
+
attachments: [...(message.attachments ?? []), ...attachments],
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
async function persistPartialAssistantMessage(conversation, body, status, turnContext, transcript = [], replyingAgentId = chatReplyingAgentId(conversation), existingMessageId) {
|
|
445
646
|
const trimmed = body.trim();
|
|
446
|
-
|
|
647
|
+
const fallbackBody = status === "stopped"
|
|
648
|
+
? "Chat run stopped before a final reply. Continue the conversation to resume from the preserved context."
|
|
649
|
+
: "Chat run failed before a final reply. Continue the conversation to resume from the preserved context.";
|
|
650
|
+
const durableBody = trimmed || (transcript.length > 0 ? fallbackBody : "");
|
|
651
|
+
if (!durableBody)
|
|
447
652
|
return null;
|
|
448
653
|
const chatTurnId = turnContext?.chatTurnId ?? randomUUID();
|
|
449
654
|
const turnVariant = turnContext?.turnVariant ?? 0;
|
|
655
|
+
if (existingMessageId) {
|
|
656
|
+
const updated = await svc.updateMessage(conversation.id, existingMessageId, {
|
|
657
|
+
kind: "message",
|
|
658
|
+
status,
|
|
659
|
+
body: durableBody,
|
|
660
|
+
transcript,
|
|
661
|
+
replyingAgentId,
|
|
662
|
+
});
|
|
663
|
+
if (updated)
|
|
664
|
+
return updated;
|
|
665
|
+
}
|
|
450
666
|
const message = await svc.addMessage(conversation.id, {
|
|
451
667
|
orgId: conversation.orgId,
|
|
452
668
|
role: "assistant",
|
|
453
669
|
kind: "message",
|
|
454
670
|
status,
|
|
455
|
-
body:
|
|
671
|
+
body: durableBody,
|
|
456
672
|
transcript,
|
|
457
673
|
replyingAgentId,
|
|
458
674
|
chatTurnId,
|
|
@@ -473,8 +689,9 @@ export function chatRoutes(db, storage) {
|
|
|
473
689
|
const status = statusParam === "resolved" || statusParam === "archived" || statusParam === "all"
|
|
474
690
|
? statusParam
|
|
475
691
|
: "active";
|
|
692
|
+
const q = typeof req.query.q === "string" ? req.query.q : undefined;
|
|
476
693
|
const userId = req.actor.type === "board" ? (req.actor.userId ?? "local-board") : null;
|
|
477
|
-
const conversations = await svc.list(orgId, { status }, userId);
|
|
694
|
+
const conversations = await svc.list(orgId, { status, q }, userId);
|
|
478
695
|
res.json(await assistantSvc.enrichConversations(conversations));
|
|
479
696
|
});
|
|
480
697
|
router.post("/orgs/:orgId/chats", validate(createChatConversationSchema), async (req, res) => {
|
|
@@ -487,6 +704,13 @@ export function chatRoutes(db, storage) {
|
|
|
487
704
|
}
|
|
488
705
|
const contextLinks = req.body.contextLinks ?? [];
|
|
489
706
|
await assertContextLinksBelongToCompany(orgId, contextLinks);
|
|
707
|
+
if (req.body.preferredAgentId) {
|
|
708
|
+
const agent = await agentsSvc.getById(req.body.preferredAgentId);
|
|
709
|
+
if (!agent || agent.orgId !== orgId) {
|
|
710
|
+
res.status(422).json({ error: "Preferred agent must belong to the same organization" });
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
}
|
|
490
714
|
const actor = getActorInfo(req);
|
|
491
715
|
const conversation = await svc.create(orgId, {
|
|
492
716
|
title: req.body.title,
|
|
@@ -578,6 +802,9 @@ export function chatRoutes(db, storage) {
|
|
|
578
802
|
res.status(404).json({ error: "Chat conversation not found" });
|
|
579
803
|
return;
|
|
580
804
|
}
|
|
805
|
+
if (!hasActiveChatGeneration(conversation.id)) {
|
|
806
|
+
await svc.markInterruptedStreamingMessages(conversation.id);
|
|
807
|
+
}
|
|
581
808
|
const messages = await svc.listMessages(conversation.id);
|
|
582
809
|
res.json(messages);
|
|
583
810
|
});
|
|
@@ -627,6 +854,7 @@ export function chatRoutes(db, storage) {
|
|
|
627
854
|
const assistantInput = await loadAssistantInput(conversation, actor);
|
|
628
855
|
const transcript = [];
|
|
629
856
|
const observedTranscript = [];
|
|
857
|
+
let modelTurnInput;
|
|
630
858
|
let fallbackOutput = null;
|
|
631
859
|
let finalChatOutput = null;
|
|
632
860
|
let finalChatStatus = "completed";
|
|
@@ -634,6 +862,7 @@ export function chatRoutes(db, storage) {
|
|
|
634
862
|
const streamed = await assistantSvc.streamChatAssistantReply({
|
|
635
863
|
...assistantInput,
|
|
636
864
|
onInvocationMeta: async (meta) => {
|
|
865
|
+
modelTurnInput = modelTurnInputFromInvocationMeta(meta);
|
|
637
866
|
currentChatTraceInput = buildChatTraceInput(traceInputBase, meta);
|
|
638
867
|
mergeChatInvocationTraceMetadata(chatObservation, meta);
|
|
639
868
|
updateExecutionObservation(observation, chatObservation, {
|
|
@@ -653,7 +882,7 @@ export function chatRoutes(db, storage) {
|
|
|
653
882
|
finalChatStatus = "failed";
|
|
654
883
|
throw new Error("Chat assistant reply was stopped before completion");
|
|
655
884
|
}
|
|
656
|
-
const created = await persistAssistantReply(assistantInput.conversation, actor, streamed.reply, turnContext, transcript, streamed.replyingAgentId);
|
|
885
|
+
const created = await persistAssistantReply(req, assistantInput.conversation, actor, streamed.reply, turnContext, transcript, streamed.replyingAgentId);
|
|
657
886
|
finalChatOutput = streamed.reply.body;
|
|
658
887
|
await logChatMessagesAdded(assistantInput.conversation, created, {
|
|
659
888
|
actorType: "system",
|
|
@@ -684,6 +913,7 @@ export function chatRoutes(db, storage) {
|
|
|
684
913
|
context: chatObservation,
|
|
685
914
|
parentObservation: observation,
|
|
686
915
|
transcript: observedTranscript,
|
|
916
|
+
initialTurnInput: modelTurnInput,
|
|
687
917
|
fallbackResult: fallbackOutput
|
|
688
918
|
? {
|
|
689
919
|
output: fallbackOutput,
|
|
@@ -728,6 +958,9 @@ export function chatRoutes(db, storage) {
|
|
|
728
958
|
});
|
|
729
959
|
}
|
|
730
960
|
logger.warn({ err, conversationId: conversation.id }, "chat assistant reply failed");
|
|
961
|
+
if (err instanceof HttpError) {
|
|
962
|
+
throw err;
|
|
963
|
+
}
|
|
731
964
|
res.status(502).json({
|
|
732
965
|
error: err instanceof Error ? err.message : "Chat assistant failed to respond",
|
|
733
966
|
});
|
|
@@ -786,6 +1019,42 @@ export function chatRoutes(db, storage) {
|
|
|
786
1019
|
let chatObservation = null;
|
|
787
1020
|
const transcript = [];
|
|
788
1021
|
const observedTranscript = [];
|
|
1022
|
+
let modelTurnInput;
|
|
1023
|
+
let assistantProgressMessage = null;
|
|
1024
|
+
let assistantProgressMessageId = null;
|
|
1025
|
+
let assistantDraftBody = "";
|
|
1026
|
+
const persistStreamProgress = async (progressConversation, replyingAgentId = chatReplyingAgentId(progressConversation)) => {
|
|
1027
|
+
if (!turnContextForPartial)
|
|
1028
|
+
return null;
|
|
1029
|
+
const input = {
|
|
1030
|
+
kind: "message",
|
|
1031
|
+
status: "streaming",
|
|
1032
|
+
body: assistantDraftBody,
|
|
1033
|
+
transcript,
|
|
1034
|
+
replyingAgentId,
|
|
1035
|
+
};
|
|
1036
|
+
if (assistantProgressMessage) {
|
|
1037
|
+
const updated = await svc.updateMessage(progressConversation.id, assistantProgressMessage.id, input);
|
|
1038
|
+
if (updated) {
|
|
1039
|
+
assistantProgressMessage = updated;
|
|
1040
|
+
assistantProgressMessageId = assistantProgressMessage.id;
|
|
1041
|
+
return assistantProgressMessage;
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
assistantProgressMessage = await svc.addMessage(progressConversation.id, {
|
|
1045
|
+
orgId: progressConversation.orgId,
|
|
1046
|
+
role: "assistant",
|
|
1047
|
+
kind: "message",
|
|
1048
|
+
status: "streaming",
|
|
1049
|
+
body: assistantDraftBody,
|
|
1050
|
+
transcript,
|
|
1051
|
+
replyingAgentId,
|
|
1052
|
+
chatTurnId: turnContextForPartial.chatTurnId,
|
|
1053
|
+
turnVariant: turnContextForPartial.turnVariant,
|
|
1054
|
+
});
|
|
1055
|
+
assistantProgressMessageId = assistantProgressMessage.id;
|
|
1056
|
+
return assistantProgressMessage;
|
|
1057
|
+
};
|
|
789
1058
|
let clientClosed = false;
|
|
790
1059
|
const handleClosed = () => {
|
|
791
1060
|
if (clientClosed || res.writableEnded)
|
|
@@ -843,6 +1112,7 @@ export function chatRoutes(db, storage) {
|
|
|
843
1112
|
...assistantInput,
|
|
844
1113
|
abortSignal: abortController.signal,
|
|
845
1114
|
onInvocationMeta: async (meta) => {
|
|
1115
|
+
modelTurnInput = modelTurnInputFromInvocationMeta(meta);
|
|
846
1116
|
currentChatTraceInput = buildChatTraceInput(traceInputBase, meta);
|
|
847
1117
|
mergeChatInvocationTraceMetadata(chatObservation, meta);
|
|
848
1118
|
updateExecutionObservation(observation, chatObservation, {
|
|
@@ -851,12 +1121,17 @@ export function chatRoutes(db, storage) {
|
|
|
851
1121
|
updateExecutionTraceIO(observation, { input: currentChatTraceInput });
|
|
852
1122
|
},
|
|
853
1123
|
onAssistantDelta: async (delta) => {
|
|
1124
|
+
assistantDraftBody = `${assistantDraftBody}${delta}`;
|
|
1125
|
+
await persistStreamProgress(assistantInput.conversation);
|
|
1126
|
+
if (clientClosed)
|
|
1127
|
+
return;
|
|
854
1128
|
writeStreamEvent(res, {
|
|
855
1129
|
type: "assistant_delta",
|
|
856
1130
|
delta,
|
|
857
1131
|
});
|
|
858
1132
|
},
|
|
859
1133
|
onAssistantState: async (state) => {
|
|
1134
|
+
await persistStreamProgress(assistantInput.conversation);
|
|
860
1135
|
if (clientClosed)
|
|
861
1136
|
return;
|
|
862
1137
|
writeStreamEvent(res, {
|
|
@@ -866,6 +1141,7 @@ export function chatRoutes(db, storage) {
|
|
|
866
1141
|
},
|
|
867
1142
|
onTranscriptEntry: async (entry) => {
|
|
868
1143
|
transcript.push(entry);
|
|
1144
|
+
await persistStreamProgress(assistantInput.conversation);
|
|
869
1145
|
if (clientClosed)
|
|
870
1146
|
return;
|
|
871
1147
|
writeStreamEvent(res, {
|
|
@@ -880,7 +1156,7 @@ export function chatRoutes(db, storage) {
|
|
|
880
1156
|
if (streamed.outcome === "stopped") {
|
|
881
1157
|
finalChatStatus = "stopped";
|
|
882
1158
|
finalChatOutput = streamed.partialBody;
|
|
883
|
-
const stoppedMessage = await persistPartialAssistantMessage(assistantInput.conversation, streamed.partialBody, "stopped", turnContextForPartial, transcript, streamed.replyingAgentId);
|
|
1159
|
+
const stoppedMessage = await persistPartialAssistantMessage(assistantInput.conversation, streamed.partialBody, "stopped", turnContextForPartial, transcript, streamed.replyingAgentId, assistantProgressMessageId);
|
|
884
1160
|
if (stoppedMessage) {
|
|
885
1161
|
await logChatMessagesAdded(assistantInput.conversation, [stoppedMessage], {
|
|
886
1162
|
actorType: "system",
|
|
@@ -906,7 +1182,7 @@ export function chatRoutes(db, storage) {
|
|
|
906
1182
|
}
|
|
907
1183
|
return;
|
|
908
1184
|
}
|
|
909
|
-
const createdMessages = await persistAssistantReply(assistantInput.conversation, actor, streamed.reply, turnContextForPartial, transcript, streamed.replyingAgentId);
|
|
1185
|
+
const createdMessages = await persistAssistantReply(req, assistantInput.conversation, actor, streamed.reply, turnContextForPartial, transcript, streamed.replyingAgentId, assistantProgressMessageId);
|
|
910
1186
|
finalChatOutput = streamed.reply.body;
|
|
911
1187
|
await logChatMessagesAdded(assistantInput.conversation, createdMessages, {
|
|
912
1188
|
actorType: "system",
|
|
@@ -942,6 +1218,14 @@ export function chatRoutes(db, storage) {
|
|
|
942
1218
|
context: chatObservation,
|
|
943
1219
|
parentObservation: observation,
|
|
944
1220
|
transcript: observedTranscript,
|
|
1221
|
+
initialTurnInput: modelTurnInput,
|
|
1222
|
+
fallbackResult: finalChatOutput
|
|
1223
|
+
? {
|
|
1224
|
+
output: finalChatOutput,
|
|
1225
|
+
subtype: finalChatStatus,
|
|
1226
|
+
isError: finalChatStatus === "failed",
|
|
1227
|
+
}
|
|
1228
|
+
: null,
|
|
945
1229
|
});
|
|
946
1230
|
finalChatOutput = finalChatOutput ?? transcriptStats.finalOutput ?? null;
|
|
947
1231
|
}
|
|
@@ -969,8 +1253,10 @@ export function chatRoutes(db, storage) {
|
|
|
969
1253
|
}
|
|
970
1254
|
catch (err) {
|
|
971
1255
|
const partialBody = err instanceof ChatAssistantStreamError ? err.partialBody : "";
|
|
1256
|
+
const generatedAttachments = err instanceof ChatAssistantStreamError ? err.generatedAttachments : [];
|
|
972
1257
|
const failedReplyingAgentId = chatReplyingAgentId(assistantConversationForPartial);
|
|
973
|
-
|
|
1258
|
+
let failedMessage = await persistPartialAssistantMessage(assistantConversationForPartial ?? conversation, partialBody, "failed", turnContextForPartial, transcript, failedReplyingAgentId, assistantProgressMessageId).catch(() => null);
|
|
1259
|
+
failedMessage = await attachGeneratedFilesToPartialMessage(assistantConversationForPartial ?? conversation, failedMessage, generatedAttachments, failedReplyingAgentId).catch(() => failedMessage);
|
|
974
1260
|
if (failedMessage && assistantConversationForPartial) {
|
|
975
1261
|
await logChatMessagesAdded(assistantConversationForPartial, [failedMessage], {
|
|
976
1262
|
actorType: "system",
|
|
@@ -1168,6 +1454,10 @@ export function chatRoutes(db, storage) {
|
|
|
1168
1454
|
return;
|
|
1169
1455
|
}
|
|
1170
1456
|
}
|
|
1457
|
+
await assertCanConvertIssueProposal(req, conversation, {
|
|
1458
|
+
messageId: req.body.messageId ?? null,
|
|
1459
|
+
proposal: req.body.proposal ?? null,
|
|
1460
|
+
});
|
|
1171
1461
|
const chatObservation = buildChatObservabilityContext(conversation, {
|
|
1172
1462
|
rootExecutionId: req.body.messageId ?? `chat-convert:${conversation.id}`,
|
|
1173
1463
|
trigger: "convert_to_issue",
|