Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
954bc76
fix(server): a follow-up promoted to Steer no longer reads as stopped
murayy Oct 14, 2026
b6ca134
fix(server): settle steered follow-ups without blocking the steered r…
murayy Oct 14, 2026
16ca007
Merge remote-tracking branch 'origin/main' into fix/steered-follow-up…
murayy Oct 14, 2026
b8a2eea
fix(server): a promoted follow-up follows its turn's restart continua…
murayy Oct 14, 2026
d884dbc
Merge remote-tracking branch 'origin/main' into fix/steered-follow-up…
murayy Oct 14, 2026
fb4b22f
fix(server): a promoted follow-up reports the steered turn's result t…
murayy Oct 14, 2026
b80bc28
Merge remote-tracking branch 'origin/main' into fix/steered-follow-up…
juliusmarminge Oct 19, 2026
f072166
test(server): cover steered delegated task settlement
juliusmarminge Oct 19, 2026
fc21bef
fix(server): settle steered delegates with their shared turn
juliusmarminge Oct 19, 2026
589b34e
fix(server): sync promoted follow-up state and result transfers
juliusmarminge Oct 19, 2026
a50cd98
feat(server): webhook filters fan out over arrays with a [] path wild…
juliusmarminge Oct 22, 2026
6d5ca07
Merge remote-tracking branch 'origin/main' into fix/issue-19351
juliusmarminge Oct 22, 2026
7beebea
test(server): ownerless monitor run settles despite an unrelated owne…
juliusmarminge Oct 22, 2026
fa914ef
fix(server): an unrelated ownerless restart cut no longer holds a joi…
juliusmarminge Oct 22, 2026
321edbb
test(server): make promoted delegation runtime evidence reproducible
juliusmarminge Oct 22, 2026
df0337f
Merge remote-tracking branch 'origin/main' into fix/issue-19351
juliusmarminge Oct 22, 2026
18e90fc
fix(server): a promoted delegated task reports approvals pending in i…
juliusmarminge Oct 22, 2026
8daceaf
Merge remote-tracking branch 'origin/main' into fix/issue-19351
juliusmarminge Oct 22, 2026
f374005
fix(ci): register promoted delegation evidence runner
juliusmarminge Oct 22, 2026
2e01a8b
Merge remote-tracking branch 'origin/main' into fix/issue-19351
juliusmarminge Nov 11, 2026
a49a734
chore(server): drop the promoted delegation evidence runner and fix t…
juliusmarminge Nov 11, 2026
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
81 changes: 41 additions & 40 deletions apps/server/src/mcp/OrchestratorMcpService.ts
Comment thread
macroscopeapp[bot] marked this conversation as resolved.
Resolved
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ import * as ProviderAdapterRegistry from "../orchestration-v2/ProviderAdapterReg
import {
subagentResultForRun,
delegatedTaskProgress,
delegatedTaskResultRun,
} from "@t3tools/provider-core/server/subagentProjection";
import {
DispatchModeLimit,
Expand Down Expand Up @@ -500,16 +501,19 @@ function endsTaskWait(result: OrchestratorMcpDelegateTaskResult): boolean {
const isApprovalRequestKind = Schema.is(ProviderRequestKind);

function pendingTaskApproval(
child: Pick<OrchestrationV2ThreadProjection, "runs" | "nodes" | "runtimeRequests">,
child: Pick<OrchestrationV2ThreadProjection, "runs" | "messages" | "nodes" | "runtimeRequests">,
taskId: NodeId,
originalRun: OrchestrationV2Run | undefined,
) {
// A follow-up promoted to Steer waits on approvals in the run it joined.
const joinedRunId = delegatedTaskResultRun(child, taskId)?.id;
const taskRunIds = new Set(
child.runs
.filter(
(run) =>
!ThreadManagementService.isTerminalRunStatus(run.status) &&
(run.delegatedTaskId === taskId ||
run.id === joinedRunId ||
// Legacy tasks own only their original run, not later turns in the child thread.
(run.delegatedTaskId === undefined && run.id === originalRun?.id)),
)
Expand Down Expand Up @@ -544,7 +548,11 @@ function directChildTaskIds(
const ids = new Set<NodeId>();
if (target.thread.forkedFrom?.type === "node") ids.add(target.thread.forkedFrom.nodeId);
for (const run of target.runs) {
if (returnedRunIds.has(run.id) && run.delegatedTaskId !== undefined) {
// A promoted follow-up's cancelled run can be off the page while its joined run is on it.
if (
run.delegatedTaskId !== undefined &&
(returnedRunIds.has(run.id) || run.status === "cancelled")
) {
ids.add(run.delegatedTaskId);
}
}
Expand All @@ -553,43 +561,31 @@ function directChildTaskIds(

function directAppOwnedChildTasks(
parent: Pick<OrchestrationV2ThreadProjection, "thread" | "subagents">,
target: Pick<OrchestrationV2ThreadProjection, "thread" | "runs">,
returnedRunIds: ReadonlySet<RunId | null>,
target: Pick<OrchestrationV2ThreadProjection, "thread">,
): ReadonlyArray<OrchestrationV2Subagent> {
if (
target.thread.lineage.parentThreadId !== parent.thread.id ||
target.thread.lineage.relationshipToParent !== "subagent"
) {
return [];
}
const original = parent.subagents.find(
(task) =>
task.origin === "app_owned" &&
task.threadId === parent.thread.id &&
task.id ===
(target.thread.forkedFrom?.type === "node" ? target.thread.forkedFrom.nodeId : undefined) &&
task.childThreadId === target.thread.id,
);
const taskIds = new Set(
target.runs
.filter((run) => returnedRunIds.has(run.id))
.map((run) => run.delegatedTaskId ?? original?.id),
);
return parent.subagents.filter(
(task) =>
taskIds.has(task.id) &&
task.origin === "app_owned" &&
task.threadId === parent.thread.id &&
task.childThreadId === target.thread.id,
);
}

// The child's transfers include the results it sent, so the parent's transfers are never read.
function taskResultTransfer(
parentThreadId: ThreadId,
child: Pick<OrchestrationV2ThreadProjection, "runs" | "contextTransfers">,
child: Pick<OrchestrationV2ThreadProjection, "runs" | "messages" | "contextTransfers">,
task: OrchestrationV2Subagent,
) {
const originalRun = delegatedTaskRun(child, task);
// A follow-up promoted to Steer shares the transfer of the run it joined.
const resultRun = delegatedTaskResultRun(child, task.id);
const resultRunIds = new Set(
child.runs
.filter(
Expand All @@ -600,6 +596,7 @@ function taskResultTransfer(
.map((run) => run.id),
);
if (originalRun !== undefined) resultRunIds.add(originalRun.id);
if (resultRun !== undefined) resultRunIds.add(resultRun.id);
return child.contextTransfers.find(
(transfer) =>
transfer.type === "subagent_result" &&
Expand All @@ -611,24 +608,17 @@ function taskResultTransfer(
}

function pageIncludesTerminalTaskResult(input: {
readonly parentThreadId: ThreadId;
readonly page: ReadonlyArray<OrchestrationV2ThreadProjection["visibleTurnItems"][number]>;
readonly task: OrchestrationV2Subagent;
readonly run: OrchestrationV2Run;
readonly target: Pick<
OrchestrationV2ThreadProjection,
"thread" | "runs" | "contextTransfers" | "messages" | "turnItems" | "providerTurns"
"thread" | "messages" | "turnItems" | "providerTurns"
>;
readonly maxChars: number;
}): boolean {
const transfer = taskResultTransfer(input.parentThreadId, input.target, input.task);
if (transfer === undefined) return false;
const run =
transfer.sourcePoint.runId === undefined
? delegatedTaskRun(input.target, input.task)
: input.target.runs.find((run) => run.id === transfer.sourcePoint.runId);
if (run === undefined || !isTerminalTaskStatus(taskStatusForRun(run))) return false;

const result = subagentResultForRun(input.target, run);
if (!isTerminalTaskStatus(taskStatusForRun(input.run))) return false;

const result = subagentResultForRun(input.target, input.run);
if (result.messageId === null && result.turnItemId === null) return false;

return input.page.some((row) => {
Expand Down Expand Up @@ -1489,9 +1479,7 @@ const make = Effect.gen(function* () {
const progress = delegatedTaskProgress(childControls);
const taskResultRun =
childRun?.delegatedTaskId === task.id
? childControls.runs
.filter((run) => run.delegatedTaskId === task.id)
.toSorted((a, b) => b.ordinal - a.ordinal)[0]
? delegatedTaskResultRun(childControls, task.id)
: progress.resultRun;
const taskWorkState =
childRun?.delegatedTaskId === task.id
Expand Down Expand Up @@ -3052,18 +3040,32 @@ const make = Effect.gen(function* () {
),
},
target,
returnedRunIds,
);
if (
parent !== undefined &&
scope.thread !== undefined &&
tasks.length > 0 &&
(input.textOffset ?? 0) === 0
) {
// Promotion moves the queued user message to its joined run, possibly outside this page.
const taskMessages = yield* threadManagement
.getThreadRecords(target.thread.id, ["messages"], {
messageIds: target.runs
.filter((run) => run.status === "cancelled" && run.delegatedTaskId !== undefined)
.map((run) => run.userMessageId)
.filter((id): id is MessageId => id !== null),
})
.pipe(Effect.mapError(threadManagementFailure));
const taskTarget = { ...target, messages: taskMessages.messages };
for (const task of tasks) {
const transfer = taskResultTransfer(parent.thread.id, target, task);
const resultRunId = transfer?.sourcePoint.runId ?? delegatedTaskRun(target, task)?.id;
const resultRunIds = resultRunId === undefined ? [] : [resultRunId];
const transfer = taskResultTransfer(parent.thread.id, taskTarget, task);
if (transfer === undefined) continue;
const run =
transfer.sourcePoint.runId === undefined
? delegatedTaskRun(target, task)
: target.runs.find((candidate) => candidate.id === transfer.sourcePoint.runId);
if (run === undefined || !returnedRunIds.has(run.id)) continue;
const resultRunIds = [run.id];
const resultRecords = yield* threadManagement
.getThreadRecords(target.thread.id, ["messages", "turnItems"], {
messageRoles: ["assistant"],
Expand All @@ -3074,9 +3076,8 @@ const make = Effect.gen(function* () {
.pipe(Effect.mapError(threadManagementFailure));
if (
pageIncludesTerminalTaskResult({
parentThreadId: parent.thread.id,
page,
task,
run,
target: { ...target, ...resultRecords },
maxChars,
})
Expand Down
Loading
Loading