Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
61 changes: 39 additions & 22 deletions apps/server/src/orchestration-v2/ProjectionStore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3193,7 +3193,7 @@ it.layer(layerTest)("ProjectionStoreV2", (it) => {
["a normalized idle command", "agent", "server", undefined, true, " \t\u00a0/COMPACT\u3000\n"],
["an ordinary prompt with idle metadata", "user", "web", "onReturn", false, "Continue"],
] as const)(
"marks the shell's latest run silent only for %s",
"keeps the shell on the user's last turn only during %s",
([name, createdBy, creationSource, idleCompaction, silent, text]) =>
Effect.gen(function* () {
const projectionStore = yield* ProjectionStore.ProjectionStoreV2;
Expand Down Expand Up @@ -3270,23 +3270,45 @@ it.layer(layerTest)("ProjectionStoreV2", (it) => {
userMessageId: messageId,
rootNodeId: null,
activeAttemptId: null,
status: "completed",
status: "running",
requestedAt: now,
startedAt: now,
completedAt: now,
completedAt: null,
checkpointId: null,
contextHandoffId: null,
},
});

const sqlShell = (yield* projectionStore.getShellSnapshot()).threads.find(
(thread) => thread.id === threadId,
);
const memoryShell = ProjectionStore.threadShellFromProjection(
yield* projectionStore.getThreadProjection(threadId),
);
assert.equal(sqlShell?.latestRunSilent, silent);
assert.equal(memoryShell.latestRunSilent, silent);
const presented = Effect.gen(function* () {
const sqlShell = (yield* projectionStore.getShellSnapshot()).threads.find(
(thread) => thread.id === threadId,
);
const memoryShell = ProjectionStore.threadShellFromProjection(
yield* projectionStore.getThreadProjection(threadId),
);
return [sqlShell, memoryShell].map((shell) => ({
latestRunId: shell?.latestRunId ?? null,
activityRunStatus: shell?.activityRunStatus ?? null,
activeRunId: shell?.activeRunId ?? null,
latestUserMessageAt: shell?.latestUserMessageAt != null,
}));
});
const visible = {
latestRunId: runId,
activityRunStatus: "running" as const,
activeRunId: runId,
latestUserMessageAt: true,
};
const hidden = {
latestRunId: null,
activityRunStatus: null,
activeRunId: null,
latestUserMessageAt: false,
};
assert.deepStrictEqual(yield* presented, [
silent ? hidden : visible,
silent ? hidden : visible,
]);

// Claude can finish the run successfully even when its /compact failed.
yield* projectionStore.apply({
Expand Down Expand Up @@ -3315,17 +3337,12 @@ it.layer(layerTest)("ProjectionStoreV2", (it) => {
updatedAt: now,
},
});
assert.equal(
(yield* projectionStore.getShellSnapshot()).threads.find(
(thread) => thread.id === threadId,
)?.latestRunSilent,
false,
);
assert.equal(
ProjectionStore.threadShellFromProjection(
yield* projectionStore.getThreadProjection(threadId),
).latestRunSilent,
false,
// A failure needs the user, so it surfaces even though its prompt stays hidden.
assert.deepStrictEqual(
yield* presented,
[visible, visible].map((shell) =>
silent ? { ...shell, latestUserMessageAt: false } : shell,
),
);
}),
);
Expand Down
116 changes: 68 additions & 48 deletions apps/server/src/orchestration-v2/ProjectionStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1115,7 +1115,6 @@ type ShellThreadRow = {
readonly latest_run_started_at: string | null;
readonly latest_run_completed_at: string | null;
readonly latest_run_stop_requested: number;
readonly latest_run_silent: number;
readonly active_run_id: string | null;
readonly active_run_status: string | null;
readonly activity_run_status: string | null;
Expand Down Expand Up @@ -1573,21 +1572,23 @@ export function threadShellFromProjection(
(left, right) =>
DateTime.toEpochMillis(right.updatedAt) - DateTime.toEpochMillis(left.updatedAt),
)[0] ?? null;
// T3 Code's own idle compactions stay out of the shell unless they fail, so thread lists,
// notifications and the relay keep presenting the user's last turn.
const runs = projection.runs.filter((run) => !isSilentRunForShell(run, projection));
const latestRun =
usageLimitRunPresentedAsLatest(
projection.runs,
runs,
projection.turnItems,
providerSession?.lastError ?? null,
) ?? latestUnheldRun(projection.runs);
) ?? latestUnheldRun(runs);
const activeRun =
projection.runs
runs
.filter(isInterruptibleRunForShell)
.toSorted((left, right) => right.ordinal - left.ordinal)[0] ?? null;
const activityRun =
projection.runs
.filter(isActivityRunForShell)
.toSorted((left, right) => right.ordinal - left.ordinal)[0] ?? null;
const liveRunIds = new Set(projection.runs.filter(isActivityRunForShell).map((run) => run.id));
runs.filter(isActivityRunForShell).toSorted((left, right) => right.ordinal - left.ordinal)[0] ??
null;
const liveRunIds = new Set(runs.filter(isActivityRunForShell).map((run) => run.id));
let pendingRuntimeRequest = pendingRuntimeRequestSummary(
projection.runtimeRequests
.filter((request) => request.status === "pending")
Expand All @@ -1614,7 +1615,7 @@ export function threadShellFromProjection(
) ?? pendingRuntimeRequest;
}
const userMessages = projection.messages
.filter((message) => message.role === "user")
.filter((message) => message.role === "user" && idleCompactionOf(message) === undefined)
.toSorted(
(left, right) =>
DateTime.toEpochMillis(right.updatedAt) - DateTime.toEpochMillis(left.updatedAt),
Expand All @@ -1625,7 +1626,7 @@ export function threadShellFromProjection(
providerThreads: projection.providerThreads,
turnItems: projection.turnItems,
activeProviderThreadId: projection.thread.activeProviderThreadId,
runs: projection.runs,
runs,
pullRequests: projection.thread.pullRequests,
});
return {
Expand Down Expand Up @@ -1665,16 +1666,6 @@ export function threadShellFromProjection(
projection.turnItems.some(
(item) => item.runId === latestRun.id && item.type === "run_interrupt_request",
),
latestRunSilent:
latestRun != null &&
projection.messages.some(
(message) =>
message.id === latestRun.userMessageId && idleCompactionOf(message) !== undefined,
) &&
!projection.turnItems.some(
(item) =>
item.runId === latestRun.id && item.type === "compaction" && item.status === "failed",
),
activeRunId: activeRun?.id ?? null,
blockedOnThreadWait: deriveBlockedOnThreadWait({ activeRun, turnItems: projection.turnItems }),
activityRunStatus: activityRun?.status ?? null,
Expand Down Expand Up @@ -1755,6 +1746,20 @@ function activeProviderGoalForShell(
return providerThreads.find((thread) => thread.id === activeProviderThreadId)?.goal ?? null;
}

function isSilentRunForShell(
run: OrchestrationV2ThreadProjection["runs"][number],
projection: Pick<OrchestrationV2ThreadProjection, "messages" | "turnItems">,
): boolean {
return (
projection.messages.some(
(message) => message.id === run.userMessageId && idleCompactionOf(message) !== undefined,
) &&
!projection.turnItems.some(
Comment thread
macroscopeapp[bot] marked this conversation as resolved.
Resolved
(item) => item.runId === run.id && item.type === "compaction" && item.status === "failed",
)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
);
}

function isInterruptibleRunForShell(run: OrchestrationV2ThreadProjection["runs"][number]): boolean {
return run.status === "preparing" || run.status === "starting" || run.status === "running";
}
Expand All @@ -1777,7 +1782,6 @@ type ShellThreadState = {
readonly latestRunStartedAt: DateTime.Utc | null;
readonly latestRunCompletedAt: DateTime.Utc | null;
readonly latestRunStopRequested: boolean;
readonly latestRunSilent: boolean;
readonly activeRunId: RunId | null;
readonly blockedOnThreadWait: boolean;
readonly activityRunStatus: ShellActivityRunStatus | null;
Expand Down Expand Up @@ -1933,7 +1937,6 @@ function shellFromState(input: {
latestRunStartedAt: input.state.latestRunStartedAt,
latestRunCompletedAt: input.state.latestRunCompletedAt,
latestRunStopRequested: input.state.latestRunStopRequested,
latestRunSilent: input.state.latestRunSilent,
activeRunId: input.state.activeRunId,
blockedOnThreadWait: input.state.blockedOnThreadWait,
activityRunStatus: input.state.activityRunStatus,
Expand Down Expand Up @@ -5743,6 +5746,42 @@ export const layer: Layer.Layer<ProjectionStoreV2, never, SqlClient.SqlClient> =
),
);

// Mirror idleCompactionOf, including JavaScript trim whitespace.
const isIdleCompactionMessage = (alias: string) => {
const payload = sql.literal(`${alias}.payload_json`);
return sql`(
${sql.literal(`${alias}.role`)} = 'user'
AND lower(trim(json_extract(${payload}, '$.text'),
char(9, 10, 11, 12, 13, 32, 160, 5760, 8192, 8193, 8194, 8195,
8196, 8197, 8198, 8199, 8200, 8201, 8202, 8232, 8233, 8239, 8287, 12288, 65279)
)) = ${IDLE_COMPACTION_TEXT}
AND coalesce(json_array_length(${payload}, '$.attachments'), 0) = 0
AND (
json_extract(${payload}, '$.idleCompaction') IS NOT NULL
OR (
json_extract(${payload}, '$.createdBy') = 'agent'
AND json_extract(${payload}, '$.creationSource') = 'server'
)
)
)`;
};
// Mirrors isSilentRunForShell.
const isSilentShellRun = (alias: string) => {
const run = sql.literal(alias);
return sql`(
EXISTS (
SELECT 1 FROM orchestration_v2_projection_messages prompt
WHERE prompt.message_id = json_extract(${run}.payload_json, '$.userMessageId')
AND ${isIdleCompactionMessage("prompt")}
) AND NOT EXISTS (
SELECT 1 FROM orchestration_v2_projection_turn_items compaction
WHERE compaction.thread_id = ${run}.thread_id AND compaction.run_id = ${run}.run_id
AND compaction.type = 'compaction'
AND compaction.status = 'failed'
)
)`;
};

const selectShellThreadRows = (
threadId?: ThreadId,
{
Expand Down Expand Up @@ -5773,36 +5812,14 @@ export const layer: Layer.Layer<ProjectionStoreV2, never, SqlClient.SqlClient> =
WHERE stop_request.thread_id = t.thread_id AND stop_request.run_id = presented.run_id
AND stop_request.type = 'run_interrupt_request'
) AS latest_run_stop_requested,
-- Mirrors idleCompactionOf, including JavaScript trim whitespace.
EXISTS (
SELECT 1 FROM orchestration_v2_projection_messages prompt
WHERE prompt.message_id = json_extract(presented.payload_json, '$.userMessageId')
AND prompt.role = 'user'
AND lower(trim(json_extract(prompt.payload_json, '$.text'),
char(9, 10, 11, 12, 13, 32, 160, 5760, 8192, 8193, 8194, 8195,
8196, 8197, 8198, 8199, 8200, 8201, 8202, 8232, 8233, 8239, 8287, 12288, 65279)
)) = ${IDLE_COMPACTION_TEXT}
AND coalesce(json_array_length(prompt.payload_json, '$.attachments'), 0) = 0
AND (
json_extract(prompt.payload_json, '$.idleCompaction') IS NOT NULL
OR (
json_extract(prompt.payload_json, '$.createdBy') = 'agent'
AND json_extract(prompt.payload_json, '$.creationSource') = 'server'
)
)
) AND NOT EXISTS (
SELECT 1 FROM orchestration_v2_projection_turn_items compaction
WHERE compaction.thread_id = t.thread_id AND compaction.run_id = presented.run_id
AND compaction.type = 'compaction'
AND compaction.status = 'failed'
) AS latest_run_silent,
active.run_id AS active_run_id,
active.status AS active_run_status,
(
SELECT r.status
FROM orchestration_v2_projection_runs r
WHERE r.thread_id = t.thread_id
AND r.status IN ('preparing', 'starting', 'running', 'waiting')
AND NOT ${isSilentShellRun("r")}
ORDER BY r.ordinal DESC, r.run_id DESC
LIMIT 1
) AS activity_run_status,
Expand All @@ -5816,6 +5833,7 @@ export const layer: Layer.Layer<ProjectionStoreV2, never, SqlClient.SqlClient> =
FROM orchestration_v2_projection_runs r
WHERE r.thread_id = t.thread_id
AND r.status IN ('preparing', 'starting', 'running', 'waiting')
AND NOT ${isSilentShellRun("r")}
ORDER BY r.ordinal DESC, r.run_id DESC
LIMIT 1
) AS activity_run_started_at,
Expand Down Expand Up @@ -5887,6 +5905,7 @@ export const layer: Layer.Layer<ProjectionStoreV2, never, SqlClient.SqlClient> =
FROM orchestration_v2_projection_messages message
WHERE message.thread_id = t.thread_id
AND message.role = 'user'
AND NOT ${isIdleCompactionMessage("message")}
ORDER BY message.updated_at DESC, message.message_id DESC
LIMIT 1
) AS latest_user_message_at,
Expand All @@ -5912,6 +5931,7 @@ export const layer: Layer.Layer<ProjectionStoreV2, never, SqlClient.SqlClient> =
FROM orchestration_v2_projection_runs r
WHERE r.thread_id = t.thread_id
AND r.status IN ('preparing', 'starting', 'running')
AND NOT ${isSilentShellRun("r")}
ORDER BY r.ordinal DESC, r.run_id DESC
LIMIT 1
)
Expand All @@ -5924,6 +5944,7 @@ export const layer: Layer.Layer<ProjectionStoreV2, never, SqlClient.SqlClient> =
candidate.status = 'queued'
AND json_extract(candidate.payload_json, '$.queueHeld') IS 1
)
AND NOT ${isSilentShellRun("candidate")}
ORDER BY candidate.ordinal DESC, candidate.run_id DESC
LIMIT 1
)
Expand Down Expand Up @@ -6139,6 +6160,7 @@ export const layer: Layer.Layer<ProjectionStoreV2, never, SqlClient.SqlClient> =
SELECT message.updated_at
FROM orchestration_v2_projection_messages message
WHERE message.thread_id = t.thread_id AND message.role = 'user'
AND NOT ${isIdleCompactionMessage("message")}
ORDER BY message.updated_at DESC, message.message_id DESC
LIMIT 1
) AS latest_user_message_at,
Expand All @@ -6154,6 +6176,7 @@ export const layer: Layer.Layer<ProjectionStoreV2, never, SqlClient.SqlClient> =
LEFT JOIN orchestration_v2_projection_runs r ON r.run_id = (
SELECT latest.run_id FROM orchestration_v2_projection_runs latest
WHERE latest.thread_id = t.thread_id
AND NOT ${isSilentShellRun("latest")}
ORDER BY latest.ordinal DESC, latest.run_id DESC
LIMIT 1
)
Expand Down Expand Up @@ -6343,7 +6366,6 @@ export const layer: Layer.Layer<ProjectionStoreV2, never, SqlClient.SqlClient> =
? null
: DateTime.makeUnsafe(row.latest_run_completed_at);
let latestRunStopRequested = row.latest_run_stop_requested === 1;
let latestRunSilent = row.latest_run_silent === 1;
if (row.blocking_run_id !== null && row.blocking_failure_payload_json !== null) {
const blockingFailure = yield* decodeTurnItemPayload(
row.blocking_failure_payload_json,
Expand All @@ -6357,7 +6379,6 @@ export const layer: Layer.Layer<ProjectionStoreV2, never, SqlClient.SqlClient> =
terminalFailureItem = blockingFailure;
latestRunId = RunId.make(row.blocking_run_id);
latestRunStopRequested = row.blocking_run_stop_requested === 1;
latestRunSilent = false;
latestRunStatus = "failed";
latestRunRequestedAt =
row.blocking_run_requested_at === null
Expand Down Expand Up @@ -6398,7 +6419,6 @@ export const layer: Layer.Layer<ProjectionStoreV2, never, SqlClient.SqlClient> =
latestRunStartedAt,
latestRunCompletedAt,
latestRunStopRequested,
latestRunSilent,
activeRunId: row.active_run_id === null ? null : RunId.make(row.active_run_id),
blockedOnThreadWait: deriveBlockedOnThreadWait({
activeRun:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@ const thread = {
id: "thread",
title: "Test thread",
archivedAt: null as string | null,
latestRunSilent: false,
hasPendingApprovals: false,
hasPendingUserInput: false,
session: null as { status: string } | null,
Expand Down Expand Up @@ -114,7 +113,6 @@ function toV2ThreadShell(input: typeof thread) {
createdBy: "user",
creationSource: "web",
latestRunId: latestTurn.turnId,
latestRunSilent: input.latestRunSilent,
activeRunId: null,
status:
latestTurn.state === "completed"
Expand Down Expand Up @@ -220,28 +218,6 @@ it.each(["hasPendingApprovals", "hasPendingUserInput"] as const)(
},
);

it.each([
["pre-expiry compaction", true, "completed", []],
["manual compaction", false, "completed", ["Thread completed"]],
["failed pre-expiry compaction", true, "error", ["Thread failed"]],
] as const)("notifies for %s only when attention is needed", async (_, silent, status, titles) => {
await using app = setup();
state.environmentIds = ["one"];
state.shells.set("one", shell({ latestRunSilent: silent }));
await using screen = await render(app.ui());
state.shells.set(
"one",
shell({
latestRunSilent: silent,
latestTurn: { turnId: "turn", state: status, completedAt: "2026-09-13T07:01:00.000Z" },
}),
);
using _timers = fakeTimers();
await screen.rerender(app.ui());
await vi.advanceTimersByTimeAsync(WORKING_EXIT_GRACE_MS);
expect(TestNotification.sent.map((notification) => notification.title)).toEqual(titles);
});

it("holds a completion through a continuation gap and notifies once it settles", async () => {
await using app = setup();
await app.render();
Expand Down
Loading
Loading