Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
13 changes: 13 additions & 0 deletions PATCH.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,19 @@ This fork stays close to `pingdotgg/t3code` and carries only the following opera
activities and the continually repainting text shimmer are not imported.
The fork's unified image/file/pdf/video upload queue replaces upstream's separate
`files` array; per-attachment progress, retry and video playback are carried through it.
- Antigravity is ported from upstream `06336460c9988f29c71e839c4c9c840c4552e077`
and its follow-ups through `d29c56a5c404cb0f58d3b2ac41762fa0d0ac28d4` onto the fork's
shared ACP V2 adapter. Managed installation, isolated per-account Google authentication,
account model catalogs, native permissions/questions, attachments and workspace skills
are available in web and native Swift settings/composers. `/logout` closes the configured
account's V2 sessions and completes as a local command; it never starts a new agent turn.
Antigravity subagent batches remain V2 tool items, active until the parent turn settles;
the native protocol supplies no individual child IDs or models, so no child threads are
invented. Commands that outlive the parent retain V2 background ownership. The fork's
process-tree supervision, cancellation quarantine and response receipts remain intact.
No V1 adapter, task bridge, pagination or SQLite migration is imported. Expo receives
only question-wire compatibility, preserving exact option IDs and custom-answer limits.

- Sidebar file drops are ported to both web sidebar layouts and search results using the
fork's unified attachment queue. Deferred drops are scoped by environment and thread,
survive repeated drops, and are cleared individually on navigation failure or when a
Expand Down
34 changes: 20 additions & 14 deletions apps/mobile/src/features/threads/PendingUserInputCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -269,12 +269,16 @@ export function PendingUserInputCard(props: PendingUserInputCardProps) {
</Text>
<View className="gap-2">
{question.options.map((option) => {
const selected = isPendingUserInputOptionSelected(draft, option.label);
const selected = isPendingUserInputOptionSelected(
draft,
option.value ?? option.label,
option.value !== undefined,
);
const description =
option.description !== option.label ? option.description : undefined;
return (
<Pressable
key={option.label}
key={option.value ?? option.label}
disabled={!canRespond}
className={cn(
"min-h-12 w-full rounded-2xl border px-3.5 py-3",
Expand All @@ -286,7 +290,7 @@ export function PendingUserInputCard(props: PendingUserInputCardProps) {
props.onSelectOption(
props.pendingUserInput.requestId,
question,
option.label,
option.value ?? option.label,
)
}
>
Expand All @@ -311,17 +315,19 @@ export function PendingUserInputCard(props: PendingUserInputCardProps) {
);
})}
</View>
<TextInput
editable={canRespond}
value={draft?.customAnswer ?? ""}
onChangeText={(value) =>
props.onChangeCustomAnswer(props.pendingUserInput.requestId, question.id, value)
}
onFocus={() => props.onInputFocusChange?.(true)}
onBlur={() => props.onInputFocusChange?.(false)}
placeholder="Or type a custom answer"
className="min-h-[54px] rounded-2xl border border-neutral-200 bg-white px-3.5 py-3 font-sans text-base text-neutral-950 dark:border-white/8 dark:bg-neutral-950/70 dark:text-neutral-50"
/>
{question.allowCustomAnswer !== false && (
<TextInput
editable={canRespond}
value={draft?.customAnswer ?? ""}
onChangeText={(value) =>
props.onChangeCustomAnswer(props.pendingUserInput.requestId, question.id, value)
}
onFocus={() => props.onInputFocusChange?.(true)}
onBlur={() => props.onInputFocusChange?.(false)}
placeholder="Or type a custom answer"
className="min-h-[54px] rounded-2xl border border-neutral-200 bg-white px-3.5 py-3 font-sans text-base text-neutral-950 dark:border-white/8 dark:bg-neutral-950/70 dark:text-neutral-50"
/>
)}
</View>
);
})}
Expand Down
19 changes: 19 additions & 0 deletions apps/mobile/src/lib/threadActivity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,25 @@ const multiSelectQuestion = {
} as const;

