@saadnvd1/agent-os 0.3.700 → 0.3.723

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.
Files changed (306) hide show
  1. package/README.md +2 -0
  2. package/app/api/agent-roles/route.test.ts +67 -0
  3. package/app/api/agent-roles/route.ts +61 -0
  4. package/app/api/analyst/[id]/recommendations/[recId]/again/route.ts +15 -0
  5. package/app/api/analyst/[id]/recommendations/[recId]/dismiss/route.ts +13 -0
  6. package/app/api/analyst/[id]/recommendations/[recId]/send/route.ts +13 -0
  7. package/app/api/analyst/[id]/review/route.ts +22 -0
  8. package/app/api/analyst/[id]/route.ts +44 -0
  9. package/app/api/analyst/route.ts +10 -0
  10. package/app/api/bus/groups/route.ts +44 -0
  11. package/app/api/bus/spawn/route.ts +34 -2
  12. package/app/api/claims/[id]/route.ts +20 -0
  13. package/app/api/claims/check/route.ts +23 -0
  14. package/app/api/claims/check.test.ts +113 -0
  15. package/app/api/claims/exchange/route.ts +42 -0
  16. package/app/api/claims/outside.test.ts +90 -0
  17. package/app/api/claims/route.test.ts +193 -0
  18. package/app/api/claims/route.ts +73 -0
  19. package/app/api/hosts/[id]/route.ts +9 -1
  20. package/app/api/models/availability/route.ts +9 -0
  21. package/app/api/models/choices/route.ts +9 -0
  22. package/app/api/peer/unlink/route.ts +17 -0
  23. package/app/api/peers/[hostId]/audit/route.ts +14 -0
  24. package/app/api/peers/[hostId]/route.ts +20 -0
  25. package/app/api/peers/gate.test.ts +64 -1
  26. package/app/api/peers/pending/route.test.ts +37 -0
  27. package/app/api/peers/pending/route.ts +11 -4
  28. package/app/api/session-groups/[id]/restore/route.ts +11 -0
  29. package/app/api/session-groups/[id]/route.ts +25 -0
  30. package/app/api/session-groups/order/route.ts +15 -0
  31. package/app/api/session-groups/route.ts +21 -0
  32. package/app/api/sessions/[id]/analyst/route.ts +11 -0
  33. package/app/api/sessions/[id]/crew/route.ts +38 -0
  34. package/app/api/sessions/[id]/export/estimate/route.ts +23 -0
  35. package/app/api/sessions/[id]/export/route.test.ts +125 -0
  36. package/app/api/sessions/[id]/export/route.ts +34 -0
  37. package/app/api/sessions/[id]/fork/route.ts +3 -0
  38. package/app/api/sessions/[id]/group/route.ts +18 -0
  39. package/app/api/sessions/[id]/model/route.ts +49 -0
  40. package/app/api/sessions/[id]/route.ts +14 -4
  41. package/app/api/sessions/[id]/summarize/route.ts +2 -0
  42. package/app/api/sessions/claims.test.ts +122 -0
  43. package/app/api/sessions/group/route.ts +17 -0
  44. package/app/api/sessions/route.ts +31 -14
  45. package/app/page.tsx +10 -0
  46. package/components/Chat/Messages.tsx +104 -10
  47. package/components/Claims/ClaimFiles.tsx +38 -0
  48. package/components/Claims/ClaimRow.tsx +82 -0
  49. package/components/Claims/ClaimsSheet.tsx +176 -0
  50. package/components/Claims/OverlapPill.tsx +31 -0
  51. package/components/Claims/StartClaimsNotice.tsx +70 -0
  52. package/components/Claims/claims-ui.test.ts +161 -0
  53. package/components/Claims/format.ts +57 -0
  54. package/components/Claims/index.ts +3 -0
  55. package/components/Draft/DraftChips.tsx +6 -1
  56. package/components/Draft/DraftPanel.tsx +90 -3
  57. package/components/Draft/ModelUnavailableDialog.tsx +78 -0
  58. package/components/Draft/RoleChips.tsx +110 -0
  59. package/components/Export/ExportActions.tsx +136 -0
  60. package/components/Export/ExportSheet.tsx +217 -0
  61. package/components/Export/RangePicker.tsx +144 -0
  62. package/components/Export/Seg.tsx +38 -0
  63. package/components/Export/TurnSelectBar.tsx +45 -0
  64. package/components/Hosts/AccountMachines.tsx +32 -18
  65. package/components/Hosts/HostRow.tsx +155 -14
  66. package/components/Hosts/IncomingLinkSheet.tsx +28 -10
  67. package/components/Hosts/PeerActivitySheet.tsx +111 -0
  68. package/components/Models/AgentRoleCard.tsx +114 -0
  69. package/components/Models/AgentRolesSection.tsx +132 -0
  70. package/components/Models/ModelBadges.tsx +48 -0
  71. package/components/Models/ModelsSection.tsx +4 -2
  72. package/components/Models/RoleModelSelect.tsx +87 -0
  73. package/components/Pane/DesktopTabBar.tsx +2 -0
  74. package/components/Pane/MobileTabBar.tsx +12 -0
  75. package/components/Pane/PaneMenu.tsx +10 -0
  76. package/components/SessionList/GroupShelves.tsx +195 -0
  77. package/components/SessionList/MoveToGroupMenu.tsx +105 -0
  78. package/components/SessionList/RowSubtitle.tsx +7 -1
  79. package/components/SessionList/SelectionToolbar.tsx +52 -1
  80. package/components/SessionList/SessionRow.tsx +19 -7
  81. package/components/SessionList/SessionRowMenu.tsx +23 -0
  82. package/components/SessionList/SessionShelves.tsx +5 -0
  83. package/components/SessionList/SidebarBody.tsx +6 -0
  84. package/components/SessionList/SidebarContextMenu.test.ts +104 -0
  85. package/components/SessionList/SidebarContextMenu.tsx +59 -0
  86. package/components/SessionList/SidebarHeader.tsx +5 -0
  87. package/components/SessionList/WorkspaceSwitcher.tsx +23 -6
  88. package/components/SessionList/index.tsx +18 -14
  89. package/components/SessionList/useSidebarData.ts +10 -0
  90. package/components/Sessions/RolePill.tsx +196 -0
  91. package/components/Settings/SettingsDialog.tsx +9 -0
  92. package/components/Share/ShareDialog.tsx +13 -0
  93. package/components/ui/notice.tsx +46 -0
  94. package/components/ui/status-pill.tsx +39 -0
  95. package/data/analyst/index.ts +3 -0
  96. package/data/analyst/keys.ts +6 -0
  97. package/data/analyst/mutations.ts +116 -0
  98. package/data/analyst/queries.ts +35 -0
  99. package/data/claims/index.ts +2 -0
  100. package/data/claims/keys.ts +5 -0
  101. package/data/claims/queries.ts +47 -0
  102. package/data/exports/index.ts +2 -0
  103. package/data/exports/keys.ts +3 -0
  104. package/data/exports/queries.ts +51 -0
  105. package/data/models/index.ts +98 -0
  106. package/data/peers/keys.ts +1 -0
  107. package/data/peers/queries.ts +31 -0
  108. package/data/push/topics.ts +9 -0
  109. package/data/session-groups/index.ts +156 -0
  110. package/data/sessions/index.ts +0 -1
  111. package/data/sessions/pin.ts +2 -6
  112. package/data/sessions/queries.ts +36 -29
  113. package/data/sessions/shelf.ts +2 -6
  114. package/data/tasks/index.ts +2 -0
  115. package/hooks/useSessions.ts +0 -12
  116. package/lib/__fixtures__/peer-rpc.ts +65 -33
  117. package/lib/agents/brief.ts +1 -0
  118. package/lib/agents/spawn.ts +4 -0
  119. package/lib/analyst/__fixtures__/items.ts +57 -0
  120. package/lib/analyst/__fixtures__/mocks.ts +49 -0
  121. package/lib/analyst/brief.ts +17 -0
  122. package/lib/analyst/buildDigest.ts +204 -0
  123. package/lib/analyst/createLink.ts +98 -0
  124. package/lib/analyst/endLink.ts +31 -0
  125. package/lib/analyst/forward.ts +109 -0
  126. package/lib/analyst/index.ts +16 -0
  127. package/lib/analyst/linkView.ts +38 -0
  128. package/lib/analyst/onTurnEnd.ts +119 -0
  129. package/lib/analyst/parseReview.ts +41 -0
  130. package/lib/analyst/pushDigest.ts +143 -0
  131. package/lib/analyst/resume.ts +35 -0
  132. package/lib/analyst/route.ts +50 -0
  133. package/lib/analyst/signals.ts +121 -0
  134. package/lib/analyst/store.ts +303 -0
  135. package/lib/analyst/types.ts +140 -0
  136. package/lib/bus/condense.ts +5 -1
  137. package/lib/bus/index.ts +13 -0
  138. package/lib/bus/read-scope.ts +12 -0
  139. package/lib/bus/role-alias.ts +70 -0
  140. package/lib/bus/targets.ts +3 -0
  141. package/lib/chat/drivers/opencode-mapper.ts +55 -1
  142. package/lib/chat/drivers/pi-approvals.ts +7 -4
  143. package/lib/chat/drivers/pi-mapper.ts +38 -0
  144. package/lib/chat/drivers/pi-text-tools.ts +104 -0
  145. package/lib/chat/drivers/pi.ts +8 -3
  146. package/lib/chat/drivers/turn-items.ts +36 -11
  147. package/lib/chat/drivers/turn-outcome.ts +108 -0
  148. package/lib/chat/events.ts +8 -0
  149. package/lib/chat/registry.ts +13 -0
  150. package/lib/chat/store.ts +19 -0
  151. package/lib/chat/turns.ts +1 -0
  152. package/lib/chat/worker/extras.ts +4 -2
  153. package/lib/claims/collectLocal.ts +123 -0
  154. package/lib/claims/describe.ts +41 -0
  155. package/lib/claims/exchange.ts +91 -0
  156. package/lib/claims/goal.ts +45 -0
  157. package/lib/claims/index.ts +10 -0
  158. package/lib/claims/loop.ts +51 -0
  159. package/lib/claims/machine.ts +21 -0
  160. package/lib/claims/mine.ts +43 -0
  161. package/lib/claims/notifyOverlaps.ts +101 -0
  162. package/lib/claims/onMerged.ts +85 -0
  163. package/lib/claims/outside.ts +36 -0
  164. package/lib/claims/overlap.ts +87 -0
  165. package/lib/claims/overlapCache.ts +86 -0
  166. package/lib/claims/overlapText.ts +36 -0
  167. package/lib/claims/peers.ts +56 -0
  168. package/lib/claims/startCheck.ts +100 -0
  169. package/lib/claims/startWarning.ts +29 -0
  170. package/lib/claims/store.ts +366 -0
  171. package/lib/claims/types.ts +102 -0
  172. package/lib/claims/view.ts +80 -0
  173. package/lib/claims/yield.ts +24 -0
  174. package/lib/db/changes.ts +12 -0
  175. package/lib/db/migrations/20261020T1400_work_claims.ts +35 -0
  176. package/lib/db/migrations/20261020T1515_peer_unlinked.ts +22 -0
  177. package/lib/db/migrations/20261020T1700_clean_connect_names.ts +18 -0
  178. package/lib/db/migrations/20261020T1800_session_groups.ts +49 -0
  179. package/lib/db/migrations/20261020T1900_task_queue_after_claim.ts +13 -0
  180. package/lib/db/migrations/20261020T2000_claim_notices.ts +20 -0
  181. package/lib/db/migrations/20261020T2100_claims_via_host.ts +21 -0
  182. package/lib/db/migrations/20261020T2200_work_claims_watch.ts +10 -0
  183. package/lib/db/migrations/20261021T1400_session_agent_roles.ts +20 -0
  184. package/lib/db/migrations/20261021T1430_analyst_links.ts +52 -0
  185. package/lib/db/queries.ts +0 -40
  186. package/lib/db/session-parts/columns.ts +3 -0
  187. package/lib/db/types.ts +19 -8
  188. package/lib/doctor/checks.ts +64 -1
  189. package/lib/done/archive.ts +2 -0
  190. package/lib/drafts.ts +4 -0
  191. package/lib/export/build.ts +470 -0
  192. package/lib/export/compact.ts +156 -0
  193. package/lib/export/estimate.ts +83 -0
  194. package/lib/export/fixtures.ts +45 -0
  195. package/lib/export/html.tsx +195 -0
  196. package/lib/export/json.ts +39 -0
  197. package/lib/export/load.ts +64 -0
  198. package/lib/export/markdown.ts +127 -0
  199. package/lib/export/request.ts +36 -0
  200. package/lib/export/service.ts +142 -0
  201. package/lib/export/summary.ts +86 -0
  202. package/lib/export/turns.ts +63 -0
  203. package/lib/export/view.ts +173 -0
  204. package/lib/git-remote.ts +43 -0
  205. package/lib/hosts/index.ts +10 -1
  206. package/lib/models/agent-binding.ts +102 -0
  207. package/lib/models/agent-roles-view.ts +157 -0
  208. package/lib/models/agent-roles.ts +87 -0
  209. package/lib/models/availability-refs.ts +17 -0
  210. package/lib/models/availability.ts +91 -0
  211. package/lib/models/catalog.ts +156 -0
  212. package/lib/models/config.ts +27 -0
  213. package/lib/models/locality.ts +33 -0
  214. package/lib/models/providers.ts +11 -0
  215. package/lib/models/resolve.ts +50 -12
  216. package/lib/models/roles.ts +12 -0
  217. package/lib/needs-you.ts +1 -1
  218. package/lib/orchestration.ts +2 -0
  219. package/lib/orchestrator/act.ts +54 -4
  220. package/lib/orchestrator/brief.ts +2 -1
  221. package/lib/orchestrator/claim-wait.ts +71 -0
  222. package/lib/orchestrator/conditions.ts +28 -1
  223. package/lib/orchestrator/describe.ts +6 -1
  224. package/lib/orchestrator/facts.ts +12 -2
  225. package/lib/orchestrator/read.ts +5 -1
  226. package/lib/orchestrator/serve.ts +37 -2
  227. package/lib/orchestrator/tool-names.ts +1 -0
  228. package/lib/orchestrator/tool-schemas.ts +31 -1
  229. package/lib/orchestrator/tools.ts +3 -1
  230. package/lib/orchestrator/untrusted.ts +5 -0
  231. package/lib/orchestrator/watcher.ts +19 -1
  232. package/lib/peer/account.ts +74 -2
  233. package/lib/peer/allowlist.ts +1 -1
  234. package/lib/peer/client.ts +26 -1
  235. package/lib/peer/link-cli.ts +19 -4
  236. package/lib/peer/links.ts +117 -5
  237. package/lib/peer/machines.ts +14 -2
  238. package/lib/peer/name.ts +32 -0
  239. package/lib/peer/pairing.ts +34 -5
  240. package/lib/peer/pin.ts +10 -3
  241. package/lib/peer/reasons.ts +7 -0
  242. package/lib/peer/revoke.ts +75 -0
  243. package/lib/peer/rpc-server.ts +53 -5
  244. package/lib/peer/unlink.ts +62 -0
  245. package/lib/peer/unlinked-proof.ts +44 -0
  246. package/lib/projects.ts +6 -2
  247. package/lib/security/demo.ts +7 -0
  248. package/lib/session-artifacts/agentos-files.ts +94 -0
  249. package/lib/session-artifacts/collect.ts +17 -0
  250. package/lib/session-groups/assignSessions.ts +81 -0
  251. package/lib/session-groups/cli.ts +61 -0
  252. package/lib/session-groups/createGroup.ts +28 -0
  253. package/lib/session-groups/index.ts +21 -0
  254. package/lib/session-groups/removeGroup.ts +36 -0
  255. package/lib/session-groups/reorderGroups.ts +31 -0
  256. package/lib/session-groups/resolveGroup.ts +19 -0
  257. package/lib/session-groups/route.ts +32 -0
  258. package/lib/session-groups/store.ts +85 -0
  259. package/lib/session-groups/types.ts +25 -0
  260. package/lib/session-groups/updateGroup.ts +35 -0
  261. package/lib/session-groups/workspace.ts +21 -0
  262. package/lib/sessions/crew.ts +72 -0
  263. package/lib/sessions/launch.ts +54 -11
  264. package/lib/settings-url.ts +1 -0
  265. package/lib/share/markdown.ts +2 -2
  266. package/lib/share/redact.ts +16 -10
  267. package/lib/share/render.tsx +1 -1
  268. package/lib/share/snapshot.ts +2 -2
  269. package/lib/sidebar/rollup.ts +1 -0
  270. package/lib/sidebar/row-menu.ts +8 -1
  271. package/lib/sidebar/shelves.ts +90 -15
  272. package/lib/sidebar/visible-order.ts +5 -0
  273. package/lib/tasks/hold-checks.ts +3 -1
  274. package/lib/tasks/holds.ts +3 -0
  275. package/lib/tasks/index.ts +11 -0
  276. package/lib/tasks/queue-rows.ts +8 -1
  277. package/lib/tasks/queue-start.ts +1 -0
  278. package/lib/tasks/queue-types.ts +9 -1
  279. package/lib/tasks/queue.ts +15 -5
  280. package/lib/tasks/schedule-types.ts +1 -0
  281. package/lib/test-temp.ts +19 -3
  282. package/lib/workspaces.ts +11 -3
  283. package/lib/worktree-deps/caches.ts +3 -2
  284. package/lib/worktree-deps/clone.ts +3 -1
  285. package/lib/worktree-deps/paths.ts +26 -0
  286. package/lib/worktree-sweep/testTemp.ts +23 -106
  287. package/package.json +1 -1
  288. package/scripts/agent-os +1 -1
  289. package/scripts/autodeploy +2 -0
  290. package/scripts/autodeploy.test.ts +31 -1
  291. package/scripts/claim-hook.sh +31 -0
  292. package/scripts/claim-hook.test.ts +230 -0
  293. package/scripts/lib/cli-links.sh +36 -0
  294. package/scripts/lib/commands.sh +3 -0
  295. package/scripts/lib/machine.sh +27 -2
  296. package/scripts/lib/machine.test.ts +25 -3
  297. package/scripts/sync-cli-links +7 -0
  298. package/scripts/sync-cli-links.test.ts +69 -0
  299. package/server.ts +12 -1
  300. package/stores/claimsUi.ts +22 -0
  301. package/stores/exportUi.ts +87 -0
  302. package/app/api/groups/[...path]/route.ts +0 -136
  303. package/app/api/groups/route.ts +0 -93
  304. package/data/groups/index.ts +0 -1
  305. package/data/groups/mutations.ts +0 -95
  306. package/hooks/useGroups.ts +0 -37
