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,699 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'vitest';
|
|
7
|
+
const testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-tower-team-run-'));
|
|
8
|
+
const dbPath = path.join(testDir, 'test.db');
|
|
9
|
+
process.env.AGENT_TOWER_DATABASE_URL = `file:${dbPath}`;
|
|
10
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
11
|
+
const __dirname = path.dirname(__filename);
|
|
12
|
+
const serverRoot = path.resolve(__dirname, '../../..');
|
|
13
|
+
const schemaPath = path.join(serverRoot, 'prisma/schema.prisma');
|
|
14
|
+
let TeamRunService;
|
|
15
|
+
let prisma;
|
|
16
|
+
const capabilities = {
|
|
17
|
+
readRoom: true,
|
|
18
|
+
postRoomMessage: true,
|
|
19
|
+
mentionMembers: true,
|
|
20
|
+
stopMemberWork: false,
|
|
21
|
+
markReadyForReview: true,
|
|
22
|
+
readFiles: true,
|
|
23
|
+
writeFiles: true,
|
|
24
|
+
runCommands: false,
|
|
25
|
+
readDiff: true,
|
|
26
|
+
mergeWorkspace: false,
|
|
27
|
+
};
|
|
28
|
+
function presetInput(name, aliases = [name.toLowerCase()]) {
|
|
29
|
+
return {
|
|
30
|
+
name,
|
|
31
|
+
aliases,
|
|
32
|
+
providerId: `provider-${name.toLowerCase()}`,
|
|
33
|
+
rolePrompt: `${name} role`,
|
|
34
|
+
capabilities,
|
|
35
|
+
workspacePolicy: 'dedicated',
|
|
36
|
+
triggerPolicy: 'MENTION_ONLY',
|
|
37
|
+
sessionPolicy: 'new_per_request',
|
|
38
|
+
avatar: null,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function userMessagesPresetInput(name, aliases = [name.toLowerCase()]) {
|
|
42
|
+
return {
|
|
43
|
+
...presetInput(name, aliases),
|
|
44
|
+
triggerPolicy: 'USER_MESSAGES',
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
async function createTask(title = 'Team task') {
|
|
48
|
+
const project = await prisma.project.create({
|
|
49
|
+
data: {
|
|
50
|
+
name: `${title} project`,
|
|
51
|
+
repoPath: testDir,
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
return prisma.task.create({
|
|
55
|
+
data: {
|
|
56
|
+
title,
|
|
57
|
+
projectId: project.id,
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
describe('TeamRunService', () => {
|
|
62
|
+
let service;
|
|
63
|
+
beforeAll(async () => {
|
|
64
|
+
execFileSync('pnpm', ['exec', 'prisma', 'db', 'push', '--skip-generate', `--schema=${schemaPath}`], {
|
|
65
|
+
cwd: serverRoot,
|
|
66
|
+
env: { ...process.env, AGENT_TOWER_DATABASE_URL: `file:${dbPath}` },
|
|
67
|
+
stdio: 'pipe',
|
|
68
|
+
});
|
|
69
|
+
const serviceModule = await import('../team-run.service.js');
|
|
70
|
+
const utilsModule = await import('../../utils/index.js');
|
|
71
|
+
TeamRunService = serviceModule.TeamRunService;
|
|
72
|
+
prisma = utilsModule.prisma;
|
|
73
|
+
});
|
|
74
|
+
beforeEach(async () => {
|
|
75
|
+
service = new TeamRunService();
|
|
76
|
+
await prisma.agentInvocation.deleteMany();
|
|
77
|
+
await prisma.workRequest.deleteMany();
|
|
78
|
+
await prisma.roomMessage.deleteMany();
|
|
79
|
+
await prisma.teamMember.deleteMany();
|
|
80
|
+
await prisma.teamRun.deleteMany();
|
|
81
|
+
await prisma.teamTemplateMember.deleteMany();
|
|
82
|
+
await prisma.teamTemplate.deleteMany();
|
|
83
|
+
await prisma.memberPreset.deleteMany();
|
|
84
|
+
await prisma.task.deleteMany();
|
|
85
|
+
await prisma.project.deleteMany();
|
|
86
|
+
});
|
|
87
|
+
afterAll(async () => {
|
|
88
|
+
await prisma.$disconnect();
|
|
89
|
+
fs.rmSync(testDir, { recursive: true, force: true });
|
|
90
|
+
});
|
|
91
|
+
it('returns structured aliases and capabilities after creating a MemberPreset', async () => {
|
|
92
|
+
const preset = await service.createMemberPreset(presetInput('Reviewer', ['reviewer', 'review']));
|
|
93
|
+
expect(preset.aliases).toEqual(['reviewer', 'review']);
|
|
94
|
+
expect(preset.capabilities).toEqual(capabilities);
|
|
95
|
+
expect(preset.sessionPolicy).toBe('new_per_request');
|
|
96
|
+
});
|
|
97
|
+
it('preserves session policy in MemberPreset and TeamMember snapshots', async () => {
|
|
98
|
+
const preset = await service.createMemberPreset({
|
|
99
|
+
...presetInput('Implementer', ['impl']),
|
|
100
|
+
sessionPolicy: 'resume_last',
|
|
101
|
+
});
|
|
102
|
+
const task = await createTask();
|
|
103
|
+
const teamRun = await service.createTeamRun(task.id, {
|
|
104
|
+
mode: 'AUTO',
|
|
105
|
+
memberPresetIds: [preset.id],
|
|
106
|
+
});
|
|
107
|
+
expect(preset.sessionPolicy).toBe('resume_last');
|
|
108
|
+
expect(teamRun.members?.[0]).toMatchObject({
|
|
109
|
+
name: 'Implementer',
|
|
110
|
+
sessionPolicy: 'resume_last',
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
it('rejects deleting a MemberPreset that is referenced by a TeamTemplate', async () => {
|
|
114
|
+
const preset = await service.createMemberPreset(presetInput('Reviewer'));
|
|
115
|
+
await service.createTeamTemplate({
|
|
116
|
+
name: 'Review team',
|
|
117
|
+
memberPresetIds: [preset.id],
|
|
118
|
+
});
|
|
119
|
+
await expect(service.deleteMemberPreset(preset.id)).rejects.toMatchObject({
|
|
120
|
+
code: 'CONFLICT',
|
|
121
|
+
statusCode: 409,
|
|
122
|
+
});
|
|
123
|
+
await expect(service.getMemberPresetById(preset.id)).resolves.toMatchObject({
|
|
124
|
+
id: preset.id,
|
|
125
|
+
name: 'Reviewer',
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
it('preserves repeated MemberPresets in TeamTemplate members', async () => {
|
|
129
|
+
const preset = await service.createMemberPreset(presetInput('Implementer', ['impl']));
|
|
130
|
+
const template = await service.createTeamTemplate({
|
|
131
|
+
name: 'Parallel implementers',
|
|
132
|
+
memberPresetIds: [preset.id, preset.id, preset.id],
|
|
133
|
+
});
|
|
134
|
+
expect(template.members).toHaveLength(3);
|
|
135
|
+
expect(template.members?.map((member) => member.memberPresetId)).toEqual([preset.id, preset.id, preset.id]);
|
|
136
|
+
expect(template.members?.map((member) => member.position)).toEqual([0, 1, 2]);
|
|
137
|
+
expect(template.members?.map((member) => member.memberPreset?.name)).toEqual([
|
|
138
|
+
'Implementer',
|
|
139
|
+
'Implementer',
|
|
140
|
+
'Implementer',
|
|
141
|
+
]);
|
|
142
|
+
});
|
|
143
|
+
it('updates TeamTemplate members with repeated MemberPresets in order', async () => {
|
|
144
|
+
const first = await service.createMemberPreset(presetInput('Implementer', ['impl']));
|
|
145
|
+
const second = await service.createMemberPreset(presetInput('Reviewer', ['review']));
|
|
146
|
+
const template = await service.createTeamTemplate({
|
|
147
|
+
name: 'Initial team',
|
|
148
|
+
memberPresetIds: [second.id],
|
|
149
|
+
});
|
|
150
|
+
const updated = await service.updateTeamTemplate(template.id, {
|
|
151
|
+
memberPresetIds: [first.id, second.id, first.id],
|
|
152
|
+
});
|
|
153
|
+
expect(updated.members).toHaveLength(3);
|
|
154
|
+
expect(updated.members?.map((member) => member.memberPresetId)).toEqual([
|
|
155
|
+
first.id,
|
|
156
|
+
second.id,
|
|
157
|
+
first.id,
|
|
158
|
+
]);
|
|
159
|
+
expect(updated.members?.map((member) => member.position)).toEqual([0, 1, 2]);
|
|
160
|
+
});
|
|
161
|
+
it('creates TeamMember snapshots from multiple MemberPresets', async () => {
|
|
162
|
+
const first = await service.createMemberPreset(presetInput('Planner', ['planner']));
|
|
163
|
+
const second = await service.createMemberPreset(presetInput('Coder', ['coder']));
|
|
164
|
+
const task = await createTask();
|
|
165
|
+
const teamRun = await service.createTeamRun(task.id, {
|
|
166
|
+
mode: 'CONFIRM',
|
|
167
|
+
memberPresetIds: [first.id, second.id],
|
|
168
|
+
});
|
|
169
|
+
expect(teamRun.members).toHaveLength(2);
|
|
170
|
+
expect(teamRun.members?.map((member) => member.name)).toEqual(['Planner', 'Coder']);
|
|
171
|
+
expect(teamRun.members?.map((member) => member.presetId)).toEqual([first.id, second.id]);
|
|
172
|
+
});
|
|
173
|
+
it('creates stable instance names when the same MemberPreset is selected more than once', async () => {
|
|
174
|
+
const preset = await service.createMemberPreset(presetInput('Implementer', ['impl']));
|
|
175
|
+
const task = await createTask();
|
|
176
|
+
const teamRun = await service.createTeamRun(task.id, {
|
|
177
|
+
mode: 'AUTO',
|
|
178
|
+
memberPresetIds: [preset.id, preset.id, preset.id],
|
|
179
|
+
});
|
|
180
|
+
expect(teamRun.members).toHaveLength(3);
|
|
181
|
+
expect(teamRun.members?.map((member) => member.name)).toEqual([
|
|
182
|
+
'Implementer #1',
|
|
183
|
+
'Implementer #2',
|
|
184
|
+
'Implementer #3',
|
|
185
|
+
]);
|
|
186
|
+
expect(teamRun.members?.map((member) => member.presetId)).toEqual([preset.id, preset.id, preset.id]);
|
|
187
|
+
expect(teamRun.members?.map((member) => member.aliases)).toEqual([['impl'], ['impl'], ['impl']]);
|
|
188
|
+
});
|
|
189
|
+
it('creates stable instance names for repeated explicit TeamRun members', async () => {
|
|
190
|
+
const task = await createTask();
|
|
191
|
+
const teamRun = await service.createTeamRun(task.id, {
|
|
192
|
+
mode: 'AUTO',
|
|
193
|
+
members: [
|
|
194
|
+
presetInput('Implementer', ['impl']),
|
|
195
|
+
presetInput('Implementer', ['impl']),
|
|
196
|
+
],
|
|
197
|
+
});
|
|
198
|
+
expect(teamRun.members).toHaveLength(2);
|
|
199
|
+
expect(teamRun.members?.map((member) => member.name)).toEqual([
|
|
200
|
+
'Implementer #1',
|
|
201
|
+
'Implementer #2',
|
|
202
|
+
]);
|
|
203
|
+
expect(teamRun.members?.map((member) => member.presetId)).toEqual([null, null]);
|
|
204
|
+
});
|
|
205
|
+
it('creates stable instance names across TeamTemplate and explicit MemberPreset duplicates', async () => {
|
|
206
|
+
const preset = await service.createMemberPreset(presetInput('Implementer', ['impl']));
|
|
207
|
+
const task = await createTask();
|
|
208
|
+
const template = await service.createTeamTemplate({
|
|
209
|
+
name: 'Three implementers',
|
|
210
|
+
memberPresetIds: [preset.id, preset.id],
|
|
211
|
+
});
|
|
212
|
+
const teamRun = await service.createTeamRun(task.id, {
|
|
213
|
+
mode: 'AUTO',
|
|
214
|
+
teamTemplateId: template.id,
|
|
215
|
+
memberPresetIds: [preset.id],
|
|
216
|
+
});
|
|
217
|
+
expect(teamRun.members).toHaveLength(3);
|
|
218
|
+
expect(teamRun.members?.map((member) => member.name)).toEqual([
|
|
219
|
+
'Implementer #1',
|
|
220
|
+
'Implementer #2',
|
|
221
|
+
'Implementer #3',
|
|
222
|
+
]);
|
|
223
|
+
});
|
|
224
|
+
it('keeps TeamMember snapshots unchanged after updating a MemberPreset', async () => {
|
|
225
|
+
const preset = await service.createMemberPreset(presetInput('Implementer', ['impl']));
|
|
226
|
+
const task = await createTask();
|
|
227
|
+
const teamRun = await service.createTeamRun(task.id, {
|
|
228
|
+
mode: 'AUTO',
|
|
229
|
+
memberPresetIds: [preset.id],
|
|
230
|
+
});
|
|
231
|
+
await service.updateMemberPreset(preset.id, {
|
|
232
|
+
name: 'Updated Implementer',
|
|
233
|
+
aliases: ['updated'],
|
|
234
|
+
});
|
|
235
|
+
const reloaded = await service.getTeamRunById(teamRun.id);
|
|
236
|
+
expect(reloaded.members?.[0]?.name).toBe('Implementer');
|
|
237
|
+
expect(reloaded.members?.[0]?.aliases).toEqual(['impl']);
|
|
238
|
+
});
|
|
239
|
+
it('creates a RoomMessage without WorkRequests when mentions are omitted', async () => {
|
|
240
|
+
const preset = await service.createMemberPreset(presetInput('Coder'));
|
|
241
|
+
const task = await createTask();
|
|
242
|
+
const teamRun = await service.createTeamRun(task.id, {
|
|
243
|
+
mode: 'AUTO',
|
|
244
|
+
memberPresetIds: [preset.id],
|
|
245
|
+
});
|
|
246
|
+
const message = await service.createRoomMessage(teamRun.id, {
|
|
247
|
+
content: 'General note',
|
|
248
|
+
});
|
|
249
|
+
expect(message.senderType).toBe('user');
|
|
250
|
+
expect(message.kind).toBe('chat');
|
|
251
|
+
expect(message.mentions).toEqual([]);
|
|
252
|
+
expect(message.workRequestIds).toEqual([]);
|
|
253
|
+
await expect(service.listWorkRequests(teamRun.id)).resolves.toEqual([]);
|
|
254
|
+
});
|
|
255
|
+
it('creates WorkRequests for USER_MESSAGES members when a user message has no mentions', async () => {
|
|
256
|
+
const leaderPreset = await service.createMemberPreset(userMessagesPresetInput('Leader'));
|
|
257
|
+
const coderPreset = await service.createMemberPreset(presetInput('Coder'));
|
|
258
|
+
const task = await createTask();
|
|
259
|
+
const teamRun = await service.createTeamRun(task.id, {
|
|
260
|
+
mode: 'AUTO',
|
|
261
|
+
memberPresetIds: [leaderPreset.id, coderPreset.id],
|
|
262
|
+
});
|
|
263
|
+
const leader = teamRun.members?.find((member) => member.name === 'Leader');
|
|
264
|
+
expect(leader).toBeDefined();
|
|
265
|
+
const message = await service.createRoomMessage(teamRun.id, {
|
|
266
|
+
content: 'General user request',
|
|
267
|
+
});
|
|
268
|
+
const requests = await service.listWorkRequests(teamRun.id);
|
|
269
|
+
expect(message.kind).toBe('chat');
|
|
270
|
+
expect(message.mentions).toEqual([]);
|
|
271
|
+
expect(message.workRequestIds).toEqual([requests[0]?.id]);
|
|
272
|
+
expect(requests).toHaveLength(1);
|
|
273
|
+
expect(requests[0]).toMatchObject({
|
|
274
|
+
requesterType: 'user',
|
|
275
|
+
requesterMemberId: null,
|
|
276
|
+
targetMemberId: leader.id,
|
|
277
|
+
triggerMessageId: message.id,
|
|
278
|
+
instruction: 'General user request',
|
|
279
|
+
ifBusy: 'queue',
|
|
280
|
+
cancelQueued: false,
|
|
281
|
+
status: 'QUEUED',
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
it('creates WorkRequests from RoomMessage mentions and writes workRequestIds back', async () => {
|
|
285
|
+
const preset = await service.createMemberPreset(presetInput('Coder'));
|
|
286
|
+
const task = await createTask();
|
|
287
|
+
const teamRun = await service.createTeamRun(task.id, {
|
|
288
|
+
mode: 'CONFIRM',
|
|
289
|
+
memberPresetIds: [preset.id],
|
|
290
|
+
});
|
|
291
|
+
const member = teamRun.members?.[0];
|
|
292
|
+
expect(member).toBeDefined();
|
|
293
|
+
const message = await service.createRoomMessage(teamRun.id, {
|
|
294
|
+
content: 'Please implement this',
|
|
295
|
+
mentions: [{ memberId: member.id, ifBusy: 'cancel_current_and_start', cancelQueued: true }],
|
|
296
|
+
});
|
|
297
|
+
const requests = await service.listWorkRequests(teamRun.id);
|
|
298
|
+
expect(message.kind).toBe('work_request');
|
|
299
|
+
expect(message.workRequestIds).toEqual([requests[0]?.id]);
|
|
300
|
+
expect(requests).toHaveLength(1);
|
|
301
|
+
expect(requests[0]).toMatchObject({
|
|
302
|
+
requesterType: 'user',
|
|
303
|
+
requesterMemberId: null,
|
|
304
|
+
targetMemberId: member.id,
|
|
305
|
+
triggerMessageId: message.id,
|
|
306
|
+
instruction: 'Please implement this',
|
|
307
|
+
ifBusy: 'cancel_current_and_start',
|
|
308
|
+
cancelQueued: true,
|
|
309
|
+
status: 'PENDING_APPROVAL',
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
it('derives TeamMember status from active invocations and open WorkRequests', async () => {
|
|
313
|
+
const runningPreset = await service.createMemberPreset(presetInput('Runner'));
|
|
314
|
+
const pendingPreset = await service.createMemberPreset(presetInput('Pending'));
|
|
315
|
+
const queuedPreset = await service.createMemberPreset(presetInput('Queued'));
|
|
316
|
+
const task = await createTask();
|
|
317
|
+
const teamRun = await service.createTeamRun(task.id, {
|
|
318
|
+
mode: 'CONFIRM',
|
|
319
|
+
memberPresetIds: [runningPreset.id, pendingPreset.id, queuedPreset.id],
|
|
320
|
+
});
|
|
321
|
+
const [runningMember, pendingMember, queuedMember] = teamRun.members ?? [];
|
|
322
|
+
expect(runningMember).toBeDefined();
|
|
323
|
+
expect(pendingMember).toBeDefined();
|
|
324
|
+
expect(queuedMember).toBeDefined();
|
|
325
|
+
const workspace = await prisma.workspace.create({
|
|
326
|
+
data: {
|
|
327
|
+
taskId: task.id,
|
|
328
|
+
branchName: 'team-shared-status',
|
|
329
|
+
worktreePath: testDir,
|
|
330
|
+
status: 'ACTIVE',
|
|
331
|
+
},
|
|
332
|
+
});
|
|
333
|
+
const runningRequest = await prisma.workRequest.create({
|
|
334
|
+
data: {
|
|
335
|
+
teamRunId: teamRun.id,
|
|
336
|
+
requesterMemberId: null,
|
|
337
|
+
requesterType: 'user',
|
|
338
|
+
targetMemberId: runningMember.id,
|
|
339
|
+
triggerMessageId: 'running-trigger',
|
|
340
|
+
instruction: 'Running work',
|
|
341
|
+
status: 'STARTED',
|
|
342
|
+
},
|
|
343
|
+
});
|
|
344
|
+
await prisma.agentInvocation.create({
|
|
345
|
+
data: {
|
|
346
|
+
teamRunId: teamRun.id,
|
|
347
|
+
workRequestId: runningRequest.id,
|
|
348
|
+
memberId: runningMember.id,
|
|
349
|
+
workspaceId: workspace.id,
|
|
350
|
+
sessionId: null,
|
|
351
|
+
status: 'RUNNING',
|
|
352
|
+
},
|
|
353
|
+
});
|
|
354
|
+
await prisma.workRequest.create({
|
|
355
|
+
data: {
|
|
356
|
+
teamRunId: teamRun.id,
|
|
357
|
+
requesterMemberId: null,
|
|
358
|
+
requesterType: 'user',
|
|
359
|
+
targetMemberId: pendingMember.id,
|
|
360
|
+
triggerMessageId: 'pending-trigger',
|
|
361
|
+
instruction: 'Pending work',
|
|
362
|
+
status: 'PENDING_APPROVAL',
|
|
363
|
+
},
|
|
364
|
+
});
|
|
365
|
+
await prisma.workRequest.create({
|
|
366
|
+
data: {
|
|
367
|
+
teamRunId: teamRun.id,
|
|
368
|
+
requesterMemberId: null,
|
|
369
|
+
requesterType: 'user',
|
|
370
|
+
targetMemberId: queuedMember.id,
|
|
371
|
+
triggerMessageId: 'queued-trigger',
|
|
372
|
+
instruction: 'Queued work',
|
|
373
|
+
status: 'QUEUED',
|
|
374
|
+
},
|
|
375
|
+
});
|
|
376
|
+
const members = await service.listTeamMembers(teamRun.id);
|
|
377
|
+
const statuses = new Map(members.map((member) => [member.id, member.status]));
|
|
378
|
+
expect(statuses.get(runningMember.id)).toBe('RUNNING');
|
|
379
|
+
expect(statuses.get(pendingMember.id)).toBe('PENDING_APPROVAL');
|
|
380
|
+
expect(statuses.get(queuedMember.id)).toBe('QUEUED');
|
|
381
|
+
});
|
|
382
|
+
it('creates WorkRequests only for the selected memberId when same-name members are mentioned', async () => {
|
|
383
|
+
const task = await createTask();
|
|
384
|
+
const teamRun = await service.createTeamRun(task.id, {
|
|
385
|
+
mode: 'AUTO',
|
|
386
|
+
members: [
|
|
387
|
+
presetInput('Coder'),
|
|
388
|
+
presetInput('Coder'),
|
|
389
|
+
],
|
|
390
|
+
});
|
|
391
|
+
const [firstCoder, secondCoder] = teamRun.members ?? [];
|
|
392
|
+
expect(firstCoder).toBeDefined();
|
|
393
|
+
expect(secondCoder).toBeDefined();
|
|
394
|
+
const message = await service.createRoomMessage(teamRun.id, {
|
|
395
|
+
content: '@Coder please implement this',
|
|
396
|
+
mentions: [{ memberId: secondCoder.id, label: secondCoder.name }],
|
|
397
|
+
});
|
|
398
|
+
const requests = await service.listWorkRequests(teamRun.id);
|
|
399
|
+
expect(message.mentions).toEqual([{ memberId: secondCoder.id, label: secondCoder.name }]);
|
|
400
|
+
expect(message.workRequestIds).toEqual([requests[0]?.id]);
|
|
401
|
+
expect(requests).toHaveLength(1);
|
|
402
|
+
expect(requests[0]).toMatchObject({
|
|
403
|
+
targetMemberId: secondCoder.id,
|
|
404
|
+
triggerMessageId: message.id,
|
|
405
|
+
instruction: '@Coder please implement this',
|
|
406
|
+
status: 'QUEUED',
|
|
407
|
+
});
|
|
408
|
+
expect(requests[0]?.targetMemberId).not.toBe(firstCoder.id);
|
|
409
|
+
});
|
|
410
|
+
it('does not parse text @name into WorkRequests when structured mentions are omitted', async () => {
|
|
411
|
+
const task = await createTask();
|
|
412
|
+
const teamRun = await service.createTeamRun(task.id, {
|
|
413
|
+
mode: 'AUTO',
|
|
414
|
+
members: [
|
|
415
|
+
presetInput('Coder'),
|
|
416
|
+
presetInput('Coder'),
|
|
417
|
+
],
|
|
418
|
+
});
|
|
419
|
+
const message = await service.createRoomMessage(teamRun.id, {
|
|
420
|
+
content: '@Coder please implement this',
|
|
421
|
+
});
|
|
422
|
+
expect(message.mentions).toEqual([]);
|
|
423
|
+
expect(message.workRequestIds).toEqual([]);
|
|
424
|
+
await expect(service.listWorkRequests(teamRun.id)).resolves.toEqual([]);
|
|
425
|
+
});
|
|
426
|
+
it('does not create USER_MESSAGES WorkRequests when a user message already has mentions', async () => {
|
|
427
|
+
const leaderPreset = await service.createMemberPreset(userMessagesPresetInput('Leader'));
|
|
428
|
+
const coderPreset = await service.createMemberPreset(presetInput('Coder'));
|
|
429
|
+
const task = await createTask();
|
|
430
|
+
const teamRun = await service.createTeamRun(task.id, {
|
|
431
|
+
mode: 'CONFIRM',
|
|
432
|
+
memberPresetIds: [leaderPreset.id, coderPreset.id],
|
|
433
|
+
});
|
|
434
|
+
const coder = teamRun.members?.find((member) => member.name === 'Coder');
|
|
435
|
+
expect(coder).toBeDefined();
|
|
436
|
+
const message = await service.createRoomMessage(teamRun.id, {
|
|
437
|
+
content: 'Please implement this',
|
|
438
|
+
mentions: [{ memberId: coder.id }],
|
|
439
|
+
});
|
|
440
|
+
const requests = await service.listWorkRequests(teamRun.id);
|
|
441
|
+
expect(message.workRequestIds).toEqual([requests[0]?.id]);
|
|
442
|
+
expect(requests).toHaveLength(1);
|
|
443
|
+
expect(requests[0]?.targetMemberId).toBe(coder.id);
|
|
444
|
+
});
|
|
445
|
+
it('creates USER_MESSAGES WorkRequests for unmentioned agent messages from other members', async () => {
|
|
446
|
+
const leaderPreset = await service.createMemberPreset(userMessagesPresetInput('Leader'));
|
|
447
|
+
const coderPreset = await service.createMemberPreset(presetInput('Coder'));
|
|
448
|
+
const task = await createTask();
|
|
449
|
+
const teamRun = await service.createTeamRun(task.id, {
|
|
450
|
+
mode: 'AUTO',
|
|
451
|
+
memberPresetIds: [leaderPreset.id, coderPreset.id],
|
|
452
|
+
});
|
|
453
|
+
const leader = teamRun.members?.find((member) => member.name === 'Leader');
|
|
454
|
+
const coder = teamRun.members?.find((member) => member.name === 'Coder');
|
|
455
|
+
expect(leader).toBeDefined();
|
|
456
|
+
expect(coder).toBeDefined();
|
|
457
|
+
const message = await service.createRoomMessage(teamRun.id, {
|
|
458
|
+
content: 'Agent result',
|
|
459
|
+
senderType: 'agent',
|
|
460
|
+
senderId: coder.id,
|
|
461
|
+
});
|
|
462
|
+
const requests = await service.listWorkRequests(teamRun.id);
|
|
463
|
+
expect(message.workRequestIds).toEqual([requests[0]?.id]);
|
|
464
|
+
expect(requests).toHaveLength(1);
|
|
465
|
+
expect(requests[0]).toMatchObject({
|
|
466
|
+
requesterType: 'agent',
|
|
467
|
+
requesterMemberId: coder.id,
|
|
468
|
+
targetMemberId: leader.id,
|
|
469
|
+
triggerMessageId: message.id,
|
|
470
|
+
instruction: 'Agent result',
|
|
471
|
+
ifBusy: 'queue',
|
|
472
|
+
cancelQueued: false,
|
|
473
|
+
status: 'QUEUED',
|
|
474
|
+
});
|
|
475
|
+
});
|
|
476
|
+
it('does not create USER_MESSAGES WorkRequests for the sending member itself', async () => {
|
|
477
|
+
const leaderPreset = await service.createMemberPreset(userMessagesPresetInput('Leader'));
|
|
478
|
+
const task = await createTask();
|
|
479
|
+
const teamRun = await service.createTeamRun(task.id, {
|
|
480
|
+
mode: 'AUTO',
|
|
481
|
+
memberPresetIds: [leaderPreset.id],
|
|
482
|
+
});
|
|
483
|
+
const leader = teamRun.members?.find((member) => member.name === 'Leader');
|
|
484
|
+
expect(leader).toBeDefined();
|
|
485
|
+
const message = await service.createRoomMessage(teamRun.id, {
|
|
486
|
+
content: 'Leader summary',
|
|
487
|
+
senderType: 'agent',
|
|
488
|
+
senderId: leader.id,
|
|
489
|
+
});
|
|
490
|
+
expect(message.workRequestIds).toEqual([]);
|
|
491
|
+
await expect(service.listWorkRequests(teamRun.id)).resolves.toEqual([]);
|
|
492
|
+
});
|
|
493
|
+
it('queues WorkRequests in AUTO mode and preserves senderInvocationId', async () => {
|
|
494
|
+
const preset = await service.createMemberPreset(presetInput('Reviewer'));
|
|
495
|
+
const task = await createTask();
|
|
496
|
+
const teamRun = await service.createTeamRun(task.id, {
|
|
497
|
+
mode: 'AUTO',
|
|
498
|
+
memberPresetIds: [preset.id],
|
|
499
|
+
});
|
|
500
|
+
const member = teamRun.members?.[0];
|
|
501
|
+
expect(member).toBeDefined();
|
|
502
|
+
const workspace = await prisma.workspace.create({
|
|
503
|
+
data: {
|
|
504
|
+
taskId: task.id,
|
|
505
|
+
branchName: 'team-shared',
|
|
506
|
+
worktreePath: testDir,
|
|
507
|
+
status: 'ACTIVE',
|
|
508
|
+
},
|
|
509
|
+
});
|
|
510
|
+
const request = await prisma.workRequest.create({
|
|
511
|
+
data: {
|
|
512
|
+
teamRunId: teamRun.id,
|
|
513
|
+
requesterMemberId: null,
|
|
514
|
+
requesterType: 'user',
|
|
515
|
+
targetMemberId: member.id,
|
|
516
|
+
triggerMessageId: 'trigger-message-1',
|
|
517
|
+
instruction: 'Original work',
|
|
518
|
+
status: 'STARTED',
|
|
519
|
+
},
|
|
520
|
+
});
|
|
521
|
+
const session = await prisma.session.create({
|
|
522
|
+
data: {
|
|
523
|
+
workspaceId: workspace.id,
|
|
524
|
+
agentType: 'CODEX',
|
|
525
|
+
providerId: member.providerId,
|
|
526
|
+
prompt: 'Do the work',
|
|
527
|
+
status: 'RUNNING',
|
|
528
|
+
},
|
|
529
|
+
});
|
|
530
|
+
const invocation = await prisma.agentInvocation.create({
|
|
531
|
+
data: {
|
|
532
|
+
teamRunId: teamRun.id,
|
|
533
|
+
workRequestId: request.id,
|
|
534
|
+
memberId: member.id,
|
|
535
|
+
workspaceId: workspace.id,
|
|
536
|
+
sessionId: session.id,
|
|
537
|
+
status: 'RUNNING',
|
|
538
|
+
},
|
|
539
|
+
});
|
|
540
|
+
const message = await service.createRoomMessage(teamRun.id, {
|
|
541
|
+
content: 'Review this change',
|
|
542
|
+
mentions: [{ memberId: member.id }],
|
|
543
|
+
senderType: 'agent',
|
|
544
|
+
senderId: member.id,
|
|
545
|
+
senderInvocationId: invocation.id,
|
|
546
|
+
});
|
|
547
|
+
const requests = await service.listWorkRequests(teamRun.id);
|
|
548
|
+
const createdRequest = requests.find((item) => item.triggerMessageId === message.id);
|
|
549
|
+
expect(message.senderInvocationId).toBe(invocation.id);
|
|
550
|
+
expect(createdRequest).toMatchObject({
|
|
551
|
+
requesterType: 'agent',
|
|
552
|
+
requesterMemberId: member.id,
|
|
553
|
+
status: 'QUEUED',
|
|
554
|
+
});
|
|
555
|
+
});
|
|
556
|
+
it('rejects agent RoomMessages when senderInvocationId does not belong to the sender', async () => {
|
|
557
|
+
const senderPreset = await service.createMemberPreset(presetInput('Sender'));
|
|
558
|
+
const otherPreset = await service.createMemberPreset(presetInput('Other'));
|
|
559
|
+
const task = await createTask();
|
|
560
|
+
const teamRun = await service.createTeamRun(task.id, {
|
|
561
|
+
mode: 'AUTO',
|
|
562
|
+
memberPresetIds: [senderPreset.id, otherPreset.id],
|
|
563
|
+
});
|
|
564
|
+
const sender = teamRun.members?.find((member) => member.name === 'Sender');
|
|
565
|
+
const other = teamRun.members?.find((member) => member.name === 'Other');
|
|
566
|
+
expect(sender).toBeDefined();
|
|
567
|
+
expect(other).toBeDefined();
|
|
568
|
+
const workspace = await prisma.workspace.create({
|
|
569
|
+
data: {
|
|
570
|
+
taskId: task.id,
|
|
571
|
+
branchName: 'team-shared',
|
|
572
|
+
worktreePath: testDir,
|
|
573
|
+
status: 'ACTIVE',
|
|
574
|
+
},
|
|
575
|
+
});
|
|
576
|
+
const request = await prisma.workRequest.create({
|
|
577
|
+
data: {
|
|
578
|
+
teamRunId: teamRun.id,
|
|
579
|
+
requesterMemberId: null,
|
|
580
|
+
requesterType: 'user',
|
|
581
|
+
targetMemberId: other.id,
|
|
582
|
+
triggerMessageId: 'trigger-message-2',
|
|
583
|
+
instruction: 'Original work',
|
|
584
|
+
status: 'STARTED',
|
|
585
|
+
},
|
|
586
|
+
});
|
|
587
|
+
const session = await prisma.session.create({
|
|
588
|
+
data: {
|
|
589
|
+
workspaceId: workspace.id,
|
|
590
|
+
agentType: 'CODEX',
|
|
591
|
+
providerId: other.providerId,
|
|
592
|
+
prompt: 'Do the work',
|
|
593
|
+
status: 'RUNNING',
|
|
594
|
+
},
|
|
595
|
+
});
|
|
596
|
+
const invocation = await prisma.agentInvocation.create({
|
|
597
|
+
data: {
|
|
598
|
+
teamRunId: teamRun.id,
|
|
599
|
+
workRequestId: request.id,
|
|
600
|
+
memberId: other.id,
|
|
601
|
+
workspaceId: workspace.id,
|
|
602
|
+
sessionId: session.id,
|
|
603
|
+
status: 'RUNNING',
|
|
604
|
+
},
|
|
605
|
+
});
|
|
606
|
+
await expect(service.createRoomMessage(teamRun.id, {
|
|
607
|
+
content: 'Spoofed result',
|
|
608
|
+
senderType: 'agent',
|
|
609
|
+
senderId: sender.id,
|
|
610
|
+
senderInvocationId: invocation.id,
|
|
611
|
+
})).rejects.toMatchObject({
|
|
612
|
+
code: 'VALIDATION_ERROR',
|
|
613
|
+
statusCode: 400,
|
|
614
|
+
});
|
|
615
|
+
});
|
|
616
|
+
it('saves and reads RoomMessage attachmentIds', async () => {
|
|
617
|
+
const preset = await service.createMemberPreset(presetInput('Coder'));
|
|
618
|
+
const task = await createTask();
|
|
619
|
+
const teamRun = await service.createTeamRun(task.id, {
|
|
620
|
+
mode: 'AUTO',
|
|
621
|
+
memberPresetIds: [preset.id],
|
|
622
|
+
});
|
|
623
|
+
const message = await service.createRoomMessage(teamRun.id, {
|
|
624
|
+
content: 'See attached',
|
|
625
|
+
attachmentIds: ['attachment-1', 'attachment-2'],
|
|
626
|
+
});
|
|
627
|
+
const messages = await service.listRoomMessages(teamRun.id);
|
|
628
|
+
expect(message.attachmentIds).toEqual(['attachment-1', 'attachment-2']);
|
|
629
|
+
expect(messages[0]?.attachmentIds).toEqual(['attachment-1', 'attachment-2']);
|
|
630
|
+
});
|
|
631
|
+
it('adds attachment markdown context to WorkRequest instructions from RoomMessage attachmentIds', async () => {
|
|
632
|
+
const preset = await service.createMemberPreset(presetInput('Coder'));
|
|
633
|
+
const task = await createTask();
|
|
634
|
+
const teamRun = await service.createTeamRun(task.id, {
|
|
635
|
+
mode: 'AUTO',
|
|
636
|
+
memberPresetIds: [preset.id],
|
|
637
|
+
});
|
|
638
|
+
const attachment = await prisma.attachment.create({
|
|
639
|
+
data: {
|
|
640
|
+
originalName: 'screenshot.png',
|
|
641
|
+
mimeType: 'image/png',
|
|
642
|
+
sizeBytes: 128,
|
|
643
|
+
storagePath: path.join(testDir, 'screenshot.png'),
|
|
644
|
+
hash: 'attachment-context-hash',
|
|
645
|
+
},
|
|
646
|
+
});
|
|
647
|
+
const message = await service.createRoomMessage(teamRun.id, {
|
|
648
|
+
content: 'Please inspect this UI',
|
|
649
|
+
mentions: [{ memberId: teamRun.members[0].id, label: 'Coder' }],
|
|
650
|
+
attachmentIds: [attachment.id],
|
|
651
|
+
});
|
|
652
|
+
const request = await prisma.workRequest.findUnique({
|
|
653
|
+
where: { id: message.workRequestIds[0] },
|
|
654
|
+
});
|
|
655
|
+
expect(request?.instruction).toBe(`Please inspect this UI\n\nAttachments:\n`);
|
|
656
|
+
});
|
|
657
|
+
it('does not duplicate WorkRequest attachment context when content already includes the storage path', async () => {
|
|
658
|
+
const preset = await service.createMemberPreset(presetInput('Coder'));
|
|
659
|
+
const task = await createTask();
|
|
660
|
+
const teamRun = await service.createTeamRun(task.id, {
|
|
661
|
+
mode: 'AUTO',
|
|
662
|
+
memberPresetIds: [preset.id],
|
|
663
|
+
});
|
|
664
|
+
const attachment = await prisma.attachment.create({
|
|
665
|
+
data: {
|
|
666
|
+
originalName: 'screenshot.png',
|
|
667
|
+
mimeType: 'image/png',
|
|
668
|
+
sizeBytes: 128,
|
|
669
|
+
storagePath: path.join(testDir, 'screenshot-dedup.png'),
|
|
670
|
+
hash: 'attachment-context-dedup-hash',
|
|
671
|
+
},
|
|
672
|
+
});
|
|
673
|
+
const content = `Please inspect this UI\n\n`;
|
|
674
|
+
const message = await service.createRoomMessage(teamRun.id, {
|
|
675
|
+
content,
|
|
676
|
+
mentions: [{ memberId: teamRun.members[0].id, label: 'Coder' }],
|
|
677
|
+
attachmentIds: [attachment.id],
|
|
678
|
+
});
|
|
679
|
+
const request = await prisma.workRequest.findUnique({
|
|
680
|
+
where: { id: message.workRequestIds[0] },
|
|
681
|
+
});
|
|
682
|
+
expect(request?.instruction).toBe(content);
|
|
683
|
+
expect(request?.instruction).not.toContain('Attachments:');
|
|
684
|
+
});
|
|
685
|
+
it('returns a clear conflict when creating a second TeamRun for one Task', async () => {
|
|
686
|
+
const preset = await service.createMemberPreset(presetInput('Coder'));
|
|
687
|
+
const task = await createTask();
|
|
688
|
+
const input = {
|
|
689
|
+
mode: 'AUTO',
|
|
690
|
+
memberPresetIds: [preset.id],
|
|
691
|
+
};
|
|
692
|
+
await service.createTeamRun(task.id, input);
|
|
693
|
+
await expect(service.createTeamRun(task.id, input)).rejects.toMatchObject({
|
|
694
|
+
code: 'CONFLICT',
|
|
695
|
+
statusCode: 409,
|
|
696
|
+
});
|
|
697
|
+
});
|
|
698
|
+
});
|
|
699
|
+
//# sourceMappingURL=team-run.service.test.js.map
|