Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/server/src/mcp/McpToolAccess.testkit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export const idleThreadProjection = (
...thread
} = shell;
return {
thread: { ...thread, lastVisitedAt: lastVisitedAt ?? null },
thread: { ...thread, taskInitialization: null, lastVisitedAt: lastVisitedAt ?? null },
runs: [],
attempts: [],
nodes: [],
Expand Down
3 changes: 3 additions & 0 deletions apps/server/src/mcp/OrchestratorMcpService.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
taskThreadInitializationSummary,
CommandId,
type RunId,
isProviderAvailable,
Expand Down Expand Up @@ -806,6 +807,8 @@ function threadDetail(
interactionMode: projection.thread.interactionMode,
linkedPullRequest: projection.thread.linkedPullRequest ?? null,
pullRequests: threadPullRequests(projection.thread.pullRequests),
taskDocumentId: projection.thread.taskDocumentId ?? null,
taskInitialization: taskThreadInitializationSummary(projection.thread.taskInitialization),
titleRegeneration:
projection.thread.titleRegeneration === undefined ||
projection.thread.titleRegeneration === null
Expand Down
42 changes: 40 additions & 2 deletions apps/server/src/mcp/toolkits/project/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,42 @@ const access = Effect.gen(function* () {
yield* readCaller();
return yield* Project.ProjectService;
});
// File-system causes stay out of the message; authored reasons are safe to show.
const taskLaunchFailure = (error: ThreadLaunch.ThreadLaunchError) =>
error.operation === "resolve-task"
? new OrchestratorMcpFailure({
code: "invalid_request",
message:
typeof error.cause === "string"
? error.cause
: "The task document or update instructions could not be read.",
})
: unavailable();

export const layer = McpToolAccess.toLayer(ProjectToolkit, {
task_thread_initialization_result: McpToolAccess.writesThreads(
() => [],
(input) =>
Effect.gen(function* () {
const scope = yield* McpInvocationContext.McpInvocationContext;
const { caller } = yield* readCaller();
if (scope.thread === undefined || scope.thread.subagent === true || caller === undefined)
return yield* new OrchestratorMcpFailure({
code: "invalid_request",
message: "Only the initializing thread can report its context outcome.",
});
const launches = yield* ThreadLaunch.ThreadLaunchService;
yield* launches
.reportTaskInitialization({
threadId: caller.id,
projectId: caller.projectId,
providerInstanceId: scope.thread.providerInstanceId,
outcome: input.outcome,
})
.pipe(Effect.mapError(taskLaunchFailure));
return { recorded: true };
}),
),
t3_thread_launch: McpToolAccess.startsThreads(
(input) => input,
(input, { runtimeMode, interactionMode, linkOrigin }) =>
Expand Down Expand Up @@ -209,6 +244,7 @@ export const layer = McpToolAccess.toLayer(ProjectToolkit, {
)
: runtimeMode;
const result = yield* ThreadMessageIntake.launchThread({
...(input.task === undefined ? {} : { task: input.task }),
commandId,
threadId,
projectId,
Expand Down Expand Up @@ -246,11 +282,13 @@ export const layer = McpToolAccess.toLayer(ProjectToolkit, {
message: error.message,
});
}
return unavailable();
return error._tag === "ThreadLaunchError" ? taskLaunchFailure(error) : unavailable();
}),
);
const thread = result.projection.thread;
const run = result.projection.runs.find((run) => run.userMessageId === messageId);
const run = result.projection.runs.find(
(run) => run.userMessageId === (thread.taskInitialization?.messageId ?? messageId),
);
return {
threadId: thread.id,
link: McpInvocationContext.threadLinkFor(scope, thread.id),
Expand Down
17 changes: 17 additions & 0 deletions apps/server/src/mcp/toolkits/project/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
OrchestrationV2DelegatedFrom,
OrchestrationV2RunStatus,
OrchestrationV2ThreadLaunchWorkspaceStrategy,
TaskThreadLaunchOptions,
RuntimeMode,
ProviderInteractionMode,
Project,
Expand Down Expand Up @@ -123,6 +124,12 @@ const ThreadLaunchTool = Tool.make("t3_thread_launch", {
description:
"Create one new top-level thread, bound to its workspace before the agent starts. Task in `message`. Omit `projectId`, model and modes to inherit yours (outside a thread, pass `projectId` or `scratch`); modes cannot exceed yours. After an error or lost reply, retry with the same `clientRequestId`. Follow it with `t3_thread_read` or `t3_thread_wait`. Mention it with its `link`.",
parameters: Schema.Struct({
task: Schema.optional(
TaskThreadLaunchOptions.annotate({
description:
"Optional registered task document and update-discipline path on the destination environment. Runs visible context initialization first. Omit message/attachments; send work after launch. No filesystem permission is added.",
}),
),
environmentId: OrchestratorMcpEnvironmentTarget,
projectId: Schema.optional(ProjectId),
scratch: Schema.optional(
Expand Down Expand Up @@ -174,7 +181,17 @@ const ThreadLaunchTool = Tool.make("t3_thread_launch", {
.annotate(Tool.Destructive, true)
.annotate(Tool.OpenWorld, true);

const TaskInitializationResultTool = Tool.make("task_thread_initialization_result", {
...shared,
description:
"Report this task thread's initialization outcome after reading its required context. Use ready only when the task, relevant parents, update discipline and workspace instructions are readable and the selected workspace matches. Otherwise report failed and explain why. This reports context readiness; it does not start user work or change permissions.",
parameters: Schema.Struct({ outcome: Schema.Literals(["ready", "failed"]) }),
success: Schema.Struct({ recorded: Schema.Boolean }),
dependencies: [...shared.dependencies, ThreadLaunchService.ThreadLaunchService],
}).annotate(Tool.Destructive, true);

export const ProjectToolkit = Toolkit.make(
TaskInitializationResultTool,
ThreadLaunchTool,
ProjectListTool,
ProjectReadTool,
Expand Down
Loading
Loading