@@ -9,6 +9,9 @@ import type { ProviderTest } from "@/lib/models/test-provider";
9
9
  import type { ResumeResult } from "@/lib/models/resume";
10
10
  import type { ModelOverride } from "@/lib/models/resolve";
11
11
  import type { Comparison } from "@/lib/usage/compare";
12
+ import type { ModelChoice } from "@/lib/models/catalog";
13
+ import type { RoleMap } from "@/lib/models/config";
14
+ import type { RoleScope, agentRolesView } from "@/lib/models/agent-roles-view";
12
15
 
13
16
  // Model routing (lib/models): role models, providers, prices.
14
17
  export const modelKeys = {
@@ -17,6 +20,29 @@ export const modelKeys = {
17
20
  compare: (days: number) => [...modelKeys.all, "compare", days] as const,
18
21
  };
19
22
 
23
+ export const agentRoleKeys = {
24
+ roles: (scope: RoleScope, id?: string | null) =>
25
+ [...modelKeys.all, "agent-roles", scope, id ?? null] as const,
26
+ choices: [...modelKeys.all, "choices"] as const,
27
+ crew: (sessionId: string) => ["sessions", sessionId, "crew"] as const,
28
+ };
29
+
30
+ export type AgentRolesResponse = ReturnType<typeof agentRolesView>;
31
+
32
+ export interface CrewResponse {
33
+ agentRole: string | null;
34
+ crewId: string | null;
35
+ members: {
36
+ id: string;
37
+ name: string;
38
+ agentRole: string | null;
39
+ model: string | null;
40
+ ref: string | null;
41
+ locality: "local" | "cloud";
42
+ availability: "up" | "down" | "unknown";
43
+ }[];
44
+ }
45
+
20
46
  export interface ModelsResponse {
21
47
  saved: ModelSettings;
22
48
  effective: EffectiveModels;
@@ -113,3 +139,75 @@ export function useCompareQuery(days: number, enabled = true) {
113
139
 
114
140
  // Every model a role can be set to: each usable provider's, as references.
115
141
  export { modelChoices } from "@/lib/models/choices";
142
+
143
+ // The agent roles (Developer, Analyst, Reviewer, Supervisor) at a scope:
144
+ // Settings, a workspace or a project.
145
+ export function useAgentRolesQuery(scope: RoleScope, id?: string | null) {
146
+ return useQuery({
147
+ queryKey: agentRoleKeys.roles(scope, id),
148
+ queryFn: async () =>
149
+ json<AgentRolesResponse>(
150
+ await fetch(
151
+ `/api/agent-roles?scope=${scope}${id ? `&id=${encodeURIComponent(id)}` : ""}`
152
+ )
153
+ ),
154
+ staleTime: 20_000,
155
+ refetchInterval: 30_000,
156
+ enabled: scope === "settings" || !!id,
157
+ });
158
+ }
159
+
160
+ export function useSaveAgentRoles(scope: RoleScope, id?: string | null) {
161
+ const queryClient = useQueryClient();
162
+ return useMutation({
163
+ mutationFn: async (input: { roles: RoleMap; fallbacks?: RoleMap }) =>
164
+ json<AgentRolesResponse>(
165
+ await send("/api/agent-roles", "PUT", { scope, id, ...input })
166
+ ),
167
+ onSuccess: (data) => {
168
+ queryClient.invalidateQueries({ queryKey: modelKeys.all });
169
+ queryClient.setQueryData(agentRoleKeys.roles(scope, id), data);
170
+ },
171
+ });
172
+ }
173
+
174
+ // Every model a picker offers, with locality and availability.
175
+ export function useModelChoices(enabled = true) {
176
+ return useQuery({
177
+ queryKey: agentRoleKeys.choices,
178
+ queryFn: async () =>
179
+ (
180
+ await json<{ choices: ModelChoice[] }>(
181
+ await fetch("/api/models/choices")
182
+ )
183
+ ).choices,
184
+ staleTime: 20_000,
185
+ refetchInterval: 30_000,
186
+ enabled,
187
+ });
188
+ }
189
+
190
+ export function useCrewQuery(sessionId: string | undefined, enabled = true) {
191
+ return useQuery({
192
+ queryKey: agentRoleKeys.crew(sessionId ?? ""),
193
+ queryFn: async () =>
194
+ json<CrewResponse>(await fetch(`/api/sessions/${sessionId}/crew`)),
195
+ staleTime: 15_000,
196
+ refetchInterval: 30_000,
197
+ enabled: !!sessionId && enabled,
198
+ });
199
+ }
200
+
201
+ export function useSwitchSessionModel(sessionId: string) {
202
+ const queryClient = useQueryClient();
203
+ return useMutation({
204
+ mutationFn: async (ref: string) =>
205
+ json<{ ok: true }>(
206
+ await send(`/api/sessions/${sessionId}/model`, "POST", { ref })
207
+ ),
208
+ onSuccess: () => {
209
+ queryClient.invalidateQueries({ queryKey: ["sessions"] });
210
+ queryClient.invalidateQueries({ queryKey: modelKeys.all });
211
+ },
212
+ });
213
+ }
@@ -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
  };
@@ -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
+ }
@@ -18,6 +18,8 @@ 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";
22
+ import { analystKeys } from "../analyst/keys";
21
23
 
22
24
  // What each pushed topic (a table that changed, lib/db/changes.ts, or a
23
25
  // pushed view) means the browser should refetch. Only queries on screen
@@ -41,6 +43,7 @@ function keysFor(topic: string): QueryKey[] {
41
43
  case "projects":
42
44
  return [projectKeys.all, sessionKeys.all];
43
45
  case "groups":
46
+ case "session_groups":
44
47
  return [sessionKeys.all];
45
48
  case "workspaces":
46
49
  return [workspaceKeys.all, orchestratorKeys.all];
@@ -76,6 +79,12 @@ function keysFor(topic: string): QueryKey[] {
76
79
  return [devServerKeys.all];
77
80
  case "learning_cards":
78
81
  return [learningCardsKeys.all, orchestratorKeys.all];
82
+ case "work_claims":
83
+ return [claimKeys.all, sessionKeys.all];
84
+ case "analyst_links":
85
+ return [analystKeys.all, sessionKeys.all];
86
+ case "analyst_recommendations":
87
+ return [analystKeys.all];
79
88
  case "chat_bookmarks":
80
89
  return [bookmarkKeys.all];
81
90
  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
+ }
@@ -6,7 +6,6 @@ export {
6
6
  useRenameSession,
7
7
  useForkSession,
8
8
  useSummarizeSession,
9
- useMoveSessionToGroup,
10
9
  useMoveSessionToProject,
11
10
  useSessionSetup,
12
11
  } from "./queries";
@@ -1,11 +1,7 @@
1
1
  import { useMutation, useQueryClient } from "@tanstack/react-query";
2
- import type { Session, Group } from "@/lib/db";
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();
@@ -1,13 +1,15 @@
1
+ import { toast } from "sonner";
1
2
  import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
2
- import type { Session, Group } from "@/lib/db";
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
- groups: Group[];
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
  });
