Skip to content
Closed
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
148 changes: 113 additions & 35 deletions apps/mobile/src/features/threads/use-composer-command-menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import {
import {
dedupeProviderSkillsByName,
getProviderSkillsForSlashMenu,
hasCompleteProviderWorkspaceSnapshot,
hasPendingProviderWorkspaceSkillDiscovery,
isProviderSkillUserInvocable,
resolveProviderSkillsForCwd,
} from "@t3tools/client-runtime/providerSkills";
Expand Down Expand Up @@ -189,60 +191,136 @@ export function useComposerCommandMenu({
reportFailure: false,
});
const selectedProviderInstanceId = selectedProviderStatus?.instanceId;
const hasWorkspaceSnapshot = Boolean(
projectCwd &&
selectedProviderStatus?.workspaceSnapshots?.some((snapshot) => snapshot.cwd === projectCwd),
const workspaceRefreshKey =
environmentId && projectCwd && selectedProviderInstanceId
? `${environmentId}:${selectedProviderInstanceId}:${projectCwd}`
: null;
const hasCompleteWorkspaceSnapshot = Boolean(
selectedProviderStatus &&
hasCompleteProviderWorkspaceSnapshot(selectedProviderStatus, projectCwd),
);
const workspaceRefreshKeyRef = useRef<string | null>(null);
const workspaceRefreshRetryRef = useRef<{ key: string; notBefore: number } | null>(null);
const hadWorkspaceSnapshotRef = useRef(false);
const workspaceRefreshAttemptKeyRef = useRef<string | null>(null);
const workspaceRefreshRetryTimerRef = useRef<{
readonly key: string;
readonly timeout: ReturnType<typeof setTimeout>;
} | null>(null);
const workspaceRefreshMissingRetryKeyRef = useRef<string | null>(null);
const [workspaceRefreshRetryVersion, setWorkspaceRefreshRetryVersion] = useState(0);
const hasPendingWorkspaceSkillDiscovery = Boolean(
selectedProviderStatus &&
hasPendingProviderWorkspaceSkillDiscovery(selectedProviderStatus, projectCwd),
);
const hasCompleteWorkspaceSnapshotRef = useRef(hasCompleteWorkspaceSnapshot);
useEffect(() => {
hasCompleteWorkspaceSnapshotRef.current = hasCompleteWorkspaceSnapshot;
workspaceRefreshAttemptKeyRef.current = null;
workspaceRefreshMissingRetryKeyRef.current = null;
if (hasCompleteWorkspaceSnapshot && workspaceRefreshRetryTimerRef.current !== null) {
clearTimeout(workspaceRefreshRetryTimerRef.current.timeout);
workspaceRefreshRetryTimerRef.current = null;
}
}, [hasCompleteWorkspaceSnapshot]);
useEffect(() => {
if (hadWorkspaceSnapshotRef.current && !hasWorkspaceSnapshot) {
workspaceRefreshKeyRef.current = null;
workspaceRefreshRetryRef.current = null;
const key = workspaceRefreshKey;
if (workspaceRefreshMissingRetryKeyRef.current !== key) {
workspaceRefreshMissingRetryKeyRef.current = null;
}
hadWorkspaceSnapshotRef.current = hasWorkspaceSnapshot;
}, [hasWorkspaceSnapshot]);
return () => {
if (workspaceRefreshRetryTimerRef.current?.key === key) {
clearTimeout(workspaceRefreshRetryTimerRef.current.timeout);
workspaceRefreshRetryTimerRef.current = null;
}
};
}, [workspaceRefreshKey]);
useEffect(() => {
if (!environmentId || !projectCwd || !selectedProviderInstanceId) return;
const key = `${environmentId}:${selectedProviderInstanceId}:${projectCwd}`;
if (workspaceRefreshKeyRef.current === key) return;
if (hasWorkspaceSnapshot) {
workspaceRefreshKeyRef.current = key;
workspaceRefreshRetryRef.current = null;
if (
!environmentId ||
!projectCwd ||
!selectedProviderInstanceId ||
workspaceRefreshKey === null
) {
return;
}
const retry = workspaceRefreshRetryRef.current;
if (retry?.key === key && Date.now() < retry.notBefore) return;
workspaceRefreshKeyRef.current = key;
const key = workspaceRefreshKey;
if (hasCompleteWorkspaceSnapshot) return;
const attemptKey = `${key}:${workspaceRefreshRetryVersion}`;
const retryLater = () => {
if (workspaceRefreshKeyRef.current !== key) return;
workspaceRefreshKeyRef.current = null;
workspaceRefreshRetryRef.current = {
key,
notBefore: Date.now() + WORKSPACE_SNAPSHOT_RETRY_COOLDOWN_MS,
};
if (
workspaceRefreshAttemptKeyRef.current !== attemptKey ||
hasCompleteWorkspaceSnapshotRef.current ||
workspaceRefreshRetryTimerRef.current !== null
) {
return;
}
const timeout = setTimeout(() => {
workspaceRefreshRetryTimerRef.current = null;
if (
workspaceRefreshAttemptKeyRef.current !== attemptKey ||
hasCompleteWorkspaceSnapshotRef.current
) {
return;
}
setWorkspaceRefreshRetryVersion((version) => version + 1);
}, WORKSPACE_SNAPSHOT_RETRY_COOLDOWN_MS);
workspaceRefreshRetryTimerRef.current = { key, timeout };
};
const retryMissingSnapshotOnce = (canRetry: boolean) => {
if (
!canRetry ||
workspaceRefreshAttemptKeyRef.current !== attemptKey ||
workspaceRefreshMissingRetryKeyRef.current === key
) {
return;
}
workspaceRefreshMissingRetryKeyRef.current = key;
retryLater();
};
const retryAfterFailedRefresh = () => {
if (hasPendingWorkspaceSkillDiscovery) {
retryLater();
return;
}
retryMissingSnapshotOnce(selectedProviderStatus?.status !== "error");
};
if (workspaceRefreshAttemptKeyRef.current === attemptKey) {
if (hasPendingWorkspaceSkillDiscovery) retryLater();
return;
}
workspaceRefreshAttemptKeyRef.current = attemptKey;
void refreshProviders({
environmentId,
input: { instanceId: selectedProviderInstanceId, cwd: projectCwd },
}).then((result) => {
const refreshed =
result._tag === "Success" &&
result.value.providers
.find((provider) => provider.instanceId === selectedProviderInstanceId)
?.workspaceSnapshots?.some((snapshot) => snapshot.cwd === projectCwd);
if (!refreshed && workspaceRefreshKeyRef.current === key) {
if (result._tag !== "Success") {
retryAfterFailedRefresh();
return;
}
const refreshedProvider = result.value.providers.find(
(provider) => provider.instanceId === selectedProviderInstanceId,
);
if (
refreshedProvider &&
hasPendingProviderWorkspaceSkillDiscovery(refreshedProvider, projectCwd)
) {
retryLater();
return;
}
}, retryLater);
retryMissingSnapshotOnce(
refreshedProvider?.status !== "error" &&
(!refreshedProvider ||
!hasCompleteProviderWorkspaceSnapshot(refreshedProvider, projectCwd)),
);
}, retryAfterFailedRefresh);
}, [
draftMessage,
environmentId,
hasWorkspaceSnapshot,
hasCompleteWorkspaceSnapshot,
hasPendingWorkspaceSkillDiscovery,
projectCwd,
refreshProviders,
selectedProviderInstanceId,
selectedProviderStatus,
workspaceRefreshKey,
workspaceRefreshRetryVersion,
]);

const trigger = useMemo(() => {
Expand Down
131 changes: 130 additions & 1 deletion apps/server/src/provider/Layers/AntigravityProvider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,14 @@ it.layer(testLayer)("Antigravity provider snapshots", (it) => {
Effect.gen(function* () {
const harness = yield* makeHarness();
yield* harness.initialize;
yield* harness.provider.snapshotForCwd("/workspace", [
{
name: "deploy",
description: "Ship it",
path: "/workspace/.agent/skills/deploy",
enabled: true,
},
]);
yield* harness.provider.onSessionStarted(started, "/workspace");
yield* harness.provider.onAvailableCommands(commands, "/workspace");
yield* Ref.set(
Expand Down Expand Up @@ -521,6 +529,14 @@ it.layer(testLayer)("Antigravity provider snapshots", (it) => {
},
];
for (const { error, installed } of failures) {
yield* harness.provider.snapshotForCwd("/workspace", [
{
name: "deploy",
description: "Ship it",
path: "/workspace/.agent/skills/deploy",
enabled: true,
},
]);
yield* harness.provider.onSessionStarted(started, "/workspace");
yield* harness.provider.onAvailableCommands(commands, "/workspace");
yield* Ref.set(harness.probe, Effect.fail(error));
Expand Down Expand Up @@ -607,14 +623,127 @@ it.layer(testLayer)("Antigravity provider snapshots", (it) => {
),
);

it.effect(
"does not let premature session startup or command updates hide later discovered skills",
() =>
Effect.scoped(
Effect.gen(function* () {
const harness = yield* makeHarness();
yield* harness.initialize;
const skills = [
{
name: "deploy",
description: "Ship it",
path: "/workspace/.agent/skills/deploy",
enabled: true,
},
];
yield* harness.provider.onSessionStarted(started, "/workspace");
yield* harness.provider.onAvailableCommands(commands, "/workspace");
const beforeDiscovery = yield* harness.provider.snapshot.getSnapshot;
expect(
beforeDiscovery.workspaceSnapshots?.find((entry) => entry.cwd === "/workspace"),
).toMatchObject({
slashCommands: commands,
skills: [],
skillsDiscoveryPending: true,
});
const otherCommands = [{ name: "review", description: "Review changes" }];
yield* harness.provider.onAvailableCommands(otherCommands, "/other-workspace");
const afterOtherWorkspace = yield* harness.provider.snapshot.getSnapshot;
expect(
afterOtherWorkspace.workspaceSnapshots?.find((entry) => entry.cwd === "/workspace")
?.slashCommands,
).toEqual(commands);
const discovered = yield* harness.provider.snapshotForCwd("/workspace", skills);
expect(discovered.skills).toEqual(skills);
const snapshot = yield* harness.provider.snapshot.getSnapshot;
expect(
snapshot.workspaceSnapshots?.find((entry) => entry.cwd === "/workspace"),
).toMatchObject({ skills, skillsDiscoveryPending: false });
expect((yield* harness.provider.snapshotForCwd("/workspace")).skills).toEqual(skills);
yield* harness.provider.onAvailableCommands(commands, "/workspace");
const afterCommands = yield* harness.provider.snapshot.getSnapshot;
expect(
afterCommands.workspaceSnapshots?.find((entry) => entry.cwd === "/workspace")?.skills,
).toEqual(skills);
}),
),
);

it.effect("keeps empty skill discoveries retryable while preserving workspace commands", () =>
Effect.scoped(
Effect.gen(function* () {
const harness = yield* makeHarness();
yield* harness.initialize;
const emptySnapshot = yield* harness.provider.snapshotForCwd("/empty", []);
expect(emptySnapshot.skills).toEqual([]);
expect(
emptySnapshot.workspaceSnapshots?.find((entry) => entry.cwd === "/empty"),
).toBeUndefined();
yield* harness.provider.onAvailableCommands(commands, "/empty");
const afterCommands = yield* harness.provider.snapshot.getSnapshot;
expect(
afterCommands.workspaceSnapshots?.find((entry) => entry.cwd === "/empty"),
).toMatchObject({
slashCommands: commands,
skills: [],
skillsDiscoveryPending: true,
});

const skills = [
{
name: "deploy",
description: "Ship it",
path: "/workspace/.agent/skills/deploy",
enabled: true,
},
];
yield* harness.provider.snapshotForCwd("/workspace", skills);
yield* harness.provider.onAvailableCommands(commands, "/workspace");
yield* harness.provider.snapshotForCwd("/workspace", []);
const existing = yield* harness.provider.snapshot.getSnapshot;
expect(
existing.workspaceSnapshots?.find((entry) => entry.cwd === "/workspace"),
).toMatchObject({
slashCommands: commands,
skills: [],
skillsDiscoveryPending: true,
});
}),
),
);

it.effect("does not register empty workspace snapshots on session start before discovery", () =>
Effect.scoped(
Effect.gen(function* () {
const harness = yield* makeHarness();
yield* harness.initialize;
yield* harness.provider.onSessionStarted(started, "/unscanned");
const snapshot = yield* harness.provider.snapshot.getSnapshot;
expect(
snapshot.workspaceSnapshots?.find((entry) => entry.cwd === "/unscanned"),
).toBeUndefined();
}),
),
);

it.effect("bounds workspace metadata without starting sessions for workspace lookup", () =>
Effect.scoped(
Effect.gen(function* () {
const harness = yield* makeHarness();
yield* harness.initialize;
yield* harness.provider.onSessionStarted(started);
yield* harness.provider.onAvailableCommands(commands);
for (let index = 0; index < 35; index++) {
yield* harness.provider.onAvailableCommands(commands, `/workspace-${index}`);
yield* harness.provider.snapshotForCwd(`/workspace-${index}`, [
{
name: "deploy",
description: "Ship it",
path: `/workspace-${index}/.agent/skills/deploy`,
enabled: true,
},
]);
}
const snapshot = yield* harness.provider.snapshotForCwd("/workspace-34");
expect(snapshot.workspaceSnapshots).toHaveLength(32);
Expand Down
Loading
Loading