authorgravatar for me@t3.ggTheo Browne <me@t3.gg> 2026-09-30 23:11:54-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-30 23:11:54-07:00
log921cb3c8bc772309ccab4c422b091d0a91497a49
tree022833348398bed1a93702256eee2351b004910f
parent7ab800a43c770b2c48a2d9306c89b6177f61ad4a
signature Signed by PGP key B5690EEEBB952194

feat(web): restart the agent session from cmd+k to load new skills and plugins (#14542)

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

10 files changed, 222 insertions(+), 28 deletions(-)

apps/server/src/provider/Layers/AntigravityProvider.test.ts+12
...@@ -620,6 +620,18 @@ it.layer(testLayer)("Antigravity provider snapshots", (it) => {...@@ -620,6 +620,18 @@ it.layer(testLayer)("Antigravity provider snapshots", (it) => {
620 after.workspaceSnapshots?.find((entry) => entry.cwd === "/workspace")?.skills,620 after.workspaceSnapshots?.find((entry) => entry.cwd === "/workspace")?.skills,
621 ).toEqual(skills);621 ).toEqual(skills);
622 expect((yield* harness.provider.snapshotForCwd("/workspace")).skills).toEqual(skills);622 expect((yield* harness.provider.snapshotForCwd("/workspace")).skills).toEqual(skills);
623
624 const rescanned = [
625 ...skills,
626 { name: "review", path: "/workspace/.agent/skills/review", enabled: true },
627 ];
628 yield* harness.provider.snapshotForCwd("/workspace", rescanned);
629 yield* harness.provider.onSessionStarted(started, "/workspace");
630 expect(
631 (yield* harness.provider.snapshot.getSnapshot).workspaceSnapshots?.find(
632 (entry) => entry.cwd === "/workspace",
633 )?.skills,
634 ).toEqual(rescanned);
623 }),635 }),
624 ),636 ),
625 );637 );
apps/server/src/provider/Layers/AntigravityProvider.ts+18-1
...@@ -377,7 +377,24 @@ export const makeAntigravityProvider = Effect.fn("makeAntigravityProvider")(func...@@ -377,7 +377,24 @@ export const makeAntigravityProvider = Effect.fn("makeAntigravityProvider")(func
377 cwd: string,377 cwd: string,
378 skills?: ServerProvider["skills"],378 skills?: ServerProvider["skills"],
379 ) {379 ) {
380 if (skills) discoveredSkills.set(cwd, skills);380 if (skills) {
381 discoveredSkills.set(cwd, skills);
382 // A rescan replaces the stored entry's skills. Session callbacks and
383 // health checks republish that entry, so it must not keep old ones.
384 yield* SubscriptionRef.update(metadata, (state) =>
385 state.draft.workspaceSnapshots?.some((entry) => entry.cwd === cwd)
386 ? {
387 ...state,
388 draft: {
389 ...state.draft,
390 workspaceSnapshots: state.draft.workspaceSnapshots.map((entry) =>
391 entry.cwd === cwd ? { ...entry, skills } : entry,
392 ),
393 },
394 }
395 : state,
396 );
397 }
381 const snapshot = yield* getSnapshot;398 const snapshot = yield* getSnapshot;
382 const workspace = snapshot.workspaceSnapshots?.find((entry) => entry.cwd === cwd);399 const workspace = snapshot.workspaceSnapshots?.find((entry) => entry.cwd === cwd);
383 const resolvedSkills = skills ?? workspace?.skills ?? discoveredSkills.get(cwd) ?? [];400 const resolvedSkills = skills ?? workspace?.skills ?? discoveredSkills.get(cwd) ?? [];
apps/server/src/provider/Layers/ProviderRegistry.test.ts+57-1
...@@ -1479,6 +1479,12 @@ it.layer(Layer.mergeAll(TestNodeServices, ServerSettingsModule.layerTest(), Test...@@ -1479,6 +1479,12 @@ it.layer(Layer.mergeAll(TestNodeServices, ServerSettingsModule.layerTest(), Test
1479 slashCommands: [],1479 slashCommands: [],
1480 } as const satisfies ServerProvider;1480 } as const satisfies ServerProvider;
1481 const snapshotCalls = yield* Ref.make(0);1481 const snapshotCalls = yield* Ref.make(0);
1482 const scopedResult = yield* Ref.make<ServerProvider>(scopedProvider);
1483 const cacheInvalidations = yield* Ref.make(0);
1484 const scanGate = yield* Ref.make<{
1485 readonly started: Deferred.Deferred<void>;
1486 readonly release: Deferred.Deferred<void>;
1487 } | null>(null);
1482 const returnPendingSnapshot = yield* Ref.make(true);1488 const returnPendingSnapshot = yield* Ref.make(true);
1483 const probeStarted = yield* Deferred.make<void>();1489 const probeStarted = yield* Deferred.make<void>();
1484 const releaseProbe = yield* Deferred.make<void>();1490 const releaseProbe = yield* Deferred.make<void>();
...@@ -1508,6 +1514,7 @@ it.layer(Layer.mergeAll(TestNodeServices, ServerSettingsModule.layerTest(), Test...@@ -1508,6 +1514,7 @@ it.layer(Layer.mergeAll(TestNodeServices, ServerSettingsModule.layerTest(), Test
1508 applyUsageLimits: () => Effect.void,1514 applyUsageLimits: () => Effect.void,
1509 },1515 },
1510 snapshotForCwd,1516 snapshotForCwd,
1517 invalidateCaches: Ref.update(cacheInvalidations, (count) => count + 1),
1511 adapter: {} as ProviderInstance["adapter"],1518 adapter: {} as ProviderInstance["adapter"],
1512 textGeneration: {} as ProviderInstance["textGeneration"],1519 textGeneration: {} as ProviderInstance["textGeneration"],
1513 });1520 });
...@@ -1517,7 +1524,13 @@ it.layer(Layer.mergeAll(TestNodeServices, ServerSettingsModule.layerTest(), Test...@@ -1517,7 +1524,13 @@ it.layer(Layer.mergeAll(TestNodeServices, ServerSettingsModule.layerTest(), Test
1517 if (yield* Ref.get(returnPendingSnapshot)) return pendingScopedProvider;1524 if (yield* Ref.get(returnPendingSnapshot)) return pendingScopedProvider;
1518 yield* Deferred.succeed(probeStarted, undefined);1525 yield* Deferred.succeed(probeStarted, undefined);
1519 yield* Deferred.await(releaseProbe);1526 yield* Deferred.await(releaseProbe);
1520 return scopedProvider;1527 const result = yield* Ref.get(scopedResult);
1528 const gate = yield* Ref.getAndSet(scanGate, null);
1529 if (gate) {
1530 yield* Deferred.succeed(gate.started, undefined);
1531 yield* Deferred.await(gate.release);
1532 }
1533 return result;
1521 }),1534 }),
1522 );1535 );
1523 const rebuiltProvider = {1536 const rebuiltProvider = {
...@@ -1593,6 +1606,49 @@ it.layer(Layer.mergeAll(TestNodeServices, ServerSettingsModule.layerTest(), Test...@@ -1593,6 +1606,49 @@ it.layer(Layer.mergeAll(TestNodeServices, ServerSettingsModule.layerTest(), Test
1593 );1606 );
1594 yield* registry.refreshWorkspaceSnapshot({ instanceId, cwd: "/workspace" });1607 yield* registry.refreshWorkspaceSnapshot({ instanceId, cwd: "/workspace" });
1595 assert.strictEqual(yield* Ref.get(snapshotCalls), 2);1608 assert.strictEqual(yield* Ref.get(snapshotCalls), 2);
1609 const newSkills = [
1610 ...scopedProvider.skills,
1611 { name: "added", path: "/workspace/added/SKILL.md", enabled: true },
1612 ];
1613 yield* Ref.set(scopedResult, { ...scopedProvider, skills: newSkills });
1614 yield* registry.refreshWorkspaceSnapshot({
1615 instanceId,
1616 cwd: "/workspace",
1617 fresh: true,
1618 });
1619 assert.strictEqual(yield* Ref.get(snapshotCalls), 3);
1620 assert.strictEqual(yield* Ref.get(cacheInvalidations), 1);
1621 assert.deepStrictEqual(
1622 (yield* registry.getProviders)[0]?.workspaceSnapshots?.map((s) => s.skills),
1623 [newSkills],
1624 );
1625
1626 // A slow fresh scan that read older files must not overwrite a
1627 // newer scan that finished first.
1628 const slowStarted = yield* Deferred.make<void>();
1629 const releaseSlow = yield* Deferred.make<void>();
1630 yield* Ref.set(scanGate, { started: slowStarted, release: releaseSlow });
1631 yield* Ref.set(scopedResult, scopedProvider);
1632 const slowScan = yield* registry
1633 .refreshWorkspaceSnapshot({ instanceId, cwd: "/workspace", fresh: true })
1634 .pipe(Effect.forkChild);
1635 yield* Deferred.await(slowStarted);
1636 const latestSkills = [
1637 ...newSkills,
1638 { name: "latest", path: "/workspace/latest/SKILL.md", enabled: true },
1639 ];
1640 yield* Ref.set(scopedResult, { ...scopedProvider, skills: latestSkills });
1641 yield* registry.refreshWorkspaceSnapshot({
1642 instanceId,
1643 cwd: "/workspace",
1644 fresh: true,
1645 });
1646 yield* Deferred.succeed(releaseSlow, undefined);
1647 yield* Fiber.join(slowScan);
1648 assert.deepStrictEqual(
1649 (yield* registry.getProviders)[0]?.workspaceSnapshots?.map((s) => s.skills),
1650 [latestSkills],
1651 );
15961652
1597 yield* Ref.set(instancesRef, [rebuiltInstance]);1653 yield* Ref.set(instancesRef, [rebuiltInstance]);
1598 yield* PubSub.publish(registryChanges, undefined);1654 yield* PubSub.publish(registryChanges, undefined);
apps/server/src/provider/Layers/ProviderRegistry.ts+67-26
...@@ -82,6 +82,15 @@ const hasModelCapabilities = (model: ServerProvider["models"][number]): boolean...@@ -82,6 +82,15 @@ const hasModelCapabilities = (model: ServerProvider["models"][number]): boolean
8282
83const MAX_WORKSPACE_SNAPSHOTS_PER_PROVIDER = 16;83const MAX_WORKSPACE_SNAPSHOTS_PER_PROVIDER = 16;
8484
85function dropProviderWorkspaceSnapshot(provider: ServerProvider, cwd: string): ServerProvider {
86 return provider.workspaceSnapshots?.some((snapshot) => snapshot.cwd === cwd)
87 ? {
88 ...provider,
89 workspaceSnapshots: provider.workspaceSnapshots.filter((snapshot) => snapshot.cwd !== cwd),
90 }
91 : provider;
92}
93
85export function upsertProviderWorkspaceSnapshot(94export function upsertProviderWorkspaceSnapshot(
86 provider: ServerProvider,95 provider: ServerProvider,
87 cwd: string,96 cwd: string,
...@@ -835,17 +844,44 @@ export const ProviderRegistryLive = Layer.effect(...@@ -835,17 +844,44 @@ export const ProviderRegistryLive = Layer.effect(
835 return yield* Ref.get(providersRef);844 return yield* Ref.get(providersRef);
836 });845 });
837846
847 const updateProviders = (
848 update: (providers: ReadonlyArray<ServerProvider>) => ReadonlyArray<ServerProvider>,
849 ) =>
850 Ref.modify(providersRef, (currentProviders) => {
851 const nextProviders = update(currentProviders);
852 return [[currentProviders, nextProviders] as const, nextProviders];
853 }).pipe(
854 Effect.tap(([previousProviders, nextProviders]) =>
855 haveProvidersChanged(previousProviders, nextProviders)
856 ? PubSub.publish(changesPubSub, nextProviders)
857 : Effect.void,
858 ),
859 Effect.map(([, nextProviders]) => nextProviders),
860 );
861
838 const refreshWorkspaceSnapshot = Effect.fn("refreshWorkspaceSnapshot")(function* (input: {862 const refreshWorkspaceSnapshot = Effect.fn("refreshWorkspaceSnapshot")(function* (input: {
839 readonly instanceId: ProviderInstanceId;863 readonly instanceId: ProviderInstanceId;
840 readonly cwd: string;864 readonly cwd: string;
865 readonly fresh?: boolean;
841 }) {866 }) {
867 // Fresh scans drop other instances' snapshots for this cwd first, so a
868 // composer on one of them scans again on next use, even when this
869 // instance is gone or cannot be scanned.
870 if (input.fresh) {
871 yield* updateProviders((providers) =>
872 providers.map((candidate) =>
873 candidate.instanceId === input.instanceId
874 ? candidate
875 : dropProviderWorkspaceSnapshot(candidate, input.cwd),
876 ),
877 );
878 }
842 const providers = yield* Ref.get(providersRef);879 const providers = yield* Ref.get(providersRef);
843 const provider = providers.find((candidate) => candidate.instanceId === input.instanceId);880 const provider = providers.find((candidate) => candidate.instanceId === input.instanceId);
844 if (881 const workspaceSnapshotOf = (candidate: ServerProvider | undefined) =>
845 !provider ||882 candidate?.workspaceSnapshots?.find((s) => s.cwd === input.cwd);
846 !provider.enabled ||883 const scannedFrom = workspaceSnapshotOf(provider);
847 provider.workspaceSnapshots?.some((s) => s.cwd === input.cwd)884 if (!provider || !provider.enabled || (!input.fresh && scannedFrom)) {
848 ) {
849 return providers;885 return providers;
850 }886 }
851 const instance = yield* instanceRegistry.getInstance(input.instanceId);887 const instance = yield* instanceRegistry.getInstance(input.instanceId);
...@@ -857,42 +893,47 @@ export const ProviderRegistryLive = Layer.effect(...@@ -857,42 +893,47 @@ export const ProviderRegistryLive = Layer.effect(
857 next.set(instance, new Set(current).add(input.cwd));893 next.set(instance, new Set(current).add(input.cwd));
858 return [true, next] as const;894 return [true, next] as const;
859 });895 });
860 if (!claimed) return yield* Ref.get(providersRef);896 // A fresh scan never joins a running one, which may predate the change.
861 return yield* instance.snapshotForCwd(input.cwd).pipe(897 if (!claimed && !input.fresh) return yield* Ref.get(providersRef);
898 // Fresh scans also re-read the machine snapshot: Claude's plugin
899 // commands come from it, not from the cwd scan.
900 const refreshMachineSnapshot = input.fresh
901 ? (instance.invalidateCaches ?? Effect.void).pipe(
902 Effect.andThen(refreshInstance(input.instanceId)),
903 )
904 : Effect.void;
905 return yield* refreshMachineSnapshot.pipe(
906 Effect.andThen(instance.snapshotForCwd(input.cwd)),
862 Effect.flatMap((scopedSnapshot) =>907 Effect.flatMap((scopedSnapshot) =>
863 scopedSnapshot.status === "error"908 scopedSnapshot.status === "error"
864 ? Ref.get(providersRef)909 ? Ref.get(providersRef)
865 : instanceRegistry.getInstance(input.instanceId).pipe(910 : instanceRegistry.getInstance(input.instanceId).pipe(
866 Effect.flatMap((currentInstance) => {911 Effect.flatMap((currentInstance) => {
867 if (currentInstance !== instance) return Ref.get(providersRef);912 if (currentInstance !== instance) return Ref.get(providersRef);
868 return Ref.modify(providersRef, (currentProviders) => {913 // Write only if the cwd's snapshot did not change during the
869 const nextProviders = currentProviders.map((candidate) =>914 // scan. A session event or another scan that landed first is newer.
915 return updateProviders((currentProviders) =>
916 currentProviders.map((candidate) =>
870 candidate.instanceId === input.instanceId &&917 candidate.instanceId === input.instanceId &&
871 !candidate.workspaceSnapshots?.some((s) => s.cwd === input.cwd)918 Equal.equals(workspaceSnapshotOf(candidate), scannedFrom)
872 ? upsertProviderWorkspaceSnapshot(candidate, input.cwd, scopedSnapshot)919 ? upsertProviderWorkspaceSnapshot(candidate, input.cwd, scopedSnapshot)
873 : candidate,920 : candidate,
874 );
875 return [[currentProviders, nextProviders] as const, nextProviders];
876 }).pipe(
877 Effect.tap(([previousProviders, nextProviders]) =>
878 haveProvidersChanged(previousProviders, nextProviders)
879 ? PubSub.publish(changesPubSub, nextProviders)
880 : Effect.void,
881 ),921 ),
882 Effect.map(([, nextProviders]) => nextProviders),
883 );922 );
884 }),923 }),
885 ),924 ),
886 ),925 ),
887 Effect.ensuring(926 Effect.ensuring(
888 Ref.update(workspaceRefreshesRef, (refreshes) => {927 claimed
889 const next = new Map(refreshes);928 ? Ref.update(workspaceRefreshesRef, (refreshes) => {
890 const current = new Set(next.get(instance));929 const next = new Map(refreshes);
891 current.delete(input.cwd);930 const current = new Set(next.get(instance));
892 if (current.size) next.set(instance, current);931 current.delete(input.cwd);
893 else next.delete(instance);932 if (current.size) next.set(instance, current);
894 return next;933 else next.delete(instance);
895 }),934 return next;
935 })
936 : Effect.void,
896 ),937 ),
897 );938 );
898 });939 });
apps/server/src/provider/Services/ProviderRegistry.ts+7
...@@ -48,9 +48,16 @@ export interface ProviderRegistryShape {...@@ -48,9 +48,16 @@ export interface ProviderRegistryShape {
48 instanceId: ProviderInstanceId,48 instanceId: ProviderInstanceId,
49 ) => Effect.Effect<ReadonlyArray<ServerProvider>>;49 ) => Effect.Effect<ReadonlyArray<ServerProvider>>;
5050
51 /**
52 * Fill the skills and slash commands snapshot for one cwd. A cwd that
53 * already has a snapshot is left alone unless `fresh` is set. A fresh scan
54 * also refreshes the instance's machine snapshot and drops other
55 * instances' snapshots for the cwd.
56 */
51 readonly refreshWorkspaceSnapshot: (input: {57 readonly refreshWorkspaceSnapshot: (input: {
52 readonly instanceId: ProviderInstanceId;58 readonly instanceId: ProviderInstanceId;
53 readonly cwd: string;59 readonly cwd: string;
60 readonly fresh?: boolean;
54 }) => Effect.Effect<ReadonlyArray<ServerProvider>>;61 }) => Effect.Effect<ReadonlyArray<ServerProvider>>;
5562
56 /**63 /**
apps/server/src/ws.ts+1
...@@ -2612,6 +2612,7 @@ const makeWsRpcLayer = (...@@ -2612,6 +2612,7 @@ const makeWsRpcLayer = (
2612 ? providerRegistry.refreshWorkspaceSnapshot({2612 ? providerRegistry.refreshWorkspaceSnapshot({
2613 instanceId: input.instanceId,2613 instanceId: input.instanceId,
2614 cwd: input.cwd,2614 cwd: input.cwd,
2615 fresh: input.fresh === true,
2615 })2616 })
2616 : input.instanceId !== undefined2617 : input.instanceId !== undefined
2617 ? providerRegistry.refreshInstance(input.instanceId)2618 ? providerRegistry.refreshInstance(input.instanceId)
apps/web/src/components/CommandPalette.tsx+52
...@@ -53,6 +53,7 @@ import {...@@ -53,6 +53,7 @@ import {
53 MonitorIcon,53 MonitorIcon,
54 MoonIcon,54 MoonIcon,
55 PaletteIcon,55 PaletteIcon,
56 RotateCcwIcon,
56 SettingsIcon,57 SettingsIcon,
57 SquarePenIcon,58 SquarePenIcon,
58 SunIcon,59 SunIcon,
...@@ -93,6 +94,8 @@ import { desktopLocalBackendId } from "../connection/desktopLocal";...@@ -93,6 +94,8 @@ import { desktopLocalBackendId } from "../connection/desktopLocal";
93import { filesystemEnvironment } from "../state/filesystem";94import { filesystemEnvironment } from "../state/filesystem";
94import { projectEnvironment } from "../state/projects";95import { projectEnvironment } from "../state/projects";
95import { useEnvironmentQuery } from "../state/query";96import { useEnvironmentQuery } from "../state/query";
97import { serverEnvironment } from "../state/server";
98import { threadEnvironment } from "../state/threads";
96import { sourceControlEnvironment } from "../state/sourceControl";99import { sourceControlEnvironment } from "../state/sourceControl";
97import { useAtomCommand } from "../state/use-atom-command";100import { useAtomCommand } from "../state/use-atom-command";
98import { useAtomQueryRunner } from "../state/use-atom-query-runner";101import { useAtomQueryRunner } from "../state/use-atom-query-runner";
...@@ -725,6 +728,12 @@ function OpenCommandPaletteDialog(props: {...@@ -725,6 +728,12 @@ function OpenCommandPaletteDialog(props: {
725 const startProjectClone = useAtomCommand(sourceControlEnvironment.startProjectClone, {728 const startProjectClone = useAtomCommand(sourceControlEnvironment.startProjectClone, {
726 reportFailure: false,729 reportFailure: false,
727 });730 });
731 const stopThreadSession = useAtomCommand(threadEnvironment.stopSession, {
732 reportFailure: false,
733 });
734 const refreshProviders = useAtomCommand(serverEnvironment.refreshProviders, {
735 reportFailure: false,
736 });
728 const { environments } = useEnvironments();737 const { environments } = useEnvironments();
729 const desktopLocalBootstraps = useDesktopLocalBootstraps();738 const desktopLocalBootstraps = useDesktopLocalBootstraps();
730 const primaryEnvironmentId = usePrimaryEnvironmentId();739 const primaryEnvironmentId = usePrimaryEnvironmentId();
...@@ -1871,6 +1880,49 @@ function OpenCommandPaletteDialog(props: {...@@ -1871,6 +1880,49 @@ function OpenCommandPaletteDialog(props: {
1871 }1880 }
1872 }1881 }
18731882
1883 if (activeThread !== null) {
1884 const thread = activeThread;
1885 actionItems.push({
1886 kind: "action",
1887 value: "action:restart-agent-session",
1888 searchTerms: ["restart", "reset", "reload", "agent", "session", "skills", "plugins", "mcp"],
1889 title: "Restart agent session",
1890 icon: <RotateCcwIcon className={ITEM_ICON_CLASS} />,
1891 // Stopping the provider process keeps the conversation: the next message
1892 // spawns a fresh one that resumes it and reloads skills, plugins, and MCP
1893 // servers. The fresh workspace scan updates the composer's slash menu.
1894 // Failures throw into executeItem's error toast.
1895 run: async () => {
1896 const { environmentId } = thread;
1897 if (thread.session && thread.session.status !== "stopped") {
1898 const stopped = await stopThreadSession({
1899 environmentId,
1900 input: { threadId: thread.id },
1901 });
1902 if (stopped._tag === "Failure") throw squashAtomCommandFailure(stopped);
1903 }
1904 // The server stops the process after accepting the command. A failed
1905 // stop shows in the thread.
1906 toastManager.add({
1907 type: "success",
1908 title: "Agent session will restart",
1909 description: "Your next message starts a fresh session.",
1910 });
1911 const project = projectByKey.get(`${environmentId}:${thread.projectId}`);
1912 if (!project) return;
1913 const refreshed = await refreshProviders({
1914 environmentId,
1915 input: {
1916 instanceId: thread.session?.providerInstanceId ?? thread.modelSelection.instanceId,
1917 cwd: thread.worktreePath ?? project.workspaceRoot,
1918 fresh: true,
1919 },
1920 });
1921 if (refreshed._tag === "Failure") throw squashAtomCommandFailure(refreshed);
1922 },
1923 });
1924 }
1925
1874 actionItems.push({1926 actionItems.push({
1875 kind: "action",1927 kind: "action",
1876 value: "action:open-file-picker",1928 value: "action:open-file-picker",
docs/user/composer.md+4
...@@ -152,6 +152,10 @@ provider. On mobile, both are also available before starting a thread on...@@ -152,6 +152,10 @@ provider. On mobile, both are also available before starting a thread on
152The slash menu also includes skills unless you turn off **Settings → General →152The slash menu also includes skills unless you turn off **Settings → General →
153Show skills in slash menu**. Only skills enabled for the provider are listed.153Show skills in slash menu**. Only skills enabled for the provider are listed.
154154
155After you add or change skills, plugins, or MCP servers, use **Restart agent
156session** in the command palette on web and desktop. The conversation continues,
157and your next message starts the agent again with the new setup.
158
155Provider commands must start the message to run. T3 Code commands such as159Provider commands must start the message to run. T3 Code commands such as
156`/model` and `/plan`, and skill mentions, work on any line.160`/model` and `/plan`, and skill mentions, work on any line.
157161
packages/client-runtime/src/state/server.ts+1
...@@ -1103,6 +1103,7 @@ export function createServerEnvironmentAtoms<R, E>(...@@ -1103,6 +1103,7 @@ export function createServerEnvironmentAtoms<R, E>(
1103 environmentId,1103 environmentId,
1104 input.instanceId ?? null,1104 input.instanceId ?? null,
1105 input.cwd ?? null,1105 input.cwd ?? null,
1106 input.fresh ?? false,
1106 input.refreshModels ?? false,1107 input.refreshModels ?? false,
1107 ]),1108 ]),
1108 },1109 },
packages/contracts/src/rpc.ts+3
...@@ -494,6 +494,9 @@ const WsServerRefreshProvidersRpc = Rpc.make(WS_METHODS.serverRefreshProviders,...@@ -494,6 +494,9 @@ const WsServerRefreshProvidersRpc = Rpc.make(WS_METHODS.serverRefreshProviders,
494 */494 */
495 instanceId: Schema.optional(ProviderInstanceId),495 instanceId: Schema.optional(ProviderInstanceId),
496 cwd: Schema.optional(TrimmedNonEmptyString),496 cwd: Schema.optional(TrimmedNonEmptyString),
497 /** With `instanceId` and `cwd`: rescan the workspace's skills and slash
498 * commands even when a snapshot for that cwd already exists. */
499 fresh: Schema.optional(Schema.Boolean),
497 /** Explicit user request: bypass T3-owned caches and rediscover models.500 /** Explicit user request: bypass T3-owned caches and rediscover models.
498 * Background status refreshes must not open agent sessions. */501 * Background status refreshes must not open agent sessions. */
499 refreshModels: Schema.optional(Schema.Boolean),502 refreshModels: Schema.optional(Schema.Boolean),