Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
5fe920c
feat(providers): add Pi coding agent
StiensWout Aug 16, 2026
fedbf46
fix(providers): address Pi review findings
StiensWout Aug 16, 2026
8d0fa60
fix(providers): never gate Pi turn start on the prompt ack
StiensWout Aug 16, 2026
456fb56
fix(providers): settle command-only Pi prompts without an agent run
StiensWout Aug 16, 2026
4696647
fix(providers): keep one start time per Pi tool call
StiensWout Aug 16, 2026
609ed85
fix(providers): settle Pi command-only turns when the idle probe fails
StiensWout Aug 16, 2026
4ddea73
fix(providers): build the Pi snapshot error from its own attributes
StiensWout Aug 16, 2026
a324a91
feat(providers): Pi session-tree rollback and session-name sync
StiensWout Aug 16, 2026
067b4f4
fix(providers): clear the Pi session-name cache on session switch
StiensWout Aug 16, 2026
e75c8ce
fix(providers): keep Pi turn refs honest after a failed entry capture
StiensWout Aug 16, 2026
60934aa
fix(providers): clear the Pi leaf cursor flag when the tree is re-bas…
StiensWout Aug 16, 2026
d2f801a
fix(providers): declare the Pi rollback snapshot capability
StiensWout Aug 16, 2026
bbd8844
feat(providers): surface pi subagent-extension tasks as native subagents
StiensWout Aug 16, 2026
efd0f2e
fix(providers): correct Pi subagent failure status and bound error logs
StiensWout Aug 16, 2026
ebb9404
fix(providers): present Pi tools aborted by Stop as interrupted
StiensWout Aug 16, 2026
6a74d5a
fix(providers): close Pi RPC spec gaps from the protocol cross-check
StiensWout Aug 16, 2026
8331b4f
fix(providers): correct Pi retry, teardown and turn-boundary handling
StiensWout Aug 16, 2026
195237f
fix(server): translate policy capability rejections into readable errors
StiensWout Aug 16, 2026
7033fbb
feat(pi): inject the T3 MCP server into Pi sessions
mwolson Aug 16, 2026
2d84434
feat(pi): bind subagent tasks as resumeable child threads
mwolson Aug 17, 2026
d46d4bb
feat(pi): advertise per-model thinking levels including Extra High
mwolson Aug 17, 2026
b9f1cc7
feat(server): restore pi user extensions and give subagent children T…
StiensWout Aug 17, 2026
b393776
feat(pi): fork threads, read snapshots, live status rows and safer stops
StiensWout Aug 17, 2026
ee30159
fix(pi): stop wrapping the first prompt so slash commands expand again
StiensWout Aug 17, 2026
aad8403
fix(providers): harden Pi lifecycle and context usage
StiensWout Aug 17, 2026
4ab4161
fix(providers): scope Pi status rows to each turn
StiensWout Aug 17, 2026
022bbd7
fix(providers): retain Pi context usage on stats gaps
StiensWout Aug 17, 2026
2a822f2
fix(providers): block Pi thread switches during turns
StiensWout Aug 17, 2026
9f1589c
fix(providers): serialize Pi thread registration
StiensWout Aug 17, 2026
d19aa81
fix(pi): show configured thinking level as default
StiensWout Aug 17, 2026
2593c0b
feat(pi): support dollar skill references
StiensWout Aug 17, 2026
26f1abe
fix(pi): fail closed after session switch
StiensWout Aug 17, 2026
6b54775
fix(pi): retry live skill discovery
StiensWout Aug 17, 2026
17abf82
fix(pi): type empty skill fallback
StiensWout Aug 17, 2026
cc47dfa
fix(pi): stop without provider stream error
StiensWout Aug 18, 2026
9969831
fix(pi): serialize session-bound operations
StiensWout Aug 18, 2026
4eea609
fix(pi): restore npm package extensions
StiensWout Aug 18, 2026
e262b88
fix(pi): preserve extension package filters
StiensWout Aug 18, 2026
281b0d5
fix(pi): honor project trust decisions
StiensWout Aug 19, 2026
20d79e7
fix(pi): avoid native path dependency
StiensWout Aug 19, 2026
ff93beb
fix(pi): retain validated project cwd
StiensWout Aug 19, 2026
2843ca2
fix(pi): keep one provider thread row per app thread
StiensWout Aug 24, 2026
ba38249
fix(pi): skip dotfile entries in extension discovery
StiensWout Aug 24, 2026
b18b226
fix(opencode): adopt handed-over provider thread identity
StiensWout Aug 24, 2026
9f9ce4a
fix(server): type the provider session test settings layer
StiensWout Aug 24, 2026
1b30c3f
test(opencode): cover ensureThread identity adoption
StiensWout Aug 24, 2026
e9d3ae6
refactor(pi): use native extension ownership
StiensWout Aug 25, 2026
0b63ac6
fix(pi): retain delayed context usage
StiensWout Aug 25, 2026
51eb715
fix(pi): complete runtime compatibility
StiensWout Aug 25, 2026
d67c84f
fix(pi): close permission and steering races
StiensWout Aug 25, 2026
3872be5
fix(pi): isolate session MCP credentials
StiensWout Aug 25, 2026
d2f5248
refactor(pi): centralize runtime mode support
StiensWout Aug 25, 2026
36a3bee
fix(pi): normalize launch args and rollback usage
StiensWout Aug 25, 2026
2fcc698
fix(pi): harden command ordering
StiensWout Aug 25, 2026
ed81cdf
fix(pi): bind deferred acks to turns
StiensWout Aug 25, 2026
f4364b2
fix(pi): Diagnose early Pi RPC process exits
mwolson Aug 17, 2026
0c8092a
fix(pi): Surface terminal provider errors
mwolson Aug 19, 2026
661ec6f
fix(pi): Expand every $ skill and keep pathless catalog entries
mwolson Aug 20, 2026
4c32a65
fix(pi): Make every reasoning choice an explicit level
mwolson Aug 22, 2026
8d00d58
fix(pi): Reset thinking when returning to Pi default
mwolson Aug 22, 2026
6e8473d
feat(pi): Surface retry and compaction lifecycle
mwolson Aug 24, 2026
8513912
fix(pi): Keep extension recovery within active turns
mwolson Aug 25, 2026
d0d58b1
refactor(pi): report context usage on provider turns
StiensWout Aug 28, 2026
02bfce3
fix(web): Remember reasoning effort per model
mwolson Aug 22, 2026
6aaed76
fix(web): Seed per-model effort memory from the sticky selection
mwolson Aug 22, 2026
31aeaee
feat(mobile): Remember reasoning effort per model
mwolson Aug 23, 2026
14ee2e1
feat(pi): Advertise /compact and send RPC compact
mwolson Aug 31, 2026
27cecb8
fix(pi): stop unowned provider turns
StiensWout Sep 1, 2026
b4331aa
test(server): cover provider-stopped subscriptions
StiensWout Sep 1, 2026
081fccd
fix(pi): normalize runtime mode and option memory
StiensWout Sep 1, 2026
34a03fb
fix(opencode): settle turns when message ids change
StiensWout Sep 1, 2026
380891c
test(opencode): model completed admission
StiensWout Sep 1, 2026
36530e8
fix(clients): handle unknown runtime modes
StiensWout Sep 1, 2026
a0bcd6d
refactor(mobile): reuse composer persistence for model options
StiensWout Sep 1, 2026
b47d7f0
fix(pi): reconcile with current orchestrator v2
StiensWout Sep 9, 2026
9d33f51
fix(pi): close parity gaps found in the pre-PR audit
StiensWout Sep 9, 2026
a37053f
fix: resolve Pi review findings and shared v2 CI failures
juliusmarminge Sep 10, 2026
0e84992
test(client-runtime): update work-log fixtures for v2 contracts
juliusmarminge Sep 10, 2026
94f16e9
test(server): replay OpenCode descendant checks and cleanup
juliusmarminge Sep 10, 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
14 changes: 14 additions & 0 deletions apps/mobile/src/components/ProviderIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,20 @@ export function ProviderIcon(props: ProviderIconProps) {
);
}

