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
16 changes: 16 additions & 0 deletions apps/server/src/mcp/OrchestratorMcpService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,16 @@ function taskPrompt(input: OrchestratorMcpDelegateTaskInput): string {
: `Act as the ${input.role} sub-agent for this task.\n\n${input.task}`;
}

function threadSettlement(
thread: Pick<OrchestrationV2ThreadShell, "settledOverride" | "settledAt">,
): Pick<OrchestratorMcpThreadListItem, "settled" | "settledAt"> {
const settled = thread.settledOverride === "settled";
return {
settled,
settledAt: settled && thread.settledAt !== null ? DateTime.formatIso(thread.settledAt) : null,
};
}

function listItemFromShell(shell: OrchestrationV2ThreadShell): OrchestratorMcpThreadListItem {
return {
threadId: shell.id,
Expand All @@ -575,6 +585,7 @@ function listItemFromShell(shell: OrchestrationV2ThreadShell): OrchestratorMcpTh
runtimeMode: shell.runtimeMode,
interactionMode: shell.interactionMode,
linkedPullRequest: shell.linkedPullRequest ?? null,
...threadSettlement(shell),
parentThreadId: shell.lineage.parentThreadId,
relationshipToParent: shell.lineage.relationshipToParent,
itemCount: shell.visibleItemCount,
Expand Down Expand Up @@ -621,6 +632,7 @@ function threadDetail(
(request) => request.status === "pending",
).length,
archived: projection.thread.archivedAt !== null,
...threadSettlement(projection.thread),
createdAt: DateTime.formatIso(projection.thread.createdAt),
updatedAt: DateTime.formatIso(projection.thread.updatedAt),
};
Expand Down Expand Up @@ -1721,6 +1733,10 @@ const make = Effect.gen(function* () {
(thread) =>
statuses === null || statuses.has(thread.activityRunStatus ?? thread.status),
)
.filter(
(thread) =>
input.settled === undefined || threadSettlement(thread).settled === input.settled,
)
.filter(
(thread) =>
titleContains === undefined ||
Expand Down
44 changes: 44 additions & 0 deletions apps/server/src/mcp/OrchestratorMcpToolkit.integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1960,6 +1960,50 @@ describe("orchestrator MCP toolkit", () => {
).toMatchObject({
title: "Metadata-managed thread",
linkedPullRequest: linked.linkedPullRequest,
settled: false,
settledAt: null,
});
expect(metadataRead.thread).toMatchObject({ settled: false, settledAt: null });

yield* orchestrator.dispatch({
type: "thread.settle",
commandId: CommandId.make("command:mcp-empty:settle"),
threadId: emptyThread.threadId,
settledAt: DateTime.makeUnsafe("2026-01-01T00:00:00.000Z"),
});
const settledReadCall = yield* invoke("t3_thread_read", {
threadId: emptyThread.threadId,
});
const settledRead = yield* decodeThreadReadResult(
settledReadCall.structuredContent,
).pipe(Effect.orDie);
expect(settledRead.thread).toMatchObject({
settled: true,
settledAt: "2026-01-01T00:00:00.000Z",
});
const settledListCall = yield* invoke("t3_thread_list", { settled: true, limit: 100 });
const settledList = yield* decodeThreadListResult(
settledListCall.structuredContent,
).pipe(Effect.orDie);
expect(settledList.threads.map((thread) => thread.threadId)).toEqual([
emptyThread.threadId,
]);
expect(settledList.threads[0]).toMatchObject({
settled: true,
settledAt: "2026-01-01T00:00:00.000Z",
});
const activeListCall = yield* invoke("t3_thread_list", { settled: false, limit: 100 });
const activeList = yield* decodeThreadListResult(activeListCall.structuredContent).pipe(
Effect.orDie,
);
expect(
activeList.threads.some((thread) => thread.threadId === emptyThread.threadId),
).toBe(false);
yield* orchestrator.dispatch({
type: "thread.unsettle",
commandId: CommandId.make("command:mcp-empty:unsettle"),
threadId: emptyThread.threadId,
reason: "user",
});

const unlinkedCall = yield* invoke("t3_thread_update", {
Expand Down
2 changes: 1 addition & 1 deletion apps/server/src/mcp/toolkits/orchestrator/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ export const CreateThreadsTool = Tool.make("create_threads", {

const ThreadListTool = Tool.make("t3_thread_list", {
description:
"List T3 threads in the calling thread's project, newest first. Filter by durable run status or title and paginate with the returned cursor. Threads from other projects are never exposed.",
"List T3 threads in the calling thread's project, newest first. Filter by durable run status, title, or settled state (settled=true lists threads the user or auto-settlement moved out of the active list) and paginate with the returned cursor. Threads from other projects are never exposed.",
parameters: OrchestratorMcpThreadListInput,
success: OrchestratorMcpThreadListResult,
failure: OrchestratorMcpFailure,
Expand Down
5 changes: 5 additions & 0 deletions packages/contracts/src/orchestratorMcp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@ export const OrchestratorMcpThreadListInput = Schema.Struct({
Schema.Array(OrchestratorMcpThreadStatus).check(Schema.isMaxLength(10)),
),
titleContains: Schema.optional(TrimmedNonEmptyString.check(Schema.isMaxLength(256))),
settled: Schema.optional(Schema.Boolean),
includeSubagents: Schema.optional(Schema.Boolean),
cursor: Schema.optional(NonNegativeInt),
limit: Schema.optional(PositiveInt.check(Schema.isLessThanOrEqualTo(100))),
Expand All @@ -302,6 +303,8 @@ export const OrchestratorMcpThreadListItem = Schema.Struct({
runtimeMode: RuntimeMode,
interactionMode: ProviderInteractionMode,
linkedPullRequest: Schema.NullOr(ThreadLinkedPullRequest),
settled: Schema.Boolean,
settledAt: Schema.NullOr(IsoDateTime),
parentThreadId: Schema.NullOr(ThreadId),
relationshipToParent: Schema.NullOr(Schema.Literals(["fork", "subagent"])),
itemCount: NonNegativeInt,
Expand Down Expand Up @@ -354,6 +357,8 @@ export const OrchestratorMcpThreadDetail = Schema.Struct({
itemCount: NonNegativeInt,
pendingRequestCount: NonNegativeInt,
archived: Schema.Boolean,
settled: Schema.Boolean,
settledAt: Schema.NullOr(IsoDateTime),
createdAt: IsoDateTime,
updatedAt: IsoDateTime,
});
Expand Down
Loading