agent-tower 0.4.15 → 0.4.16-beta.3
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/app.test.js +2 -0
- package/dist/app.test.js.map +1 -1
- package/dist/core/event-bus.d.ts +2 -0
- package/dist/core/event-bus.d.ts.map +1 -1
- package/dist/core/event-bus.js.map +1 -1
- package/dist/executors/__tests__/codex.executor.test.d.ts +2 -0
- package/dist/executors/__tests__/codex.executor.test.d.ts.map +1 -0
- package/dist/executors/__tests__/codex.executor.test.js +28 -0
- package/dist/executors/__tests__/codex.executor.test.js.map +1 -0
- package/dist/executors/codex.executor.d.ts +1 -0
- package/dist/executors/codex.executor.d.ts.map +1 -1
- package/dist/executors/codex.executor.js +19 -1
- package/dist/executors/codex.executor.js.map +1 -1
- package/dist/git/git-cli.d.ts +18 -1
- package/dist/git/git-cli.d.ts.map +1 -1
- package/dist/git/git-cli.js +17 -1
- package/dist/git/git-cli.js.map +1 -1
- package/dist/git/worktree.manager.d.ts +29 -2
- package/dist/git/worktree.manager.d.ts.map +1 -1
- package/dist/git/worktree.manager.js +137 -16
- package/dist/git/worktree.manager.js.map +1 -1
- package/dist/git/worktree.manager.test.d.ts +2 -0
- package/dist/git/worktree.manager.test.d.ts.map +1 -0
- package/dist/git/worktree.manager.test.js +104 -0
- package/dist/git/worktree.manager.test.js.map +1 -0
- package/dist/mcp/context.d.ts +3 -0
- package/dist/mcp/context.d.ts.map +1 -1
- package/dist/mcp/context.js +10 -1
- package/dist/mcp/context.js.map +1 -1
- package/dist/mcp/http-client.d.ts +24 -1
- package/dist/mcp/http-client.d.ts.map +1 -1
- package/dist/mcp/http-client.js +37 -3
- package/dist/mcp/http-client.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +190 -0
- package/dist/mcp/server.js.map +1 -1
- package/dist/middleware/tunnel-auth.d.ts.map +1 -1
- package/dist/middleware/tunnel-auth.js +2 -0
- package/dist/middleware/tunnel-auth.js.map +1 -1
- package/dist/output/__tests__/codex-parser.test.d.ts +2 -0
- package/dist/output/__tests__/codex-parser.test.d.ts.map +1 -0
- package/dist/output/__tests__/codex-parser.test.js +148 -0
- package/dist/output/__tests__/codex-parser.test.js.map +1 -0
- package/dist/output/codex-parser.d.ts +12 -0
- package/dist/output/codex-parser.d.ts.map +1 -1
- package/dist/output/codex-parser.js +129 -12
- package/dist/output/codex-parser.js.map +1 -1
- package/dist/routes/__tests__/attachments.test.d.ts +2 -0
- package/dist/routes/__tests__/attachments.test.d.ts.map +1 -0
- package/dist/routes/__tests__/attachments.test.js +86 -0
- package/dist/routes/__tests__/attachments.test.js.map +1 -0
- package/dist/routes/__tests__/filesystem.test.d.ts +2 -0
- package/dist/routes/__tests__/filesystem.test.d.ts.map +1 -0
- package/dist/routes/__tests__/filesystem.test.js +80 -0
- package/dist/routes/__tests__/filesystem.test.js.map +1 -0
- package/dist/routes/__tests__/previews.test.d.ts +2 -0
- package/dist/routes/__tests__/previews.test.d.ts.map +1 -0
- package/dist/routes/__tests__/previews.test.js +89 -0
- package/dist/routes/__tests__/previews.test.js.map +1 -0
- package/dist/routes/__tests__/tasks.test.d.ts +2 -0
- package/dist/routes/__tests__/tasks.test.d.ts.map +1 -0
- package/dist/routes/__tests__/tasks.test.js +72 -0
- package/dist/routes/__tests__/tasks.test.js.map +1 -0
- package/dist/routes/attachments.d.ts.map +1 -1
- package/dist/routes/attachments.js +36 -16
- package/dist/routes/attachments.js.map +1 -1
- package/dist/routes/filesystem.d.ts.map +1 -1
- package/dist/routes/filesystem.js +24 -3
- package/dist/routes/filesystem.js.map +1 -1
- package/dist/routes/index.d.ts.map +1 -1
- package/dist/routes/index.js +6 -0
- package/dist/routes/index.js.map +1 -1
- package/dist/routes/previews.d.ts +6 -0
- package/dist/routes/previews.d.ts.map +1 -0
- package/dist/routes/previews.js +413 -0
- package/dist/routes/previews.js.map +1 -0
- package/dist/routes/projects.d.ts.map +1 -1
- package/dist/routes/projects.js +1 -0
- package/dist/routes/projects.js.map +1 -1
- package/dist/routes/system.d.ts.map +1 -1
- package/dist/routes/system.js +35 -1
- package/dist/routes/system.js.map +1 -1
- package/dist/routes/tasks.js +2 -2
- package/dist/routes/tasks.js.map +1 -1
- package/dist/routes/team-runs.d.ts +11 -0
- package/dist/routes/team-runs.d.ts.map +1 -0
- package/dist/routes/team-runs.js +309 -0
- package/dist/routes/team-runs.js.map +1 -0
- package/dist/routes/tunnel.d.ts.map +1 -1
- package/dist/routes/tunnel.js +20 -0
- package/dist/routes/tunnel.js.map +1 -1
- package/dist/routes/workspaces.d.ts.map +1 -1
- package/dist/routes/workspaces.js +15 -1
- package/dist/routes/workspaces.js.map +1 -1
- package/dist/services/__tests__/preview.service.test.d.ts +2 -0
- package/dist/services/__tests__/preview.service.test.d.ts.map +1 -0
- package/dist/services/__tests__/preview.service.test.js +29 -0
- package/dist/services/__tests__/preview.service.test.js.map +1 -0
- package/dist/services/__tests__/session-manager.team-run.test.d.ts +2 -0
- package/dist/services/__tests__/session-manager.team-run.test.d.ts.map +1 -0
- package/dist/services/__tests__/session-manager.team-run.test.js +286 -0
- package/dist/services/__tests__/session-manager.team-run.test.js.map +1 -0
- package/dist/services/__tests__/task.service.test.d.ts +2 -0
- package/dist/services/__tests__/task.service.test.d.ts.map +1 -0
- package/dist/services/__tests__/task.service.test.js +65 -0
- package/dist/services/__tests__/task.service.test.js.map +1 -0
- package/dist/services/__tests__/team-lock.service.test.d.ts +2 -0
- package/dist/services/__tests__/team-lock.service.test.d.ts.map +1 -0
- package/dist/services/__tests__/team-lock.service.test.js +81 -0
- package/dist/services/__tests__/team-lock.service.test.js.map +1 -0
- package/dist/services/__tests__/team-reconciler.service.test.d.ts +2 -0
- package/dist/services/__tests__/team-reconciler.service.test.d.ts.map +1 -0
- package/dist/services/__tests__/team-reconciler.service.test.js +1536 -0
- package/dist/services/__tests__/team-reconciler.service.test.js.map +1 -0
- package/dist/services/__tests__/team-run.service.test.d.ts +2 -0
- package/dist/services/__tests__/team-run.service.test.d.ts.map +1 -0
- package/dist/services/__tests__/team-run.service.test.js +699 -0
- package/dist/services/__tests__/team-run.service.test.js.map +1 -0
- package/dist/services/__tests__/team-scheduler.service.test.d.ts +2 -0
- package/dist/services/__tests__/team-scheduler.service.test.d.ts.map +1 -0
- package/dist/services/__tests__/team-scheduler.service.test.js +1688 -0
- package/dist/services/__tests__/team-scheduler.service.test.js.map +1 -0
- package/dist/services/__tests__/tunnel.service.test.d.ts +2 -0
- package/dist/services/__tests__/tunnel.service.test.d.ts.map +1 -0
- package/dist/services/__tests__/tunnel.service.test.js +138 -0
- package/dist/services/__tests__/tunnel.service.test.js.map +1 -0
- package/dist/services/__tests__/workspace.service.test.d.ts +2 -0
- package/dist/services/__tests__/workspace.service.test.d.ts.map +1 -0
- package/dist/services/__tests__/workspace.service.test.js +695 -0
- package/dist/services/__tests__/workspace.service.test.js.map +1 -0
- package/dist/services/attachment-context.d.ts +3 -0
- package/dist/services/attachment-context.d.ts.map +1 -0
- package/dist/services/attachment-context.js +34 -0
- package/dist/services/attachment-context.js.map +1 -0
- package/dist/services/preview.service.d.ts +19 -0
- package/dist/services/preview.service.d.ts.map +1 -0
- package/dist/services/preview.service.js +147 -0
- package/dist/services/preview.service.js.map +1 -0
- package/dist/services/project.service.d.ts +2 -0
- package/dist/services/project.service.d.ts.map +1 -1
- package/dist/services/project.service.js +87 -18
- package/dist/services/project.service.js.map +1 -1
- package/dist/services/session-manager.d.ts +43 -1
- package/dist/services/session-manager.d.ts.map +1 -1
- package/dist/services/session-manager.js +110 -2
- package/dist/services/session-manager.js.map +1 -1
- package/dist/services/task.service.d.ts +6 -0
- package/dist/services/task.service.d.ts.map +1 -1
- package/dist/services/task.service.js +15 -3
- package/dist/services/task.service.js.map +1 -1
- package/dist/services/team-lock.service.d.ts +25 -0
- package/dist/services/team-lock.service.d.ts.map +1 -0
- package/dist/services/team-lock.service.js +56 -0
- package/dist/services/team-lock.service.js.map +1 -0
- package/dist/services/team-reconciler.service.d.ts +44 -0
- package/dist/services/team-reconciler.service.d.ts.map +1 -0
- package/dist/services/team-reconciler.service.js +286 -0
- package/dist/services/team-reconciler.service.js.map +1 -0
- package/dist/services/team-run-events.d.ts +13 -0
- package/dist/services/team-run-events.d.ts.map +1 -0
- package/dist/services/team-run-events.js +27 -0
- package/dist/services/team-run-events.js.map +1 -0
- package/dist/services/team-run.service.d.ts +92 -0
- package/dist/services/team-run.service.d.ts.map +1 -0
- package/dist/services/team-run.service.js +835 -0
- package/dist/services/team-run.service.js.map +1 -0
- package/dist/services/team-scheduler.service.d.ts +104 -0
- package/dist/services/team-scheduler.service.d.ts.map +1 -0
- package/dist/services/team-scheduler.service.js +843 -0
- package/dist/services/team-scheduler.service.js.map +1 -0
- package/dist/services/tunnel.service.d.ts +31 -5
- package/dist/services/tunnel.service.d.ts.map +1 -1
- package/dist/services/tunnel.service.js +293 -32
- package/dist/services/tunnel.service.js.map +1 -1
- package/dist/services/workspace.service.d.ts +161 -7
- package/dist/services/workspace.service.d.ts.map +1 -1
- package/dist/services/workspace.service.js +396 -51
- package/dist/services/workspace.service.js.map +1 -1
- package/dist/socket/events.d.ts +1 -1
- package/dist/socket/events.d.ts.map +1 -1
- package/dist/socket/events.js.map +1 -1
- package/dist/socket/socket-gateway.d.ts.map +1 -1
- package/dist/socket/socket-gateway.js +5 -1
- package/dist/socket/socket-gateway.js.map +1 -1
- package/dist/web/assets/AgentDemoPage-Bf6labVB.js +1 -0
- package/dist/web/assets/{DemoPage-XwuS8vNB.js → DemoPage-DlfG47rV.js} +3 -3
- package/dist/web/assets/{GeneralSettingsPage-CliIgpwf.js → GeneralSettingsPage-DefqwzVn.js} +1 -1
- package/dist/web/assets/MemberAvatar-DVw_TedB.js +1 -0
- package/dist/web/assets/NotificationSettingsPage-C9h1U1Za.js +1 -0
- package/dist/web/assets/{ProfileSettingsPage-CkU_kZKG.js → ProfileSettingsPage-BkZE2yVP.js} +1 -1
- package/dist/web/assets/ProjectKanbanPage-B1Ckl1uY.js +89 -0
- package/dist/web/assets/ProjectSettingsPage-ByZ13awb.js +2 -0
- package/dist/web/assets/{ProviderSettingsPage-CfvdeoEU.js → ProviderSettingsPage-DSQYe8B6.js} +12 -12
- package/dist/web/assets/TeamSettingsPage-DUukJ_Ih.js +1 -0
- package/dist/web/assets/agent-tower-logo-COx9gy77.png +0 -0
- package/dist/web/assets/{button-BWFTEdOr.js → button-Bpm98eOV.js} +1 -1
- package/dist/web/assets/{chevron-down-CuPdBAx-.js → chevron-down-DSKKXCi8.js} +1 -1
- package/dist/web/assets/{chevron-right-Cs8vYTMn.js → chevron-right-CZdDV9GU.js} +1 -1
- package/dist/web/assets/chevron-up-gnnlwvYe.js +1 -0
- package/dist/web/assets/{circle-check-BXZTzqw0.js → circle-check-DeD_VuLK.js} +1 -1
- package/dist/web/assets/{code-block-OCS4YCEC-BxUpvXK_.js → code-block-OCS4YCEC-BrGjkdjS.js} +1 -1
- package/dist/web/assets/{confirm-dialog-CDLHRthd.js → confirm-dialog-CEVVvAcE.js} +1 -1
- package/dist/web/assets/folder-picker-ZBQlFEWL.js +1 -0
- package/dist/web/assets/index-B5g4V0NU.js +13 -0
- package/dist/web/assets/index-ltjI8o6A.css +1 -0
- package/dist/web/assets/loader-circle-GMfBClX0.js +1 -0
- package/dist/web/assets/{log-adapter-CeKrvZcz.js → log-adapter-DKKM3sxS.js} +1 -1
- package/dist/web/assets/{mermaid-NOHMQCX5-BOSwJqP0.js → mermaid-NOHMQCX5-D5USvUiZ.js} +44 -44
- package/dist/web/assets/modal-JMpuh-LG.js +1 -0
- package/dist/web/assets/{pencil-BMxBxIhw.js → pencil-QrCW47nn.js} +1 -1
- package/dist/web/assets/{select-BUmRG0LY.js → select-CINRzLiE.js} +1 -1
- package/dist/web/assets/upload-vFxZxKHo.js +1 -0
- package/dist/web/assets/{use-profiles-C1vlPE-2.js → use-profiles-SrVWPYv0.js} +1 -1
- package/dist/web/assets/{use-providers-Cdxr4Jbz.js → use-providers-BihMydl0.js} +1 -1
- package/dist/web/avatars/presets/avatar-preset-01-developer.png +0 -0
- package/dist/web/avatars/presets/avatar-preset-02-architect.png +0 -0
- package/dist/web/avatars/presets/avatar-preset-03-tester.png +0 -0
- package/dist/web/avatars/presets/avatar-preset-04-devops.png +0 -0
- package/dist/web/avatars/presets/avatar-preset-05-data-scientist.png +0 -0
- package/dist/web/avatars/presets/avatar-preset-06-frontend.png +0 -0
- package/dist/web/avatars/presets/avatar-preset-07-backend.png +0 -0
- package/dist/web/avatars/presets/avatar-preset-08-security.png +0 -0
- package/dist/web/avatars/presets/avatar-preset-09-project-manager.png +0 -0
- package/dist/web/avatars/presets/avatar-preset-10-product-manager.png +0 -0
- package/dist/web/avatars/presets/avatar-preset-11-scrum-master.png +0 -0
- package/dist/web/avatars/presets/avatar-preset-12-tech-lead.png +0 -0
- package/dist/web/avatars/presets/avatar-preset-13-coordinator.png +0 -0
- package/dist/web/avatars/presets/avatar-preset-14-mentor.png +0 -0
- package/dist/web/avatars/presets/avatar-preset-15-reviewer.png +0 -0
- package/dist/web/avatars/presets/avatar-preset-16-ui-designer.png +0 -0
- package/dist/web/avatars/presets/avatar-preset-17-ux-researcher.png +0 -0
- package/dist/web/avatars/presets/avatar-preset-18-documenter.png +0 -0
- package/dist/web/avatars/presets/avatar-preset-19-translator.png +0 -0
- package/dist/web/avatars/presets/avatar-preset-20-analyst.png +0 -0
- package/dist/web/avatars/presets/avatar-preset-21-consultant.png +0 -0
- package/dist/web/avatars/presets/avatar-preset-22-creative-director.png +0 -0
- package/dist/web/avatars/presets/avatar-preset-23-support.png +0 -0
- package/dist/web/avatars/presets/avatar-preset-24-assistant.png +0 -0
- package/dist/web/avatars/presets/avatar-preset-25-robot.png +0 -0
- package/dist/web/avatars/presets/avatar-preset-grid.png +0 -0
- package/dist/web/index.html +2 -2
- package/node_modules/@agent-tower/shared/dist/socket/events.d.ts +10 -0
- package/node_modules/@agent-tower/shared/dist/socket/events.d.ts.map +1 -1
- package/node_modules/@agent-tower/shared/dist/socket/events.js +1 -0
- package/node_modules/@agent-tower/shared/dist/socket/events.js.map +1 -1
- package/node_modules/@agent-tower/shared/dist/types.d.ts +161 -0
- package/node_modules/@agent-tower/shared/dist/types.d.ts.map +1 -1
- package/node_modules/@agent-tower/shared/dist/types.js.map +1 -1
- package/node_modules/@prisma/client/.prisma/client/default.d.ts +1 -0
- package/node_modules/@prisma/client/.prisma/client/default.js +1 -0
- package/node_modules/@prisma/client/.prisma/client/edge.d.ts +1 -0
- package/node_modules/@prisma/client/.prisma/client/edge.js +396 -0
- package/node_modules/@prisma/client/.prisma/client/index-browser.js +385 -0
- package/node_modules/@prisma/client/.prisma/client/index.d.ts +26996 -0
- package/node_modules/@prisma/client/.prisma/client/index.js +421 -0
- package/node_modules/@prisma/client/.prisma/client/libquery_engine-darwin-arm64.dylib.node +0 -0
- package/node_modules/@prisma/client/.prisma/client/package.json +97 -0
- package/node_modules/@prisma/client/.prisma/client/query_engine-windows.dll.node +0 -0
- package/node_modules/@prisma/client/.prisma/client/schema.prisma +296 -0
- package/node_modules/@prisma/client/.prisma/client/wasm.d.ts +1 -0
- package/node_modules/@prisma/client/.prisma/client/wasm.js +385 -0
- package/node_modules/@prisma/client/package.json +3 -2
- package/package.json +2 -1
- package/prisma/migrations/20260515000000_add_workspace_preview_target/migration.sql +2 -0
- package/prisma/migrations/20260518000000_add_team_run_collaboration/migration.sql +150 -0
- package/prisma/migrations/20260522000000_add_team_member_session_policy/migration.sql +2 -0
- package/prisma/migrations/20260526000000_add_team_run_main_and_dedicated_workspaces/migration.sql +21 -0
- package/prisma/schema.prisma +147 -1
- package/dist/web/assets/AgentDemoPage-ClnGPAV9.js +0 -1
- package/dist/web/assets/NotificationSettingsPage-y3vhVgPv.js +0 -1
- package/dist/web/assets/ProjectKanbanPage-BddzfZRV.js +0 -87
- package/dist/web/assets/ProjectSettingsPage-B6xhbziO.js +0 -2
- package/dist/web/assets/circle-alert-EUyZcWhp.js +0 -1
- package/dist/web/assets/folder-picker-CUbhsnhi.js +0 -1
- package/dist/web/assets/index-BGvfX18x.css +0 -1
- package/dist/web/assets/index-CHN8jahE.js +0 -13
- package/dist/web/assets/loader-circle-BHzDVpxt.js +0 -1
- package/dist/web/assets/modal-D_AU4URz.js +0 -1
- package/dist/web/assets/use-projects-Bcd5hIOY.js +0 -1
|
@@ -0,0 +1,835 @@
|
|
|
1
|
+
import { ServiceError, NotFoundError, ValidationError } from '../errors.js';
|
|
2
|
+
import { prisma } from '../utils/index.js';
|
|
3
|
+
import { appendAttachmentMarkdownContext } from './attachment-context.js';
|
|
4
|
+
import { emitTeamRunInvalidated } from './team-run-events.js';
|
|
5
|
+
const DEFAULT_CAPABILITIES = {
|
|
6
|
+
readRoom: false,
|
|
7
|
+
postRoomMessage: false,
|
|
8
|
+
mentionMembers: false,
|
|
9
|
+
stopMemberWork: false,
|
|
10
|
+
markReadyForReview: false,
|
|
11
|
+
readFiles: false,
|
|
12
|
+
writeFiles: false,
|
|
13
|
+
runCommands: false,
|
|
14
|
+
readDiff: false,
|
|
15
|
+
mergeWorkspace: false,
|
|
16
|
+
};
|
|
17
|
+
const DEFAULT_SESSION_POLICY = 'new_per_request';
|
|
18
|
+
const ACTIVE_INVOCATION_STATUSES = [
|
|
19
|
+
'QUEUED',
|
|
20
|
+
'RUNNING',
|
|
21
|
+
'SESSION_ENDED',
|
|
22
|
+
'WAITING_ROOM_REPLY',
|
|
23
|
+
];
|
|
24
|
+
const OPEN_WORK_REQUEST_STATUSES = [
|
|
25
|
+
'PENDING_APPROVAL',
|
|
26
|
+
'QUEUED',
|
|
27
|
+
];
|
|
28
|
+
function parseJsonField(value, fallback) {
|
|
29
|
+
if (value == null || value === '') {
|
|
30
|
+
return fallback;
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
return JSON.parse(value);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return fallback;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function stringifyJson(value) {
|
|
40
|
+
return JSON.stringify(value);
|
|
41
|
+
}
|
|
42
|
+
function toIso(date) {
|
|
43
|
+
return date.toISOString();
|
|
44
|
+
}
|
|
45
|
+
function applyStableInstanceNames(snapshots) {
|
|
46
|
+
const totalsByName = new Map();
|
|
47
|
+
for (const snapshot of snapshots) {
|
|
48
|
+
totalsByName.set(snapshot.name, (totalsByName.get(snapshot.name) ?? 0) + 1);
|
|
49
|
+
}
|
|
50
|
+
const seenByName = new Map();
|
|
51
|
+
return snapshots.map((snapshot) => {
|
|
52
|
+
const total = totalsByName.get(snapshot.name) ?? 0;
|
|
53
|
+
if (total <= 1) {
|
|
54
|
+
return snapshot;
|
|
55
|
+
}
|
|
56
|
+
const instanceIndex = (seenByName.get(snapshot.name) ?? 0) + 1;
|
|
57
|
+
seenByName.set(snapshot.name, instanceIndex);
|
|
58
|
+
return {
|
|
59
|
+
...snapshot,
|
|
60
|
+
name: `${snapshot.name} #${instanceIndex}`,
|
|
61
|
+
};
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
function toConflict(message) {
|
|
65
|
+
return new ServiceError(message, 'CONFLICT', 409);
|
|
66
|
+
}
|
|
67
|
+
function isUniqueConstraintError(error) {
|
|
68
|
+
return typeof error === 'object'
|
|
69
|
+
&& error !== null
|
|
70
|
+
&& 'code' in error
|
|
71
|
+
&& error.code === 'P2002';
|
|
72
|
+
}
|
|
73
|
+
function buildInitialTaskRoomMessageContent(task) {
|
|
74
|
+
const title = task.title.trim();
|
|
75
|
+
if (title.length === 0) {
|
|
76
|
+
throw new ValidationError('Task title is required to create a TeamRun');
|
|
77
|
+
}
|
|
78
|
+
const description = task.description?.trim() ?? '';
|
|
79
|
+
return [title, description].filter(Boolean).join('\n\n');
|
|
80
|
+
}
|
|
81
|
+
function extractMentionTokens(content) {
|
|
82
|
+
const tokens = [];
|
|
83
|
+
const tokenPattern = /(^|[^\p{L}\p{N}_-])@([^\s@]+)/gu;
|
|
84
|
+
const trailingPunctuation = /[.,!?;:,。!?;:、)\]}>"'”’)】》]+$/u;
|
|
85
|
+
for (const match of content.matchAll(tokenPattern)) {
|
|
86
|
+
const token = (match[2] ?? '').replace(trailingPunctuation, '');
|
|
87
|
+
if (token.length > 0) {
|
|
88
|
+
tokens.push(token);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return tokens;
|
|
92
|
+
}
|
|
93
|
+
function deriveStructuredMentionsFromContent(content, members) {
|
|
94
|
+
const matchesByToken = new Map();
|
|
95
|
+
for (const member of members) {
|
|
96
|
+
const labels = Array.from(new Set([
|
|
97
|
+
member.name,
|
|
98
|
+
...parseJsonField(member.aliases, []),
|
|
99
|
+
].map((label) => label.trim()).filter(Boolean)));
|
|
100
|
+
for (const label of labels) {
|
|
101
|
+
const matches = matchesByToken.get(label) ?? [];
|
|
102
|
+
if (!matches.some((match) => match.memberId === member.id)) {
|
|
103
|
+
matches.push({ memberId: member.id, label });
|
|
104
|
+
}
|
|
105
|
+
matchesByToken.set(label, matches);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const mentions = [];
|
|
109
|
+
const mentionedMemberIds = new Set();
|
|
110
|
+
const tokens = extractMentionTokens(content);
|
|
111
|
+
for (const token of tokens) {
|
|
112
|
+
const matches = matchesByToken.get(token) ?? [];
|
|
113
|
+
if (matches.length !== 1 || mentionedMemberIds.has(matches[0].memberId)) {
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
const match = matches[0];
|
|
117
|
+
mentions.push({ memberId: match.memberId, label: match.label });
|
|
118
|
+
mentionedMemberIds.add(match.memberId);
|
|
119
|
+
}
|
|
120
|
+
return { mentions, tokenCount: tokens.length };
|
|
121
|
+
}
|
|
122
|
+
function assertMentionsReferenceMembers(mentions, memberIds) {
|
|
123
|
+
for (const mention of mentions) {
|
|
124
|
+
if (!memberIds.has(mention.memberId)) {
|
|
125
|
+
throw new NotFoundError('TeamMember', mention.memberId);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
function resolveRoomMessageTargetRequests({ mentions, members, senderType, requesterMemberId, allowUserMessageFallback = true, }) {
|
|
130
|
+
if (mentions.length > 0) {
|
|
131
|
+
return mentions.map((mention) => ({
|
|
132
|
+
targetMemberId: mention.memberId,
|
|
133
|
+
ifBusy: mention.ifBusy ?? 'queue',
|
|
134
|
+
cancelQueued: mention.cancelQueued ?? false,
|
|
135
|
+
}));
|
|
136
|
+
}
|
|
137
|
+
if (!allowUserMessageFallback) {
|
|
138
|
+
return [];
|
|
139
|
+
}
|
|
140
|
+
if (senderType !== 'user' && senderType !== 'agent') {
|
|
141
|
+
return [];
|
|
142
|
+
}
|
|
143
|
+
return members
|
|
144
|
+
.filter((member) => member.triggerPolicy === 'USER_MESSAGES')
|
|
145
|
+
.filter((member) => member.id !== requesterMemberId)
|
|
146
|
+
.map((member) => ({
|
|
147
|
+
targetMemberId: member.id,
|
|
148
|
+
ifBusy: 'queue',
|
|
149
|
+
cancelQueued: false,
|
|
150
|
+
}));
|
|
151
|
+
}
|
|
152
|
+
export class TeamRunService {
|
|
153
|
+
async listMemberPresets() {
|
|
154
|
+
const presets = await prisma.memberPreset.findMany({
|
|
155
|
+
orderBy: { createdAt: 'desc' },
|
|
156
|
+
});
|
|
157
|
+
return presets.map((preset) => this.serializeMemberPreset(preset));
|
|
158
|
+
}
|
|
159
|
+
async getMemberPresetById(id) {
|
|
160
|
+
const preset = await prisma.memberPreset.findUnique({ where: { id } });
|
|
161
|
+
if (!preset) {
|
|
162
|
+
throw new NotFoundError('MemberPreset', id);
|
|
163
|
+
}
|
|
164
|
+
return this.serializeMemberPreset(preset);
|
|
165
|
+
}
|
|
166
|
+
async createMemberPreset(input) {
|
|
167
|
+
const preset = await prisma.memberPreset.create({
|
|
168
|
+
data: {
|
|
169
|
+
name: input.name,
|
|
170
|
+
aliases: stringifyJson(input.aliases),
|
|
171
|
+
providerId: input.providerId,
|
|
172
|
+
rolePrompt: input.rolePrompt,
|
|
173
|
+
capabilities: stringifyJson(input.capabilities),
|
|
174
|
+
workspacePolicy: input.workspacePolicy,
|
|
175
|
+
triggerPolicy: input.triggerPolicy,
|
|
176
|
+
sessionPolicy: input.sessionPolicy,
|
|
177
|
+
avatar: input.avatar ?? null,
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
return this.serializeMemberPreset(preset);
|
|
181
|
+
}
|
|
182
|
+
async updateMemberPreset(id, input) {
|
|
183
|
+
await this.getMemberPresetById(id);
|
|
184
|
+
if (Object.keys(input).length === 0) {
|
|
185
|
+
throw new ValidationError('At least one member preset field is required');
|
|
186
|
+
}
|
|
187
|
+
const preset = await prisma.memberPreset.update({
|
|
188
|
+
where: { id },
|
|
189
|
+
data: {
|
|
190
|
+
...(input.name !== undefined ? { name: input.name } : {}),
|
|
191
|
+
...(input.aliases !== undefined ? { aliases: stringifyJson(input.aliases) } : {}),
|
|
192
|
+
...(input.providerId !== undefined ? { providerId: input.providerId } : {}),
|
|
193
|
+
...(input.rolePrompt !== undefined ? { rolePrompt: input.rolePrompt } : {}),
|
|
194
|
+
...(input.capabilities !== undefined ? { capabilities: stringifyJson(input.capabilities) } : {}),
|
|
195
|
+
...(input.workspacePolicy !== undefined ? { workspacePolicy: input.workspacePolicy } : {}),
|
|
196
|
+
...(input.triggerPolicy !== undefined ? { triggerPolicy: input.triggerPolicy } : {}),
|
|
197
|
+
...(input.sessionPolicy !== undefined ? { sessionPolicy: input.sessionPolicy } : {}),
|
|
198
|
+
...(input.avatar !== undefined ? { avatar: input.avatar } : {}),
|
|
199
|
+
},
|
|
200
|
+
});
|
|
201
|
+
return this.serializeMemberPreset(preset);
|
|
202
|
+
}
|
|
203
|
+
async deleteMemberPreset(id) {
|
|
204
|
+
await this.getMemberPresetById(id);
|
|
205
|
+
const templateMemberCount = await prisma.teamTemplateMember.count({
|
|
206
|
+
where: { memberPresetId: id },
|
|
207
|
+
});
|
|
208
|
+
if (templateMemberCount > 0) {
|
|
209
|
+
throw toConflict(`MemberPreset is used by ${templateMemberCount} TeamTemplate member(s): ${id}`);
|
|
210
|
+
}
|
|
211
|
+
await prisma.memberPreset.delete({ where: { id } });
|
|
212
|
+
}
|
|
213
|
+
async listTeamTemplates() {
|
|
214
|
+
const templates = await prisma.teamTemplate.findMany({
|
|
215
|
+
include: { members: { orderBy: { position: 'asc' } } },
|
|
216
|
+
orderBy: { createdAt: 'desc' },
|
|
217
|
+
});
|
|
218
|
+
return Promise.all(templates.map((template) => this.serializeTeamTemplate(template)));
|
|
219
|
+
}
|
|
220
|
+
async getTeamTemplateById(id) {
|
|
221
|
+
const template = await prisma.teamTemplate.findUnique({
|
|
222
|
+
where: { id },
|
|
223
|
+
include: { members: { orderBy: { position: 'asc' } } },
|
|
224
|
+
});
|
|
225
|
+
if (!template) {
|
|
226
|
+
throw new NotFoundError('TeamTemplate', id);
|
|
227
|
+
}
|
|
228
|
+
return this.serializeTeamTemplate(template);
|
|
229
|
+
}
|
|
230
|
+
async createTeamTemplate(input) {
|
|
231
|
+
const members = this.normalizeTeamTemplateMembers(input);
|
|
232
|
+
await this.assertMemberPresetsExist(members.map((member) => member.memberPresetId));
|
|
233
|
+
const template = await prisma.teamTemplate.create({
|
|
234
|
+
data: {
|
|
235
|
+
name: input.name,
|
|
236
|
+
members: {
|
|
237
|
+
create: members,
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
include: { members: { orderBy: { position: 'asc' } } },
|
|
241
|
+
});
|
|
242
|
+
return this.serializeTeamTemplate(template);
|
|
243
|
+
}
|
|
244
|
+
async updateTeamTemplate(id, input) {
|
|
245
|
+
await this.getTeamTemplateById(id);
|
|
246
|
+
if (input.name === undefined
|
|
247
|
+
&& input.memberPresetIds === undefined
|
|
248
|
+
&& input.members === undefined) {
|
|
249
|
+
throw new ValidationError('At least one team template field is required');
|
|
250
|
+
}
|
|
251
|
+
const shouldReplaceMembers = input.members !== undefined || input.memberPresetIds !== undefined;
|
|
252
|
+
const members = shouldReplaceMembers ? this.normalizeTeamTemplateMembers(input) : [];
|
|
253
|
+
if (shouldReplaceMembers) {
|
|
254
|
+
await this.assertMemberPresetsExist(members.map((member) => member.memberPresetId));
|
|
255
|
+
}
|
|
256
|
+
await prisma.$transaction(async (tx) => {
|
|
257
|
+
if (input.name !== undefined) {
|
|
258
|
+
await tx.teamTemplate.update({
|
|
259
|
+
where: { id },
|
|
260
|
+
data: { name: input.name },
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
if (shouldReplaceMembers) {
|
|
264
|
+
await tx.teamTemplateMember.deleteMany({ where: { teamTemplateId: id } });
|
|
265
|
+
if (members.length > 0) {
|
|
266
|
+
await tx.teamTemplateMember.createMany({
|
|
267
|
+
data: members.map((member) => ({
|
|
268
|
+
teamTemplateId: id,
|
|
269
|
+
memberPresetId: member.memberPresetId,
|
|
270
|
+
position: member.position,
|
|
271
|
+
})),
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
return this.getTeamTemplateById(id);
|
|
277
|
+
}
|
|
278
|
+
async deleteTeamTemplate(id) {
|
|
279
|
+
await this.getTeamTemplateById(id);
|
|
280
|
+
await prisma.teamTemplate.delete({ where: { id } });
|
|
281
|
+
}
|
|
282
|
+
async createTeamRun(taskId, input) {
|
|
283
|
+
const task = await prisma.task.findUnique({ where: { id: taskId } });
|
|
284
|
+
if (!task) {
|
|
285
|
+
throw new NotFoundError('Task', taskId);
|
|
286
|
+
}
|
|
287
|
+
buildInitialTaskRoomMessageContent(task);
|
|
288
|
+
const existing = await prisma.teamRun.findUnique({ where: { taskId } });
|
|
289
|
+
if (existing) {
|
|
290
|
+
throw toConflict(`Task already has a TeamRun: ${taskId}`);
|
|
291
|
+
}
|
|
292
|
+
const snapshots = applyStableInstanceNames(await this.buildTeamMemberSnapshots(input));
|
|
293
|
+
if (snapshots.length === 0) {
|
|
294
|
+
throw new ValidationError('TeamRun must include at least one member');
|
|
295
|
+
}
|
|
296
|
+
let teamRunId = '';
|
|
297
|
+
try {
|
|
298
|
+
await prisma.$transaction(async (tx) => {
|
|
299
|
+
const teamRun = await tx.teamRun.create({
|
|
300
|
+
data: {
|
|
301
|
+
taskId,
|
|
302
|
+
mode: input.mode,
|
|
303
|
+
},
|
|
304
|
+
});
|
|
305
|
+
teamRunId = teamRun.id;
|
|
306
|
+
for (const snapshot of snapshots) {
|
|
307
|
+
await tx.teamMember.create({
|
|
308
|
+
data: {
|
|
309
|
+
teamRunId: teamRun.id,
|
|
310
|
+
presetId: snapshot.presetId,
|
|
311
|
+
name: snapshot.name,
|
|
312
|
+
aliases: stringifyJson(snapshot.aliases),
|
|
313
|
+
providerId: snapshot.providerId,
|
|
314
|
+
rolePrompt: snapshot.rolePrompt,
|
|
315
|
+
capabilities: stringifyJson(snapshot.capabilities),
|
|
316
|
+
workspacePolicy: snapshot.workspacePolicy,
|
|
317
|
+
triggerPolicy: snapshot.triggerPolicy,
|
|
318
|
+
sessionPolicy: snapshot.sessionPolicy,
|
|
319
|
+
avatar: snapshot.avatar,
|
|
320
|
+
},
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
catch (error) {
|
|
326
|
+
if (isUniqueConstraintError(error)) {
|
|
327
|
+
throw toConflict(`Task already has a TeamRun: ${taskId}`);
|
|
328
|
+
}
|
|
329
|
+
throw error;
|
|
330
|
+
}
|
|
331
|
+
const teamRun = await this.getTeamRunById(teamRunId);
|
|
332
|
+
await emitTeamRunInvalidated({
|
|
333
|
+
teamRunId,
|
|
334
|
+
taskId,
|
|
335
|
+
projectId: task.projectId,
|
|
336
|
+
scopes: ['team-run', 'team-members', 'task'],
|
|
337
|
+
reason: 'team-run-created',
|
|
338
|
+
});
|
|
339
|
+
return teamRun;
|
|
340
|
+
}
|
|
341
|
+
async createTeamRunWithInitialRoomMessage(taskId, input) {
|
|
342
|
+
const snapshots = applyStableInstanceNames(await this.buildTeamMemberSnapshots(input));
|
|
343
|
+
if (snapshots.length === 0) {
|
|
344
|
+
throw new ValidationError('TeamRun must include at least one member');
|
|
345
|
+
}
|
|
346
|
+
let teamRunId = '';
|
|
347
|
+
let projectId = '';
|
|
348
|
+
let createdTeamRun = null;
|
|
349
|
+
try {
|
|
350
|
+
createdTeamRun = await prisma.$transaction(async (tx) => {
|
|
351
|
+
const task = await tx.task.findUnique({ where: { id: taskId } });
|
|
352
|
+
if (!task) {
|
|
353
|
+
throw new NotFoundError('Task', taskId);
|
|
354
|
+
}
|
|
355
|
+
projectId = task.projectId;
|
|
356
|
+
const initialContent = buildInitialTaskRoomMessageContent(task);
|
|
357
|
+
const existing = await tx.teamRun.findUnique({ where: { taskId } });
|
|
358
|
+
if (existing) {
|
|
359
|
+
throw toConflict(`Task already has a TeamRun: ${taskId}`);
|
|
360
|
+
}
|
|
361
|
+
const teamRun = await tx.teamRun.create({
|
|
362
|
+
data: {
|
|
363
|
+
taskId,
|
|
364
|
+
mode: input.mode,
|
|
365
|
+
},
|
|
366
|
+
});
|
|
367
|
+
teamRunId = teamRun.id;
|
|
368
|
+
const members = [];
|
|
369
|
+
for (const snapshot of snapshots) {
|
|
370
|
+
const member = await tx.teamMember.create({
|
|
371
|
+
data: {
|
|
372
|
+
teamRunId: teamRun.id,
|
|
373
|
+
presetId: snapshot.presetId,
|
|
374
|
+
name: snapshot.name,
|
|
375
|
+
aliases: stringifyJson(snapshot.aliases),
|
|
376
|
+
providerId: snapshot.providerId,
|
|
377
|
+
rolePrompt: snapshot.rolePrompt,
|
|
378
|
+
capabilities: stringifyJson(snapshot.capabilities),
|
|
379
|
+
workspacePolicy: snapshot.workspacePolicy,
|
|
380
|
+
triggerPolicy: snapshot.triggerPolicy,
|
|
381
|
+
sessionPolicy: snapshot.sessionPolicy,
|
|
382
|
+
avatar: snapshot.avatar,
|
|
383
|
+
},
|
|
384
|
+
});
|
|
385
|
+
members.push(member);
|
|
386
|
+
}
|
|
387
|
+
const initialMentionParse = deriveStructuredMentionsFromContent(initialContent, members);
|
|
388
|
+
const message = await tx.roomMessage.create({
|
|
389
|
+
data: {
|
|
390
|
+
teamRunId: teamRun.id,
|
|
391
|
+
senderType: 'user',
|
|
392
|
+
senderId: null,
|
|
393
|
+
senderInvocationId: null,
|
|
394
|
+
kind: 'chat',
|
|
395
|
+
content: initialContent,
|
|
396
|
+
mentions: stringifyJson(initialMentionParse.mentions),
|
|
397
|
+
artifactRefs: stringifyJson([]),
|
|
398
|
+
attachmentIds: stringifyJson([]),
|
|
399
|
+
workRequestIds: stringifyJson([]),
|
|
400
|
+
},
|
|
401
|
+
});
|
|
402
|
+
const workRequestStatus = input.mode === 'CONFIRM'
|
|
403
|
+
? 'PENDING_APPROVAL'
|
|
404
|
+
: 'QUEUED';
|
|
405
|
+
const targetRequests = resolveRoomMessageTargetRequests({
|
|
406
|
+
mentions: initialMentionParse.mentions,
|
|
407
|
+
members,
|
|
408
|
+
senderType: 'user',
|
|
409
|
+
requesterMemberId: null,
|
|
410
|
+
allowUserMessageFallback: initialMentionParse.tokenCount === 0,
|
|
411
|
+
});
|
|
412
|
+
const workRequestIds = [];
|
|
413
|
+
for (const targetRequest of targetRequests) {
|
|
414
|
+
const workRequest = await tx.workRequest.create({
|
|
415
|
+
data: {
|
|
416
|
+
teamRunId: teamRun.id,
|
|
417
|
+
requesterMemberId: null,
|
|
418
|
+
requesterType: 'user',
|
|
419
|
+
targetMemberId: targetRequest.targetMemberId,
|
|
420
|
+
triggerMessageId: message.id,
|
|
421
|
+
instruction: initialContent,
|
|
422
|
+
ifBusy: targetRequest.ifBusy,
|
|
423
|
+
cancelQueued: targetRequest.cancelQueued,
|
|
424
|
+
status: workRequestStatus,
|
|
425
|
+
},
|
|
426
|
+
});
|
|
427
|
+
workRequestIds.push(workRequest.id);
|
|
428
|
+
}
|
|
429
|
+
if (workRequestIds.length > 0) {
|
|
430
|
+
await tx.roomMessage.update({
|
|
431
|
+
where: { id: message.id },
|
|
432
|
+
data: { workRequestIds: stringifyJson(workRequestIds) },
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
const created = await tx.teamRun.findUnique({
|
|
436
|
+
where: { id: teamRun.id },
|
|
437
|
+
include: this.teamRunInclude(),
|
|
438
|
+
});
|
|
439
|
+
if (!created) {
|
|
440
|
+
throw new NotFoundError('TeamRun', teamRun.id);
|
|
441
|
+
}
|
|
442
|
+
return created;
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
catch (error) {
|
|
446
|
+
if (isUniqueConstraintError(error)) {
|
|
447
|
+
throw toConflict(`Task already has a TeamRun: ${taskId}`);
|
|
448
|
+
}
|
|
449
|
+
throw error;
|
|
450
|
+
}
|
|
451
|
+
const serialized = this.serializeTeamRun(createdTeamRun);
|
|
452
|
+
await emitTeamRunInvalidated({
|
|
453
|
+
teamRunId,
|
|
454
|
+
taskId,
|
|
455
|
+
projectId,
|
|
456
|
+
scopes: ['team-run', 'team-members', 'room-messages', 'work-requests', 'task'],
|
|
457
|
+
reason: 'team-run-created',
|
|
458
|
+
});
|
|
459
|
+
return serialized;
|
|
460
|
+
}
|
|
461
|
+
async getTaskTeamRun(taskId) {
|
|
462
|
+
const teamRun = await prisma.teamRun.findUnique({
|
|
463
|
+
where: { taskId },
|
|
464
|
+
include: this.teamRunInclude(),
|
|
465
|
+
});
|
|
466
|
+
if (!teamRun) {
|
|
467
|
+
throw new NotFoundError('TeamRun for task', taskId);
|
|
468
|
+
}
|
|
469
|
+
return this.serializeTeamRun(teamRun);
|
|
470
|
+
}
|
|
471
|
+
async getTeamRunById(id) {
|
|
472
|
+
const teamRun = await prisma.teamRun.findUnique({
|
|
473
|
+
where: { id },
|
|
474
|
+
include: this.teamRunInclude(),
|
|
475
|
+
});
|
|
476
|
+
if (!teamRun) {
|
|
477
|
+
throw new NotFoundError('TeamRun', id);
|
|
478
|
+
}
|
|
479
|
+
return this.serializeTeamRun(teamRun);
|
|
480
|
+
}
|
|
481
|
+
async listTeamMembers(teamRunId) {
|
|
482
|
+
await this.assertTeamRunExists(teamRunId);
|
|
483
|
+
const [members, invocations, workRequests] = await Promise.all([
|
|
484
|
+
prisma.teamMember.findMany({
|
|
485
|
+
where: { teamRunId },
|
|
486
|
+
orderBy: { createdAt: 'asc' },
|
|
487
|
+
}),
|
|
488
|
+
prisma.agentInvocation.findMany({
|
|
489
|
+
where: {
|
|
490
|
+
teamRunId,
|
|
491
|
+
status: { in: ACTIVE_INVOCATION_STATUSES },
|
|
492
|
+
},
|
|
493
|
+
}),
|
|
494
|
+
prisma.workRequest.findMany({
|
|
495
|
+
where: {
|
|
496
|
+
teamRunId,
|
|
497
|
+
status: { in: OPEN_WORK_REQUEST_STATUSES },
|
|
498
|
+
},
|
|
499
|
+
}),
|
|
500
|
+
]);
|
|
501
|
+
const memberStatuses = this.deriveTeamMemberStatuses(members, invocations, workRequests);
|
|
502
|
+
return members.map((member) => this.serializeTeamMember(member, memberStatuses.get(member.id)));
|
|
503
|
+
}
|
|
504
|
+
async listRoomMessages(teamRunId) {
|
|
505
|
+
await this.assertTeamRunExists(teamRunId);
|
|
506
|
+
const messages = await prisma.roomMessage.findMany({
|
|
507
|
+
where: { teamRunId },
|
|
508
|
+
orderBy: { createdAt: 'asc' },
|
|
509
|
+
});
|
|
510
|
+
return messages.map((message) => this.serializeRoomMessage(message));
|
|
511
|
+
}
|
|
512
|
+
async listWorkRequests(teamRunId) {
|
|
513
|
+
await this.assertTeamRunExists(teamRunId);
|
|
514
|
+
const workRequests = await prisma.workRequest.findMany({
|
|
515
|
+
where: { teamRunId },
|
|
516
|
+
orderBy: { createdAt: 'asc' },
|
|
517
|
+
});
|
|
518
|
+
return workRequests.map((workRequest) => this.serializeWorkRequest(workRequest));
|
|
519
|
+
}
|
|
520
|
+
async listAgentInvocations(teamRunId) {
|
|
521
|
+
await this.assertTeamRunExists(teamRunId);
|
|
522
|
+
const invocations = await prisma.agentInvocation.findMany({
|
|
523
|
+
where: { teamRunId },
|
|
524
|
+
orderBy: { createdAt: 'asc' },
|
|
525
|
+
});
|
|
526
|
+
return invocations.map((invocation) => this.serializeAgentInvocation(invocation));
|
|
527
|
+
}
|
|
528
|
+
async createRoomMessage(teamRunId, input) {
|
|
529
|
+
const teamRun = await prisma.teamRun.findUnique({ where: { id: teamRunId } });
|
|
530
|
+
if (!teamRun) {
|
|
531
|
+
throw new NotFoundError('TeamRun', teamRunId);
|
|
532
|
+
}
|
|
533
|
+
const mentions = input.mentions ?? [];
|
|
534
|
+
const senderType = input.senderType ?? 'user';
|
|
535
|
+
const kind = input.kind ?? (mentions.length > 0 ? 'work_request' : 'chat');
|
|
536
|
+
const workRequestStatus = teamRun.mode === 'CONFIRM'
|
|
537
|
+
? 'PENDING_APPROVAL'
|
|
538
|
+
: 'QUEUED';
|
|
539
|
+
const workRequestInstruction = await appendAttachmentMarkdownContext(input.content, input.attachmentIds);
|
|
540
|
+
let messageId = '';
|
|
541
|
+
await prisma.$transaction(async (tx) => {
|
|
542
|
+
const members = await tx.teamMember.findMany({ where: { teamRunId } });
|
|
543
|
+
const memberIds = new Set(members.map((member) => member.id));
|
|
544
|
+
assertMentionsReferenceMembers(mentions, memberIds);
|
|
545
|
+
const requesterMemberId = senderType === 'agent'
|
|
546
|
+
&& input.senderId != null
|
|
547
|
+
&& memberIds.has(input.senderId)
|
|
548
|
+
? input.senderId
|
|
549
|
+
: null;
|
|
550
|
+
if (senderType === 'agent') {
|
|
551
|
+
if (!input.senderId || !memberIds.has(input.senderId)) {
|
|
552
|
+
throw new ValidationError('Agent RoomMessage senderId must be a TeamMember in this TeamRun');
|
|
553
|
+
}
|
|
554
|
+
if (input.senderInvocationId) {
|
|
555
|
+
const invocation = await tx.agentInvocation.findFirst({
|
|
556
|
+
where: {
|
|
557
|
+
id: input.senderInvocationId,
|
|
558
|
+
teamRunId,
|
|
559
|
+
memberId: input.senderId,
|
|
560
|
+
},
|
|
561
|
+
select: { id: true },
|
|
562
|
+
});
|
|
563
|
+
if (!invocation) {
|
|
564
|
+
throw new ValidationError('Agent RoomMessage senderInvocationId must belong to the sender member in this TeamRun');
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
const message = await tx.roomMessage.create({
|
|
569
|
+
data: {
|
|
570
|
+
teamRunId,
|
|
571
|
+
senderType,
|
|
572
|
+
senderId: input.senderId ?? null,
|
|
573
|
+
senderInvocationId: input.senderInvocationId ?? null,
|
|
574
|
+
kind,
|
|
575
|
+
content: input.content,
|
|
576
|
+
mentions: stringifyJson(mentions),
|
|
577
|
+
artifactRefs: stringifyJson(input.artifactRefs ?? []),
|
|
578
|
+
attachmentIds: stringifyJson(input.attachmentIds ?? []),
|
|
579
|
+
workRequestIds: stringifyJson([]),
|
|
580
|
+
},
|
|
581
|
+
});
|
|
582
|
+
messageId = message.id;
|
|
583
|
+
const targetRequests = resolveRoomMessageTargetRequests({
|
|
584
|
+
mentions,
|
|
585
|
+
members,
|
|
586
|
+
senderType,
|
|
587
|
+
requesterMemberId,
|
|
588
|
+
});
|
|
589
|
+
const workRequestIds = [];
|
|
590
|
+
for (const targetRequest of targetRequests) {
|
|
591
|
+
const workRequest = await tx.workRequest.create({
|
|
592
|
+
data: {
|
|
593
|
+
teamRunId,
|
|
594
|
+
requesterMemberId,
|
|
595
|
+
requesterType: senderType,
|
|
596
|
+
targetMemberId: targetRequest.targetMemberId,
|
|
597
|
+
triggerMessageId: message.id,
|
|
598
|
+
instruction: workRequestInstruction,
|
|
599
|
+
ifBusy: targetRequest.ifBusy,
|
|
600
|
+
cancelQueued: targetRequest.cancelQueued,
|
|
601
|
+
status: workRequestStatus,
|
|
602
|
+
},
|
|
603
|
+
});
|
|
604
|
+
workRequestIds.push(workRequest.id);
|
|
605
|
+
}
|
|
606
|
+
if (workRequestIds.length > 0) {
|
|
607
|
+
await tx.roomMessage.update({
|
|
608
|
+
where: { id: message.id },
|
|
609
|
+
data: { workRequestIds: stringifyJson(workRequestIds) },
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
});
|
|
613
|
+
const message = await prisma.roomMessage.findUnique({ where: { id: messageId } });
|
|
614
|
+
if (!message) {
|
|
615
|
+
throw new NotFoundError('RoomMessage', messageId);
|
|
616
|
+
}
|
|
617
|
+
const serialized = this.serializeRoomMessage(message);
|
|
618
|
+
await emitTeamRunInvalidated({
|
|
619
|
+
teamRunId,
|
|
620
|
+
taskId: teamRun.taskId,
|
|
621
|
+
scopes: ['room-messages', 'work-requests', 'team-run'],
|
|
622
|
+
reason: 'room-message-created',
|
|
623
|
+
});
|
|
624
|
+
return serialized;
|
|
625
|
+
}
|
|
626
|
+
normalizeTeamTemplateMembers(input) {
|
|
627
|
+
const members = input.members
|
|
628
|
+
?? input.memberPresetIds?.map((memberPresetId, index) => ({ memberPresetId, position: index }))
|
|
629
|
+
?? [];
|
|
630
|
+
return members.map((member, index) => ({
|
|
631
|
+
memberPresetId: member.memberPresetId,
|
|
632
|
+
position: member.position ?? index,
|
|
633
|
+
}));
|
|
634
|
+
}
|
|
635
|
+
async buildTeamMemberSnapshots(input) {
|
|
636
|
+
const snapshots = [];
|
|
637
|
+
if (input.teamTemplateId) {
|
|
638
|
+
const template = await prisma.teamTemplate.findUnique({
|
|
639
|
+
where: { id: input.teamTemplateId },
|
|
640
|
+
include: { members: { orderBy: { position: 'asc' } } },
|
|
641
|
+
});
|
|
642
|
+
if (!template) {
|
|
643
|
+
throw new NotFoundError('TeamTemplate', input.teamTemplateId);
|
|
644
|
+
}
|
|
645
|
+
const presets = await this.findMemberPresetsByIds(template.members.map((member) => member.memberPresetId));
|
|
646
|
+
snapshots.push(...presets.map((preset) => this.memberPresetToSnapshot(preset)));
|
|
647
|
+
}
|
|
648
|
+
if (input.memberPresetIds && input.memberPresetIds.length > 0) {
|
|
649
|
+
const presets = await this.findMemberPresetsByIds(input.memberPresetIds);
|
|
650
|
+
snapshots.push(...presets.map((preset) => this.memberPresetToSnapshot(preset)));
|
|
651
|
+
}
|
|
652
|
+
if (input.members && input.members.length > 0) {
|
|
653
|
+
snapshots.push(...input.members.map((member) => ({
|
|
654
|
+
presetId: null,
|
|
655
|
+
name: member.name,
|
|
656
|
+
aliases: member.aliases,
|
|
657
|
+
providerId: member.providerId,
|
|
658
|
+
rolePrompt: member.rolePrompt,
|
|
659
|
+
capabilities: member.capabilities,
|
|
660
|
+
workspacePolicy: member.workspacePolicy,
|
|
661
|
+
triggerPolicy: member.triggerPolicy,
|
|
662
|
+
sessionPolicy: member.sessionPolicy,
|
|
663
|
+
avatar: member.avatar ?? null,
|
|
664
|
+
})));
|
|
665
|
+
}
|
|
666
|
+
return snapshots;
|
|
667
|
+
}
|
|
668
|
+
memberPresetToSnapshot(preset) {
|
|
669
|
+
return {
|
|
670
|
+
presetId: preset.id,
|
|
671
|
+
name: preset.name,
|
|
672
|
+
aliases: parseJsonField(preset.aliases, []),
|
|
673
|
+
providerId: preset.providerId,
|
|
674
|
+
rolePrompt: preset.rolePrompt,
|
|
675
|
+
capabilities: parseJsonField(preset.capabilities, DEFAULT_CAPABILITIES),
|
|
676
|
+
workspacePolicy: preset.workspacePolicy,
|
|
677
|
+
triggerPolicy: preset.triggerPolicy,
|
|
678
|
+
sessionPolicy: preset.sessionPolicy || DEFAULT_SESSION_POLICY,
|
|
679
|
+
avatar: preset.avatar ?? null,
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
async findMemberPresetsByIds(ids) {
|
|
683
|
+
if (ids.length === 0) {
|
|
684
|
+
return [];
|
|
685
|
+
}
|
|
686
|
+
const presets = await prisma.memberPreset.findMany({
|
|
687
|
+
where: { id: { in: ids } },
|
|
688
|
+
});
|
|
689
|
+
const presetById = new Map(presets.map((preset) => [preset.id, preset]));
|
|
690
|
+
const missing = ids.find((id) => !presetById.has(id));
|
|
691
|
+
if (missing) {
|
|
692
|
+
throw new NotFoundError('MemberPreset', missing);
|
|
693
|
+
}
|
|
694
|
+
return ids.map((id) => presetById.get(id));
|
|
695
|
+
}
|
|
696
|
+
async assertMemberPresetsExist(ids) {
|
|
697
|
+
await this.findMemberPresetsByIds(ids);
|
|
698
|
+
}
|
|
699
|
+
async assertTeamRunExists(id) {
|
|
700
|
+
const count = await prisma.teamRun.count({ where: { id } });
|
|
701
|
+
if (count === 0) {
|
|
702
|
+
throw new NotFoundError('TeamRun', id);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
teamRunInclude() {
|
|
706
|
+
return {
|
|
707
|
+
members: { orderBy: { createdAt: 'asc' } },
|
|
708
|
+
messages: { orderBy: { createdAt: 'asc' } },
|
|
709
|
+
workRequests: { orderBy: { createdAt: 'asc' } },
|
|
710
|
+
invocations: { orderBy: { createdAt: 'asc' } },
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
serializeMemberPreset(preset) {
|
|
714
|
+
return {
|
|
715
|
+
...preset,
|
|
716
|
+
aliases: parseJsonField(preset.aliases, []),
|
|
717
|
+
capabilities: parseJsonField(preset.capabilities, DEFAULT_CAPABILITIES),
|
|
718
|
+
workspacePolicy: preset.workspacePolicy,
|
|
719
|
+
triggerPolicy: preset.triggerPolicy,
|
|
720
|
+
sessionPolicy: preset.sessionPolicy || DEFAULT_SESSION_POLICY,
|
|
721
|
+
avatar: preset.avatar ?? null,
|
|
722
|
+
createdAt: toIso(preset.createdAt),
|
|
723
|
+
updatedAt: toIso(preset.updatedAt),
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
async serializeTeamTemplate(template) {
|
|
727
|
+
const members = template.members ?? await prisma.teamTemplateMember.findMany({
|
|
728
|
+
where: { teamTemplateId: template.id },
|
|
729
|
+
orderBy: { position: 'asc' },
|
|
730
|
+
});
|
|
731
|
+
const presetIds = members.map((member) => member.memberPresetId);
|
|
732
|
+
const presets = presetIds.length > 0
|
|
733
|
+
? await prisma.memberPreset.findMany({ where: { id: { in: presetIds } } })
|
|
734
|
+
: [];
|
|
735
|
+
const presetById = new Map(presets.map((preset) => [preset.id, preset]));
|
|
736
|
+
return {
|
|
737
|
+
...template,
|
|
738
|
+
createdAt: toIso(template.createdAt),
|
|
739
|
+
updatedAt: toIso(template.updatedAt),
|
|
740
|
+
members: members.map((member) => {
|
|
741
|
+
const preset = presetById.get(member.memberPresetId);
|
|
742
|
+
return {
|
|
743
|
+
...member,
|
|
744
|
+
memberPreset: preset ? this.serializeMemberPreset(preset) : undefined,
|
|
745
|
+
};
|
|
746
|
+
}),
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
serializeTeamRun(teamRun) {
|
|
750
|
+
const memberStatuses = this.deriveTeamMemberStatuses(teamRun.members ?? [], teamRun.invocations ?? [], teamRun.workRequests ?? []);
|
|
751
|
+
return {
|
|
752
|
+
...teamRun,
|
|
753
|
+
mode: teamRun.mode,
|
|
754
|
+
reviewReason: teamRun.reviewReason,
|
|
755
|
+
createdAt: toIso(teamRun.createdAt),
|
|
756
|
+
updatedAt: toIso(teamRun.updatedAt),
|
|
757
|
+
members: teamRun.members?.map((member) => this.serializeTeamMember(member, memberStatuses.get(member.id))),
|
|
758
|
+
messages: teamRun.messages?.map((message) => this.serializeRoomMessage(message)),
|
|
759
|
+
workRequests: teamRun.workRequests?.map((workRequest) => this.serializeWorkRequest(workRequest)),
|
|
760
|
+
invocations: teamRun.invocations?.map((invocation) => this.serializeAgentInvocation(invocation)),
|
|
761
|
+
};
|
|
762
|
+
}
|
|
763
|
+
deriveTeamMemberStatuses(members, invocations = [], workRequests = []) {
|
|
764
|
+
const statuses = new Map();
|
|
765
|
+
for (const member of members) {
|
|
766
|
+
statuses.set(member.id, this.deriveTeamMemberStatus(member.id, invocations, workRequests));
|
|
767
|
+
}
|
|
768
|
+
return statuses;
|
|
769
|
+
}
|
|
770
|
+
deriveTeamMemberStatus(memberId, invocations, workRequests) {
|
|
771
|
+
const memberInvocations = invocations.filter((invocation) => invocation.memberId === memberId);
|
|
772
|
+
if (memberInvocations.some((invocation) => invocation.status === 'RUNNING'))
|
|
773
|
+
return 'RUNNING';
|
|
774
|
+
if (memberInvocations.some((invocation) => invocation.status === 'WAITING_ROOM_REPLY'))
|
|
775
|
+
return 'WAITING_ROOM_REPLY';
|
|
776
|
+
if (memberInvocations.some((invocation) => invocation.status === 'SESSION_ENDED'))
|
|
777
|
+
return 'SESSION_ENDED';
|
|
778
|
+
if (memberInvocations.some((invocation) => invocation.status === 'QUEUED'))
|
|
779
|
+
return 'QUEUED';
|
|
780
|
+
const memberWorkRequests = workRequests.filter((request) => request.targetMemberId === memberId);
|
|
781
|
+
if (memberWorkRequests.some((request) => request.status === 'QUEUED'))
|
|
782
|
+
return 'QUEUED';
|
|
783
|
+
if (memberWorkRequests.some((request) => request.status === 'PENDING_APPROVAL'))
|
|
784
|
+
return 'PENDING_APPROVAL';
|
|
785
|
+
return 'IDLE';
|
|
786
|
+
}
|
|
787
|
+
serializeTeamMember(member, status) {
|
|
788
|
+
return {
|
|
789
|
+
...member,
|
|
790
|
+
aliases: parseJsonField(member.aliases, []),
|
|
791
|
+
capabilities: parseJsonField(member.capabilities, DEFAULT_CAPABILITIES),
|
|
792
|
+
workspacePolicy: member.workspacePolicy,
|
|
793
|
+
triggerPolicy: member.triggerPolicy,
|
|
794
|
+
sessionPolicy: member.sessionPolicy || DEFAULT_SESSION_POLICY,
|
|
795
|
+
avatar: member.avatar ?? null,
|
|
796
|
+
status: status ?? 'IDLE',
|
|
797
|
+
createdAt: toIso(member.createdAt),
|
|
798
|
+
updatedAt: toIso(member.updatedAt),
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
serializeRoomMessage(message) {
|
|
802
|
+
return {
|
|
803
|
+
...message,
|
|
804
|
+
senderType: message.senderType,
|
|
805
|
+
kind: message.kind,
|
|
806
|
+
mentions: parseJsonField(message.mentions, []),
|
|
807
|
+
workRequestIds: parseJsonField(message.workRequestIds, null),
|
|
808
|
+
artifactRefs: parseJsonField(message.artifactRefs, null),
|
|
809
|
+
attachmentIds: parseJsonField(message.attachmentIds, null),
|
|
810
|
+
createdAt: toIso(message.createdAt),
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
serializeWorkRequest(workRequest) {
|
|
814
|
+
return {
|
|
815
|
+
...workRequest,
|
|
816
|
+
requesterType: workRequest.requesterType,
|
|
817
|
+
ifBusy: workRequest.ifBusy,
|
|
818
|
+
status: workRequest.status,
|
|
819
|
+
createdAt: toIso(workRequest.createdAt),
|
|
820
|
+
updatedAt: toIso(workRequest.updatedAt),
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
serializeAgentInvocation(invocation) {
|
|
824
|
+
return {
|
|
825
|
+
...invocation,
|
|
826
|
+
status: invocation.status,
|
|
827
|
+
createdAt: toIso(invocation.createdAt),
|
|
828
|
+
updatedAt: toIso(invocation.updatedAt),
|
|
829
|
+
nextRoomReplyReminderAt: invocation.nextRoomReplyReminderAt
|
|
830
|
+
? toIso(invocation.nextRoomReplyReminderAt)
|
|
831
|
+
: null,
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
//# sourceMappingURL=team-run.service.js.map
|