import { type CommandId, type RuntimeRequestId, ThreadId, type OrchestrationV2ThreadProjection, type RunId, OwnedWorkItemId, OrchestratorMcpFailure, type OrchestrationV2Command, OPEN_OWNED_WORK_STATUSES, } from "@t3tools/contracts"; import * as Effect from "effect/Effect"; import * as SecureRandom from "effect/crypto/SecureRandom"; import { modelSelectionCommandType } from "@t3tools/shared/model"; import * as McpInvocationContext from "../../McpInvocationContext.ts"; import * as McpToolAccess from "../../McpToolAccess.ts"; import { dispatchFailure, newCommandId, readCaller, readThread, unavailable, } from "../../threadAccess.ts"; import * as ThreadSearch from "../../../orchestration-v2/ThreadSearch.ts"; import * as ScheduledTasks from "../../../scheduledTasks/ScheduledTaskService.ts"; import { queuedRunsInDeliveryOrder } from "../../../orchestration-v2/QueuedRunOrder.ts"; import { ThreadToolkit } from "./tools.ts"; function queueEntry( projection: Pick, runId: RunId, limit: number, ) { const run = projection.runs.find((run) => run.id === runId && run.status === "queued"); const message = projection.messages.find((message) => message.id === run?.userMessageId); if (run === undefined || message === undefined) return undefined; const characters = Array.from(message.text); return { queuedRunId: run.id, text: characters.slice(0, limit).join(""), truncated: characters.length > limit, }; } const dispatch = Effect.fn("mcp.dispatchThreadCommand")(function* ( threadId: ThreadId | undefined, command: (common: { commandId: CommandId; threadId: ThreadId }) => OrchestrationV2Command, ) { const { threads, projection } = yield* readThread(threadId); const result = yield* threads .dispatch(command({ commandId: yield* newCommandId(), threadId: projection.thread.id })) .pipe(Effect.mapError(dispatchFailure)); return { sequence: result.sequence }; }); const readQuestion = Effect.fn("mcp.readQuestion")(function* (input: { threadId?: ThreadId | undefined; requestId: RuntimeRequestId; }) { const context = yield* readThread(input.threadId, ["runtimeRequests", "turnItems"]); const request = context.projection.runtimeRequests.find( (request) => request.id === input.requestId && request.kind === "user_input", ); const item = context.projection.turnItems.find( (item) => item.type === "user_input_request" && item.requestId === input.requestId, ); if (request === undefined || item?.type !== "user_input_request") return yield* new OrchestratorMcpFailure({ code: "invalid_request", message: "The pending user-input request was not found.", }); if (request.status !== "pending") return yield* new OrchestratorMcpFailure({ code: "invalid_request", message: request.status === "resolved" ? "The user-input request was already answered." : `The user-input request is no longer pending (${request.status}).`, }); return { ...context, request, item }; }); /** A field the chosen action needs. `null` counts as given, as in `beforeRunId: null`. */ const required = (value: A | undefined, name: string) => value === undefined ? Effect.fail( new OrchestratorMcpFailure({ code: "invalid_request", message: `This action needs \`${name}\`.`, }), ) : Effect.succeed(value); const writesThread =