@@ -195,6 +176,25 @@ export interface LaunchSessionInput {
195
176
  prompt: string;
196
177
  images?: ChatImage[];
197
178
  files?: ChatFile[];
179
+ // Start in an agent role (lib/models/agent-roles): its binding picks the
180
+ // model, and `model` (when not blank) overrides it for this session.
181
+ agentRole?: string;
182
+ // The crew it joins: the session that began it.
183
+ crewId?: string;
184
+ // When the role's model is down, run on the role's fallback.
185
+ onUnavailable?: "fallback";
186
+ }
187
+
188
+ // The role's model server isn't answering (HTTP 409): nothing was started.
189
+ export class ModelUnavailableError extends Error {
190
+ constructor(
191
+ message: string,
192
+ readonly role: string,
193
+ readonly ref: string,
194
+ readonly fallback: string | null
195
+ ) {
196
+ super(message);
197
+ }
198
198
  }
199
199
 
200
200
  interface LaunchSessionResponse {
@@ -218,6 +218,13 @@ export function useLaunchSession() {
218
218
  body: JSON.stringify(input),
219
219
  });
220
220
  const data = await res.json();
221
+ if (res.status === 409 && data.code === "model_unavailable")
222
+ throw new ModelUnavailableError(
223
+ data.error,
224
+ data.role,
225
+ data.ref,
226
+ data.fallback ?? null
227
+ );
221
228
  if (!res.ok || data.error)
222
229
  throw new Error(data.error ?? "Couldn't start");
223
230
  return data;
@@ -1,12 +1,8 @@
1
1
  import { useMutation, useQueryClient } from "@tanstack/react-query";
2
- import type { Session, Group } from "@/lib/db";
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, {
@@ -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.
@@ -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
  }