agent-tower 0.6.0-beta.11 → 0.6.0-beta.13
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.d.ts.map +1 -1
- package/dist/app.js +4 -0
- package/dist/app.js.map +1 -1
- package/dist/cli.js +39 -19
- package/dist/cli.js.map +1 -1
- package/dist/executors/__tests__/base.executor.test.js +133 -1
- package/dist/executors/__tests__/base.executor.test.js.map +1 -1
- package/dist/executors/__tests__/stdin-prompt.executors.test.js +18 -0
- package/dist/executors/__tests__/stdin-prompt.executors.test.js.map +1 -1
- package/dist/executors/base.executor.d.ts +8 -0
- package/dist/executors/base.executor.d.ts.map +1 -1
- package/dist/executors/base.executor.js +336 -56
- package/dist/executors/base.executor.js.map +1 -1
- package/dist/executors/index.d.ts +2 -1
- package/dist/executors/index.d.ts.map +1 -1
- package/dist/executors/index.js +1 -1
- package/dist/executors/index.js.map +1 -1
- package/dist/executors/start-error.d.ts +17 -0
- package/dist/executors/start-error.d.ts.map +1 -1
- package/dist/executors/start-error.js +36 -1
- package/dist/executors/start-error.js.map +1 -1
- package/dist/index.js +39 -29
- package/dist/index.js.map +1 -1
- package/dist/mcp/http-client.test.js +20 -0
- package/dist/mcp/http-client.test.js.map +1 -1
- package/dist/mcp/tools/__tests__/sessions.test.d.ts +2 -0
- package/dist/mcp/tools/__tests__/sessions.test.d.ts.map +1 -0
- package/dist/mcp/tools/__tests__/sessions.test.js +33 -0
- package/dist/mcp/tools/__tests__/sessions.test.js.map +1 -0
- package/dist/routes/__tests__/sessions-runtime.test.js +75 -3
- package/dist/routes/__tests__/sessions-runtime.test.js.map +1 -1
- package/dist/routes/conversations.d.ts.map +1 -1
- package/dist/routes/conversations.js +5 -1
- package/dist/routes/conversations.js.map +1 -1
- package/dist/routes/sessions.d.ts.map +1 -1
- package/dist/routes/sessions.js +13 -2
- package/dist/routes/sessions.js.map +1 -1
- package/dist/routes/team-runs.d.ts.map +1 -1
- package/dist/routes/team-runs.js +13 -1
- package/dist/routes/team-runs.js.map +1 -1
- package/dist/runtime/__tests__/acp-driver-lifecycle.test.js +86 -0
- package/dist/runtime/__tests__/acp-driver-lifecycle.test.js.map +1 -1
- package/dist/runtime/__tests__/acp-process-manager.test.js +393 -6
- package/dist/runtime/__tests__/acp-process-manager.test.js.map +1 -1
- package/dist/runtime/__tests__/cli-driver-lifecycle.test.d.ts +2 -0
- package/dist/runtime/__tests__/cli-driver-lifecycle.test.d.ts.map +1 -0
- package/dist/runtime/__tests__/cli-driver-lifecycle.test.js +244 -0
- package/dist/runtime/__tests__/cli-driver-lifecycle.test.js.map +1 -0
- package/dist/runtime/__tests__/launch-cleanup-registry.test.d.ts +2 -0
- package/dist/runtime/__tests__/launch-cleanup-registry.test.d.ts.map +1 -0
- package/dist/runtime/__tests__/launch-cleanup-registry.test.js +154 -0
- package/dist/runtime/__tests__/launch-cleanup-registry.test.js.map +1 -0
- package/dist/runtime/__tests__/runtime-coordinator.test.js +117 -1
- package/dist/runtime/__tests__/runtime-coordinator.test.js.map +1 -1
- package/dist/runtime/__tests__/server-entry-shutdown.test.d.ts +2 -0
- package/dist/runtime/__tests__/server-entry-shutdown.test.d.ts.map +1 -0
- package/dist/runtime/__tests__/server-entry-shutdown.test.js +38 -0
- package/dist/runtime/__tests__/server-entry-shutdown.test.js.map +1 -0
- package/dist/runtime/__tests__/shutdown-coordinator.test.d.ts +2 -0
- package/dist/runtime/__tests__/shutdown-coordinator.test.d.ts.map +1 -0
- package/dist/runtime/__tests__/shutdown-coordinator.test.js +28 -0
- package/dist/runtime/__tests__/shutdown-coordinator.test.js.map +1 -0
- package/dist/runtime/acp/acp-driver.d.ts.map +1 -1
- package/dist/runtime/acp/acp-driver.js +108 -35
- package/dist/runtime/acp/acp-driver.js.map +1 -1
- package/dist/runtime/acp/agents/managed-directory.d.ts.map +1 -1
- package/dist/runtime/acp/agents/managed-directory.js +3 -1
- package/dist/runtime/acp/agents/managed-directory.js.map +1 -1
- package/dist/runtime/acp/agents/managed-directory.test.d.ts +2 -0
- package/dist/runtime/acp/agents/managed-directory.test.d.ts.map +1 -0
- package/dist/runtime/acp/agents/managed-directory.test.js +25 -0
- package/dist/runtime/acp/agents/managed-directory.test.js.map +1 -0
- package/dist/runtime/acp/launch-cleanup-registry.d.ts +32 -0
- package/dist/runtime/acp/launch-cleanup-registry.d.ts.map +1 -0
- package/dist/runtime/acp/launch-cleanup-registry.js +176 -0
- package/dist/runtime/acp/launch-cleanup-registry.js.map +1 -0
- package/dist/runtime/acp/process-manager.d.ts +58 -0
- package/dist/runtime/acp/process-manager.d.ts.map +1 -1
- package/dist/runtime/acp/process-manager.js +489 -33
- package/dist/runtime/acp/process-manager.js.map +1 -1
- package/dist/runtime/cli-driver.d.ts.map +1 -1
- package/dist/runtime/cli-driver.js +230 -46
- package/dist/runtime/cli-driver.js.map +1 -1
- package/dist/runtime/contracts.d.ts +21 -2
- package/dist/runtime/contracts.d.ts.map +1 -1
- package/dist/runtime/index.d.ts +1 -0
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/index.js +1 -0
- package/dist/runtime/index.js.map +1 -1
- package/dist/runtime/runtime-coordinator.d.ts +8 -1
- package/dist/runtime/runtime-coordinator.d.ts.map +1 -1
- package/dist/runtime/runtime-coordinator.js +118 -18
- package/dist/runtime/runtime-coordinator.js.map +1 -1
- package/dist/runtime/server-entry-shutdown.d.ts +12 -0
- package/dist/runtime/server-entry-shutdown.d.ts.map +1 -0
- package/dist/runtime/server-entry-shutdown.js +23 -0
- package/dist/runtime/server-entry-shutdown.js.map +1 -0
- package/dist/runtime/shutdown-coordinator.d.ts +17 -0
- package/dist/runtime/shutdown-coordinator.d.ts.map +1 -0
- package/dist/runtime/shutdown-coordinator.js +67 -0
- package/dist/runtime/shutdown-coordinator.js.map +1 -0
- package/dist/services/__tests__/conversation.service.test.js +160 -0
- package/dist/services/__tests__/conversation.service.test.js.map +1 -1
- package/dist/services/__tests__/database-maintenance.service.test.js +97 -0
- package/dist/services/__tests__/database-maintenance.service.test.js.map +1 -1
- package/dist/services/__tests__/member-heartbeat-scheduler.test.js +6 -0
- package/dist/services/__tests__/member-heartbeat-scheduler.test.js.map +1 -1
- package/dist/services/__tests__/member-heartbeat.test.js +7 -11
- package/dist/services/__tests__/member-heartbeat.test.js.map +1 -1
- package/dist/services/__tests__/session-manager.lifecycle.test.js +402 -12
- package/dist/services/__tests__/session-manager.lifecycle.test.js.map +1 -1
- package/dist/services/__tests__/session-manager.team-run.test.js +422 -29
- package/dist/services/__tests__/session-manager.team-run.test.js.map +1 -1
- package/dist/services/__tests__/team-queue-recovery.integration.test.js +1 -0
- package/dist/services/__tests__/team-queue-recovery.integration.test.js.map +1 -1
- package/dist/services/__tests__/team-reconciler.service.test.js +1282 -4
- package/dist/services/__tests__/team-reconciler.service.test.js.map +1 -1
- package/dist/services/__tests__/team-run.service.test.js +161 -1
- package/dist/services/__tests__/team-run.service.test.js.map +1 -1
- package/dist/services/__tests__/team-scheduler.service.test.js +334 -6
- package/dist/services/__tests__/team-scheduler.service.test.js.map +1 -1
- package/dist/services/conversation.service.d.ts.map +1 -1
- package/dist/services/conversation.service.js +21 -2
- package/dist/services/conversation.service.js.map +1 -1
- package/dist/services/database-maintenance.service.d.ts.map +1 -1
- package/dist/services/database-maintenance.service.js +68 -17
- package/dist/services/database-maintenance.service.js.map +1 -1
- package/dist/services/member-heartbeat-scheduler.d.ts +6 -5
- package/dist/services/member-heartbeat-scheduler.d.ts.map +1 -1
- package/dist/services/member-heartbeat-scheduler.js +7 -5
- package/dist/services/member-heartbeat-scheduler.js.map +1 -1
- package/dist/services/session-manager.d.ts +124 -14
- package/dist/services/session-manager.d.ts.map +1 -1
- package/dist/services/session-manager.js +1035 -116
- package/dist/services/session-manager.js.map +1 -1
- package/dist/services/session-runtime-cleanup-gate.d.ts +25 -0
- package/dist/services/session-runtime-cleanup-gate.d.ts.map +1 -0
- package/dist/services/session-runtime-cleanup-gate.js +284 -0
- package/dist/services/session-runtime-cleanup-gate.js.map +1 -0
- package/dist/services/task.service.d.ts +2 -2
- package/dist/services/team-member-admission-barrier.d.ts +3 -0
- package/dist/services/team-member-admission-barrier.d.ts.map +1 -0
- package/dist/services/team-member-admission-barrier.js +26 -0
- package/dist/services/team-member-admission-barrier.js.map +1 -0
- package/dist/services/team-reconciler.service.d.ts +30 -7
- package/dist/services/team-reconciler.service.d.ts.map +1 -1
- package/dist/services/team-reconciler.service.js +528 -111
- package/dist/services/team-reconciler.service.js.map +1 -1
- package/dist/services/team-run.service.d.ts.map +1 -1
- package/dist/services/team-run.service.js +37 -13
- package/dist/services/team-run.service.js.map +1 -1
- package/dist/services/team-scheduler.service.d.ts +8 -6
- package/dist/services/team-scheduler.service.d.ts.map +1 -1
- package/dist/services/team-scheduler.service.js +323 -120
- package/dist/services/team-scheduler.service.js.map +1 -1
- package/dist/services/workspace-background-process-manager.d.ts.map +1 -1
- package/dist/services/workspace-background-process-manager.js +2 -5
- package/dist/services/workspace-background-process-manager.js.map +1 -1
- package/dist/services/workspace.service.d.ts +15 -8
- package/dist/services/workspace.service.d.ts.map +1 -1
- package/dist/socket/index.d.ts.map +1 -1
- package/dist/socket/index.js +27 -14
- package/dist/socket/index.js.map +1 -1
- package/dist/utils/__tests__/unix-process-identity.test.d.ts +2 -0
- package/dist/utils/__tests__/unix-process-identity.test.d.ts.map +1 -0
- package/dist/utils/__tests__/unix-process-identity.test.js +73 -0
- package/dist/utils/__tests__/unix-process-identity.test.js.map +1 -0
- package/dist/utils/error-log.d.ts +2 -0
- package/dist/utils/error-log.d.ts.map +1 -1
- package/dist/utils/error-log.js +40 -3
- package/dist/utils/error-log.js.map +1 -1
- package/dist/utils/error-log.test.js +14 -2
- package/dist/utils/error-log.test.js.map +1 -1
- package/dist/utils/process-launch.d.ts +2 -2
- package/dist/utils/process-launch.d.ts.map +1 -1
- package/dist/utils/process-launch.js +339 -79
- package/dist/utils/process-launch.js.map +1 -1
- package/dist/utils/process-launch.test.js +168 -10
- package/dist/utils/process-launch.test.js.map +1 -1
- package/dist/utils/spawned-process-identity.d.ts +7 -0
- package/dist/utils/spawned-process-identity.d.ts.map +1 -0
- package/dist/utils/spawned-process-identity.js +36 -0
- package/dist/utils/spawned-process-identity.js.map +1 -0
- package/dist/utils/tree-cleanup-channel.d.ts +18 -0
- package/dist/utils/tree-cleanup-channel.d.ts.map +1 -0
- package/dist/utils/tree-cleanup-channel.js +16 -0
- package/dist/utils/tree-cleanup-channel.js.map +1 -0
- package/dist/utils/tree-cleanup-channel.test.d.ts +2 -0
- package/dist/utils/tree-cleanup-channel.test.d.ts.map +1 -0
- package/dist/utils/tree-cleanup-channel.test.js +15 -0
- package/dist/utils/tree-cleanup-channel.test.js.map +1 -0
- package/dist/utils/unix-process-identity.d.ts +13 -1
- package/dist/utils/unix-process-identity.d.ts.map +1 -1
- package/dist/utils/unix-process-identity.js +146 -25
- package/dist/utils/unix-process-identity.js.map +1 -1
- package/node_modules/@agent-tower/shared/dist/types.d.ts +1 -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/package.json +1 -1
- package/prisma/migrations/20260824000000_add_execution_process_ownership/migration.sql +10 -0
- package/prisma/migrations/20260824010000_add_runtime_cleanup_recovery/migration.sql +10 -0
- package/prisma/migrations/20260824020000_add_runtime_launch_admission/migration.sql +72 -0
- package/prisma/migrations/20260826000000_add_conversation_turn_queue/migration.sql +19 -0
- package/prisma/schema.prisma +50 -7
|
@@ -7,6 +7,7 @@ import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vites
|
|
|
7
7
|
import { AgentType, SessionStatus } from '../../types/index.js';
|
|
8
8
|
import { EventBus } from '../../core/event-bus.js';
|
|
9
9
|
import { withWorkspaceBackgroundServicePolicy } from '../../prompts/workspace-background-service-policy.js';
|
|
10
|
+
import { appendAgentOutputIntentInstructions } from '../../prompts/agent-output-intents.js';
|
|
10
11
|
import { TeamLockService } from '../team-lock.service.js';
|
|
11
12
|
import { AGENT_SUBPROCESS_BLOCKED_ENV_KEYS, AGENT_TOWER_MCP_IDENTITY_ENV_KEYS, AGENT_TOWER_MCP_SERVICE_ENV_KEYS, } from '../../executors/execution-env.js';
|
|
12
13
|
import { AGENT_API_CREDENTIAL_ENV, validateAgentApiCredential, } from '../../utils/agent-api-credential.js';
|
|
@@ -122,15 +123,39 @@ function expectServiceEnvFiltered(fullEnv) {
|
|
|
122
123
|
expect(validateAgentApiCredential(fullEnv[AGENT_API_CREDENTIAL_ENV])).toMatchObject({ sessionId: fullEnv.AGENT_TOWER_SESSION_ID });
|
|
123
124
|
}
|
|
124
125
|
function createPty() {
|
|
126
|
+
let exited = false;
|
|
127
|
+
let exitListeners = [];
|
|
128
|
+
const emitExit = (event) => {
|
|
129
|
+
if (exited)
|
|
130
|
+
return;
|
|
131
|
+
exited = true;
|
|
132
|
+
for (const listener of [...exitListeners])
|
|
133
|
+
listener(event);
|
|
134
|
+
};
|
|
135
|
+
const kill = vi.fn(() => emitExit({ exitCode: 0 }));
|
|
125
136
|
return {
|
|
126
137
|
pid: 12345,
|
|
127
138
|
onData: vi.fn(() => ({ dispose: vi.fn() })),
|
|
128
|
-
onExit: vi.fn((
|
|
129
|
-
|
|
130
|
-
|
|
139
|
+
onExit: vi.fn((listener) => {
|
|
140
|
+
exitListeners.push(listener);
|
|
141
|
+
return {
|
|
142
|
+
dispose: vi.fn(() => {
|
|
143
|
+
exitListeners = exitListeners.filter((candidate) => candidate !== listener);
|
|
144
|
+
}),
|
|
145
|
+
};
|
|
146
|
+
}),
|
|
131
147
|
write: vi.fn(),
|
|
132
148
|
resize: vi.fn(),
|
|
133
|
-
kill
|
|
149
|
+
kill,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
function spawnResult(pid, pty = createPty()) {
|
|
153
|
+
return {
|
|
154
|
+
pid,
|
|
155
|
+
processGroupId: String(pid),
|
|
156
|
+
birthMarker: `test-birth:${pid}`,
|
|
157
|
+
ownershipToken: `test-owner:${pid}`,
|
|
158
|
+
pty,
|
|
134
159
|
};
|
|
135
160
|
}
|
|
136
161
|
async function createWorkspace() {
|
|
@@ -183,14 +208,8 @@ describe('SessionManager TeamRun env injection', () => {
|
|
|
183
208
|
beforeEach(async () => {
|
|
184
209
|
seedServiceEnv();
|
|
185
210
|
vi.clearAllMocks();
|
|
186
|
-
spawnMock.mockImplementation(async () => (
|
|
187
|
-
|
|
188
|
-
pty: createPty(),
|
|
189
|
-
}));
|
|
190
|
-
spawnFollowUpMock.mockImplementation(async () => ({
|
|
191
|
-
pid: 12346,
|
|
192
|
-
pty: createPty(),
|
|
193
|
-
}));
|
|
211
|
+
spawnMock.mockImplementation(async () => spawnResult(12345));
|
|
212
|
+
spawnFollowUpMock.mockImplementation(async () => spawnResult(12346));
|
|
194
213
|
manager = new SessionManager(new EventBus());
|
|
195
214
|
await prisma.executionProcess.deleteMany();
|
|
196
215
|
await prisma.agentInvocation.deleteMany();
|
|
@@ -261,6 +280,16 @@ describe('SessionManager TeamRun env injection', () => {
|
|
|
261
280
|
status: 'RUNNING',
|
|
262
281
|
},
|
|
263
282
|
});
|
|
283
|
+
spawnMock.mockImplementationOnce(async (config) => {
|
|
284
|
+
expect(config.env.toObject().AGENT_TOWER_SESSION_ID).toBe(session.id);
|
|
285
|
+
await expect(prisma.session.findUnique({ where: { id: session.id } })).resolves.toMatchObject({
|
|
286
|
+
status: SessionStatus.RUNNING,
|
|
287
|
+
runtimeLaunchState: 'CLAIMED',
|
|
288
|
+
runtimeLaunchClaimCount: 1,
|
|
289
|
+
runtimeLaunchResolvedCount: 0,
|
|
290
|
+
});
|
|
291
|
+
return spawnResult(12345);
|
|
292
|
+
});
|
|
264
293
|
await manager.start(session.id);
|
|
265
294
|
const spawnConfig = spawnMock.mock.calls[0][0];
|
|
266
295
|
expect(spawnConfig.env.toObject()).toMatchObject({
|
|
@@ -277,7 +306,74 @@ describe('SessionManager TeamRun env injection', () => {
|
|
|
277
306
|
AGENT_TOWER_TEAM_RUN_ID: teamRun.id,
|
|
278
307
|
AGENT_TOWER_MEMBER_ID: member.id,
|
|
279
308
|
});
|
|
280
|
-
|
|
309
|
+
await expect(prisma.session.findUnique({ where: { id: session.id } })).resolves.toMatchObject({
|
|
310
|
+
runtimeLaunchState: 'PROCESS_RECORDED',
|
|
311
|
+
runtimeLaunchClaimCount: 1,
|
|
312
|
+
runtimeLaunchResolvedCount: 1,
|
|
313
|
+
runtimeLaunchProcessCount: 1,
|
|
314
|
+
});
|
|
315
|
+
await manager.destroyAll();
|
|
316
|
+
});
|
|
317
|
+
it('revokes TeamRun dispatch before a direct Session stop waits for unresolved cleanup', async () => {
|
|
318
|
+
vi.spyOn(console, 'warn').mockImplementation(() => undefined);
|
|
319
|
+
const { task, workspace } = await createWorkspace();
|
|
320
|
+
const teamRun = await prisma.teamRun.create({ data: { taskId: task.id, mode: 'AUTO' } });
|
|
321
|
+
const member = await prisma.teamMember.create({
|
|
322
|
+
data: {
|
|
323
|
+
teamRunId: teamRun.id,
|
|
324
|
+
name: 'Direct stop member',
|
|
325
|
+
aliases: '[]',
|
|
326
|
+
providerId: 'codex-default',
|
|
327
|
+
rolePrompt: 'Role',
|
|
328
|
+
capabilities: '{}',
|
|
329
|
+
workspacePolicy: 'shared',
|
|
330
|
+
triggerPolicy: 'MENTION_ONLY',
|
|
331
|
+
},
|
|
332
|
+
});
|
|
333
|
+
const request = await prisma.workRequest.create({
|
|
334
|
+
data: {
|
|
335
|
+
teamRunId: teamRun.id,
|
|
336
|
+
requesterType: 'user',
|
|
337
|
+
targetMemberId: member.id,
|
|
338
|
+
triggerMessageId: 'direct-stop-cleanup-pending',
|
|
339
|
+
instruction: 'Run',
|
|
340
|
+
status: 'STARTED',
|
|
341
|
+
},
|
|
342
|
+
});
|
|
343
|
+
const session = await prisma.session.create({
|
|
344
|
+
data: {
|
|
345
|
+
workspaceId: workspace.id,
|
|
346
|
+
agentType: AgentType.CODEX,
|
|
347
|
+
providerId: 'codex-default',
|
|
348
|
+
prompt: 'prompt',
|
|
349
|
+
status: SessionStatus.RUNNING,
|
|
350
|
+
runtimeLaunchState: 'CLAIMED',
|
|
351
|
+
runtimeLaunchClaimCount: 1,
|
|
352
|
+
},
|
|
353
|
+
});
|
|
354
|
+
const invocation = await prisma.agentInvocation.create({
|
|
355
|
+
data: {
|
|
356
|
+
teamRunId: teamRun.id,
|
|
357
|
+
workRequestId: request.id,
|
|
358
|
+
memberId: member.id,
|
|
359
|
+
workspaceId: workspace.id,
|
|
360
|
+
sessionId: session.id,
|
|
361
|
+
status: 'RUNNING',
|
|
362
|
+
},
|
|
363
|
+
});
|
|
364
|
+
await manager.stop(session.id);
|
|
365
|
+
await expect(prisma.agentInvocation.findUniqueOrThrow({ where: { id: invocation.id } })).resolves.toMatchObject({
|
|
366
|
+
status: 'RUNNING',
|
|
367
|
+
dispatchRevokedAt: expect.any(Date),
|
|
368
|
+
});
|
|
369
|
+
await expect(prisma.workRequest.findUniqueOrThrow({ where: { id: request.id } })).resolves.toMatchObject({
|
|
370
|
+
status: 'STARTED',
|
|
371
|
+
});
|
|
372
|
+
await expect(prisma.session.findUniqueOrThrow({ where: { id: session.id } })).resolves.toMatchObject({
|
|
373
|
+
status: SessionStatus.RUNNING,
|
|
374
|
+
runtimeLaunchState: 'QUARANTINED',
|
|
375
|
+
runtimeLaunchResolvedCount: 0,
|
|
376
|
+
});
|
|
281
377
|
});
|
|
282
378
|
it('injects targeted test port env when the invocation has allocated ports', async () => {
|
|
283
379
|
const { task, workspace } = await createWorkspace();
|
|
@@ -352,7 +448,7 @@ describe('SessionManager TeamRun env injection', () => {
|
|
|
352
448
|
VITE_PORT: '21001',
|
|
353
449
|
E2E_PORT: '21002',
|
|
354
450
|
});
|
|
355
|
-
manager.destroyAll();
|
|
451
|
+
await manager.destroyAll();
|
|
356
452
|
});
|
|
357
453
|
it('injects only the workspace-bound session identity for a solo session', async () => {
|
|
358
454
|
const { workspace } = await createWorkspace();
|
|
@@ -377,7 +473,7 @@ describe('SessionManager TeamRun env injection', () => {
|
|
|
377
473
|
expect(fullEnv).not.toHaveProperty('AGENT_TOWER_INVOCATION_ID');
|
|
378
474
|
expect(fullEnv).not.toHaveProperty('AGENT_TOWER_TEAM_RUN_ID');
|
|
379
475
|
expect(fullEnv).not.toHaveProperty('AGENT_TOWER_MEMBER_ID');
|
|
380
|
-
manager.destroyAll();
|
|
476
|
+
await manager.destroyAll();
|
|
381
477
|
});
|
|
382
478
|
it('starts a new Tower session as an executor follow-up while injecting the new invocation env', async () => {
|
|
383
479
|
const { task, workspace } = await createWorkspace();
|
|
@@ -441,6 +537,16 @@ describe('SessionManager TeamRun env injection', () => {
|
|
|
441
537
|
status: 'RUNNING',
|
|
442
538
|
},
|
|
443
539
|
});
|
|
540
|
+
spawnFollowUpMock.mockImplementationOnce(async (config) => {
|
|
541
|
+
expect(config.env.toObject().AGENT_TOWER_SESSION_ID).toBe(nextSession.id);
|
|
542
|
+
await expect(prisma.session.findUnique({ where: { id: nextSession.id } })).resolves.toMatchObject({
|
|
543
|
+
status: SessionStatus.RUNNING,
|
|
544
|
+
runtimeLaunchState: 'CLAIMED',
|
|
545
|
+
runtimeLaunchClaimCount: 1,
|
|
546
|
+
runtimeLaunchResolvedCount: 0,
|
|
547
|
+
});
|
|
548
|
+
return spawnResult(12346);
|
|
549
|
+
});
|
|
444
550
|
const startTurnSpy = vi.spyOn(manager.runtimeCoordinator, 'startTurn');
|
|
445
551
|
await manager.startFollowUp(nextSession.id, previousSession.id);
|
|
446
552
|
expect(spawnFollowUpMock).toHaveBeenCalledTimes(1);
|
|
@@ -452,7 +558,7 @@ describe('SessionManager TeamRun env injection', () => {
|
|
|
452
558
|
expect(spawnFollowUpMock.mock.calls[0][1]).toBe('agent-native-session-1');
|
|
453
559
|
expect(spawnMock).not.toHaveBeenCalled();
|
|
454
560
|
const spawnConfig = spawnFollowUpMock.mock.calls[0][0];
|
|
455
|
-
expect(spawnConfig.prompt).toBe(withWorkspaceBackgroundServicePolicy('next prompt'));
|
|
561
|
+
expect(spawnConfig.prompt).toBe(appendAgentOutputIntentInstructions(withWorkspaceBackgroundServicePolicy('next prompt')));
|
|
456
562
|
expect(spawnConfig.env.toObject()).toMatchObject({
|
|
457
563
|
AGENT_TOWER_SESSION_ID: nextSession.id,
|
|
458
564
|
AGENT_TOWER_INVOCATION_ID: invocation.id,
|
|
@@ -469,10 +575,14 @@ describe('SessionManager TeamRun env injection', () => {
|
|
|
469
575
|
});
|
|
470
576
|
await expect(prisma.session.findUnique({ where: { id: nextSession.id } })).resolves.toMatchObject({
|
|
471
577
|
status: SessionStatus.RUNNING,
|
|
578
|
+
runtimeLaunchState: 'PROCESS_RECORDED',
|
|
579
|
+
runtimeLaunchClaimCount: 1,
|
|
580
|
+
runtimeLaunchResolvedCount: 1,
|
|
581
|
+
runtimeLaunchProcessCount: 1,
|
|
472
582
|
});
|
|
473
|
-
manager.destroyAll();
|
|
583
|
+
await manager.destroyAll();
|
|
474
584
|
});
|
|
475
|
-
it('
|
|
585
|
+
it('quarantines a spawned process when its process row cannot be written during session start', async () => {
|
|
476
586
|
const { task, workspace } = await createWorkspace();
|
|
477
587
|
const pty = createPty();
|
|
478
588
|
spawnMock.mockImplementationOnce(async () => {
|
|
@@ -480,7 +590,7 @@ describe('SessionManager TeamRun env injection', () => {
|
|
|
480
590
|
where: { id: task.id },
|
|
481
591
|
data: { deletedAt: new Date() },
|
|
482
592
|
});
|
|
483
|
-
return
|
|
593
|
+
return spawnResult(22345, pty);
|
|
484
594
|
});
|
|
485
595
|
const session = await prisma.session.create({
|
|
486
596
|
data: {
|
|
@@ -498,7 +608,10 @@ describe('SessionManager TeamRun env injection', () => {
|
|
|
498
608
|
expect(pty.kill).toHaveBeenCalled();
|
|
499
609
|
await expect(prisma.executionProcess.count({ where: { sessionId: session.id } })).resolves.toBe(0);
|
|
500
610
|
await expect(prisma.session.findUnique({ where: { id: session.id } })).resolves.toMatchObject({
|
|
501
|
-
status: SessionStatus.
|
|
611
|
+
status: SessionStatus.RUNNING,
|
|
612
|
+
runtimeLaunchState: 'QUARANTINED',
|
|
613
|
+
runtimeLaunchClaimCount: 1,
|
|
614
|
+
runtimeLaunchResolvedCount: 0,
|
|
502
615
|
});
|
|
503
616
|
});
|
|
504
617
|
it('normalizes a real CommandBuildError as a deterministic start error', async () => {
|
|
@@ -517,6 +630,13 @@ describe('SessionManager TeamRun env injection', () => {
|
|
|
517
630
|
code: 'AGENT_COMMAND_UNAVAILABLE',
|
|
518
631
|
statusCode: 400,
|
|
519
632
|
});
|
|
633
|
+
await expect(prisma.session.findUnique({ where: { id: session.id } })).resolves.toMatchObject({
|
|
634
|
+
status: SessionStatus.CANCELLED,
|
|
635
|
+
runtimeLaunchState: 'SAFE_PRE_CHILD_FAILURE',
|
|
636
|
+
runtimeLaunchClaimCount: 1,
|
|
637
|
+
runtimeLaunchResolvedCount: 1,
|
|
638
|
+
runtimeLaunchProcessCount: 0,
|
|
639
|
+
});
|
|
520
640
|
});
|
|
521
641
|
it('reports a missing executor as a deterministic start error', async () => {
|
|
522
642
|
const { workspace } = await createWorkspace();
|
|
@@ -646,7 +766,7 @@ describe('SessionManager TeamRun env injection', () => {
|
|
|
646
766
|
},
|
|
647
767
|
});
|
|
648
768
|
const pty = createPty();
|
|
649
|
-
spawnMock.mockResolvedValueOnce(
|
|
769
|
+
spawnMock.mockResolvedValueOnce(spawnResult(52345, pty));
|
|
650
770
|
await manager.start(session.id);
|
|
651
771
|
await expect(prisma.executionProcess.findFirst({ where: { sessionId: session.id } })).resolves.toMatchObject({
|
|
652
772
|
pid: 52345,
|
|
@@ -661,7 +781,163 @@ describe('SessionManager TeamRun env injection', () => {
|
|
|
661
781
|
});
|
|
662
782
|
});
|
|
663
783
|
});
|
|
664
|
-
it('
|
|
784
|
+
it('rejects an initial start after direct stop has cancelled the Session', async () => {
|
|
785
|
+
const { workspace } = await createWorkspace();
|
|
786
|
+
const session = await prisma.session.create({
|
|
787
|
+
data: {
|
|
788
|
+
workspaceId: workspace.id,
|
|
789
|
+
agentType: AgentType.CODEX,
|
|
790
|
+
providerId: 'codex-default',
|
|
791
|
+
prompt: 'cancelled before spawn',
|
|
792
|
+
status: SessionStatus.CANCELLED,
|
|
793
|
+
},
|
|
794
|
+
});
|
|
795
|
+
await expect(manager.start(session.id)).rejects.toMatchObject({
|
|
796
|
+
code: 'SESSION_NOT_ADMITTED',
|
|
797
|
+
});
|
|
798
|
+
expect(spawnMock).not.toHaveBeenCalled();
|
|
799
|
+
await expect(prisma.session.findUnique({ where: { id: session.id } })).resolves.toMatchObject({
|
|
800
|
+
status: SessionStatus.CANCELLED,
|
|
801
|
+
});
|
|
802
|
+
});
|
|
803
|
+
it('rejects a cross-Tower resume start after direct stop cancelled the new Session', async () => {
|
|
804
|
+
const { workspace } = await createWorkspace();
|
|
805
|
+
const previousSession = await prisma.session.create({
|
|
806
|
+
data: {
|
|
807
|
+
workspaceId: workspace.id,
|
|
808
|
+
agentType: AgentType.CODEX,
|
|
809
|
+
providerId: 'codex-default',
|
|
810
|
+
prompt: 'previous prompt',
|
|
811
|
+
status: SessionStatus.COMPLETED,
|
|
812
|
+
externalSessionId: 'native-resume-session',
|
|
813
|
+
},
|
|
814
|
+
});
|
|
815
|
+
const nextSession = await prisma.session.create({
|
|
816
|
+
data: {
|
|
817
|
+
workspaceId: workspace.id,
|
|
818
|
+
agentType: AgentType.CODEX,
|
|
819
|
+
providerId: 'codex-default',
|
|
820
|
+
prompt: 'cancelled resume',
|
|
821
|
+
status: SessionStatus.CANCELLED,
|
|
822
|
+
},
|
|
823
|
+
});
|
|
824
|
+
await expect(manager.startFollowUp(nextSession.id, previousSession.id)).rejects.toMatchObject({
|
|
825
|
+
code: 'SESSION_NOT_ADMITTED',
|
|
826
|
+
});
|
|
827
|
+
expect(spawnFollowUpMock).not.toHaveBeenCalled();
|
|
828
|
+
await expect(prisma.session.findUnique({ where: { id: nextSession.id } })).resolves.toMatchObject({
|
|
829
|
+
status: SessionStatus.CANCELLED,
|
|
830
|
+
});
|
|
831
|
+
});
|
|
832
|
+
it('does not spawn a real resume_last follow-up when pre-invocation direct stop wins', async () => {
|
|
833
|
+
const { task, workspace } = await createWorkspace();
|
|
834
|
+
const teamRun = await prisma.teamRun.create({ data: { taskId: task.id, mode: 'AUTO' } });
|
|
835
|
+
const member = await prisma.teamMember.create({
|
|
836
|
+
data: {
|
|
837
|
+
teamRunId: teamRun.id,
|
|
838
|
+
name: 'Resume member',
|
|
839
|
+
aliases: '[]',
|
|
840
|
+
providerId: 'codex-default',
|
|
841
|
+
rolePrompt: 'Continue prior work',
|
|
842
|
+
capabilities: JSON.stringify({
|
|
843
|
+
readRoom: true,
|
|
844
|
+
postRoomMessage: true,
|
|
845
|
+
mentionMembers: true,
|
|
846
|
+
stopMemberWork: false,
|
|
847
|
+
markReadyForReview: false,
|
|
848
|
+
readFiles: true,
|
|
849
|
+
writeFiles: true,
|
|
850
|
+
runCommands: false,
|
|
851
|
+
readDiff: true,
|
|
852
|
+
mergeWorkspace: false,
|
|
853
|
+
}),
|
|
854
|
+
workspacePolicy: 'shared',
|
|
855
|
+
triggerPolicy: 'MENTION_ONLY',
|
|
856
|
+
sessionPolicy: 'resume_last',
|
|
857
|
+
queueManagementPolicy: 'own_only',
|
|
858
|
+
},
|
|
859
|
+
});
|
|
860
|
+
const previousRequest = await prisma.workRequest.create({
|
|
861
|
+
data: {
|
|
862
|
+
teamRunId: teamRun.id,
|
|
863
|
+
requesterType: 'user',
|
|
864
|
+
targetMemberId: member.id,
|
|
865
|
+
triggerMessageId: 'previous-resume-request',
|
|
866
|
+
instruction: 'Previous work',
|
|
867
|
+
status: 'COMPLETED',
|
|
868
|
+
},
|
|
869
|
+
});
|
|
870
|
+
const previousSession = await prisma.session.create({
|
|
871
|
+
data: {
|
|
872
|
+
workspaceId: workspace.id,
|
|
873
|
+
agentType: AgentType.CODEX,
|
|
874
|
+
providerId: member.providerId,
|
|
875
|
+
prompt: 'previous prompt',
|
|
876
|
+
status: SessionStatus.COMPLETED,
|
|
877
|
+
externalSessionId: 'native-resume-context',
|
|
878
|
+
},
|
|
879
|
+
});
|
|
880
|
+
await prisma.agentInvocation.create({
|
|
881
|
+
data: {
|
|
882
|
+
teamRunId: teamRun.id,
|
|
883
|
+
workRequestId: previousRequest.id,
|
|
884
|
+
memberId: member.id,
|
|
885
|
+
workspaceId: workspace.id,
|
|
886
|
+
sessionId: previousSession.id,
|
|
887
|
+
status: 'COMPLETED',
|
|
888
|
+
},
|
|
889
|
+
});
|
|
890
|
+
const nextRequest = await prisma.workRequest.create({
|
|
891
|
+
data: {
|
|
892
|
+
teamRunId: teamRun.id,
|
|
893
|
+
requesterType: 'user',
|
|
894
|
+
targetMemberId: member.id,
|
|
895
|
+
triggerMessageId: 'next-resume-request',
|
|
896
|
+
instruction: 'Continue with the native context',
|
|
897
|
+
status: 'QUEUED',
|
|
898
|
+
},
|
|
899
|
+
});
|
|
900
|
+
let resolveSessionCreated;
|
|
901
|
+
const sessionCreated = new Promise((resolve) => {
|
|
902
|
+
resolveSessionCreated = resolve;
|
|
903
|
+
});
|
|
904
|
+
let allowCreateReturn;
|
|
905
|
+
const createReturn = new Promise((resolve) => {
|
|
906
|
+
allowCreateReturn = resolve;
|
|
907
|
+
});
|
|
908
|
+
const originalCreate = manager.create.bind(manager);
|
|
909
|
+
vi.spyOn(manager, 'create').mockImplementationOnce(async (...args) => {
|
|
910
|
+
const session = await originalCreate(...args);
|
|
911
|
+
resolveSessionCreated(session);
|
|
912
|
+
await createReturn;
|
|
913
|
+
return session;
|
|
914
|
+
});
|
|
915
|
+
const scheduler = new TeamSchedulerService(new TeamLockService(), {
|
|
916
|
+
workspaceService: { create: vi.fn(async () => workspace) },
|
|
917
|
+
sessionManager: manager,
|
|
918
|
+
getProviderById: vi.fn(() => ({
|
|
919
|
+
id: member.providerId,
|
|
920
|
+
name: 'Codex',
|
|
921
|
+
agentType: AgentType.CODEX,
|
|
922
|
+
env: {},
|
|
923
|
+
config: {},
|
|
924
|
+
isDefault: true,
|
|
925
|
+
})),
|
|
926
|
+
});
|
|
927
|
+
const starting = scheduler.startNextSessions(teamRun.id);
|
|
928
|
+
const createdSession = await sessionCreated;
|
|
929
|
+
await scheduler.stopSession(createdSession.id);
|
|
930
|
+
allowCreateReturn();
|
|
931
|
+
await expect(starting).resolves.toEqual([]);
|
|
932
|
+
expect(spawnFollowUpMock).not.toHaveBeenCalled();
|
|
933
|
+
await expect(prisma.session.findUnique({ where: { id: createdSession.id } })).resolves.toMatchObject({
|
|
934
|
+
status: SessionStatus.CANCELLED,
|
|
935
|
+
});
|
|
936
|
+
await expect(prisma.workRequest.findUnique({ where: { id: nextRequest.id } })).resolves.toMatchObject({
|
|
937
|
+
status: 'CANCELLED',
|
|
938
|
+
});
|
|
939
|
+
});
|
|
940
|
+
it('quarantines a spawned follow-up when its process row cannot be written', async () => {
|
|
665
941
|
const { task, workspace } = await createWorkspace();
|
|
666
942
|
const previousSession = await prisma.session.create({
|
|
667
943
|
data: {
|
|
@@ -688,7 +964,7 @@ describe('SessionManager TeamRun env injection', () => {
|
|
|
688
964
|
where: { id: task.id },
|
|
689
965
|
data: { deletedAt: new Date() },
|
|
690
966
|
});
|
|
691
|
-
return
|
|
967
|
+
return spawnResult(32345, pty);
|
|
692
968
|
});
|
|
693
969
|
await expect(manager.startFollowUp(nextSession.id, previousSession.id)).rejects.toMatchObject({
|
|
694
970
|
code: 'NOT_FOUND',
|
|
@@ -698,10 +974,13 @@ describe('SessionManager TeamRun env injection', () => {
|
|
|
698
974
|
expect(pty.kill).toHaveBeenCalled();
|
|
699
975
|
await expect(prisma.executionProcess.count({ where: { sessionId: nextSession.id } })).resolves.toBe(0);
|
|
700
976
|
await expect(prisma.session.findUnique({ where: { id: nextSession.id } })).resolves.toMatchObject({
|
|
701
|
-
status: SessionStatus.
|
|
977
|
+
status: SessionStatus.RUNNING,
|
|
978
|
+
runtimeLaunchState: 'QUARANTINED',
|
|
979
|
+
runtimeLaunchClaimCount: 1,
|
|
980
|
+
runtimeLaunchResolvedCount: 0,
|
|
702
981
|
});
|
|
703
982
|
});
|
|
704
|
-
it('
|
|
983
|
+
it('quarantines a spawned reply process when its process row cannot be written', async () => {
|
|
705
984
|
const { task, workspace } = await createWorkspace();
|
|
706
985
|
const session = await prisma.session.create({
|
|
707
986
|
data: {
|
|
@@ -718,7 +997,7 @@ describe('SessionManager TeamRun env injection', () => {
|
|
|
718
997
|
where: { id: task.id },
|
|
719
998
|
data: { deletedAt: new Date() },
|
|
720
999
|
});
|
|
721
|
-
return
|
|
1000
|
+
return spawnResult(42345, pty);
|
|
722
1001
|
});
|
|
723
1002
|
await expect(manager.sendMessage(session.id, 'continue')).rejects.toMatchObject({
|
|
724
1003
|
code: 'NOT_FOUND',
|
|
@@ -727,7 +1006,121 @@ describe('SessionManager TeamRun env injection', () => {
|
|
|
727
1006
|
expect(pty.kill).toHaveBeenCalled();
|
|
728
1007
|
await expect(prisma.executionProcess.count({ where: { sessionId: session.id } })).resolves.toBe(0);
|
|
729
1008
|
await expect(prisma.session.findUnique({ where: { id: session.id } })).resolves.toMatchObject({
|
|
730
|
-
status: SessionStatus.
|
|
1009
|
+
status: SessionStatus.RUNNING,
|
|
1010
|
+
runtimeLaunchState: 'QUARANTINED',
|
|
1011
|
+
runtimeLaunchClaimCount: 1,
|
|
1012
|
+
runtimeLaunchResolvedCount: 0,
|
|
1013
|
+
});
|
|
1014
|
+
});
|
|
1015
|
+
it('rejects a revoked TeamRun reminder before replacing the active turn', async () => {
|
|
1016
|
+
const { task, workspace } = await createWorkspace();
|
|
1017
|
+
const teamRun = await prisma.teamRun.create({ data: { taskId: task.id, mode: 'AUTO' } });
|
|
1018
|
+
const member = await prisma.teamMember.create({
|
|
1019
|
+
data: {
|
|
1020
|
+
teamRunId: teamRun.id,
|
|
1021
|
+
name: 'Reminder member',
|
|
1022
|
+
aliases: '[]',
|
|
1023
|
+
providerId: 'codex-default',
|
|
1024
|
+
rolePrompt: 'Role',
|
|
1025
|
+
capabilities: '{}',
|
|
1026
|
+
workspacePolicy: 'shared',
|
|
1027
|
+
triggerPolicy: 'MENTION_ONLY',
|
|
1028
|
+
sessionPolicy: 'new_per_request',
|
|
1029
|
+
queueManagementPolicy: 'own_only',
|
|
1030
|
+
},
|
|
1031
|
+
});
|
|
1032
|
+
const request = await prisma.workRequest.create({
|
|
1033
|
+
data: {
|
|
1034
|
+
teamRunId: teamRun.id,
|
|
1035
|
+
requesterType: 'user',
|
|
1036
|
+
targetMemberId: member.id,
|
|
1037
|
+
triggerMessageId: 'revoked-reminder-request',
|
|
1038
|
+
instruction: 'Run',
|
|
1039
|
+
status: 'STARTED',
|
|
1040
|
+
},
|
|
1041
|
+
});
|
|
1042
|
+
const session = await prisma.session.create({
|
|
1043
|
+
data: {
|
|
1044
|
+
workspaceId: workspace.id,
|
|
1045
|
+
agentType: AgentType.CODEX,
|
|
1046
|
+
providerId: 'codex-default',
|
|
1047
|
+
prompt: 'active prompt',
|
|
1048
|
+
status: SessionStatus.PENDING,
|
|
1049
|
+
},
|
|
1050
|
+
});
|
|
1051
|
+
const invocation = await prisma.agentInvocation.create({
|
|
1052
|
+
data: {
|
|
1053
|
+
teamRunId: teamRun.id,
|
|
1054
|
+
workRequestId: request.id,
|
|
1055
|
+
memberId: member.id,
|
|
1056
|
+
workspaceId: workspace.id,
|
|
1057
|
+
sessionId: session.id,
|
|
1058
|
+
status: 'RUNNING',
|
|
1059
|
+
},
|
|
1060
|
+
});
|
|
1061
|
+
const activePty = createPty();
|
|
1062
|
+
spawnMock.mockResolvedValueOnce({ pid: 43001, pty: activePty });
|
|
1063
|
+
await manager.start(session.id);
|
|
1064
|
+
await prisma.agentInvocation.update({
|
|
1065
|
+
where: { id: invocation.id },
|
|
1066
|
+
data: { dispatchRevokedAt: new Date() },
|
|
1067
|
+
});
|
|
1068
|
+
await expect(manager.sendMessage(session.id, 'late reminder', undefined, invocation.id)).rejects.toMatchObject({ code: 'SESSION_NOT_ADMITTED' });
|
|
1069
|
+
expect(spawnMock).toHaveBeenCalledTimes(1);
|
|
1070
|
+
expect(activePty.kill).not.toHaveBeenCalled();
|
|
1071
|
+
await expect(prisma.session.findUnique({ where: { id: session.id } })).resolves.toMatchObject({
|
|
1072
|
+
status: SessionStatus.RUNNING,
|
|
1073
|
+
});
|
|
1074
|
+
await manager.stop(session.id, { skipTeamRunReconcile: true });
|
|
1075
|
+
});
|
|
1076
|
+
it('rejects a direct follow-up bound to a terminal TeamRun invocation', async () => {
|
|
1077
|
+
const { task, workspace } = await createWorkspace();
|
|
1078
|
+
const teamRun = await prisma.teamRun.create({ data: { taskId: task.id, mode: 'AUTO' } });
|
|
1079
|
+
const member = await prisma.teamMember.create({
|
|
1080
|
+
data: {
|
|
1081
|
+
teamRunId: teamRun.id,
|
|
1082
|
+
name: 'Terminal member',
|
|
1083
|
+
aliases: '[]',
|
|
1084
|
+
providerId: 'codex-default',
|
|
1085
|
+
rolePrompt: 'Role',
|
|
1086
|
+
capabilities: '{}',
|
|
1087
|
+
workspacePolicy: 'shared',
|
|
1088
|
+
triggerPolicy: 'MENTION_ONLY',
|
|
1089
|
+
},
|
|
1090
|
+
});
|
|
1091
|
+
const request = await prisma.workRequest.create({
|
|
1092
|
+
data: {
|
|
1093
|
+
teamRunId: teamRun.id,
|
|
1094
|
+
requesterType: 'user',
|
|
1095
|
+
targetMemberId: member.id,
|
|
1096
|
+
triggerMessageId: 'terminal-direct-send',
|
|
1097
|
+
instruction: 'Run',
|
|
1098
|
+
status: 'COMPLETED',
|
|
1099
|
+
},
|
|
1100
|
+
});
|
|
1101
|
+
const session = await prisma.session.create({
|
|
1102
|
+
data: {
|
|
1103
|
+
workspaceId: workspace.id,
|
|
1104
|
+
agentType: AgentType.CODEX,
|
|
1105
|
+
providerId: 'codex-default',
|
|
1106
|
+
prompt: 'completed prompt',
|
|
1107
|
+
status: SessionStatus.COMPLETED,
|
|
1108
|
+
},
|
|
1109
|
+
});
|
|
1110
|
+
const invocation = await prisma.agentInvocation.create({
|
|
1111
|
+
data: {
|
|
1112
|
+
teamRunId: teamRun.id,
|
|
1113
|
+
workRequestId: request.id,
|
|
1114
|
+
memberId: member.id,
|
|
1115
|
+
workspaceId: workspace.id,
|
|
1116
|
+
sessionId: session.id,
|
|
1117
|
+
status: 'COMPLETED',
|
|
1118
|
+
},
|
|
1119
|
+
});
|
|
1120
|
+
await expect(manager.sendMessage(session.id, 'late direct follow-up', undefined, invocation.id)).rejects.toMatchObject({ code: 'SESSION_NOT_ADMITTED' });
|
|
1121
|
+
expect(spawnMock).not.toHaveBeenCalled();
|
|
1122
|
+
await expect(prisma.session.findUnique({ where: { id: session.id } })).resolves.toMatchObject({
|
|
1123
|
+
status: SessionStatus.COMPLETED,
|
|
731
1124
|
});
|
|
732
1125
|
});
|
|
733
1126
|
it('does not record a TeamRun heartbeat from the local user_message patch created by sendMessage', async () => {
|
|
@@ -787,14 +1180,14 @@ describe('SessionManager TeamRun env injection', () => {
|
|
|
787
1180
|
nextRoomReplyReminderAt,
|
|
788
1181
|
},
|
|
789
1182
|
});
|
|
790
|
-
await manager.sendMessage(session.id, 'heartbeat nudge');
|
|
1183
|
+
await manager.sendMessage(session.id, 'heartbeat nudge', undefined, invocation.id);
|
|
791
1184
|
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
792
1185
|
const reloaded = await prisma.agentInvocation.findUniqueOrThrow({ where: { id: invocation.id } });
|
|
793
1186
|
expect(reloaded.lastHeartbeatAt?.toISOString()).toBe(lastHeartbeatAt.toISOString());
|
|
794
1187
|
expect(reloaded.roomReplyReminderCount).toBe(3);
|
|
795
1188
|
expect(reloaded.nextRoomReplyReminderAt?.toISOString()).toBe(nextRoomReplyReminderAt.toISOString());
|
|
796
1189
|
expect(spawnMock).toHaveBeenCalledTimes(1);
|
|
797
|
-
manager.destroyAll();
|
|
1190
|
+
await manager.destroyAll();
|
|
798
1191
|
});
|
|
799
1192
|
});
|
|
800
1193
|
//# sourceMappingURL=session-manager.team-run.test.js.map
|