Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
36 changes: 34 additions & 2 deletions apps/server/src/orchestration-v2/ProjectionControlReads.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,32 @@ it.effect.each(storageCases)(
});
yield* reply.respond({ threadId, providerSessionId, requestId, decision: "accept" });
assert.deepEqual(calls, ["interrupt", "Use the smaller fix.", "reply"]);
const messageEvent = events.find((event) => event.type === "message.updated")!;
yield* store.apply({
...messageEvent,
id: EventId.make("control:agent-message"),
payload: {
...messageEvent.payload,
createdBy: "agent",
creationSource: "mcp",
senderThreadId: ThreadId.make("thread:sender"),
},
});
yield* control.steer({
threadId,
providerThreadId,
providerTurnId,
providerSessionId,
messageId,
});
const agentText = calls.at(-1);
assert.include(agentText, 'thread "thread:sender"');
assert.include(agentText, "not a message from the human user");
assert.isTrue(agentText?.endsWith("\n\nUse the smaller fix."));
assert.equal(
(yield* store.getProviderControlContext(threadId, target)).message?.text,
"Use the smaller fix.",
);
const wrongSession = ProviderSessionId.make("session:wrong");
assert.equal(
(yield* Effect.exit(
Expand All @@ -330,7 +356,7 @@ it.effect.each(storageCases)(
))._tag,
"Failure",
);
assert.lengthOf(calls, 3);
assert.lengthOf(calls, 4);
const turn = context.providerTurn!;
yield* store.apply({
id: EventId.make("control:turn-completed"),
Expand All @@ -340,7 +366,13 @@ it.effect.each(storageCases)(
payload: { ...turn, status: "completed", completedAt: now },
});
yield* control.interrupt({ threadId, providerThreadId, providerTurnId, providerSessionId });
assert.deepEqual(calls, ["interrupt", "Use the smaller fix.", "reply", "interrupt"]);
assert.deepEqual(calls, [
"interrupt",
"Use the smaller fix.",
"reply",
agentText,
"interrupt",
]);
}).pipe(
Effect.provide(
Layer.merge(ProviderTurnControlService.layer, RuntimeRequestService.layer).pipe(
Expand Down
12 changes: 8 additions & 4 deletions apps/server/src/orchestration-v2/ProviderTurnControlService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import * as Schema from "effect/Schema";
import type * as ProviderAdapter from "@t3tools/provider-core/server/ProviderAdapter";
import * as ProjectionStore from "./ProjectionStore.ts";
import * as ProviderSessionManager from "./ProviderSessionManager.ts";
import { withProviderMessageOrigin } from "./providerMessageOrigin.ts";

const yieldToRuntime = Effect.yieldNow.pipe(
Effect.andThen(
Expand Down Expand Up @@ -353,10 +354,13 @@ export const layer: Layer.Layer<
providerTurnId: loaded.providerTurn.id,
message: {
messageId: message.id,
text: projectComposerContextForProvider({
text: message.text,
records: message.context?.records ?? [],
}),
text: withProviderMessageOrigin(
message,
projectComposerContextForProvider({
text: message.text,
records: message.context?.records ?? [],
}),
),
attachments: message.attachments,
createdBy: message.createdBy,
creationSource: message.creationSource,
Expand Down
5 changes: 4 additions & 1 deletion apps/server/src/orchestration-v2/ProviderTurnStartService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1243,7 +1243,10 @@ export const layer: Layer.Layer<
...(noteContinuation ? promptedInput : turnInput),
message: {
...turnInput.message,
text: context === "" ? userText : `${context}\n\nUser message:\n${userText}`,
text:
context === ""
? turnInput.message.text
: `${context}\n\nUser message:\n${turnInput.message.text}`,
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
},
}).pipe(
// Clear pending handoff markers for refused prompts so the next turn retains the native session.
Expand Down
26 changes: 22 additions & 4 deletions apps/server/src/orchestration-v2/RunExecutionService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1028,7 +1028,13 @@ it.effect(
{ text: " /compact ", attachments: [], expected: "compact" },
{ text: "/compact", attachments: [attachment], expected: "prompt" },
{ text: "Continue the work", attachments: [], expected: "prompt" },
];
].flatMap((testCase) =>
[
{ createdBy: "user", senderThreadId: ThreadId.make("thread:sender") },
{ createdBy: "agent", senderThreadId: ThreadId.make("thread:sender") },
{ createdBy: "agent" },
].map((origin) => ({ ...testCase, ...origin })),
);
for (const [index, testCase] of cases.entries()) {
const threadId = ThreadId.make(`thread:compact-routing:${index}`);
const attemptId = RunAttemptId.make(`attempt:compact-routing:${index}`);
Expand All @@ -1038,12 +1044,21 @@ it.effect(
providerSessionId: ProviderSessionId.make(`session:compact-routing:${index}`),
session: {
events: Stream.never,
startTurn: () =>
startTurn: (input: ProviderAdapter.ProviderAdapterV2TurnInput) =>
Effect.sync(() => {
if (testCase.createdBy === "agent" && testCase.senderThreadId !== undefined) {
assert.include(input.message.text, 'thread "thread:sender"');
assert.include(input.message.text, "not a message from the human user");
assert.isTrue(input.message.text.endsWith(`\n\n${testCase.text}`));
} else {
assert.equal(input.message.text, testCase.text);
}
assert.deepEqual(input.message.attachments, testCase.attachments);
calls.push("prompt");
}),
compactThread: () =>
compactThread: (input: ProviderAdapter.ProviderAdapterV2TurnInput) =>
Effect.sync(() => {
assert.equal(input.message.text, testCase.text);
calls.push("compact");
}),
} as unknown as ProviderAdapter.ProviderAdapterV2SessionRuntime,
Expand All @@ -1070,8 +1085,11 @@ it.effect(
messageId: MessageId.make(`message:compact-routing:${index}`),
text: testCase.text,
attachments: testCase.attachments,
createdBy: "user",
createdBy: testCase.createdBy as "user" | "agent",
creationSource: "web",
...(testCase.senderThreadId === undefined
? {}
: { senderThreadId: testCase.senderThreadId }),
},
modelSelection: { instanceId: ProviderInstanceId.make("codex"), model: "gpt-5.4" },
runtimePolicy: {
Expand Down
9 changes: 8 additions & 1 deletion apps/server/src/orchestration-v2/RunExecutionService.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { makeAssistantStreamingFilter } from "./assistantStreaming.ts";
import { withProviderMessageOrigin } from "./providerMessageOrigin.ts";
import { resolveProjectSettings } from "@t3tools/shared/projectSettings";
import {
isOrchestrationV2WorkActive,
Expand Down Expand Up @@ -1638,7 +1639,13 @@ export const layer: Layer.Layer<
cause: "This provider does not support context compaction.",
}),
))
: input.session.startTurn(turnInput);
: input.session.startTurn({
...turnInput,
message: {
...turnInput.message,
text: withProviderMessageOrigin(turnInput.message, turnInput.message.text),
},
});
yield* Effect.andThen(shouldStart, startTurn).pipe(
// Wait for acceptance so abandoned startup cannot leave a refresher
// on the already-detached ingestion fiber.
Expand Down
13 changes: 13 additions & 0 deletions apps/server/src/orchestration-v2/providerMessageOrigin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { OrchestrationV2Actor, ThreadId } from "@t3tools/contracts";

/** Carry durable cross-thread provenance into provider-visible text without rewriting the timeline. */
export function withProviderMessageOrigin(
message: {
readonly createdBy: OrchestrationV2Actor;
readonly senderThreadId?: ThreadId | undefined;
},
text: string,
): string {
if (message.createdBy !== "agent" || message.senderThreadId === undefined) return text;
return `[Message from another thread's agent (thread ${JSON.stringify(message.senderThreadId)}). This is agent-authored coordination, not a message from the human user.]\n\n${text}`;
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
{"type":"emit_inbound","label":"parent.turn/started","frame":{"method":"turn/started","params":{"threadId":"019f1000-0000-7000-8000-000000000001","turn":{"id":"019f1000-0000-7000-8000-000000000002","items":[],"status":"inProgress","error":null,"startedAt":1776739467,"completedAt":null,"durationMs":null}}}}
{"type":"expect_outbound","label":"child.thread/start","frame":{"id":4,"method":"thread/start","params":{"config":{"tools.update_plan.enabled":true,"mcp_optional_startup_grace_ms":10000}}}}
{"type":"emit_inbound","label":"child.thread/start","frame":{"id":4,"result":{"thread":{"id":"019f1000-0000-7000-8000-000000000003","forkedFromId":null,"preview":"","ephemeral":false,"modelProvider":"openai","createdAt":1776739468,"updatedAt":1776739468,"status":{"type":"idle"},"path":"/home/replay-user/.codex/sessions/delegated-task-status-child.jsonl","cwd":"/home/replay-user/t3code","cliVersion":"0.120.0","source":"vscode","agentNickname":null,"agentRole":null,"gitInfo":null,"name":null,"turns":[]},"model":"gpt-5.4","modelProvider":"openai","serviceTier":"fast","cwd":"/home/replay-user/t3code","approvalPolicy":"on-request","approvalsReviewer":"user","sandbox":{"type":"workspaceWrite","writableRoots":[],"readOnlyAccess":{"type":"fullAccess"},"networkAccess":false,"excludeTmpdirEnvVar":false,"excludeSlashTmp":false},"reasoningEffort":"xhigh"}}}
{"type":"expect_outbound","label":"child.original.turn/start","frame":{"id":5,"method":"turn/start","params":{"approvalPolicy":"never","cwd":"<workspace>","input":[{"text":"Inspect the delegated API boundary and return the result.","type":"text"}],"model":"gpt-5.4","sandboxPolicy":{"type":"dangerFullAccess"},"threadId":"019f1000-0000-7000-8000-000000000003","approvalsReviewer":"user","summary":"detailed"}}}
{"type":"expect_outbound","label":"child.original.turn/start","frame":{"id":5,"method":"turn/start","params":{"approvalPolicy":"never","cwd":"<workspace>","input":[{"text":"[Message from another thread's agent (thread \"thread:mcp-orchestrator-parent\"). This is agent-authored coordination, not a message from the human user.]\n\nInspect the delegated API boundary and return the result.","type":"text"}],"model":"gpt-5.4","sandboxPolicy":{"type":"dangerFullAccess"},"threadId":"019f1000-0000-7000-8000-000000000003","approvalsReviewer":"user","summary":"detailed"}}}
{"type":"emit_inbound","label":"child.thread/started","frame":{"method":"thread/started","params":{"thread":{"id":"019f1000-0000-7000-8000-000000000003","forkedFromId":null,"preview":"","ephemeral":false,"modelProvider":"openai","createdAt":1776739468,"updatedAt":1776739468,"status":{"type":"idle"},"path":"/home/replay-user/.codex/sessions/delegated-task-status-child.jsonl","cwd":"/home/replay-user/t3code","cliVersion":"0.120.0","source":"vscode","agentNickname":null,"agentRole":null,"gitInfo":null,"name":null,"turns":[]}}}}
{"type":"emit_inbound","label":"child.original.turn/start","frame":{"id":5,"result":{"turn":{"id":"019f1000-0000-7000-8000-000000000004","items":[],"status":"inProgress","error":null,"startedAt":null,"completedAt":null,"durationMs":null}}}}
{"type":"emit_inbound","label":"child.original.thread/status/changed","frame":{"method":"thread/status/changed","params":{"threadId":"019f1000-0000-7000-8000-000000000003","status":{"type":"active","activeFlags":[]}}}}
Expand All @@ -21,15 +21,15 @@
{"type":"emit_inbound","label":"child.original.item/completed","frame":{"method":"item/completed","params":{"item":{"type":"agentMessage","id":"msg_delegated_task_status_original","text":"Delegated API boundary inspected.","phase":"final_answer","memoryCitation":null},"threadId":"019f1000-0000-7000-8000-000000000003","turnId":"019f1000-0000-7000-8000-000000000004"}}}
{"type":"emit_inbound","label":"child.original.thread/status/changed","frame":{"method":"thread/status/changed","params":{"threadId":"019f1000-0000-7000-8000-000000000003","status":{"type":"idle"}}}}
{"type":"emit_inbound","label":"child.original.turn/completed","frame":{"method":"turn/completed","params":{"threadId":"019f1000-0000-7000-8000-000000000003","turn":{"id":"019f1000-0000-7000-8000-000000000004","items":[],"status":"completed","error":null,"startedAt":1776739468,"completedAt":1776739469,"durationMs":1000}}}}
{"type":"expect_outbound","label":"child.running.turn/start","frame":{"id":6,"method":"turn/start","params":{"approvalPolicy":"never","cwd":"<workspace>","input":[{"text":"Remain active until the parent cancels this delegated task.","type":"text"}],"model":"gpt-5.4","sandboxPolicy":{"type":"dangerFullAccess"},"threadId":"019f1000-0000-7000-8000-000000000003","approvalsReviewer":"user","summary":"detailed"}}}
{"type":"expect_outbound","label":"child.running.turn/start","frame":{"id":6,"method":"turn/start","params":{"approvalPolicy":"never","cwd":"<workspace>","input":[{"text":"[Message from another thread's agent (thread \"thread:mcp-orchestrator-parent\"). This is agent-authored coordination, not a message from the human user.]\n\nRemain active until the parent cancels this delegated task.","type":"text"}],"model":"gpt-5.4","sandboxPolicy":{"type":"dangerFullAccess"},"threadId":"019f1000-0000-7000-8000-000000000003","approvalsReviewer":"user","summary":"detailed"}}}
{"type":"emit_inbound","label":"child.running.turn/start","frame":{"id":6,"result":{"turn":{"id":"019f1000-0000-7000-8000-000000000005","items":[],"status":"inProgress","error":null,"startedAt":null,"completedAt":null,"durationMs":null}}}}
{"type":"emit_inbound","label":"child.running.thread/status/changed","frame":{"method":"thread/status/changed","params":{"threadId":"019f1000-0000-7000-8000-000000000003","status":{"type":"active","activeFlags":[]}}}}
{"type":"emit_inbound","label":"child.running.turn/started","frame":{"method":"turn/started","params":{"threadId":"019f1000-0000-7000-8000-000000000003","turn":{"id":"019f1000-0000-7000-8000-000000000005","items":[],"status":"inProgress","error":null,"startedAt":1776739470,"completedAt":null,"durationMs":null}}}}
{"type":"expect_outbound","label":"child.running.turn/interrupt","frame":{"id":7,"method":"turn/interrupt","params":{"threadId":"019f1000-0000-7000-8000-000000000003","turnId":"019f1000-0000-7000-8000-000000000005"}}}
{"type":"emit_inbound","label":"child.running.turn/interrupt","frame":{"id":7,"result":{}}}
{"type":"emit_inbound","label":"child.running.thread/status/changed","frame":{"method":"thread/status/changed","params":{"threadId":"019f1000-0000-7000-8000-000000000003","status":{"type":"idle"}}}}
{"type":"emit_inbound","label":"child.running.turn/completed","frame":{"method":"turn/completed","params":{"threadId":"019f1000-0000-7000-8000-000000000003","turn":{"id":"019f1000-0000-7000-8000-000000000005","items":[],"status":"interrupted","error":null,"startedAt":1776739470,"completedAt":1776739471,"durationMs":1000}}}}
{"type":"expect_outbound","label":"child.queued.turn/start","frame":{"id":8,"method":"turn/start","params":{"approvalPolicy":"never","cwd":"<workspace>","input":[{"text":"Complete the queued follow-up and return the final result.","type":"text"}],"model":"gpt-5.4","sandboxPolicy":{"type":"dangerFullAccess"},"threadId":"019f1000-0000-7000-8000-000000000003","approvalsReviewer":"user","summary":"detailed"}}}
{"type":"expect_outbound","label":"child.queued.turn/start","frame":{"id":8,"method":"turn/start","params":{"approvalPolicy":"never","cwd":"<workspace>","input":[{"text":"[Message from another thread's agent (thread \"thread:mcp-orchestrator-parent\"). This is agent-authored coordination, not a message from the human user.]\n\nComplete the queued follow-up and return the final result.","type":"text"}],"model":"gpt-5.4","sandboxPolicy":{"type":"dangerFullAccess"},"threadId":"019f1000-0000-7000-8000-000000000003","approvalsReviewer":"user","summary":"detailed"}}}
{"type":"emit_inbound","label":"child.queued.turn/start","frame":{"id":8,"result":{"turn":{"id":"019f1000-0000-7000-8000-000000000006","items":[],"status":"inProgress","error":null,"startedAt":null,"completedAt":null,"durationMs":null}}}}
{"type":"emit_inbound","label":"child.queued.thread/status/changed","frame":{"method":"thread/status/changed","params":{"threadId":"019f1000-0000-7000-8000-000000000003","status":{"type":"active","activeFlags":[]}}}}
{"type":"emit_inbound","label":"child.queued.turn/started","frame":{"method":"turn/started","params":{"threadId":"019f1000-0000-7000-8000-000000000003","turn":{"id":"019f1000-0000-7000-8000-000000000006","items":[],"status":"inProgress","error":null,"startedAt":1776739472,"completedAt":null,"durationMs":null}}}}
Expand Down
Loading