if (props.provider === "pi") {
const foreground = isDarkMode ? "#F5F5F5" : "#0F0F0F";
return (
<Svg width={size} height={size} viewBox="165.29 165.29 469.43 469.43" fill="none">
<Path
fill={foreground}
fillRule="evenodd"
d="M165.29 165.29H517.36V400H400V517.36H282.65V634.72H165.29ZM282.65 282.65V400H400V282.65Z"
/>
<Path fill={foreground} d="M517.36 400H634.72V634.72H517.36Z" />
</Svg>
);
}

if (props.provider === "opencode") {
return (
<Svg width={size} height={size} viewBox="0 0 32 40" fill="none">
Expand Down
17 changes: 14 additions & 3 deletions apps/mobile/src/features/threads/ThreadComposer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ import {
} from "../voice-input/ComposerDictationControl";
import { useVoiceInputController } from "../voice-input/useVoiceInputController";
import { resolveVoiceComposerPresentation } from "../voice-input/voiceInputPresentation";
import {
rememberModelOptions,
withRememberedModelOptions,
} from "../../state/use-model-option-memory";
import {
type ExistingThreadSettingsRouteSession,
useExistingThreadSettingsRoutePresentation,
Expand Down Expand Up @@ -497,10 +501,17 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer
providerInstanceId: currentModelSelection.instanceId,
providerGroups: threadProviderGroups,
selectedModel: currentModelSelection,
onSelectModel: (option) => props.onUpdateModelSelection(option.selection),
onSelectModel: (option) =>
props.onUpdateModelSelection(withRememberedModelOptions(option.selection)),
optionDescriptors: providerOptionDescriptors,
onUpdateOptionSelections: (options) =>
props.onUpdateModelSelection({ ...currentModelSelection, options }),
onUpdateOptionSelections: (options) => {
rememberModelOptions(
currentModelSelection.instanceId,
currentModelSelection.model,
options ?? [],
);
props.onUpdateModelSelection({ ...currentModelSelection, options });
},
runtimeMode: currentRuntimeMode,
onUpdateRuntimeMode: props.onUpdateRuntimeMode,
}),
Expand Down
34 changes: 29 additions & 5 deletions apps/mobile/src/features/threads/ThreadSettingsSheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import type { ModelOption, ProviderGroup } from "../../lib/modelOptions";
import { applyProviderOptionSelection } from "../../lib/providerOptions";
import { resolveProviderOptionDescriptors } from "../../lib/providerOptions";
import { useUniwindTheme } from "../../lib/useUniwindTheme";
import { rememberModelOptions } from "../../state/use-model-option-memory";
import {
NativeHeaderToolbar,
NativeStackScreenOptions,
Expand All @@ -62,7 +63,11 @@ import {
NATIVE_MAIL_SEARCH_TOOLBAR_CONTENT_INSET,
NATIVE_MAIL_SEARCH_TOOLBAR_SUPPORTED,
} from "../layout/native-mail-search-toolbar";
import { RUNTIME_MODE_CHOICES, selectableChoices } from "./thread-settings-options";
import {
compatibleRuntimeModeForChoices,
runtimeModeChoicesForSupportedModes,
selectableChoices,
} from "./thread-settings-options";
import {
canCommitPendingModel,
modelMatchesCatalogQuery,
Expand Down Expand Up @@ -374,6 +379,7 @@ type ThreadSettingsSessionValue = {
readonly providerInstanceId?: ProviderInstanceId;
readonly providerGroups: ReadonlyArray<ProviderGroup>;
readonly runtimeMode: RuntimeMode;
readonly runtimeModeChoices: ReturnType<typeof runtimeModeChoicesForSupportedModes>;
readonly onUpdateRuntimeMode: (mode: RuntimeMode) => void;
readonly displayedDescriptors: ReadonlyArray<ProviderOptionDescriptor>;
readonly providerExpansionOverrides: ReadonlySet<string>;
Expand Down Expand Up @@ -434,6 +440,20 @@ function ThreadSettingsSessionProvider(
: props.optionDescriptors,
[pendingModel, props.optionDescriptors],
);
const displayedModel = useMemo(
() =>
pendingModel ??
props.providerGroups.flatMap((group) => group.models).find((option) => isApplied(option)) ??
null,
[isApplied, pendingModel, props.providerGroups],
);
const runtimeModeChoices = runtimeModeChoicesForSupportedModes(
displayedModel?.supportedRuntimeModes,
);
const compatibleRuntimeMode = compatibleRuntimeModeForChoices(
props.runtimeMode,
runtimeModeChoices,
);

const hasLegacyModels = useMemo(
() => props.providerGroups.some((group) => group.models.some((model) => model.isLegacy)),
Expand Down Expand Up @@ -461,6 +481,7 @@ function ThreadSettingsSessionProvider(
return;
}
if (pendingModel) {
rememberModelOptions(pendingModel.selection.instanceId, pendingModel.selection.model, next);
setPendingModel({
...pendingModel,
selection: { ...pendingModel.selection, options: next },
Expand Down Expand Up @@ -501,7 +522,8 @@ function ThreadSettingsSessionProvider(
environmentId: props.environmentId,
providerInstanceId: props.providerInstanceId,
providerGroups: props.providerGroups,
runtimeMode: props.runtimeMode,
runtimeMode: compatibleRuntimeMode,
runtimeModeChoices,
onUpdateRuntimeMode: props.onUpdateRuntimeMode,
displayedDescriptors,
providerExpansionOverrides,
Expand All @@ -523,6 +545,7 @@ function ThreadSettingsSessionProvider(
[
applyOptionChange,
commitPendingModel,
compatibleRuntimeMode,
displayedDescriptors,
providerExpansionOverrides,
hasLegacyModels,
Expand All @@ -535,7 +558,7 @@ function ThreadSettingsSessionProvider(
providerFilter,
props.onUpdateRuntimeMode,
props.providerGroups,
props.runtimeMode,
runtimeModeChoices,
searchQuery,
showLegacyToggle,
toggleProvider,
Expand Down Expand Up @@ -760,7 +783,8 @@ function ThreadSettingsOptionsItem(props: {
isLast
label="Runtime"
value={
RUNTIME_MODE_CHOICES.find((choice) => choice.mode === session.runtimeMode)?.label
session.runtimeModeChoices.find((choice) => choice.mode === session.runtimeMode)
?.label
}
onPress={() => props.onOpenSubmenu({ kind: "runtime" })}
/>
Expand Down Expand Up @@ -910,7 +934,7 @@ function ThreadSettingsChoiceContent(props: {
const submenuContent =
props.submenu.kind === "runtime"
? {
rows: RUNTIME_MODE_CHOICES.map((choice) => ({
rows: session.runtimeModeChoices.map((choice) => ({
id: choice.mode,
label: choice.label,
description: choice.description,
Expand Down
10 changes: 9 additions & 1 deletion apps/mobile/src/features/threads/new-task-flow-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ import {
capturePendingTaskEditorWriteBaseline,
flushPendingTaskEditorWrite,
} from "../../state/pending-task-editor-writes";
import {
rememberModelOptions,
withRememberedModelOptions,
} from "../../state/use-model-option-memory";
import { useDebouncedValue, usePaginatedBranches } from "../../state/queries";
import { vcsEnvironment } from "../../state/vcs";
import {
Expand Down Expand Up @@ -224,6 +228,7 @@ export function NewTaskFlowProvider(props: React.PropsWithChildren) {
const groupingSettings = useMobileProjectGroupingSettings();
const { enabled: legacyPlanModeEnabled, loaded: planModePreferenceLoaded } =
useLegacyPlanModeState();

const projectScopes = useMemo(
() =>
sortHomeProjectScopes({
Expand Down Expand Up @@ -528,7 +533,9 @@ export function NewTaskFlowProvider(props: React.PropsWithChildren) {
if (!option) {
return;
}
const selection = options ? { ...option.selection, options } : option.selection;
const selection = withRememberedModelOptions(
options ? { ...option.selection, options } : option.selection,
);
const provider = selectedEnvironmentServerConfig?.providers.find(
(candidate) => candidate.instanceId === selection.instanceId,
);
Expand All @@ -547,6 +554,7 @@ export function NewTaskFlowProvider(props: React.PropsWithChildren) {
if (!selectedModel || !selectedProjectDraftKey) {
return;
}
rememberModelOptions(selectedModel.instanceId, selectedModel.model, options ?? []);
const nextSelection: ModelSelection = options
? { ...selectedModel, options }
: {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { ProviderOptionDescriptor } from "@t3tools/contracts";
import { describe, expect, it } from "vite-plus/test";

import { selectableChoices } from "./thread-settings-options";
import { runtimeModeChoicesForSupportedModes, selectableChoices } from "./thread-settings-options";

const effortDescriptor: Extract<ProviderOptionDescriptor, { type: "select" }> = {
id: "effort",
Expand All @@ -27,3 +27,9 @@ describe("selectableChoices", () => {
]);
});
});

describe("runtimeModeChoicesForSupportedModes", () => {
it("keeps controls usable when forward-compatible decoding removes every advertised mode", () => {
expect(runtimeModeChoicesForSupportedModes([])).toHaveLength(4);
});
});
17 changes: 17 additions & 0 deletions apps/mobile/src/features/threads/thread-settings-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,23 @@ export const RUNTIME_MODE_CHOICES: ReadonlyArray<{
},
];

export function runtimeModeChoicesForSupportedModes(
supportedRuntimeModes: ReadonlyArray<RuntimeMode> | undefined,
) {
return supportedRuntimeModes && supportedRuntimeModes.length > 0
? RUNTIME_MODE_CHOICES.filter((choice) => supportedRuntimeModes.includes(choice.mode))
: RUNTIME_MODE_CHOICES;
}

export function compatibleRuntimeModeForChoices(
runtimeMode: RuntimeMode,
choices: ReadonlyArray<{ readonly mode: RuntimeMode }>,
): RuntimeMode {
return choices.some((choice) => choice.mode === runtimeMode)
? runtimeMode
: (choices[0]?.mode ?? runtimeMode);
}

export function selectableChoices(
descriptor: Extract<ProviderOptionDescriptor, { type: "select" }>,
) {
Expand Down
6 changes: 6 additions & 0 deletions apps/mobile/src/lib/modelOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { MenuAction } from "@react-native-menu/menu";
import type {
ModelCapabilities,
ModelSelection,
RuntimeMode,
ServerConfig as T3ServerConfig,
} from "@t3tools/contracts";
import {
Expand All @@ -16,6 +17,7 @@ export type ModelOption = {
readonly providerKey: string;
readonly providerLabel: string;
readonly providerDriver: string;
readonly supportedRuntimeModes?: ReadonlyArray<RuntimeMode>;
readonly isDefault: boolean;
readonly isLegacy: boolean;
readonly isUnavailable?: boolean;
Expand All @@ -37,6 +39,7 @@ function providerDisplayLabel(provider: {
if (provider.displayName) return provider.displayName;
if (provider.driver === "codex") return "Codex";
if (provider.driver === "claudeAgent") return "Claude";
if (provider.driver === "pi") return "Pi";
return provider.instanceId;
}

Expand Down Expand Up @@ -174,6 +177,9 @@ export function buildModelOptions(
providerKey: provider.instanceId,
providerLabel,
providerDriver: provider.driver,
...(provider.supportedRuntimeModes === undefined
? {}
: { supportedRuntimeModes: provider.supportedRuntimeModes }),
isDefault: model.isDefault === true,
isLegacy: model.isLegacy === true,
capabilities: model.capabilities,
Expand Down
40 changes: 40 additions & 0 deletions apps/mobile/src/state/use-composer-drafts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ import {
getComposerDraftSnapshot,
mergeComposerDraftContentState,
migrateLegacyNewTaskDraft,
modelOptionMemoryAtom,
releaseUnusedComposerAttachmentFiles,
removeComposerDraftsForEnvironment,
resetComposerDraftsLoadState,
Expand Down Expand Up @@ -202,6 +203,7 @@ afterEach(() => {
appAtomRegistry.set(composerDraftsAtom, {});
appAtomRegistry.set(composerCloudDraftsAtom, { accountId: null, signedOut: {} });
appAtomRegistry.set(stickyComposerModelSelectionAtom, null);
appAtomRegistry.set(modelOptionMemoryAtom, {});
appAtomRegistry.set(threadOutboxManager.queuedMessagesByThreadKeyAtom, {});
composerAttachmentCleanupMocks.remove.mockClear();
composerAttachmentCleanupMocks.releaseUploads.mockReset();
Expand Down Expand Up @@ -1235,6 +1237,44 @@ describe("mobile composer drafts", () => {
});
});

it("decodes model option memory from the composer document", () => {
expect(
decodePersistedComposerState({
schemaVersion: 1,
drafts: {},
modelOptionMemory: {
pi: { "xai/grok-4.6": [{ id: "thinking", value: "xhigh" }] },
},
}).modelOptionMemory,
).toEqual({ pi: { "xai/grok-4.6": [{ id: "thinking", value: "xhigh" }] } });
});

it("merges persisted option memory without replacing newer choices", async () => {
composerDraftFileMocks.setDocument({
schemaVersion: 1,
drafts: {},
modelOptionMemory: {
pi: {
"xai/grok-4.6": [{ id: "thinking", value: "high" }],
"openai/gpt-5.4": [{ id: "thinking", value: "medium" }],
},
},
});
appAtomRegistry.set(modelOptionMemoryAtom, {
pi: { "xai/grok-4.6": [{ id: "thinking", value: "xhigh" }] },
});

ensureComposerDraftsLoaded();
await waitForComposerDraftsLoaded();

expect(appAtomRegistry.get(modelOptionMemoryAtom)).toEqual({
pi: {
"xai/grok-4.6": [{ id: "thinking", value: "xhigh" }],
"openai/gpt-5.4": [{ id: "thinking", value: "medium" }],
},
});
});

it("waits for hydration before persisting the latest composer state", async () => {
vi.useFakeTimers();
composerDraftFileMocks.setDocument({
Expand Down
Loading
Loading