describe("pending user input answers", () => {
it("preserves opaque values without matching other choices by trimmed labels", () => {
const question = {
...singleSelectQuestion,
allowCustomAnswer: false,
options: [
{ label: "Choice", description: "", value: " choice: opaque " },
{ label: "Choice", description: "", value: "choice: opaque" },
],
};
const draft = togglePendingUserInputOptionSelection(question, undefined, " choice: opaque ");
expect(isPendingUserInputOptionSelected(draft, " choice: opaque ", true)).toBe(true);
expect(isPendingUserInputOptionSelected(draft, "choice: opaque", true)).toBe(false);
expect(
buildPendingUserInputAnswers([question], {
runtime: { ...draft, customAnswer: "not allowed" },
}),
).toEqual({ runtime: " choice: opaque " });
});

it("replaces single-select options and toggles multi-select options", () => {
expect(
togglePendingUserInputOptionSelection(
Expand Down
17 changes: 11 additions & 6 deletions apps/mobile/src/lib/threadActivity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,16 +219,15 @@ function normalizeSelectedOptionLabels(
return [];
}

return Array.from(
new Set(value.map((entry) => entry.trim()).filter((entry) => entry.length > 0)),
);
return Array.from(new Set(value.filter((entry) => entry.length > 0)));
}

function resolvePendingUserInputAnswer(
question: ThreadUserInputQuestion,
draft: PendingUserInputDraftAnswer | undefined,
): string | ReadonlyArray<string> | null {
const customAnswer = normalizeDraftAnswer(draft?.customAnswer);
const customAnswer =
question.allowCustomAnswer === false ? null : normalizeDraftAnswer(draft?.customAnswer);
if (customAnswer) {
return customAnswer;
}
Expand Down Expand Up @@ -1070,20 +1069,26 @@ export function setPendingUserInputCustomAnswer(
export function isPendingUserInputOptionSelected(
draft: PendingUserInputDraftAnswer | undefined,
optionLabel: string,
exactValue = false,
): boolean {
if (normalizeDraftAnswer(draft?.customAnswer)) {
return false;
}

return normalizeSelectedOptionLabels(draft?.selectedOptionLabels).includes(optionLabel.trim());
const selected = normalizeSelectedOptionLabels(draft?.selectedOptionLabels);
return exactValue
? selected.includes(optionLabel)
: selected.some((entry) => entry.trim() === optionLabel.trim());
}

export function togglePendingUserInputOptionSelection(
question: ThreadUserInputQuestion,
draft: PendingUserInputDraftAnswer | undefined,
optionLabel: string,
): PendingUserInputDraftAnswer {
const normalizedOptionLabel = optionLabel.trim();
const normalizedOptionLabel = question.options.some((option) => option.value === optionLabel)
? optionLabel
: optionLabel.trim();

if (question.multiSelect) {
const selectedOptionLabels = normalizeSelectedOptionLabels(draft?.selectedOptionLabels);
Expand Down
4 changes: 3 additions & 1 deletion apps/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@
"node-pty": "^1.1.0",
"stream-chain": "4.2.5",
"stream-json": "3.6.0",
"yaml": "catalog:"
"yaml": "catalog:",
"yauzl": "^3.4.0"
},
"devDependencies": {
"@effect/vitest": "catalog:",
Expand All @@ -51,6 +52,7 @@
"@t3tools/web": "workspace:*",
"@types/bun": "1.3.14",
"@types/node": "catalog:",
"@types/yauzl": "^3.4.0",
"effect-acp": "workspace:*",
"effect-codex-app-server": "workspace:*",
"vite-plus": "catalog:"
Expand Down
7 changes: 6 additions & 1 deletion apps/server/scripts/acp-mock-agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1105,7 +1105,12 @@ const program = Effect.gen(function* () {
const permission = yield* agent.client.requestPermission({
sessionId: requestedSessionId,
toolCall: {
toolCallId: index === 0 ? toolCallId : `${toolCallId}-${index + 1}`,
toolCallId:
process.env.T3_ACP_NATIVE_QUESTION === "1"
? `interaction_${toolCallId}`
: index === 0
? toolCallId
: `${toolCallId}-${index + 1}`,
title: process.env.T3_ACP_PERMISSION_TITLE ?? `\`${command}\``,
kind: "execute",
status: "pending",
Expand Down
9 changes: 9 additions & 0 deletions apps/server/src/auth/RpcAuthorization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,15 @@ export const RPC_REQUIRED_SCOPES = {
[WS_METHODS.serverGetProcessResourceHistory]: AuthOrchestrationReadScope,
[WS_METHODS.serverGetResourceTelemetryHistory]: AuthOrchestrationReadScope,
[WS_METHODS.serverRetryResourceTelemetry]: AuthOrchestrationOperateScope,
[WS_METHODS.providerAuthStart]: AuthOrchestrationOperateScope,
[WS_METHODS.providerAuthComplete]: AuthOrchestrationOperateScope,
[WS_METHODS.providerAuthCancel]: AuthOrchestrationOperateScope,
[WS_METHODS.providerAuthLogout]: AuthOrchestrationOperateScope,
[WS_METHODS.providerAuthSubscribe]: AuthOrchestrationOperateScope,
[WS_METHODS.providerInstallStart]: AuthOrchestrationOperateScope,
[WS_METHODS.providerInstallCancel]: AuthOrchestrationOperateScope,
[WS_METHODS.providerInstallSubscribe]: AuthOrchestrationReadScope,
[WS_METHODS.providerInstallRemove]: AuthOrchestrationOperateScope,
[WS_METHODS.providerConsumeResetCredit]: AuthOrchestrationOperateScope,
[WS_METHODS.serverGetUsageSummary]: AuthOrchestrationReadScope,
[WS_METHODS.serverRefreshUsageRates]: AuthOrchestrationReadScope,
Expand Down
Loading
Loading