feat(orchestrator): MS23-P1-005 Mission Control proxy API (#722)
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <jason@diversecanvas.com> Co-committed-by: Jason Woltje <jason@diversecanvas.com>
This commit was merged in pull request #722.
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
import { Injectable, NotFoundException } from "@nestjs/common";
|
||||
import type { AgentMessage, AgentSession, IAgentProvider, InjectResult } from "@mosaic/shared";
|
||||
import type { Prisma } from "@prisma/client";
|
||||
import { PrismaService } from "../../prisma/prisma.service";
|
||||
import { AgentProviderRegistry } from "../agents/agent-provider.registry";
|
||||
|
||||
type MissionControlAction = "inject" | "pause" | "resume" | "kill";
|
||||
|
||||
const DEFAULT_OPERATOR_ID = "mission-control";
|
||||
|
||||
@Injectable()
|
||||
export class MissionControlService {
|
||||
constructor(
|
||||
private readonly registry: AgentProviderRegistry,
|
||||
private readonly prisma: PrismaService
|
||||
) {}
|
||||
|
||||
listSessions(): Promise<AgentSession[]> {
|
||||
return this.registry.listAllSessions();
|
||||
}
|
||||
|
||||
async getSession(sessionId: string): Promise<AgentSession> {
|
||||
const resolved = await this.registry.getProviderForSession(sessionId);
|
||||
if (!resolved) {
|
||||
throw new NotFoundException(`Session ${sessionId} not found`);
|
||||
}
|
||||
|
||||
return resolved.session;
|
||||
}
|
||||
|
||||
async getMessages(sessionId: string, limit?: number, before?: string): Promise<AgentMessage[]> {
|
||||
const { provider } = await this.getProviderForSessionOrThrow(sessionId);
|
||||
return provider.getMessages(sessionId, limit, before);
|
||||
}
|
||||
|
||||
async injectMessage(
|
||||
sessionId: string,
|
||||
message: string,
|
||||
operatorId = DEFAULT_OPERATOR_ID
|
||||
): Promise<InjectResult> {
|
||||
const { provider } = await this.getProviderForSessionOrThrow(sessionId);
|
||||
const result = await provider.injectMessage(sessionId, message);
|
||||
|
||||
await this.writeOperatorAuditLog({
|
||||
sessionId,
|
||||
providerId: provider.providerId,
|
||||
operatorId,
|
||||
action: "inject",
|
||||
content: message,
|
||||
payload: { message },
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async pauseSession(sessionId: string, operatorId = DEFAULT_OPERATOR_ID): Promise<void> {
|
||||
const { provider } = await this.getProviderForSessionOrThrow(sessionId);
|
||||
await provider.pauseSession(sessionId);
|
||||
|
||||
await this.writeOperatorAuditLog({
|
||||
sessionId,
|
||||
providerId: provider.providerId,
|
||||
operatorId,
|
||||
action: "pause",
|
||||
payload: {},
|
||||
});
|
||||
}
|
||||
|
||||
async resumeSession(sessionId: string, operatorId = DEFAULT_OPERATOR_ID): Promise<void> {
|
||||
const { provider } = await this.getProviderForSessionOrThrow(sessionId);
|
||||
await provider.resumeSession(sessionId);
|
||||
|
||||
await this.writeOperatorAuditLog({
|
||||
sessionId,
|
||||
providerId: provider.providerId,
|
||||
operatorId,
|
||||
action: "resume",
|
||||
payload: {},
|
||||
});
|
||||
}
|
||||
|
||||
async killSession(
|
||||
sessionId: string,
|
||||
force = true,
|
||||
operatorId = DEFAULT_OPERATOR_ID
|
||||
): Promise<void> {
|
||||
const { provider } = await this.getProviderForSessionOrThrow(sessionId);
|
||||
await provider.killSession(sessionId, force);
|
||||
|
||||
await this.writeOperatorAuditLog({
|
||||
sessionId,
|
||||
providerId: provider.providerId,
|
||||
operatorId,
|
||||
action: "kill",
|
||||
payload: { force },
|
||||
});
|
||||
}
|
||||
|
||||
async streamMessages(sessionId: string): Promise<AsyncIterable<AgentMessage>> {
|
||||
const { provider } = await this.getProviderForSessionOrThrow(sessionId);
|
||||
return provider.streamMessages(sessionId);
|
||||
}
|
||||
|
||||
private async getProviderForSessionOrThrow(
|
||||
sessionId: string
|
||||
): Promise<{ provider: IAgentProvider; session: AgentSession }> {
|
||||
const resolved = await this.registry.getProviderForSession(sessionId);
|
||||
|
||||
if (!resolved) {
|
||||
throw new NotFoundException(`Session ${sessionId} not found`);
|
||||
}
|
||||
|
||||
return resolved;
|
||||
}
|
||||
|
||||
private toJsonValue(value: Record<string, unknown>): Prisma.InputJsonValue {
|
||||
return value as Prisma.InputJsonValue;
|
||||
}
|
||||
|
||||
private async writeOperatorAuditLog(params: {
|
||||
sessionId: string;
|
||||
providerId: string;
|
||||
operatorId: string;
|
||||
action: MissionControlAction;
|
||||
content?: string;
|
||||
payload: Record<string, unknown>;
|
||||
}): Promise<void> {
|
||||
await this.prisma.operatorAuditLog.create({
|
||||
data: {
|
||||
sessionId: params.sessionId,
|
||||
userId: params.operatorId,
|
||||
provider: params.providerId,
|
||||
action: params.action,
|
||||
...(params.content !== undefined ? { content: params.content } : {}),
|
||||
metadata: this.toJsonValue({ payload: params.payload }),
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user