Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
7304508
feat(web): redesign composer and conversation timeline
maria-rcks Oct 31, 2026
35d03a7
refactor(chat): remove unused work log helpers
maria-rcks Oct 31, 2026
6998ed6
fix(chat): stop tool shine while waiting for input
maria-rcks Oct 31, 2026
77e59e7
fix(chat): keep paused timer clear of question text
maria-rcks Oct 31, 2026
9737141
fix(chat): keep approval decisions together on narrow screens
maria-rcks Oct 31, 2026
110b289
fix(chat): match composer corner and send slot dimensions
maria-rcks Oct 31, 2026
3875262
fix(chat): match the collapsed working window dimensions
maria-rcks Oct 31, 2026
2f931e1
fix(chat): acknowledge custom question answers in the footer
maria-rcks Oct 31, 2026
9cf67c0
style(chat): format grouped approval actions
maria-rcks Oct 31, 2026
c1c54d6
fix(chat): summarize wake-ups and retain the latest answer
maria-rcks Oct 31, 2026
9746519
fix(chat): use the pill radius token and accessible wake-up label
maria-rcks Oct 31, 2026
24861a6
style(chat): format the composer surface
maria-rcks Oct 31, 2026
e691c4e
fix(chat): align timeline rows and attachment layouts with design
maria-rcks Oct 31, 2026
a4931fd
fix(chat): keep row class helper compiler eligible
maria-rcks Oct 31, 2026
f0dcd0b
fix(chat): expose the existing interrupt action on live work rows
maria-rcks Oct 31, 2026
3fb79ac
fix(chat): keep interrupt available in grouped live rows
maria-rcks Oct 31, 2026
1543f6f
fix(chat): keep turn interrupt off task summary rows
maria-rcks Oct 31, 2026
b7ba347
fix(chat): match composer suggestion and picker menus to the design
maria-rcks Oct 31, 2026
5451c07
fix(chat): keep branch picker separators stable
maria-rcks Oct 31, 2026
dd2960c
fix(chat): let the picker text role own its weight
maria-rcks Oct 31, 2026
d22553f
fix(chat): match the option c composer and integrated tray
maria-rcks Oct 31, 2026
9224e95
fix(chat): validate plan actions and queued reorder commands
maria-rcks Oct 31, 2026
62b0820
fix(chat): keep work and notices in the composer tray
maria-rcks Oct 31, 2026
736c698
fix(chat): reserve the machine chip for remote threads
maria-rcks Oct 31, 2026
598de64
fix(chat): wire approval shortcuts in the composer tray
maria-rcks Oct 31, 2026
230c2aa
fix(chat): settle completed tasks and cap the prompt at eight lines
maria-rcks Oct 31, 2026
2064af0
fix(chat): keep tray work bound to the active run
maria-rcks Oct 31, 2026
a6d44ff
fix(chat): accept approval keys from the focused workspace
maria-rcks Oct 31, 2026
57676eb
fix(chat): collapse composer controls on narrow panes
maria-rcks Oct 31, 2026
d37466b
fix(chat): keep picked attachments above the prompt
maria-rcks Oct 31, 2026
6f8b8d8
fix(chat): preview queue order while dragging
maria-rcks Oct 31, 2026
c601bbe
fix(chat): show the branch picker chevron in the tray
maria-rcks Oct 31, 2026
16be8a2
fix(chat): stage file rows without an automatic reference
maria-rcks Oct 31, 2026
e82ec27
fix(chat): keep question actions in the tray panel
maria-rcks Oct 31, 2026
58de01d
fix(chat): align composer and requests with final design
maria-rcks Oct 31, 2026
b2dec86
fix(chat): anchor composer and branch popup to design edges
maria-rcks Oct 31, 2026
5d3fcc7
fix(chat): keep approval actions together and remove unused drop overlay
maria-rcks Oct 31, 2026
5974808
fix(chat): keep tray border inside composer dimensions
maria-rcks Oct 31, 2026
5418559
fix(chat): reconcile node views outside React lifecycle
maria-rcks Oct 31, 2026
b8c31d7
fix(chat): preserve synchronous caret updates and popup width
maria-rcks Oct 31, 2026
a1edbff
fix(web): steered turns' fold rows each show their own time and edits…
flamboh Nov 2, 2026
9570a66
fix(web): a finished turn's label stays readable next to still-runnin…
flamboh Nov 2, 2026
99e8f50
fix(web): steered turns read like a chat with one fold at the end (#2…
flamboh Nov 2, 2026
dea5277
fix(web): the working window expands downward without moving the bar …
flamboh Nov 2, 2026
15838f4
refactor(chat): remove obsolete thinking interrupt flag
maria-rcks Nov 3, 2026
f54df84
fix(web): keep background work off turn fold rows
maria-rcks Nov 2, 2026
00015fb
feat(web): context chips drop the pill and read as colored text, in r…
flamboh Nov 2, 2026
db8ac48
fix(web): narrow composers collapse the env and branch chips instead …
flamboh Nov 2, 2026
58e4db4
fix(web): scroll collapsed working history independently
maria-rcks Nov 3, 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
4 changes: 2 additions & 2 deletions apps/mobile/src/features/threads/ComposerCommandPopover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ function itemIcon(item: ComposerCommandItem): AppSymbolName | null {
function groupLabel(triggerKind: ComposerTriggerKind | null): string | null {
switch (triggerKind) {
case "pull-request":
return "Pull requests";
return "Pull requests and threads";
case "slash-command":
return "Commands";
case "skill":
Expand All @@ -147,7 +147,7 @@ function emptyText(triggerKind: ComposerTriggerKind | null, isLoading: boolean):
}
switch (triggerKind) {
case "pull-request":
return "No matching pull requests.";
return "No matching pull requests or threads.";
case "path":
return "No matching files or folders.";
case "skill":
Expand Down
21 changes: 20 additions & 1 deletion apps/mobile/src/features/threads/PendingApprovalCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,26 @@ export function PendingApprovalCard(props: PendingApprovalCardProps) {
<Text className="font-t3-bold text-lg text-foreground">
{props.approval.appName ?? props.approval.requestKind}
</Text>
{props.approval.detail ? (
{props.approval.files && props.approval.files.length > 0 ? (
<View className="gap-1">
{props.approval.files.map((file) => (
<View key={`${file.operation ?? ""}:${file.path}`} className="flex-row gap-2">
<Text
className="flex-1 font-sans text-sm text-foreground-secondary"
numberOfLines={1}
ellipsizeMode="middle"
>
{file.path}
</Text>
{file.additions !== undefined || file.deletions !== undefined ? (
<Text className="font-mono text-xs text-foreground-secondary">
+{file.additions ?? 0} −{file.deletions ?? 0}
</Text>
) : null}
</View>
))}
</View>
) : props.approval.detail ? (
<Text className="font-sans text-sm leading-normal text-foreground-secondary">
{props.approval.detail}
</Text>
Expand Down
24 changes: 19 additions & 5 deletions apps/mobile/src/features/threads/use-composer-command-menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -301,10 +301,11 @@ export function useComposerCommandMenu({
cwd: trigger?.kind === "path" ? projectCwd : null,
query: trigger?.kind === "path" ? trigger.query : null,
});
const isThreadTrigger = trigger?.kind === "path" || trigger?.kind === "pull-request";
// Mounts older settled threads matching the mention, which shells may leave out.
useThreadSearch(
trigger?.kind === "path" && environmentId ? [environmentId] : [],
trigger?.kind === "path" ? trigger.query : "",
isThreadTrigger && environmentId ? [environmentId, ...linkedEnvironments.keys()] : [],
trigger?.query ?? "",
{ titlesOnly: true },
);
const pullRequestSearch = useComposerPullRequestSearch({
Expand All @@ -319,9 +320,21 @@ export function useComposerCommandMenu({
if (!trigger) return [];

if (trigger.kind === "pull-request") {
return pullRequestSearch.entries.map((entry) => ({
// `#` lists pull requests, then threads; a bare `#` offers the most recent threads.
const threadItems = environmentId
? matchComposerThreadItems({
shells: threadShells,
environmentId,
linkedEnvironments,
excludeThreadId: currentThreadId,
query: trigger.query,
allowEmptyQuery: true,
now,
})
: [];
const pullRequestItems = pullRequestSearch.entries.map((entry) => ({
id: `pr:${entry.projectId}:${entry.repository}:${entry.number}`,
type: "pull-request",
type: "pull-request" as const,
pullRequest: {
number: entry.number,
title: entry.title,
Expand All @@ -338,6 +351,7 @@ export function useComposerCommandMenu({
: `${entry.repository} · `
}${entry.title}`,
}));
return [...pullRequestItems, ...threadItems];
}

if (trigger.kind === "slash-command") {
Expand Down Expand Up @@ -520,7 +534,7 @@ export function useComposerCommandMenu({
return;
}
if (item.type === "thread") {
if (!ownerKey || trigger.kind !== "path") return;
if (!ownerKey || (trigger.kind !== "path" && trigger.kind !== "pull-request")) return;
const shell = threadShells.find(
(candidate) =>
candidate.environmentId === item.thread.environmentId &&
Expand Down
10 changes: 5 additions & 5 deletions apps/server/src/guest/GuestShareService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ import {
} from "@t3tools/contracts";
import { chatMarkdownImageSources } from "@t3tools/shared/markdownPipeline";
import { htmlRenderFromToolItem } from "@t3tools/shared/toolOutput";
import {
projectDomainEventForWire,
projectTurnItemForDetail,
projectTurnItemForWire,
} from "@t3tools/shared/wireProjection";
import * as Context from "effect/Context";
import * as Crypto from "effect/Crypto";
import * as DateTime from "effect/DateTime";
Expand Down Expand Up @@ -55,11 +60,6 @@ import { GUEST_FAILURE } from "../orchestration-v2/GuestShareDecider.ts";
import * as Orchestrator from "../orchestration-v2/Orchestrator.ts";
import { coalesceThreadLiveStream } from "../orchestration-v2/ThreadLiveEventCoalescer.ts";
import { buildBoundedThreadStreamSnapshot } from "../orchestration-v2/ThreadStream.ts";
import {
projectDomainEventForWire,
projectTurnItemForDetail,
projectTurnItemForWire,
} from "../orchestration-v2/WireProjection.ts";
import * as ProviderRegistry from "../provider/ProviderRegistry.ts";
import * as ServerSettings from "../serverSettings.ts";
import * as AnalyticsService from "../telemetry/AnalyticsService.ts";
Expand Down
191 changes: 121 additions & 70 deletions apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2170,84 +2170,135 @@ describe("CodexAdapterV2 post-settle continuation", () => {
),
);

it.effect("compacts Codex with the native RPC and completes the compaction turn", () =>
Effect.scoped(
Effect.gen(function* () {
const nativeThreadId = "compact-thread";
const nativeTurnId = "compact-turn";
const item = { type: "contextCompaction", id: "compact-item" };
const transcript = makeCodexReplayTranscript({
scenario: "native-compaction",
entries: [
...codexReplayPreamble({ nativeThreadId, nativeTurnId, prompt: "unused" }).slice(0, 5),
{
type: "expect_outbound",
label: "compact",
frame: {
id: 3,
method: "thread/compact/start",
params: { threadId: nativeThreadId },
it.effect.each(["reset", "billable-only", "missing", "other-turn-reset", "late-reset"] as const)(
"compacts Codex with %s token telemetry",
(usageKind) =>
Effect.scoped(
Effect.gen(function* () {
const nativeThreadId = "compact-thread";
const nativeTurnId = "compact-turn";
const item = { type: "contextCompaction", id: "compact-item" };
const usageFrame = (tokens: number, inputTokens: number, turnId = nativeTurnId) => ({
type: "emit_inbound" as const,
label: "usage",
frame: {
method: "thread/tokenUsage/updated",
params: {
threadId: nativeThreadId,
turnId,
tokenUsage: {
total: {
totalTokens: 9437579,
inputTokens: 9408294,
cachedInputTokens: 8982400,
outputTokens: 29285,
reasoningOutputTokens: 8756,
},
last: {
totalTokens: tokens,
inputTokens,
cachedInputTokens: 0,
outputTokens: 0,
reasoningOutputTokens: 0,
},
modelContextWindow: 258400,
},
},
},
{ type: "emit_inbound", label: "compact", frame: { id: 3, result: {} } },
{
type: "emit_inbound",
label: "start",
frame: {
method: "turn/started",
params: {
threadId: nativeThreadId,
turn: makeCodexReplayTurn({ id: nativeTurnId, status: "inProgress" }),
});
const transcript = makeCodexReplayTranscript({
scenario: "native-compaction",
entries: [
...codexReplayPreamble({ nativeThreadId, nativeTurnId, prompt: "unused" }).slice(
0,
5,
),
{
type: "expect_outbound",
label: "compact",
frame: {
id: 3,
method: "thread/compact/start",
params: { threadId: nativeThreadId },
},
},
},
...(["item/started", "item/completed"] as const).map((method) => ({
type: "emit_inbound" as const,
label: method,
frame: { method, params: { threadId: nativeThreadId, turnId: nativeTurnId, item } },
})),
{
type: "emit_inbound",
label: "complete",
frame: {
method: "turn/completed",
params: {
threadId: nativeThreadId,
turn: makeCodexReplayTurn({ id: nativeTurnId, status: "completed" }),
{ type: "emit_inbound", label: "compact", frame: { id: 3, result: {} } },
{
type: "emit_inbound",
label: "start",
frame: {
method: "turn/started",
params: {
threadId: nativeThreadId,
turn: makeCodexReplayTurn({ id: nativeTurnId, status: "inProgress" }),
},
},
},
},
],
});
const harness = yield* makeCodexReplayHarness(transcript);
assert.isDefined(harness.runtime.compactThread);
yield* harness.runtime.compactThread!(
makeCodexTestTurnInput({
threadId: harness.threadId,
providerThread: harness.providerThread,
now: yield* DateTime.now,
attemptId: RunAttemptId.make("compact-attempt"),
text: "/compact",
}),
);
yield* harness.firstTerminal;
const items = harness.events.flatMap((event) =>
event.type === "turn_item.updated" && event.turnItem.type === "compaction"
? [event.turnItem]
: [],
);
assert.deepEqual(
items.map((entry) => entry.status),
["running", "completed"],
);
assert.equal(items[0]?.id, items[1]?.id);
assert.equal(harness.terminalEvents()[0]?.status, "completed");
}).pipe(
Effect.provide(
Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
...(usageKind === "missing" ? [] : [usageFrame(223313, 222435)]),
{
type: "emit_inbound",
label: "compaction-start",
frame: {
method: "item/started",
params: { threadId: nativeThreadId, turnId: nativeTurnId, item },
},
},
...(usageKind === "missing" ? [] : [usageFrame(226619, 223200)]),
...(usageKind === "reset" ? [usageFrame(21607, 0)] : []),
...(usageKind === "other-turn-reset" ? [usageFrame(21607, 0, "other-turn")] : []),
{
type: "emit_inbound",
label: "compaction-complete",
frame: {
method: "item/completed",
params: { threadId: nativeThreadId, turnId: nativeTurnId, item },
},
},
...(usageKind === "late-reset" ? [usageFrame(21607, 0)] : []),
{
type: "emit_inbound",
label: "complete",
frame: {
method: "turn/completed",
params: {
threadId: nativeThreadId,
turn: makeCodexReplayTurn({ id: nativeTurnId, status: "completed" }),
},
},
},
],
});
const harness = yield* makeCodexReplayHarness(transcript);
assert.isDefined(harness.runtime.compactThread);
yield* harness.runtime.compactThread!(
makeCodexTestTurnInput({
threadId: harness.threadId,
providerThread: harness.providerThread,
now: yield* DateTime.now,
attemptId: RunAttemptId.make("compact-attempt"),
text: "/compact",
}),
);
yield* harness.firstTerminal;
const items = harness.events.flatMap((event) =>
event.type === "turn_item.updated" && event.turnItem.type === "compaction"
? [event.turnItem]
: [],
);
assert.deepEqual(
items.map((entry) => entry.status),
["running", "completed"],
);
assert.equal(items[0]?.id, items[1]?.id);
assert.equal(items[1]?.beforeTokenCount, usageKind === "missing" ? undefined : 223313);
assert.equal(items[1]?.afterTokenCount, usageKind === "reset" ? 21607 : undefined);
assert.equal(harness.terminalEvents()[0]?.status, "completed");
}).pipe(
Effect.provide(
Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
),
),
),
),
);

it.effect.each(["failed", "interrupted", "Stop"] as const)(
Expand Down
Loading
Loading