@saadnvd1/agent-os 0.3.684 → 0.3.692
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/api/dev-servers/detect/route.ts +1 -2
- package/app/api/discovered-models/route.ts +34 -0
- package/app/api/projects/[id]/dev-servers/[dsId]/route.ts +2 -4
- package/app/page.tsx +1 -2
- package/components/Artifacts/ArtifactsPanel.tsx +1 -1
- package/components/Draft/DraftPanel.tsx +10 -2
- package/components/Draft/draftModels.test.ts +31 -0
- package/components/Draft/draftModels.ts +17 -0
- package/components/FileExplorer/FileTabs.tsx +1 -1
- package/components/Pane/DesktopTabBar.tsx +2 -2
- package/components/QuickSwitcher.tsx +24 -21
- package/components/SessionList/SessionRowMenu.tsx +0 -1
- package/components/Settings/ExperimentsSection.test.ts +7 -1
- package/components/Settings/NoApprovalsSection.test.ts +7 -1
- package/components/Terminal/TerminalToolbar.tsx +1 -1
- package/components/Terminal/VirtualKeyboard.tsx +1 -1
- package/components/WorkerCard.tsx +1 -7
- package/components/Workspaces/WorkspaceNameDialog.tsx +5 -3
- package/components/a/AButton.tsx +1 -6
- package/components/a/AIconButton.tsx +1 -5
- package/contexts/PaneContext.tsx +1 -6
- package/data/agents/index.ts +37 -0
- package/data/lumifyhub/index.ts +2 -4
- package/hooks/useDirectoryBrowser.ts +5 -4
- package/hooks/useDrawerAnimation.ts +11 -14
- package/hooks/useFileDrop.ts +2 -6
- package/hooks/useKeybarVisibility.ts +10 -29
- package/hooks/useSidebarPinned.ts +7 -23
- package/hooks/useSpeechRecognition.ts +19 -5
- package/hooks/useStoredFlag.ts +47 -0
- package/hooks/useViewport.ts +11 -17
- package/lib/approvals/no-approvals.ts +1 -7
- package/lib/artifacts/store.ts +1 -2
- package/lib/away/report.ts +1 -2
- package/lib/away/run.ts +1 -2
- package/lib/away/state.ts +1 -2
- package/lib/bookmarks/store.ts +1 -2
- package/lib/bus/delivery.ts +1 -2
- package/lib/bus/index.ts +1 -2
- package/lib/chat/compact-context.ts +1 -2
- package/lib/chat/discovered-models.ts +87 -0
- package/lib/chat/stop.ts +1 -2
- package/lib/chat/worker/host.ts +1 -1
- package/lib/code-health/reports.ts +1 -2
- package/lib/db/lumifyhub.ts +2 -4
- package/lib/db/schedules-move.ts +1 -2
- package/lib/db/session-parts/tasks.ts +1 -2
- package/lib/db/stacks.ts +3 -9
- package/lib/db/types.ts +1 -7
- package/lib/doctor/checks.ts +7 -9
- package/lib/done/archive.ts +1 -2
- package/lib/experiments/meter.ts +1 -7
- package/lib/health/env/classes.ts +0 -2
- package/lib/health/env/recordHit.ts +1 -2
- package/lib/health/spin.ts +1 -4
- package/lib/health/store.ts +1 -2
- package/lib/hosts/index.ts +1 -2
- package/lib/hosts/peer-sync.ts +2 -4
- package/lib/hosts/remote-api.ts +1 -2
- package/lib/lumifyhub/types.ts +2 -8
- package/lib/model-catalog.ts +9 -0
- package/lib/notify/index.ts +1 -2
- package/lib/notify/push/index.ts +2 -7
- package/lib/orchestrator/external-pr.ts +1 -2
- package/lib/orchestrator/gates.ts +1 -6
- package/lib/orchestrator/needs-you-items.ts +16 -20
- package/lib/orchestrator/pr-wake.ts +1 -2
- package/lib/orchestrator/rebase.ts +1 -2
- package/lib/orchestrator/review.ts +1 -2
- package/lib/orchestrator/usage.ts +1 -2
- package/lib/plans/claim.ts +1 -2
- package/lib/plans/features.ts +4 -11
- package/lib/plans/release.ts +2 -4
- package/lib/ports.ts +1 -2
- package/lib/program-status/records.ts +1 -1
- package/lib/project-config/database.ts +1 -2
- package/lib/projects.ts +1 -2
- package/lib/recipes/settle.ts +2 -4
- package/lib/recipes/store.ts +2 -4
- package/lib/schedules/store.ts +2 -4
- package/lib/search/conversations/backfill.ts +2 -4
- package/lib/security/auth.ts +1 -2
- package/lib/security/passkeys.ts +1 -2
- package/lib/security/presence.ts +1 -7
- package/lib/session-artifacts/types.ts +1 -8
- package/lib/session-audit/transcript.ts +1 -3
- package/lib/session-logs/index.ts +1 -2
- package/lib/sessions/launch.ts +13 -7
- package/lib/share/snapshot.ts +1 -7
- package/lib/share/store.ts +1 -2
- package/lib/sidebar/orchestrator-row.ts +1 -6
- package/lib/sidebar/row-menu.ts +1 -2
- package/lib/sidebar/shelves.ts +1 -6
- package/lib/simulator/index.ts +1 -2
- package/lib/simulator/lease.ts +1 -5
- package/lib/stacks/index.ts +1 -1
- package/lib/stacks/plan.ts +1 -1
- package/lib/stacks/restack.ts +1 -2
- package/lib/stacks/tick.ts +1 -2
- package/lib/status/socket.ts +1 -2
- package/lib/tasks/contract/authorize.ts +1 -2
- package/lib/tasks/contract/plan.ts +1 -2
- package/lib/tasks/contract/sync.ts +1 -2
- package/lib/tasks/holds.ts +1 -5
- package/lib/tasks/index.ts +1 -2
- package/lib/tasks/loop-types.ts +1 -6
- package/lib/tasks/move-guard.ts +1 -2
- package/lib/tasks/move.ts +1 -2
- package/lib/tasks/moved-away.ts +1 -2
- package/lib/tasks/pause-hold.ts +1 -2
- package/lib/tasks/pause.ts +3 -6
- package/lib/tasks/pr-activity.ts +1 -3
- package/lib/tasks/queue-rows.ts +2 -4
- package/lib/tasks/queue-types.ts +1 -5
- package/lib/tasks/queue.ts +1 -2
- package/lib/tasks/refresh-base.ts +1 -2
- package/lib/tasks/start.ts +2 -4
- package/lib/tasks/starter.ts +1 -2
- package/lib/tasks/state.ts +1 -6
- package/lib/theme-config.ts +1 -8
- package/lib/updates/banner.ts +1 -6
- package/lib/updates/run.ts +1 -2
- package/lib/usage/aggregate.ts +1 -1
- package/lib/usage/nightly.ts +1 -2
- package/lib/usage/rate-limits.ts +1 -2
- package/lib/usage/turns.ts +1 -2
- package/lib/usage/windows.ts +5 -7
- package/lib/workspaces.ts +1 -2
- package/lib/worktree-sweep/databases.ts +0 -1
- package/package.json +3 -3
|
@@ -16,8 +16,7 @@ export async function GET(request: NextRequest) {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
const project = queries.getProject(db).get(projectId) as
|
|
19
|
-
|
|
|
20
|
-
| undefined;
|
|
19
|
+
Project | undefined;
|
|
21
20
|
if (!project) {
|
|
22
21
|
return NextResponse.json({ error: "Project not found" }, { status: 404 });
|
|
23
22
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { discoveredModels } from "@/lib/chat/discovered-models";
|
|
4
|
+
import { getProject } from "@/lib/projects";
|
|
5
|
+
|
|
6
|
+
const query = z.object({
|
|
7
|
+
agent: z.string().min(1),
|
|
8
|
+
projectId: z.string().optional(),
|
|
9
|
+
refresh: z.string().optional(),
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
// GET /api/discovered-models?agent=&projectId=&refresh=1 - the models an agent
|
|
13
|
+
// reports for a project's folder, for a draft that has no session yet. Empty
|
|
14
|
+
// for an agent with a fixed list.
|
|
15
|
+
export async function GET(request: NextRequest) {
|
|
16
|
+
const parsed = query.safeParse(
|
|
17
|
+
Object.fromEntries(request.nextUrl.searchParams)
|
|
18
|
+
);
|
|
19
|
+
if (!parsed.success)
|
|
20
|
+
return NextResponse.json({ error: "agent is required" }, { status: 400 });
|
|
21
|
+
const { agent, projectId, refresh } = parsed.data;
|
|
22
|
+
const folder = projectId ? getProject(projectId)?.working_directory : null;
|
|
23
|
+
try {
|
|
24
|
+
return NextResponse.json({
|
|
25
|
+
models: await discoveredModels(agent, folder, refresh !== undefined),
|
|
26
|
+
});
|
|
27
|
+
} catch (error) {
|
|
28
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
29
|
+
return NextResponse.json(
|
|
30
|
+
{ error: `Couldn't list the models: ${message}` },
|
|
31
|
+
{ status: 502 }
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -12,8 +12,7 @@ export async function PATCH(request: NextRequest, { params }: RouteParams) {
|
|
|
12
12
|
const { dsId } = await params;
|
|
13
13
|
|
|
14
14
|
const existing = queries.getProjectDevServer(db).get(dsId) as
|
|
15
|
-
|
|
|
16
|
-
| undefined;
|
|
15
|
+
ProjectDevServer | undefined;
|
|
17
16
|
if (!existing) {
|
|
18
17
|
return NextResponse.json(
|
|
19
18
|
{ error: "Dev server config not found" },
|
|
@@ -49,8 +48,7 @@ export async function DELETE(request: NextRequest, { params }: RouteParams) {
|
|
|
49
48
|
const { dsId } = await params;
|
|
50
49
|
|
|
51
50
|
const existing = queries.getProjectDevServer(db).get(dsId) as
|
|
52
|
-
|
|
|
53
|
-
| undefined;
|
|
51
|
+
ProjectDevServer | undefined;
|
|
54
52
|
if (!existing) {
|
|
55
53
|
return NextResponse.json(
|
|
56
54
|
{ error: "Dev server config not found" },
|
package/app/page.tsx
CHANGED
|
@@ -237,8 +237,7 @@ function HomeContent() {
|
|
|
237
237
|
|
|
238
238
|
// Get terminal for a pane, with fallback to first available
|
|
239
239
|
const getTerminalWithFallback = useCallback(():
|
|
240
|
-
|
|
241
|
-
| undefined => {
|
|
240
|
+
{ terminal: TerminalHandle; paneId: string; tabId: string } | undefined => {
|
|
242
241
|
debugLog(
|
|
243
242
|
`getTerminalWithFallback called, total refs: ${terminalRefs.current.size}, focusedPaneId: ${focusedPaneId}`
|
|
244
243
|
);
|
|
@@ -141,7 +141,7 @@ export function ArtifactsPanel({
|
|
|
141
141
|
</button>
|
|
142
142
|
)}
|
|
143
143
|
</div>
|
|
144
|
-
<div className="
|
|
144
|
+
<div className="mt-2.5 flex scrollbar-none gap-1.5 overflow-x-auto">
|
|
145
145
|
{(Object.keys(FILTER_LABELS) as ArtifactFilter[])
|
|
146
146
|
.filter((f) => f === "all" || tally[f])
|
|
147
147
|
.map((f) => (
|
|
@@ -8,7 +8,8 @@ import { Button } from "@/components/ui/button";
|
|
|
8
8
|
import type { ChatFile, ChatImage } from "@/lib/chat/events";
|
|
9
9
|
import type { Project } from "@/lib/db";
|
|
10
10
|
import { draftComposerKey, type Draft } from "@/lib/drafts";
|
|
11
|
-
import {
|
|
11
|
+
import { discoversModels } from "@/lib/model-catalog";
|
|
12
|
+
import { useDiscoveredModels } from "@/data/agents";
|
|
12
13
|
import { useHostsQuery } from "@/data/hosts";
|
|
13
14
|
import { useGitCheck } from "@/data/git/queries";
|
|
14
15
|
import { useLaunchSession } from "@/data/sessions";
|
|
@@ -18,6 +19,7 @@ import { setPendingPrompt } from "@/stores/initialPrompt";
|
|
|
18
19
|
import { draftsActions, draftsStore, newDraft } from "@/stores/drafts";
|
|
19
20
|
import { DraftChips } from "./DraftChips";
|
|
20
21
|
import { DraftHeading } from "./DraftHeading";
|
|
22
|
+
import { draftModels } from "./draftModels";
|
|
21
23
|
|
|
22
24
|
// A new session before its first send: choices on top, a composer below.
|
|
23
25
|
// Sending makes the session (and its worktree) and turns this tab into it.
|
|
@@ -41,6 +43,12 @@ export function DraftPanel({
|
|
|
41
43
|
const { data: git } = useGitCheck(
|
|
42
44
|
project && draft?.hostId === "local" ? project.working_directory : ""
|
|
43
45
|
);
|
|
46
|
+
const discovers = !!draft && discoversModels(draft.agentType);
|
|
47
|
+
const found = useDiscoveredModels(
|
|
48
|
+
draft?.agentType ?? "",
|
|
49
|
+
draft?.projectId ?? null,
|
|
50
|
+
discovers
|
|
51
|
+
);
|
|
44
52
|
const launch = useLaunchSession();
|
|
45
53
|
const createTask = useCreateTask();
|
|
46
54
|
const [prefill, setPrefill] = useState<{ text: string; at: number }>();
|
|
@@ -148,7 +156,7 @@ export function DraftPanel({
|
|
|
148
156
|
}
|
|
149
157
|
onSend={(text, images, files) => void send(text, images, files)}
|
|
150
158
|
onStop={() => {}}
|
|
151
|
-
models={
|
|
159
|
+
models={draftModels(draft.agentType, found)}
|
|
152
160
|
model={draft.model}
|
|
153
161
|
onSetModel={(model) => draftsActions.update(draft.id, { model })}
|
|
154
162
|
access={draft.openPr ? undefined : draft.access}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { draftModels } from "./draftModels";
|
|
3
|
+
|
|
4
|
+
const qwen = { value: "ollama/qwen3", label: "Qwen 3" };
|
|
5
|
+
|
|
6
|
+
describe("draftModels", () => {
|
|
7
|
+
it("lists the default and then what OpenCode reports", () => {
|
|
8
|
+
const list = draftModels("opencode", {
|
|
9
|
+
data: [qwen],
|
|
10
|
+
isPending: false,
|
|
11
|
+
isError: false,
|
|
12
|
+
});
|
|
13
|
+
expect(list.map((m) => m.value)).toEqual(["default", "ollama/qwen3"]);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("says so beside the default while loading and on failure", () => {
|
|
17
|
+
const loading = draftModels("opencode", {
|
|
18
|
+
isPending: true,
|
|
19
|
+
isError: false,
|
|
20
|
+
});
|
|
21
|
+
const failed = draftModels("opencode", { isPending: false, isError: true });
|
|
22
|
+
expect(loading).toHaveLength(1);
|
|
23
|
+
expect(loading[0].label).toContain("loading");
|
|
24
|
+
expect(failed[0].label).toContain("couldn't");
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("keeps a fixed list as it is", () => {
|
|
28
|
+
const list = draftModels("claude", { isPending: true, isError: false });
|
|
29
|
+
expect(list.map((m) => m.value)).toContain("opus");
|
|
30
|
+
});
|
|
31
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ChatModel } from "@/lib/chat/events";
|
|
2
|
+
import type { Draft } from "@/lib/drafts";
|
|
3
|
+
import { discoversModels, getModelOptions } from "@/lib/model-catalog";
|
|
4
|
+
|
|
5
|
+
// A fixed list, or the agent's default followed by what it reports; while it
|
|
6
|
+
// loads or if it fails, the default alone says so.
|
|
7
|
+
export function draftModels(
|
|
8
|
+
agentType: Draft["agentType"],
|
|
9
|
+
found: { data?: ChatModel[]; isPending: boolean; isError: boolean }
|
|
10
|
+
): ChatModel[] {
|
|
11
|
+
const options = getModelOptions(agentType);
|
|
12
|
+
if (!discoversModels(agentType)) return options;
|
|
13
|
+
if (found.data) return [...options, ...found.data];
|
|
14
|
+
const [first, ...rest] = options;
|
|
15
|
+
const note = found.isError ? "couldn't list models" : "loading models…";
|
|
16
|
+
return [{ ...first, label: `${first.label} (${note})` }, ...rest];
|
|
17
|
+
}
|
|
@@ -41,7 +41,7 @@ export function FileTabs({
|
|
|
41
41
|
return (
|
|
42
42
|
<div
|
|
43
43
|
ref={scrollRef}
|
|
44
|
-
className="bg-muted/30 scrollbar-none
|
|
44
|
+
className="bg-muted/30 flex scrollbar-none items-center gap-0.5 overflow-x-auto px-1"
|
|
45
45
|
>
|
|
46
46
|
{files.map((file) => {
|
|
47
47
|
const isActive = file.path === activeFilePath;
|
|
@@ -102,7 +102,7 @@ export function DesktopTabBar({
|
|
|
102
102
|
return (
|
|
103
103
|
<div
|
|
104
104
|
className={cn(
|
|
105
|
-
"
|
|
105
|
+
"@container/bar flex scrollbar-none items-center gap-1 overflow-x-auto px-1.5 py-1.5 transition-colors",
|
|
106
106
|
"shadow-[inset_0_-1px_0_hsl(var(--foreground)/0.06)]",
|
|
107
107
|
isFocused ? "bg-foreground/[0.025]" : "bg-transparent"
|
|
108
108
|
)}
|
|
@@ -110,7 +110,7 @@ export function DesktopTabBar({
|
|
|
110
110
|
{leading}
|
|
111
111
|
|
|
112
112
|
{/* Tabs */}
|
|
113
|
-
<div className="
|
|
113
|
+
<div className="flex min-w-28 flex-1 scrollbar-none items-center gap-0.5 overflow-x-auto">
|
|
114
114
|
{tabs.map((tab) => (
|
|
115
115
|
<div
|
|
116
116
|
key={tab.id}
|
|
@@ -37,14 +37,33 @@ export function QuickSwitcher({
|
|
|
37
37
|
currentSessionId,
|
|
38
38
|
activeSessionWorkingDir,
|
|
39
39
|
}: QuickSwitcherProps) {
|
|
40
|
-
const [
|
|
41
|
-
const [query,
|
|
40
|
+
const [chosenMode, setMode] = useState<"sessions" | "code">("sessions");
|
|
41
|
+
const [query, setQueryState] = useState("");
|
|
42
42
|
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
43
43
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
44
44
|
|
|
45
45
|
// Check if ripgrep is available
|
|
46
46
|
const { data: ripgrepAvailable } = useRipgrepAvailable();
|
|
47
47
|
|
|
48
|
+
// Code search needs ripgrep
|
|
49
|
+
const mode = ripgrepAvailable === false ? "sessions" : chosenMode;
|
|
50
|
+
|
|
51
|
+
const setQuery = (next: string) => {
|
|
52
|
+
setQueryState(next);
|
|
53
|
+
setSelectedIndex(0);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// Reset state when dialog opens
|
|
57
|
+
const [wasOpen, setWasOpen] = useState(open);
|
|
58
|
+
if (open !== wasOpen) {
|
|
59
|
+
setWasOpen(open);
|
|
60
|
+
if (open) {
|
|
61
|
+
setMode("sessions");
|
|
62
|
+
setQueryState("");
|
|
63
|
+
setSelectedIndex(0);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
48
67
|
// Filter sessions based on search query
|
|
49
68
|
const filteredSessions = sessions.filter((session) => {
|
|
50
69
|
if (!query) return true;
|
|
@@ -56,28 +75,12 @@ export function QuickSwitcher({
|
|
|
56
75
|
);
|
|
57
76
|
});
|
|
58
77
|
|
|
59
|
-
// Reset state when dialog opens
|
|
60
78
|
useEffect(() => {
|
|
61
|
-
if (open)
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
setSelectedIndex(0);
|
|
65
|
-
setTimeout(() => inputRef.current?.focus(), 50);
|
|
66
|
-
}
|
|
79
|
+
if (!open) return;
|
|
80
|
+
const t = setTimeout(() => inputRef.current?.focus(), 50);
|
|
81
|
+
return () => clearTimeout(t);
|
|
67
82
|
}, [open]);
|
|
68
83
|
|
|
69
|
-
// Force sessions mode if ripgrep is not available
|
|
70
|
-
useEffect(() => {
|
|
71
|
-
if (ripgrepAvailable === false && mode === "code") {
|
|
72
|
-
setMode("sessions");
|
|
73
|
-
}
|
|
74
|
-
}, [ripgrepAvailable, mode]);
|
|
75
|
-
|
|
76
|
-
// Reset selected index when filtered results change
|
|
77
|
-
useEffect(() => {
|
|
78
|
-
setSelectedIndex(0);
|
|
79
|
-
}, [query]);
|
|
80
|
-
|
|
81
84
|
// Handle keyboard navigation
|
|
82
85
|
const handleKeyDown = useCallback(
|
|
83
86
|
(e: React.KeyboardEvent) => {
|
|
@@ -32,7 +32,6 @@ import { selectionActions } from "@/stores/sessionSelection";
|
|
|
32
32
|
import { schedulesUiActions } from "@/stores/schedulesUi";
|
|
33
33
|
import { recipesUiActions } from "@/stores/recipesUi";
|
|
34
34
|
import { rowGroup } from "@/lib/recipes/chip";
|
|
35
|
-
import { finishActions } from "@/lib/sidebar/row-actions";
|
|
36
35
|
import {
|
|
37
36
|
useArchiveAction,
|
|
38
37
|
useDoneAction,
|
|
@@ -3,7 +3,13 @@ import { act, createElement } from "react";
|
|
|
3
3
|
import { createRoot, type Root } from "react-dom/client";
|
|
4
4
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
5
5
|
|
|
6
|
-
for (const key of [
|
|
6
|
+
for (const key of [
|
|
7
|
+
"Event",
|
|
8
|
+
"CustomEvent",
|
|
9
|
+
"FocusEvent",
|
|
10
|
+
"HTMLButtonElement",
|
|
11
|
+
"HTMLFormElement",
|
|
12
|
+
])
|
|
7
13
|
Object.defineProperty(globalThis, key, {
|
|
8
14
|
value: (window as unknown as Record<string, unknown>)[key],
|
|
9
15
|
configurable: true,
|
|
@@ -4,7 +4,13 @@ import { createRoot, type Root } from "react-dom/client";
|
|
|
4
4
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
5
5
|
|
|
6
6
|
// Radix dispatches its own events; they must be jsdom's, not Node's.
|
|
7
|
-
for (const key of [
|
|
7
|
+
for (const key of [
|
|
8
|
+
"Event",
|
|
9
|
+
"CustomEvent",
|
|
10
|
+
"FocusEvent",
|
|
11
|
+
"HTMLButtonElement",
|
|
12
|
+
"HTMLFormElement",
|
|
13
|
+
])
|
|
8
14
|
Object.defineProperty(globalThis, key, {
|
|
9
15
|
value: (window as unknown as Record<string, unknown>)[key],
|
|
10
16
|
configurable: true,
|
|
@@ -380,7 +380,7 @@ export function TerminalToolbar({
|
|
|
380
380
|
onInsert={sendText}
|
|
381
381
|
/>
|
|
382
382
|
<div
|
|
383
|
-
className="bg-background/95 border-border scrollbar-none
|
|
383
|
+
className="bg-background/95 border-border flex scrollbar-none items-center gap-1 overflow-x-auto border-t px-2 py-1.5 backdrop-blur"
|
|
384
384
|
onTouchEnd={(e) => e.stopPropagation()}
|
|
385
385
|
>
|
|
386
386
|
{/* Mic button */}
|
|
@@ -295,7 +295,7 @@ function TerminalShortcutsBar({
|
|
|
295
295
|
onClose={() => setShowPasteModal(false)}
|
|
296
296
|
onPaste={handleModalPaste}
|
|
297
297
|
/>
|
|
298
|
-
<div className="scrollbar-none
|
|
298
|
+
<div className="flex scrollbar-none items-center gap-1.5 overflow-x-auto px-2 py-1.5">
|
|
299
299
|
{/* Paste button */}
|
|
300
300
|
<FastButton
|
|
301
301
|
onPress={handlePaste}
|
|
@@ -19,13 +19,7 @@ import {
|
|
|
19
19
|
import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip";
|
|
20
20
|
|
|
21
21
|
export type WorkerStatus =
|
|
22
|
-
| "
|
|
23
|
-
| "running"
|
|
24
|
-
| "waiting"
|
|
25
|
-
| "idle"
|
|
26
|
-
| "completed"
|
|
27
|
-
| "failed"
|
|
28
|
-
| "dead";
|
|
22
|
+
"pending" | "running" | "waiting" | "idle" | "completed" | "failed" | "dead";
|
|
29
23
|
|
|
30
24
|
export interface WorkerInfo {
|
|
31
25
|
id: string;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { useState } from "react";
|
|
4
4
|
import {
|
|
5
5
|
Dialog,
|
|
6
6
|
DialogContent,
|
|
@@ -28,9 +28,11 @@ export function WorkspaceNameDialog({
|
|
|
28
28
|
onClose,
|
|
29
29
|
}: WorkspaceNameDialogProps) {
|
|
30
30
|
const [name, setName] = useState(initialName);
|
|
31
|
-
|
|
31
|
+
const [synced, setSynced] = useState({ open, initialName });
|
|
32
|
+
if (synced.open !== open || synced.initialName !== initialName) {
|
|
33
|
+
setSynced({ open, initialName });
|
|
32
34
|
if (open) setName(initialName);
|
|
33
|
-
}
|
|
35
|
+
}
|
|
34
36
|
|
|
35
37
|
return (
|
|
36
38
|
<Dialog open={open} onOpenChange={(o) => !o && onClose()}>
|
package/components/a/AButton.tsx
CHANGED
|
@@ -33,12 +33,7 @@ import { cn } from "@/lib/utils";
|
|
|
33
33
|
|
|
34
34
|
export type AButtonSize = "sm" | "md" | "lg";
|
|
35
35
|
export type AButtonVariant =
|
|
36
|
-
| "
|
|
37
|
-
| "ghost"
|
|
38
|
-
| "outline"
|
|
39
|
-
| "secondary"
|
|
40
|
-
| "destructive"
|
|
41
|
-
| "link";
|
|
36
|
+
"default" | "ghost" | "outline" | "secondary" | "destructive" | "link";
|
|
42
37
|
|
|
43
38
|
export interface AButtonProps extends Omit<
|
|
44
39
|
React.ButtonHTMLAttributes<HTMLButtonElement>,
|
|
@@ -33,11 +33,7 @@ import { cn } from "@/lib/utils";
|
|
|
33
33
|
|
|
34
34
|
export type AIconButtonSize = "sm" | "md" | "lg";
|
|
35
35
|
export type AIconButtonHighlight =
|
|
36
|
-
| "
|
|
37
|
-
| "green"
|
|
38
|
-
| "red"
|
|
39
|
-
| "orange"
|
|
40
|
-
| "purple";
|
|
36
|
+
"blue" | "green" | "red" | "orange" | "purple";
|
|
41
37
|
|
|
42
38
|
export interface AIconButtonProps extends Omit<
|
|
43
39
|
React.ButtonHTMLAttributes<HTMLButtonElement>,
|
package/contexts/PaneContext.tsx
CHANGED
|
@@ -27,12 +27,7 @@ import {
|
|
|
27
27
|
import { useViewport } from "@/hooks/useViewport";
|
|
28
28
|
|
|
29
29
|
export type ViewMode =
|
|
30
|
-
| "
|
|
31
|
-
| "files"
|
|
32
|
-
| "git"
|
|
33
|
-
| "workers"
|
|
34
|
-
| "log"
|
|
35
|
-
| "artifacts";
|
|
30
|
+
"terminal" | "files" | "git" | "workers" | "log" | "artifacts";
|
|
36
31
|
|
|
37
32
|
export interface PaneViewState {
|
|
38
33
|
viewMode: ViewMode;
|
package/data/agents/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
2
2
|
import type { AgentProbe } from "@/lib/agents/probe";
|
|
3
|
+
import type { ChatModel } from "@/lib/chat/events";
|
|
3
4
|
|
|
4
5
|
export const agentKeys = {
|
|
5
6
|
all: ["agents"] as const,
|
|
@@ -35,3 +36,39 @@ export function useRecheckAgents() {
|
|
|
35
36
|
onSuccess: (agents) => client.setQueryData(agentKeys.status(), agents),
|
|
36
37
|
});
|
|
37
38
|
}
|
|
39
|
+
|
|
40
|
+
export const discoveredModelKeys = {
|
|
41
|
+
list: (agent: string, projectId: string | null) =>
|
|
42
|
+
[...agentKeys.all, "models", agent, projectId] as const,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// What an agent without a fixed list reports it can run, for a draft's
|
|
46
|
+
// picker. Off for agents with a fixed list.
|
|
47
|
+
export function useDiscoveredModels(
|
|
48
|
+
agent: string,
|
|
49
|
+
projectId: string | null,
|
|
50
|
+
enabled: boolean
|
|
51
|
+
) {
|
|
52
|
+
const client = useQueryClient();
|
|
53
|
+
const key = discoveredModelKeys.list(agent, projectId);
|
|
54
|
+
const fetchModels = async (refresh: boolean) => {
|
|
55
|
+
const params = new URLSearchParams({ agent });
|
|
56
|
+
if (projectId) params.set("projectId", projectId);
|
|
57
|
+
if (refresh) params.set("refresh", "1");
|
|
58
|
+
const res = await fetch(`/api/discovered-models?${params}`);
|
|
59
|
+
const data = await res.json().catch(() => ({}));
|
|
60
|
+
if (!res.ok) throw new Error(data.error || "Couldn't list the models");
|
|
61
|
+
return data.models as ChatModel[];
|
|
62
|
+
};
|
|
63
|
+
const query = useQuery({
|
|
64
|
+
queryKey: key,
|
|
65
|
+
queryFn: () => fetchModels(false),
|
|
66
|
+
enabled,
|
|
67
|
+
staleTime: 5 * 60_000,
|
|
68
|
+
retry: false,
|
|
69
|
+
});
|
|
70
|
+
return {
|
|
71
|
+
...query,
|
|
72
|
+
refresh: async () => client.setQueryData(key, await fetchModels(true)),
|
|
73
|
+
};
|
|
74
|
+
}
|
package/data/lumifyhub/index.ts
CHANGED
|
@@ -86,8 +86,7 @@ export function useLhWorkspaces(enabled: boolean) {
|
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
export type WorkspaceLinkTarget =
|
|
89
|
-
| {
|
|
90
|
-
| { create: true; name?: string };
|
|
89
|
+
{ lhWorkspaceId: string } | { create: true; name?: string };
|
|
91
90
|
|
|
92
91
|
export function useLinkWorkspace() {
|
|
93
92
|
return useInvalidating(
|
|
@@ -120,8 +119,7 @@ export function useWorkspaceBoards(workspaceId: string | null) {
|
|
|
120
119
|
}
|
|
121
120
|
|
|
122
121
|
export type BoardLinkTarget =
|
|
123
|
-
| {
|
|
124
|
-
| { create: true; title?: string };
|
|
122
|
+
{ boardId: string } | { create: true; title?: string };
|
|
125
123
|
|
|
126
124
|
export function useLinkBoard() {
|
|
127
125
|
return useInvalidating(
|
|
@@ -32,13 +32,14 @@ export function useDirectoryBrowser(options: UseDirectoryBrowserOptions = {}) {
|
|
|
32
32
|
const currentPath = data?.resolvedPath || requestedPath;
|
|
33
33
|
|
|
34
34
|
// Filter and sort files from query data
|
|
35
|
+
const dataFiles = data?.files;
|
|
35
36
|
const files = useMemo(() => {
|
|
36
|
-
if (!
|
|
37
|
+
if (!dataFiles) return [];
|
|
37
38
|
const items = filterRef.current
|
|
38
|
-
?
|
|
39
|
-
:
|
|
39
|
+
? dataFiles.filter(filterRef.current)
|
|
40
|
+
: dataFiles;
|
|
40
41
|
return sortFiles(items);
|
|
41
|
-
}, [
|
|
42
|
+
}, [dataFiles]);
|
|
42
43
|
|
|
43
44
|
const filteredFiles = useMemo(
|
|
44
45
|
() =>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState, useEffect
|
|
1
|
+
import { useState, useEffect } from "react";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Hook for smooth drawer enter animations.
|
|
@@ -6,22 +6,19 @@ import { useState, useEffect, useRef } from "react";
|
|
|
6
6
|
*/
|
|
7
7
|
export function useDrawerAnimation(open: boolean) {
|
|
8
8
|
const [isAnimatingIn, setIsAnimatingIn] = useState(false);
|
|
9
|
-
const hasAnimated = useRef(false);
|
|
10
9
|
|
|
11
10
|
useEffect(() => {
|
|
12
|
-
if (open
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
if (!open) {
|
|
21
|
-
hasAnimated.current = false;
|
|
11
|
+
if (!open) return;
|
|
12
|
+
let inner = 0;
|
|
13
|
+
const outer = requestAnimationFrame(() => {
|
|
14
|
+
inner = requestAnimationFrame(() => setIsAnimatingIn(true));
|
|
15
|
+
});
|
|
16
|
+
return () => {
|
|
17
|
+
cancelAnimationFrame(outer);
|
|
18
|
+
cancelAnimationFrame(inner);
|
|
22
19
|
setIsAnimatingIn(false);
|
|
23
|
-
}
|
|
20
|
+
};
|
|
24
21
|
}, [open]);
|
|
25
22
|
|
|
26
|
-
return isAnimatingIn;
|
|
23
|
+
return open && isAnimatingIn;
|
|
27
24
|
}
|
package/hooks/useFileDrop.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { useState, useCallback,
|
|
3
|
+
import { useState, useCallback, type RefObject } from "react";
|
|
4
4
|
|
|
5
5
|
interface UseFileDropOptions {
|
|
6
6
|
/** Disable drop handling (e.g., while uploading) */
|
|
@@ -29,11 +29,7 @@ export function useFileDrop(
|
|
|
29
29
|
const [isDragging, setIsDragging] = useState(false);
|
|
30
30
|
|
|
31
31
|
// Reset drag state when disabled
|
|
32
|
-
|
|
33
|
-
if (options?.disabled) {
|
|
34
|
-
setIsDragging(false);
|
|
35
|
-
}
|
|
36
|
-
}, [options?.disabled]);
|
|
32
|
+
if (options?.disabled && isDragging) setIsDragging(false);
|
|
37
33
|
|
|
38
34
|
const handleDragOver = useCallback(
|
|
39
35
|
(e: React.DragEvent) => {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { useCallback } from "react";
|
|
4
|
+
import { useStoredFlag } from "./useStoredFlag";
|
|
4
5
|
|
|
5
6
|
const STORAGE_KEY = "agentOS-keybar-visible";
|
|
6
7
|
|
|
@@ -9,34 +10,14 @@ const STORAGE_KEY = "agentOS-keybar-visible";
|
|
|
9
10
|
* Default: hidden on mobile to maximize terminal space.
|
|
10
11
|
*/
|
|
11
12
|
export function useKeybarVisibility() {
|
|
12
|
-
const [isVisible,
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
}, []);
|
|
22
|
-
|
|
23
|
-
const toggle = useCallback(() => {
|
|
24
|
-
setIsVisible((prev) => {
|
|
25
|
-
const next = !prev;
|
|
26
|
-
localStorage.setItem(STORAGE_KEY, String(next));
|
|
27
|
-
return next;
|
|
28
|
-
});
|
|
29
|
-
}, []);
|
|
30
|
-
|
|
31
|
-
const show = useCallback(() => {
|
|
32
|
-
setIsVisible(true);
|
|
33
|
-
localStorage.setItem(STORAGE_KEY, "true");
|
|
34
|
-
}, []);
|
|
35
|
-
|
|
36
|
-
const hide = useCallback(() => {
|
|
37
|
-
setIsVisible(false);
|
|
38
|
-
localStorage.setItem(STORAGE_KEY, "false");
|
|
39
|
-
}, []);
|
|
13
|
+
const [isVisible, setVisible] = useStoredFlag(STORAGE_KEY, false);
|
|
14
|
+
|
|
15
|
+
const toggle = useCallback(
|
|
16
|
+
() => setVisible(!isVisible),
|
|
17
|
+
[isVisible, setVisible]
|
|
18
|
+
);
|
|
19
|
+
const show = useCallback(() => setVisible(true), [setVisible]);
|
|
20
|
+
const hide = useCallback(() => setVisible(false), [setVisible]);
|
|
40
21
|
|
|
41
22
|
return { isVisible, toggle, show, hide };
|
|
42
23
|
}
|