( handle: (params: P) => Effect.Effect, ) => McpToolAccess.writesThreads((params: P) => [params.threadId], handle); export const layer = McpToolAccess.toLayer(ThreadToolkit, { run_scheduled_task_now: McpToolAccess.writesEnvironment((input) => Effect.gen(function* () { const scheduler = yield* ScheduledTasks.ScheduledTaskService; const { tasks } = yield* scheduler.list().pipe(Effect.mapError(unavailable)); if (!tasks.some((task) => task.id === input.taskId)) return yield* new OrchestratorMcpFailure({ code: "invalid_request", message: "The scheduled task was not found.", }); const { task, threadId } = yield* scheduler .runNow({ id: input.taskId }) .pipe( Effect.mapError( (error) => new OrchestratorMcpFailure({ code: "orchestration_error", message: error.message }), ), ); const scope = yield* McpInvocationContext.McpInvocationContext; return { taskId: task.id, threadId, ...(threadId === null ? {} : { link: McpInvocationContext.threadLinkFor(scope, threadId), }), lastRunStatus: task.lastRunStatus, runCount: task.runCount, nextRunAt: task.nextRunAt, }; }), ), t3_thread_search: McpToolAccess.reads((input) => Effect.gen(function* () { const { caller } = yield* readCaller(); const scope = yield* McpInvocationContext.McpInvocationContext; const threadSearch = yield* ThreadSearch.ThreadSearch; const { matches } = yield* threadSearch .searchThreads({ ...input, projectId: input.projectId ?? caller?.projectId }) .pipe(Effect.mapError(unavailable)); return { matches: matches.map((match) => ({ ...match, link: McpInvocationContext.threadLinkFor(scope, match.threadId, match.title), })), }; }), ), t3_roster_find: McpToolAccess.reads((input) => Effect.gen(function* () { const { caller } = yield* readCaller(); const scope = yield* McpInvocationContext.McpInvocationContext; const threadSearch = yield* ThreadSearch.ThreadSearch; const { matches } = yield* threadSearch .findRoster({ topic: input.topic, ...(input.limit === undefined ? {} : { limit: input.limit }), projectId: input.projectId ?? caller?.projectId, }) .pipe(Effect.mapError(unavailable)); return { matches: matches.map((match) => ({ ...match, link: McpInvocationContext.threadLinkFor(scope, match.threadId, match.title), })), }; }), ), t3_thread_transfer: McpToolAccess.either( (input) => input.action === "status", McpToolAccess.reads((input) => Effect.gen(function* () { const { projection } = yield* readThread(input.threadId, ["contextTransfers"]); return { transfers: projection.contextTransfers.map( ({ id, sourceThreadId, targetThreadId, status }) => ({ id, sourceThreadId, targetThreadId, status, }), ), }; }), ), McpToolAccess.writesThreads( (input) => [input.threadId, input.targetThreadId], (input) => Effect.gen(function* () { const sourcePoint = input.sourcePoint ?? { type: "latest_stable" as const }; const { threads, projection } = yield* readThread(input.threadId); const commandId = yield* newCommandId(); if (input.action === "merge_back") { const targetThreadId = yield* required(input.targetThreadId, "targetThreadId"); yield* readThread(targetThreadId); const result = yield* threads .dispatch({ type: "thread.merge_back", commandId, sourceThreadId: projection.thread.id, targetThreadId, sourcePoint, createdBy: "agent", creationSource: "mcp", }) .pipe(Effect.mapError(dispatchFailure)); return { sequence: result.sequence, targetThreadId }; } const targetThreadId = ThreadId.make(`${commandId}:fork`); const result = yield* threads .dispatch({ type: "thread.fork", commandId, sourceThreadId: projection.thread.id, targetThreadId, sourcePoint, ...(input.title === undefined ? {} : { title: input.title }), createdBy: "agent", creationSource: "mcp", }) .pipe(Effect.mapError(dispatchFailure)); return { sequence: result.sequence, targetThreadId }; }), ), ), t3_thread_configure: McpToolAccess.either( (input) => input.modelSelection === undefined, McpToolAccess.reads((input) => Effect.gen(function* () { const { projection: { thread }, } = yield* readThread(input.threadId); return { threadId: thread.id, modelSelection: thread.modelSelection, runtimeMode: thread.runtimeMode, interactionMode: thread.interactionMode, }; }), ), writesThread((input) => Effect.gen(function* () { const modelSelection = yield* required(input.modelSelection, "modelSelection"); const { threads, projection: { thread }, } = yield* readThread(input.threadId); const type = modelSelectionCommandType(thread.providerInstanceId, modelSelection); const result = yield* threads .dispatch({ type, threadId: thread.id, commandId: yield* newCommandId(), modelSelection }) .pipe(Effect.mapError(dispatchFailure)); return { sequence: result.sequence }; }), ), ), t3_pending_request: McpToolAccess.either( (input) => input.action !== "respond", McpToolAccess.reads((input) => Effect.gen(function* () { if (input.action === "list") { const { projection } = yield* readThread(input.threadId, ["runtimeRequests"]); return { requestIds: projection.runtimeRequests .filter((request) => request.kind === "user_input" && request.status === "pending") .map((request) => request.id), }; } const requestId = yield* required(input.requestId, "requestId"); const { item } = yield* readQuestion({ threadId: input.threadId, requestId }); return { requestId, questions: item.questions }; }), ), writesThread((input) => Effect.gen(function* () { const requestId = yield* required(input.requestId, "requestId"); const answers = yield* required(input.answers, "answers"); const { threads, projection } = yield* readQuestion({ threadId: input.threadId, requestId, }); const result = yield* threads .dispatch({ type: "runtime-request.respond", threadId: projection.thread.id, commandId: yield* newCommandId(), requestId, answers, }) .pipe(Effect.mapError(dispatchFailure)); return { sequence: result.sequence }; }), ), ), t3_queue: McpToolAccess.either( (input) => input.action === "list" || input.action === "read", McpToolAccess.reads((input) => Effect.gen(function* () { const { projection } = yield* readThread(input.threadId, ["runs", "messages"]); if (input.action === "list") { const runs = queuedRunsInDeliveryOrder(projection); const cursor = input.cursor ?? 0; const end = cursor + (input.limit ?? 20); return { items: runs.slice(cursor, end).flatMap((run) => { const entry = queueEntry(projection, run.id, 1000); return entry === undefined ? [] : [entry]; }), nextCursor: end < runs.length ? end : null, }; } const queuedRunId = yield* required(input.queuedRunId, "queuedRunId"); return ( queueEntry(projection, queuedRunId, 16000) ?? (yield* new OrchestratorMcpFailure({ code: "invalid_request", message: "The queued message was not found.", })) ); }), ), writesThread((input) => Effect.gen(function* () { const runId = yield* required(input.queuedRunId, "queuedRunId"); switch (input.action) { case "edit": { const text = yield* required(input.text, "text"); return yield* dispatch(input.threadId, (common) => ({ ...common, type: "queued-run.edit", runId, text, })); } case "reorder": { const beforeRunId = yield* required(input.beforeRunId, "beforeRunId"); return yield* dispatch(input.threadId, (common) => ({ ...common, type: "queued-run.reorder", runId, beforeRunId, })); } case "promote_to_steer": { const targetRunId = yield* required(input.targetRunId, "targetRunId"); return yield* dispatch(input.threadId, (common) => ({ ...common, type: "queued-message.promote-to-steer", queuedRunId: runId, targetRunId, })); } default: return yield* dispatch(input.threadId, (common) => ({ ...common, type: "queued-run.cancel", runId, })); } }), ), ), t3_owned_work: McpToolAccess.either( (input) => input.action === "list" || input.action === "read", McpToolAccess.reads((input) => Effect.gen(function* () { const { projection } = yield* readThread(input.threadId); const items = projection.thread.ownedWorkItems ?? []; if (input.action === "list") { return { items: input.openOnly === true ? items.filter((item) => OPEN_OWNED_WORK_STATUSES.has(item.status)) : items, }; } const itemId = yield* required(input.itemId, "itemId"); const item = items.find((candidate) => candidate.id === itemId); return ( item ?? (yield* new OrchestratorMcpFailure({ code: "invalid_request", message: "The owned work item was not found.", })) ); }), ), writesThread((input) => Effect.gen(function* () { const { projection, caller } = yield* readThread(input.threadId); const actorThreadId = caller?.id ?? projection.thread.id; switch (input.action) { case "assign": { const title = yield* required(input.title, "title"); const random = yield* SecureRandom.SecureRandom; const itemId = input.itemId ?? OwnedWorkItemId.make(`owned-work:${yield* random.uuidV4()}`); return yield* dispatch(input.threadId, (common) => ({ ...common, type: "owned-work.assign", itemId, title, description: input.description ?? "", assigneeThreadId: input.assigneeThreadId, createdByThreadId: actorThreadId, })); } case "claim": { const itemId = yield* required(input.itemId, "itemId"); return yield* dispatch(input.threadId, (common) => ({ ...common, type: "owned-work.claim", itemId, claimedByThreadId: actorThreadId, })); } case "progress": { const itemId = yield* required(input.itemId, "itemId"); const progress = yield* required(input.progress, "progress"); return yield* dispatch(input.threadId, (common) => ({ ...common, type: "owned-work.progress", itemId, progress, actorThreadId, })); } case "handoff": { const itemId = yield* required(input.itemId, "itemId"); const toThreadId = yield* required(input.toThreadId, "toThreadId"); return yield* dispatch(input.threadId, (common) => ({ ...common, type: "owned-work.handoff", itemId, toThreadId, note: input.note, actorThreadId, })); } case "hold": { const itemId = yield* required(input.itemId, "itemId"); const reason = yield* required(input.reason, "reason"); return yield* dispatch(input.threadId, (common) => ({ ...common, type: "owned-work.hold", itemId, reason, actorThreadId, })); } case "resume": { const itemId = yield* required(input.itemId, "itemId"); return yield* dispatch(input.threadId, (common) => ({ ...common, type: "owned-work.resume", itemId, note: input.note, actorThreadId, })); } case "attention": { const itemId = yield* required(input.itemId, "itemId"); const reason = yield* required(input.reason, "reason"); return yield* dispatch(input.threadId, (common) => ({ ...common, type: "owned-work.attention", itemId, reason, actorThreadId, })); } case "complete": { const itemId = yield* required(input.itemId, "itemId"); const closureReason = yield* required(input.closureReason, "closureReason"); return yield* dispatch(input.threadId, (common) => ({ ...common, type: "owned-work.complete", itemId, closureReason, closureTarget: input.closureTarget, closureNote: input.note, actorThreadId, })); } default: { const itemId = yield* required(input.itemId, "itemId"); return yield* dispatch(input.threadId, (common) => ({ ...common, type: "owned-work.cancel", itemId, closureNote: input.note, actorThreadId, })); } } }), ), ), t3_work_review: McpToolAccess.either( (input) => input.action === "list" || input.action === "read", McpToolAccess.reads((input) => Effect.gen(function* () { // Default MCP thread reads only load user_input turn items; approval cards need their own fetch. const context = yield* readThread(input.threadId, ["runtimeRequests"]); const projection = yield* context.threads .getProjectThreadRecords( { projectId: context.projection.thread.projectId, threadId: context.projection.thread.id, }, ["runtimeRequests", "turnItems"], { turnItemTypes: ["approval_request"] }, ) .pipe(Effect.mapError(unavailable)); const summarize = (requestId: RuntimeRequestId) => { const request = projection.runtimeRequests.find( (candidate) => candidate.id === requestId && candidate.kind === "work_review", ); if (request === undefined) return undefined; const item = projection.turnItems.findLast( (candidate) => candidate.type === "approval_request" && candidate.requestId === request.id, ); return { requestId: request.id, status: request.status, ...(item?.type === "approval_request" && item.prompt ? { criteria: item.prompt } : {}), ...(item?.type === "approval_request" && item.title ? { name: item.title } : {}), ...(item?.type === "approval_request" && item.reason ? { note: item.reason } : {}), ...(request.decision === undefined ? {} : { decision: request.decision }), ...(request.deliverable === undefined ? {} : { deliverable: request.deliverable }), createdAt: request.createdAt, }; }; if (input.action === "list") { const reviews = projection.runtimeRequests .filter((request) => request.kind === "work_review") .filter((request) => (input.openOnly === true ? request.status === "pending" : true)) .flatMap((request) => { const summary = summarize(request.id); return summary === undefined ? [] : [summary]; }); return { reviews }; } const requestId = yield* required(input.requestId, "requestId"); return ( summarize(requestId) ?? (yield* new OrchestratorMcpFailure({ code: "invalid_request", message: "The work review was not found.", })) ); }), ), writesThread((input) => Effect.gen(function* () { switch (input.action) { case "request": { const criteria = yield* required(input.criteria, "criteria"); return yield* dispatch(input.threadId, (common) => ({ ...common, type: "work-review.request", criteria, ...(input.name === undefined ? {} : { name: input.name }), ...(input.note === undefined ? {} : { note: input.note }), ...(input.deliverable === undefined ? {} : { deliverable: input.deliverable }), })); } case "accept": case "reject": { const requestId = yield* required(input.requestId, "requestId"); // Orchestration work reviews resolve without a live provider session, so decline // feedback is not supported on this path; `note` is only used when requesting. return yield* dispatch(input.threadId, (common) => ({ ...common, type: "runtime-request.respond", requestId, decision: input.action === "accept" ? ("accept" as const) : ("decline" as const), })); } default: return yield* new OrchestratorMcpFailure({ code: "invalid_request", message: "Unsupported work review action.", }); } }), ), ), t3_thread_organize: writesThread((input) => Effect.gen(function* () { const { threads, projection, caller } = yield* readThread(input.threadId); const common = { commandId: yield* newCommandId(), threadId: projection.thread.id }; if (input.action === "settle") { return yield* threads .settleThread({ ...common, byOwnAgent: caller?.id === projection.thread.id }) .pipe(Effect.mapError(dispatchFailure)); } let command: OrchestrationV2Command; switch (input.action) { case "snooze": if (input.snoozedUntil === undefined) { return yield* new OrchestratorMcpFailure({ code: "invalid_request", message: "snooze requires snoozedUntil.", }); } command = { ...common, type: "thread.snooze", snoozedUntil: input.snoozedUntil }; break; case "unsnooze": case "unsettle": command = { ...common, type: `thread.${input.action}`, reason: "user" }; break; case "mark_unread": command = { ...common, type: "thread.mark-unread" }; break; default: command = { ...common, type: `thread.${input.action}` }; } const result = yield* threads.dispatch(command).pipe(Effect.mapError(dispatchFailure)); return { sequence: result.sequence }; }), ), });