@saadnvd1/agent-os 0.3.700 → 0.3.715
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/README.md +1 -0
- package/app/api/bus/groups/route.ts +44 -0
- package/app/api/bus/spawn/route.ts +12 -1
- package/app/api/claims/[id]/route.ts +20 -0
- package/app/api/claims/check/route.ts +23 -0
- package/app/api/claims/check.test.ts +113 -0
- package/app/api/claims/exchange/route.ts +42 -0
- package/app/api/claims/outside.test.ts +90 -0
- package/app/api/claims/route.test.ts +193 -0
- package/app/api/claims/route.ts +73 -0
- package/app/api/hosts/[id]/route.ts +9 -1
- package/app/api/peer/unlink/route.ts +17 -0
- package/app/api/peers/[hostId]/audit/route.ts +14 -0
- package/app/api/peers/[hostId]/route.ts +20 -0
- package/app/api/peers/gate.test.ts +64 -1
- package/app/api/peers/pending/route.test.ts +37 -0
- package/app/api/peers/pending/route.ts +11 -4
- package/app/api/session-groups/[id]/restore/route.ts +11 -0
- package/app/api/session-groups/[id]/route.ts +25 -0
- package/app/api/session-groups/order/route.ts +15 -0
- package/app/api/session-groups/route.ts +21 -0
- package/app/api/sessions/[id]/fork/route.ts +3 -0
- package/app/api/sessions/[id]/group/route.ts +18 -0
- package/app/api/sessions/[id]/route.ts +14 -4
- package/app/api/sessions/[id]/summarize/route.ts +2 -0
- package/app/api/sessions/claims.test.ts +122 -0
- package/app/api/sessions/group/route.ts +17 -0
- package/app/api/sessions/route.ts +15 -14
- package/app/page.tsx +5 -0
- package/components/Claims/ClaimFiles.tsx +38 -0
- package/components/Claims/ClaimRow.tsx +82 -0
- package/components/Claims/ClaimsSheet.tsx +176 -0
- package/components/Claims/OverlapPill.tsx +31 -0
- package/components/Claims/StartClaimsNotice.tsx +70 -0
- package/components/Claims/claims-ui.test.ts +161 -0
- package/components/Claims/format.ts +57 -0
- package/components/Claims/index.ts +3 -0
- package/components/Draft/DraftPanel.tsx +40 -2
- package/components/Hosts/AccountMachines.tsx +32 -18
- package/components/Hosts/HostRow.tsx +155 -14
- package/components/Hosts/IncomingLinkSheet.tsx +28 -10
- package/components/Hosts/PeerActivitySheet.tsx +111 -0
- package/components/SessionList/GroupShelves.tsx +195 -0
- package/components/SessionList/MoveToGroupMenu.tsx +105 -0
- package/components/SessionList/RowSubtitle.tsx +7 -1
- package/components/SessionList/SelectionToolbar.tsx +52 -1
- package/components/SessionList/SessionRow.tsx +1 -0
- package/components/SessionList/SessionRowMenu.tsx +17 -0
- package/components/SessionList/SessionShelves.tsx +5 -0
- package/components/SessionList/SidebarBody.tsx +6 -0
- package/components/SessionList/SidebarContextMenu.test.ts +104 -0
- package/components/SessionList/SidebarContextMenu.tsx +59 -0
- package/components/SessionList/SidebarHeader.tsx +5 -0
- package/components/SessionList/WorkspaceSwitcher.tsx +23 -6
- package/components/SessionList/index.tsx +18 -14
- package/components/SessionList/useSidebarData.ts +10 -0
- package/components/ui/notice.tsx +46 -0
- package/components/ui/status-pill.tsx +39 -0
- package/data/claims/index.ts +2 -0
- package/data/claims/keys.ts +5 -0
- package/data/claims/queries.ts +47 -0
- package/data/peers/keys.ts +1 -0
- package/data/peers/queries.ts +31 -0
- package/data/push/topics.ts +4 -0
- package/data/session-groups/index.ts +156 -0
- package/data/sessions/index.ts +0 -1
- package/data/sessions/pin.ts +2 -6
- package/data/sessions/queries.ts +10 -29
- package/data/sessions/shelf.ts +2 -6
- package/data/tasks/index.ts +2 -0
- package/hooks/useSessions.ts +0 -12
- package/lib/__fixtures__/peer-rpc.ts +65 -33
- package/lib/agents/spawn.ts +4 -0
- package/lib/bus/index.ts +8 -0
- package/lib/claims/collectLocal.ts +121 -0
- package/lib/claims/describe.ts +41 -0
- package/lib/claims/exchange.ts +91 -0
- package/lib/claims/goal.ts +45 -0
- package/lib/claims/index.ts +10 -0
- package/lib/claims/loop.ts +51 -0
- package/lib/claims/machine.ts +21 -0
- package/lib/claims/mine.ts +43 -0
- package/lib/claims/notifyOverlaps.ts +101 -0
- package/lib/claims/onMerged.ts +85 -0
- package/lib/claims/outside.ts +36 -0
- package/lib/claims/overlap.ts +87 -0
- package/lib/claims/overlapCache.ts +86 -0
- package/lib/claims/overlapText.ts +36 -0
- package/lib/claims/peers.ts +56 -0
- package/lib/claims/startCheck.ts +100 -0
- package/lib/claims/startWarning.ts +29 -0
- package/lib/claims/store.ts +366 -0
- package/lib/claims/types.ts +102 -0
- package/lib/claims/view.ts +80 -0
- package/lib/claims/yield.ts +24 -0
- package/lib/db/changes.ts +6 -0
- package/lib/db/migrations/20261020T1400_work_claims.ts +35 -0
- package/lib/db/migrations/20261020T1515_peer_unlinked.ts +22 -0
- package/lib/db/migrations/20261020T1700_clean_connect_names.ts +18 -0
- package/lib/db/migrations/20261020T1800_session_groups.ts +49 -0
- package/lib/db/migrations/20261020T1900_task_queue_after_claim.ts +13 -0
- package/lib/db/migrations/20261020T2000_claim_notices.ts +20 -0
- package/lib/db/migrations/20261020T2100_claims_via_host.ts +21 -0
- package/lib/db/migrations/20261020T2200_work_claims_watch.ts +10 -0
- package/lib/db/queries.ts +0 -40
- package/lib/db/session-parts/columns.ts +1 -0
- package/lib/db/types.ts +16 -8
- package/lib/doctor/checks.ts +64 -1
- package/lib/git-remote.ts +43 -0
- package/lib/hosts/index.ts +10 -1
- package/lib/orchestration.ts +2 -0
- package/lib/orchestrator/act.ts +54 -4
- package/lib/orchestrator/brief.ts +2 -1
- package/lib/orchestrator/claim-wait.ts +71 -0
- package/lib/orchestrator/conditions.ts +28 -1
- package/lib/orchestrator/describe.ts +6 -1
- package/lib/orchestrator/facts.ts +5 -0
- package/lib/orchestrator/serve.ts +37 -2
- package/lib/orchestrator/tool-names.ts +1 -0
- package/lib/orchestrator/tool-schemas.ts +31 -1
- package/lib/orchestrator/tools.ts +3 -1
- package/lib/orchestrator/watcher.ts +19 -1
- package/lib/peer/account.ts +74 -2
- package/lib/peer/allowlist.ts +1 -1
- package/lib/peer/client.ts +26 -1
- package/lib/peer/link-cli.ts +19 -4
- package/lib/peer/links.ts +117 -5
- package/lib/peer/machines.ts +14 -2
- package/lib/peer/name.ts +32 -0
- package/lib/peer/pairing.ts +34 -5
- package/lib/peer/pin.ts +10 -3
- package/lib/peer/reasons.ts +7 -0
- package/lib/peer/revoke.ts +75 -0
- package/lib/peer/rpc-server.ts +53 -5
- package/lib/peer/unlink.ts +62 -0
- package/lib/peer/unlinked-proof.ts +44 -0
- package/lib/projects.ts +5 -2
- package/lib/security/demo.ts +7 -0
- package/lib/session-groups/assignSessions.ts +81 -0
- package/lib/session-groups/cli.ts +61 -0
- package/lib/session-groups/createGroup.ts +28 -0
- package/lib/session-groups/index.ts +21 -0
- package/lib/session-groups/removeGroup.ts +36 -0
- package/lib/session-groups/reorderGroups.ts +31 -0
- package/lib/session-groups/resolveGroup.ts +19 -0
- package/lib/session-groups/route.ts +32 -0
- package/lib/session-groups/store.ts +85 -0
- package/lib/session-groups/types.ts +25 -0
- package/lib/session-groups/updateGroup.ts +35 -0
- package/lib/session-groups/workspace.ts +21 -0
- package/lib/sessions/launch.ts +5 -2
- package/lib/sidebar/rollup.ts +1 -0
- package/lib/sidebar/row-menu.ts +2 -0
- package/lib/sidebar/shelves.ts +90 -15
- package/lib/sidebar/visible-order.ts +5 -0
- package/lib/tasks/hold-checks.ts +3 -1
- package/lib/tasks/holds.ts +3 -0
- package/lib/tasks/index.ts +5 -0
- package/lib/tasks/queue-rows.ts +8 -1
- package/lib/tasks/queue-start.ts +1 -0
- package/lib/tasks/queue-types.ts +9 -1
- package/lib/tasks/queue.ts +15 -5
- package/lib/tasks/schedule-types.ts +1 -0
- package/lib/test-temp.ts +19 -3
- package/lib/workspaces.ts +11 -3
- package/lib/worktree-sweep/testTemp.ts +23 -106
- package/package.json +1 -1
- package/scripts/agent-os +1 -1
- package/scripts/autodeploy +2 -0
- package/scripts/autodeploy.test.ts +31 -1
- package/scripts/claim-hook.sh +31 -0
- package/scripts/claim-hook.test.ts +230 -0
- package/scripts/lib/cli-links.sh +36 -0
- package/scripts/lib/commands.sh +3 -0
- package/scripts/lib/machine.sh +27 -2
- package/scripts/lib/machine.test.ts +25 -3
- package/scripts/sync-cli-links +7 -0
- package/scripts/sync-cli-links.test.ts +69 -0
- package/server.ts +3 -0
- package/stores/claimsUi.ts +22 -0
- package/app/api/groups/[...path]/route.ts +0 -136
- package/app/api/groups/route.ts +0 -93
- package/data/groups/index.ts +0 -1
- package/data/groups/mutations.ts +0 -95
- package/hooks/useGroups.ts +0 -37
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
import { useCallback, useMemo } from "react";
|
|
4
4
|
import { useSnapshot } from "valtio";
|
|
5
5
|
import type { Session } from "@/lib/db";
|
|
6
|
+
import type { SessionGroup } from "@/lib/sidebar/shelves";
|
|
6
7
|
import {
|
|
7
8
|
buildShelves,
|
|
8
9
|
inWorkspace,
|
|
10
|
+
orderGroups,
|
|
9
11
|
projectsInWorkspace,
|
|
10
12
|
} from "@/lib/sidebar/shelves";
|
|
11
13
|
import type { TaskState } from "@/lib/tasks/state";
|
|
@@ -25,6 +27,7 @@ import type { SessionStatus } from "./SessionList.types";
|
|
|
25
27
|
|
|
26
28
|
// One empty list for queries still loading, so memos below hold.
|
|
27
29
|
const NONE: never[] = [];
|
|
30
|
+
const NONE_GROUPS: SessionGroup[] = [];
|
|
28
31
|
|
|
29
32
|
// Everything the flat list needs: the current workspace's sessions, sorted
|
|
30
33
|
// onto shelves by the remembered filters.
|
|
@@ -55,6 +58,7 @@ export function useSidebarData(
|
|
|
55
58
|
() => sessionsQuery.data?.sessions ?? [],
|
|
56
59
|
[sessionsQuery.data]
|
|
57
60
|
);
|
|
61
|
+
const allGroups = sessionsQuery.data?.sessionGroups ?? NONE_GROUPS;
|
|
58
62
|
// Keyed by its contents: the hosts query hands back a new object each time.
|
|
59
63
|
const linkedKey = JSON.stringify(useLinkedHostNames());
|
|
60
64
|
const linked = useMemo(
|
|
@@ -102,6 +106,10 @@ export function useSidebarData(
|
|
|
102
106
|
),
|
|
103
107
|
projectId: project?.id ?? null,
|
|
104
108
|
currentOrchestrators,
|
|
109
|
+
groups: orderGroups(
|
|
110
|
+
allGroups.filter((g) => !workspace || g.workspace_id === workspace.id),
|
|
111
|
+
workspaces.map((w) => w.id)
|
|
112
|
+
),
|
|
105
113
|
};
|
|
106
114
|
// The rollups count the whole list; the filter only narrows what shows.
|
|
107
115
|
const all = buildShelves(input);
|
|
@@ -120,6 +128,7 @@ export function useSidebarData(
|
|
|
120
128
|
now,
|
|
121
129
|
linked,
|
|
122
130
|
sessions,
|
|
131
|
+
allGroups,
|
|
123
132
|
projects,
|
|
124
133
|
workspaces,
|
|
125
134
|
tasks,
|
|
@@ -235,6 +244,7 @@ export function useSidebarData(
|
|
|
235
244
|
return {
|
|
236
245
|
ui,
|
|
237
246
|
shelves,
|
|
247
|
+
groups: allGroups,
|
|
238
248
|
queued,
|
|
239
249
|
toStart,
|
|
240
250
|
machines,
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { AlertTriangle, Info } from "lucide-react";
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
|
|
5
|
+
const TONES = {
|
|
6
|
+
info: {
|
|
7
|
+
box: "bg-muted/60",
|
|
8
|
+
icon: <Info className="text-muted-foreground mt-0.5 h-4 w-4 shrink-0" />,
|
|
9
|
+
},
|
|
10
|
+
warning: {
|
|
11
|
+
box: "bg-amber-500/10",
|
|
12
|
+
icon: (
|
|
13
|
+
<AlertTriangle className="mt-0.5 h-4 w-4 shrink-0 text-amber-600 dark:text-amber-400" />
|
|
14
|
+
),
|
|
15
|
+
},
|
|
16
|
+
} as const;
|
|
17
|
+
|
|
18
|
+
/** A short message in a tinted box, with an optional action on the right. */
|
|
19
|
+
export function Notice({
|
|
20
|
+
tone = "info",
|
|
21
|
+
action,
|
|
22
|
+
className,
|
|
23
|
+
children,
|
|
24
|
+
}: {
|
|
25
|
+
tone?: keyof typeof TONES;
|
|
26
|
+
action?: ReactNode;
|
|
27
|
+
className?: string;
|
|
28
|
+
children: ReactNode;
|
|
29
|
+
}) {
|
|
30
|
+
return (
|
|
31
|
+
<div
|
|
32
|
+
role="status"
|
|
33
|
+
className={cn(
|
|
34
|
+
"flex flex-col gap-2 rounded-lg p-3 text-sm sm:flex-row sm:items-center",
|
|
35
|
+
TONES[tone].box,
|
|
36
|
+
className
|
|
37
|
+
)}
|
|
38
|
+
>
|
|
39
|
+
<div className="flex min-w-0 flex-1 gap-2">
|
|
40
|
+
{TONES[tone].icon}
|
|
41
|
+
<div className="min-w-0 flex-1">{children}</div>
|
|
42
|
+
</div>
|
|
43
|
+
{action}
|
|
44
|
+
</div>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { ComponentProps } from "react";
|
|
2
|
+
import { cn } from "@/lib/utils";
|
|
3
|
+
|
|
4
|
+
const TONES = {
|
|
5
|
+
amber: "bg-amber-500/15 text-amber-700 dark:text-amber-400",
|
|
6
|
+
muted: "bg-muted text-muted-foreground",
|
|
7
|
+
} as const;
|
|
8
|
+
|
|
9
|
+
/** A small coloured pill for a status. A button when it has an onClick. */
|
|
10
|
+
export function StatusPill({
|
|
11
|
+
tone,
|
|
12
|
+
className,
|
|
13
|
+
onClick,
|
|
14
|
+
...props
|
|
15
|
+
}: Omit<ComponentProps<"span">, "onClick"> & {
|
|
16
|
+
tone: keyof typeof TONES;
|
|
17
|
+
onClick?: () => void;
|
|
18
|
+
}) {
|
|
19
|
+
const classes = cn(
|
|
20
|
+
"inline-flex max-w-full min-w-0 items-center gap-1 rounded-md px-1.5 py-0.5 text-[11px] font-semibold",
|
|
21
|
+
TONES[tone],
|
|
22
|
+
className
|
|
23
|
+
);
|
|
24
|
+
if (!onClick) return <span className={classes} {...props} />;
|
|
25
|
+
return (
|
|
26
|
+
<button
|
|
27
|
+
type="button"
|
|
28
|
+
onClick={(e) => {
|
|
29
|
+
e.stopPropagation();
|
|
30
|
+
onClick();
|
|
31
|
+
}}
|
|
32
|
+
className={cn(
|
|
33
|
+
classes,
|
|
34
|
+
"relative cursor-pointer before:absolute before:-inset-x-1 before:-inset-y-3 hover:brightness-110"
|
|
35
|
+
)}
|
|
36
|
+
{...(props as ComponentProps<"button">)}
|
|
37
|
+
/>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { useMutation, useQuery } from "@tanstack/react-query";
|
|
2
|
+
import type { ClaimsView } from "@/lib/claims/view";
|
|
3
|
+
import type { StartWarning } from "@/lib/claims/startWarning";
|
|
4
|
+
import { usePollWhenOffline } from "../push/connection";
|
|
5
|
+
import { claimKeys } from "./keys";
|
|
6
|
+
|
|
7
|
+
// Claims here and on linked machines. Pushed when a claim changes; it polls
|
|
8
|
+
// only while the stream is down.
|
|
9
|
+
export function useClaims(opts: { project?: string; enabled?: boolean } = {}) {
|
|
10
|
+
const { project, enabled = true } = opts;
|
|
11
|
+
return useQuery({
|
|
12
|
+
queryKey: claimKeys.list(project),
|
|
13
|
+
queryFn: async (): Promise<ClaimsView> => {
|
|
14
|
+
const res = await fetch(
|
|
15
|
+
`/api/claims${project ? `?project=${encodeURIComponent(project)}` : ""}`
|
|
16
|
+
);
|
|
17
|
+
if (!res.ok) throw new Error("Failed to load claims");
|
|
18
|
+
return res.json();
|
|
19
|
+
},
|
|
20
|
+
enabled,
|
|
21
|
+
staleTime: 5000,
|
|
22
|
+
refetchInterval: usePollWhenOffline(15000),
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface StartCheckInput {
|
|
27
|
+
projectId: string;
|
|
28
|
+
name?: string;
|
|
29
|
+
prompt: string;
|
|
30
|
+
branch?: string | null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Who else is on the work a person is about to start. A failure means no
|
|
34
|
+
// warning: it never stops a start.
|
|
35
|
+
export function useCheckStart() {
|
|
36
|
+
return useMutation({
|
|
37
|
+
mutationFn: async (input: StartCheckInput): Promise<StartWarning[]> => {
|
|
38
|
+
const res = await fetch("/api/claims/check", {
|
|
39
|
+
method: "POST",
|
|
40
|
+
headers: { "Content-Type": "application/json" },
|
|
41
|
+
body: JSON.stringify(input),
|
|
42
|
+
});
|
|
43
|
+
if (!res.ok) return [];
|
|
44
|
+
return ((await res.json()) as { overlaps: StartWarning[] }).overlaps;
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
}
|
package/data/peers/keys.ts
CHANGED
|
@@ -3,4 +3,5 @@ export const peerKeys = {
|
|
|
3
3
|
account: () => [...peerKeys.all, "account"] as const,
|
|
4
4
|
pending: () => [...peerKeys.all, "pending"] as const,
|
|
5
5
|
link: (id: string) => [...peerKeys.all, "link", id] as const,
|
|
6
|
+
audit: (hostId: string) => [...peerKeys.all, "audit", hostId] as const,
|
|
6
7
|
};
|
package/data/peers/queries.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
2
|
+
import type { AuditRow } from "@/lib/peer/audit";
|
|
2
3
|
import type { AccountMachines } from "@/lib/peer/machines";
|
|
3
4
|
import type { PendingView } from "@/lib/peer/pairing";
|
|
4
5
|
import type { Outgoing } from "@/lib/peer/pairing-out";
|
|
@@ -139,3 +140,33 @@ export function useAnswerLink() {
|
|
|
139
140
|
}),
|
|
140
141
|
};
|
|
141
142
|
}
|
|
143
|
+
|
|
144
|
+
/** Unlinks a machine linked through Connect, here and (if it answers) there. */
|
|
145
|
+
export function useUnlinkPeer() {
|
|
146
|
+
const queryClient = useQueryClient();
|
|
147
|
+
return useMutation({
|
|
148
|
+
mutationFn: async (hostId: string) =>
|
|
149
|
+
json<{ name: string; told: boolean; removed: boolean }>(
|
|
150
|
+
await fetch(`/api/peers/${hostId}`, { method: "DELETE" })
|
|
151
|
+
),
|
|
152
|
+
onSettled: () => {
|
|
153
|
+
void queryClient.invalidateQueries({ queryKey: hostKeys.all });
|
|
154
|
+
void queryClient.invalidateQueries({ queryKey: peerKeys.account() });
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** Every call to and from a linked machine, newest first. */
|
|
160
|
+
export function usePeerAudit(hostId: string | null) {
|
|
161
|
+
return useQuery({
|
|
162
|
+
queryKey: peerKeys.audit(hostId ?? ""),
|
|
163
|
+
queryFn: async () =>
|
|
164
|
+
(
|
|
165
|
+
await json<{ rows: AuditRow[] }>(
|
|
166
|
+
await fetch(`/api/peers/${hostId}/audit`)
|
|
167
|
+
)
|
|
168
|
+
).rows,
|
|
169
|
+
enabled: !!hostId,
|
|
170
|
+
refetchInterval: 15_000,
|
|
171
|
+
});
|
|
172
|
+
}
|
package/data/push/topics.ts
CHANGED
|
@@ -18,6 +18,7 @@ import { archivedKeys } from "../done";
|
|
|
18
18
|
import { awayKeys } from "../away";
|
|
19
19
|
import { bookmarkKeys } from "../bookmarks/keys";
|
|
20
20
|
import { learningCardsKeys } from "../learning-cards";
|
|
21
|
+
import { claimKeys } from "../claims/keys";
|
|
21
22
|
|
|
22
23
|
// What each pushed topic (a table that changed, lib/db/changes.ts, or a
|
|
23
24
|
// pushed view) means the browser should refetch. Only queries on screen
|
|
@@ -41,6 +42,7 @@ function keysFor(topic: string): QueryKey[] {
|
|
|
41
42
|
case "projects":
|
|
42
43
|
return [projectKeys.all, sessionKeys.all];
|
|
43
44
|
case "groups":
|
|
45
|
+
case "session_groups":
|
|
44
46
|
return [sessionKeys.all];
|
|
45
47
|
case "workspaces":
|
|
46
48
|
return [workspaceKeys.all, orchestratorKeys.all];
|
|
@@ -76,6 +78,8 @@ function keysFor(topic: string): QueryKey[] {
|
|
|
76
78
|
return [devServerKeys.all];
|
|
77
79
|
case "learning_cards":
|
|
78
80
|
return [learningCardsKeys.all, orchestratorKeys.all];
|
|
81
|
+
case "work_claims":
|
|
82
|
+
return [claimKeys.all, sessionKeys.all];
|
|
79
83
|
case "chat_bookmarks":
|
|
80
84
|
return [bookmarkKeys.all];
|
|
81
85
|
default:
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
2
|
+
import { toast } from "sonner";
|
|
3
|
+
import type { SessionGroup } from "@/lib/sidebar/shelves";
|
|
4
|
+
import { sessionKeys } from "@/data/sessions/keys";
|
|
5
|
+
import type { SessionsResponse } from "@/data/sessions/queries";
|
|
6
|
+
|
|
7
|
+
async function send<T>(
|
|
8
|
+
url: string,
|
|
9
|
+
method: string,
|
|
10
|
+
body?: unknown
|
|
11
|
+
): Promise<T> {
|
|
12
|
+
const res = await fetch(url, {
|
|
13
|
+
method,
|
|
14
|
+
headers: { "Content-Type": "application/json" },
|
|
15
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
16
|
+
});
|
|
17
|
+
const data = await res.json().catch(() => ({}));
|
|
18
|
+
if (!res.ok) throw new Error(data.error ?? "Could not change the group");
|
|
19
|
+
return data as T;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Groups ride along in the sessions response, so every edit patches that
|
|
23
|
+
// one cache entry and lets the change push settle the rest.
|
|
24
|
+
function useGroupCache() {
|
|
25
|
+
const queryClient = useQueryClient();
|
|
26
|
+
const key = sessionKeys.list();
|
|
27
|
+
return {
|
|
28
|
+
key,
|
|
29
|
+
refresh: () => queryClient.invalidateQueries({ queryKey: key }),
|
|
30
|
+
async patch(edit: (old: SessionsResponse) => SessionsResponse) {
|
|
31
|
+
await queryClient.cancelQueries({ queryKey: key });
|
|
32
|
+
const previous = queryClient.getQueryData<SessionsResponse>(key);
|
|
33
|
+
queryClient.setQueryData<SessionsResponse>(
|
|
34
|
+
key,
|
|
35
|
+
(old) => old && edit(old)
|
|
36
|
+
);
|
|
37
|
+
return { previous };
|
|
38
|
+
},
|
|
39
|
+
restore(previous?: SessionsResponse) {
|
|
40
|
+
if (previous) queryClient.setQueryData(key, previous);
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function useCreateGroup() {
|
|
46
|
+
const cache = useGroupCache();
|
|
47
|
+
return useMutation({
|
|
48
|
+
mutationFn: (v: { name: string; workspaceId: string | null }) =>
|
|
49
|
+
send<{ sessionGroup: SessionGroup }>("/api/session-groups", "POST", v),
|
|
50
|
+
onSuccess: ({ sessionGroup }) =>
|
|
51
|
+
cache.patch((old) => ({
|
|
52
|
+
...old,
|
|
53
|
+
sessionGroups: [...old.sessionGroups, sessionGroup],
|
|
54
|
+
})),
|
|
55
|
+
onError: (e: Error) => toast.error(e.message),
|
|
56
|
+
onSettled: cache.refresh,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function useUpdateGroup() {
|
|
61
|
+
const cache = useGroupCache();
|
|
62
|
+
return useMutation({
|
|
63
|
+
mutationFn: ({
|
|
64
|
+
id,
|
|
65
|
+
...patch
|
|
66
|
+
}: {
|
|
67
|
+
id: string;
|
|
68
|
+
name?: string;
|
|
69
|
+
collapsed?: boolean;
|
|
70
|
+
}) => send(`/api/session-groups/${id}`, "PATCH", patch),
|
|
71
|
+
onMutate: ({ id, ...patch }) =>
|
|
72
|
+
cache.patch((old) => ({
|
|
73
|
+
...old,
|
|
74
|
+
sessionGroups: old.sessionGroups.map((g) =>
|
|
75
|
+
g.id === id ? { ...g, ...patch } : g
|
|
76
|
+
),
|
|
77
|
+
})),
|
|
78
|
+
onError: (e: Error, _v, ctx) => {
|
|
79
|
+
cache.restore(ctx?.previous);
|
|
80
|
+
toast.error(e.message);
|
|
81
|
+
},
|
|
82
|
+
onSettled: cache.refresh,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function useReorderGroups() {
|
|
87
|
+
const cache = useGroupCache();
|
|
88
|
+
return useMutation({
|
|
89
|
+
mutationFn: (v: { workspaceId: string | null; ids: string[] }) =>
|
|
90
|
+
send("/api/session-groups/order", "POST", v),
|
|
91
|
+
onMutate: ({ ids }) =>
|
|
92
|
+
cache.patch((old) => ({
|
|
93
|
+
...old,
|
|
94
|
+
sessionGroups: old.sessionGroups.map((g) =>
|
|
95
|
+
ids.includes(g.id) ? { ...g, sort_order: ids.indexOf(g.id) } : g
|
|
96
|
+
),
|
|
97
|
+
})),
|
|
98
|
+
onError: (e: Error, _v, ctx) => {
|
|
99
|
+
cache.restore(ctx?.previous);
|
|
100
|
+
toast.error(e.message);
|
|
101
|
+
},
|
|
102
|
+
onSettled: cache.refresh,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Deleting is soft: the toast's Undo restores the group and its members.
|
|
107
|
+
export function useDeleteGroup() {
|
|
108
|
+
const cache = useGroupCache();
|
|
109
|
+
return useMutation({
|
|
110
|
+
mutationFn: (g: { id: string; name: string }) =>
|
|
111
|
+
send(`/api/session-groups/${g.id}`, "DELETE"),
|
|
112
|
+
onMutate: (g) =>
|
|
113
|
+
cache.patch((old) => ({
|
|
114
|
+
...old,
|
|
115
|
+
sessionGroups: old.sessionGroups.filter((x) => x.id !== g.id),
|
|
116
|
+
})),
|
|
117
|
+
onSuccess: (_d, g) =>
|
|
118
|
+
toast(`Deleted group ${g.name}`, {
|
|
119
|
+
description: "Its sessions went back to the shelves.",
|
|
120
|
+
duration: 10_000,
|
|
121
|
+
action: {
|
|
122
|
+
label: "Undo",
|
|
123
|
+
onClick: () =>
|
|
124
|
+
send(`/api/session-groups/${g.id}/restore`, "POST")
|
|
125
|
+
.catch((e: Error) => toast.error(e.message))
|
|
126
|
+
.finally(cache.refresh),
|
|
127
|
+
},
|
|
128
|
+
}),
|
|
129
|
+
onError: (e: Error, _v, ctx) => {
|
|
130
|
+
cache.restore(ctx?.previous);
|
|
131
|
+
toast.error(e.message);
|
|
132
|
+
},
|
|
133
|
+
onSettled: cache.refresh,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// One or many sessions into a group, or out of it with groupId null.
|
|
138
|
+
export function useAssignGroup() {
|
|
139
|
+
const cache = useGroupCache();
|
|
140
|
+
return useMutation({
|
|
141
|
+
mutationFn: (v: { ids: string[]; groupId: string | null }) =>
|
|
142
|
+
send("/api/sessions/group", "POST", v),
|
|
143
|
+
onMutate: ({ ids, groupId }) =>
|
|
144
|
+
cache.patch((old) => ({
|
|
145
|
+
...old,
|
|
146
|
+
sessions: old.sessions.map((s) =>
|
|
147
|
+
ids.includes(s.id) ? { ...s, session_group_id: groupId } : s
|
|
148
|
+
),
|
|
149
|
+
})),
|
|
150
|
+
onError: (e: Error, _v, ctx) => {
|
|
151
|
+
cache.restore(ctx?.previous);
|
|
152
|
+
toast.error(e.message);
|
|
153
|
+
},
|
|
154
|
+
onSettled: cache.refresh,
|
|
155
|
+
});
|
|
156
|
+
}
|
package/data/sessions/index.ts
CHANGED
package/data/sessions/pin.ts
CHANGED
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
2
|
-
import type { Session
|
|
2
|
+
import type { Session } from "@/lib/db";
|
|
3
3
|
import { sessionKeys } from "./keys";
|
|
4
|
-
|
|
5
|
-
interface SessionsResponse {
|
|
6
|
-
sessions: Session[];
|
|
7
|
-
groups: Group[];
|
|
8
|
-
}
|
|
4
|
+
import type { SessionsResponse } from "./queries";
|
|
9
5
|
|
|
10
6
|
export function usePinSession() {
|
|
11
7
|
const queryClient = useQueryClient();
|
package/data/sessions/queries.ts
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
|
+
import { toast } from "sonner";
|
|
1
2
|
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
|
2
|
-
import type { Session
|
|
3
|
+
import type { Session } from "@/lib/db";
|
|
4
|
+
import type { SessionGroup } from "@/lib/sidebar/shelves";
|
|
3
5
|
import type { AgentType } from "@/lib/providers";
|
|
4
6
|
import type { ChatAccess, ChatFile, ChatImage } from "@/lib/chat/events";
|
|
5
7
|
import { sessionKeys } from "./keys";
|
|
6
8
|
import { usePollWhenOffline, usePushConnected } from "../push/connection";
|
|
7
9
|
|
|
8
|
-
interface SessionsResponse {
|
|
10
|
+
export interface SessionsResponse {
|
|
9
11
|
sessions: Session[];
|
|
10
|
-
|
|
12
|
+
sessionGroups: SessionGroup[];
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
async function fetchSessions(): Promise<SessionsResponse> {
|
|
@@ -128,31 +130,6 @@ export function useSummarizeSession() {
|
|
|
128
130
|
});
|
|
129
131
|
}
|
|
130
132
|
|
|
131
|
-
export function useMoveSessionToGroup() {
|
|
132
|
-
const queryClient = useQueryClient();
|
|
133
|
-
|
|
134
|
-
return useMutation({
|
|
135
|
-
mutationFn: async ({
|
|
136
|
-
sessionId,
|
|
137
|
-
groupPath,
|
|
138
|
-
}: {
|
|
139
|
-
sessionId: string;
|
|
140
|
-
groupPath: string;
|
|
141
|
-
}) => {
|
|
142
|
-
const res = await fetch(`/api/sessions/${sessionId}`, {
|
|
143
|
-
method: "PATCH",
|
|
144
|
-
headers: { "Content-Type": "application/json" },
|
|
145
|
-
body: JSON.stringify({ groupPath }),
|
|
146
|
-
});
|
|
147
|
-
if (!res.ok) throw new Error("Failed to move session");
|
|
148
|
-
return res.json();
|
|
149
|
-
},
|
|
150
|
-
onSuccess: () => {
|
|
151
|
-
queryClient.invalidateQueries({ queryKey: sessionKeys.list() });
|
|
152
|
-
},
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
|
|
156
133
|
export function useMoveSessionToProject() {
|
|
157
134
|
const queryClient = useQueryClient();
|
|
158
135
|
|
|
@@ -172,7 +149,11 @@ export function useMoveSessionToProject() {
|
|
|
172
149
|
if (!res.ok) throw new Error("Failed to move session");
|
|
173
150
|
return res.json();
|
|
174
151
|
},
|
|
175
|
-
onSuccess: () => {
|
|
152
|
+
onSuccess: (data: { clearedGroup?: string | null }) => {
|
|
153
|
+
if (data.clearedGroup)
|
|
154
|
+
toast(`Removed from ${data.clearedGroup}`, {
|
|
155
|
+
description: "Groups stay inside one workspace.",
|
|
156
|
+
});
|
|
176
157
|
queryClient.invalidateQueries({ queryKey: sessionKeys.list() });
|
|
177
158
|
},
|
|
178
159
|
});
|
package/data/sessions/shelf.ts
CHANGED
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
2
|
-
import type { Session
|
|
2
|
+
import type { Session } from "@/lib/db";
|
|
3
3
|
import { toSqliteTime } from "@/lib/session-meta";
|
|
4
4
|
import { sessionKeys } from "./keys";
|
|
5
|
-
|
|
6
|
-
interface SessionsResponse {
|
|
7
|
-
sessions: Session[];
|
|
8
|
-
groups: Group[];
|
|
9
|
-
}
|
|
5
|
+
import type { SessionsResponse } from "./queries";
|
|
10
6
|
|
|
11
7
|
async function post(url: string, body: object) {
|
|
12
8
|
const res = await fetch(url, {
|
package/data/tasks/index.ts
CHANGED
|
@@ -47,6 +47,8 @@ export function useCreateTask() {
|
|
|
47
47
|
priority?: Priority;
|
|
48
48
|
baseBranch?: string;
|
|
49
49
|
hostId?: string;
|
|
50
|
+
// Queue behind other work: a task, or "claim:<id>".
|
|
51
|
+
after?: string;
|
|
50
52
|
view?: "chat" | "terminal";
|
|
51
53
|
}) =>
|
|
52
54
|
// Over the workspace's running task limit it's queued instead.
|
package/hooks/useSessions.ts
CHANGED
|
@@ -6,20 +6,17 @@ import {
|
|
|
6
6
|
useRenameSession,
|
|
7
7
|
useForkSession,
|
|
8
8
|
useSummarizeSession,
|
|
9
|
-
useMoveSessionToGroup,
|
|
10
9
|
useMoveSessionToProject,
|
|
11
10
|
} from "@/data/sessions";
|
|
12
11
|
|
|
13
12
|
export function useSessions() {
|
|
14
13
|
const { data, refetch } = useSessionsQuery();
|
|
15
14
|
const sessions = data?.sessions ?? [];
|
|
16
|
-
const groups = data?.groups ?? [];
|
|
17
15
|
|
|
18
16
|
const deleteMutation = useDeleteSession();
|
|
19
17
|
const renameMutation = useRenameSession();
|
|
20
18
|
const forkMutation = useForkSession();
|
|
21
19
|
const summarizeMutation = useSummarizeSession();
|
|
22
|
-
const moveToGroupMutation = useMoveSessionToGroup();
|
|
23
20
|
const moveToProjectMutation = useMoveSessionToProject();
|
|
24
21
|
|
|
25
22
|
const fetchSessions = useCallback(async () => {
|
|
@@ -61,13 +58,6 @@ export function useSessions() {
|
|
|
61
58
|
[summarizeMutation]
|
|
62
59
|
);
|
|
63
60
|
|
|
64
|
-
const moveSessionToGroup = useCallback(
|
|
65
|
-
async (sessionId: string, groupPath: string) => {
|
|
66
|
-
await moveToGroupMutation.mutateAsync({ sessionId, groupPath });
|
|
67
|
-
},
|
|
68
|
-
[moveToGroupMutation]
|
|
69
|
-
);
|
|
70
|
-
|
|
71
61
|
const moveSessionToProject = useCallback(
|
|
72
62
|
async (sessionId: string, projectId: string) => {
|
|
73
63
|
await moveToProjectMutation.mutateAsync({ sessionId, projectId });
|
|
@@ -77,7 +67,6 @@ export function useSessions() {
|
|
|
77
67
|
|
|
78
68
|
return {
|
|
79
69
|
sessions,
|
|
80
|
-
groups,
|
|
81
70
|
loaded: data !== undefined,
|
|
82
71
|
summarizingSessionId: summarizeMutation.isPending
|
|
83
72
|
? (summarizeMutation.variables as string)
|
|
@@ -88,7 +77,6 @@ export function useSessions() {
|
|
|
88
77
|
renameSession,
|
|
89
78
|
forkSession,
|
|
90
79
|
summarizeSession,
|
|
91
|
-
moveSessionToGroup,
|
|
92
80
|
moveSessionToProject,
|
|
93
81
|
};
|
|
94
82
|
}
|