@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
@@ -1,9 +1,11 @@
1
1
  "use client";
2
2
 
3
3
  import { memo } from "react";
4
+ import { useSnapshot } from "valtio";
4
5
  import {
5
6
  Check,
6
7
  ChevronRight,
8
+ Download,
7
9
  Circle,
8
10
  CircleDot,
9
11
  Quote,
@@ -24,6 +26,8 @@ import { Markdown } from "./Markdown";
24
26
  import { openSessionLink } from "./session-link";
25
27
  import { UndoButton } from "./Undo";
26
28
  import { useRowState } from "./rowState";
29
+ import { useBookmarkChat } from "@/components/Bookmarks/context";
30
+ import { exportUi, exportUiActions, pickedRange } from "@/stores/exportUi";
27
31
  import {
28
32
  BookmarkButton,
29
33
  TurnBookmark,
@@ -153,6 +157,71 @@ function SendStatus({ pending }: { pending: PendingStatus }) {
153
157
  );
154
158
  }
155
159
 
160
+ // The turn this message begins, while turns are being picked for an export.
161
+ function useExportTurn(itemId: string): number | null {
162
+ const ui = useSnapshot(exportUi);
163
+ if (!ui.selecting) return null;
164
+ return ui.turns.find((t) => t.id === itemId)?.n ?? null;
165
+ }
166
+
167
+ // A tick in the gutter and a tinted row: tap anywhere on it to pick the turn.
168
+ function TurnTick({
169
+ turn,
170
+ children,
171
+ }: {
172
+ turn: number;
173
+ children: React.ReactNode;
174
+ }) {
175
+ const { pick } = useSnapshot(exportUi);
176
+ const range = pickedRange(pick);
177
+ const on = !!range && turn >= range.from && turn <= range.to;
178
+ return (
179
+ <div
180
+ className={cn(
181
+ "-mx-2 flex items-start gap-2 rounded-xl px-2",
182
+ on && "bg-primary/10"
183
+ )}
184
+ >
185
+ <button
186
+ type="button"
187
+ role="checkbox"
188
+ aria-checked={on}
189
+ aria-label={`Turn ${turn}`}
190
+ onClick={() => exportUiActions.tap(turn)}
191
+ className={cn(
192
+ "mt-1 flex h-11 min-w-11 shrink-0 flex-col items-center justify-center rounded-md border text-[11px]",
193
+ on
194
+ ? "border-primary bg-primary text-primary-foreground"
195
+ : "text-muted-foreground"
196
+ )}
197
+ >
198
+ {on ? <Check className="h-4 w-4" /> : turn}
199
+ </button>
200
+ <div className="min-w-0 flex-1">{children}</div>
201
+ </div>
202
+ );
203
+ }
204
+
205
+ // Export this message's turn and everything after it.
206
+ function ExportFromHere({ itemId }: { itemId: string }) {
207
+ const chat = useBookmarkChat();
208
+ if (!chat) return null;
209
+ return (
210
+ <button
211
+ type="button"
212
+ aria-label="Export from here"
213
+ title="Export from here"
214
+ onClick={(e) => {
215
+ e.stopPropagation();
216
+ exportUiActions.open(chat.sessionId, "", itemId);
217
+ }}
218
+ className="text-muted-foreground hover:text-foreground hover:bg-foreground/[0.06] flex h-11 w-11 shrink-0 items-center justify-center rounded-md opacity-40 group-hover:opacity-100 focus-visible:opacity-100 md:h-7 md:w-7 md:opacity-0"
219
+ >
220
+ <Download className="h-3.5 w-3.5" />
221
+ </button>
222
+ );
223
+ }
224
+
156
225
  export function UserMessage({
157
226
  item,
158
227
  onUndo,
@@ -162,6 +231,7 @@ export function UserMessage({
162
231
  onUndo?: () => void;
163
232
  pending?: PendingStatus;
164
233
  }) {
234
+ const turn = useExportTurn(item.id);
165
235
  const message = (
166
236
  <div className="group flex flex-col items-end gap-1">
167
237
  {item.from && (
@@ -204,6 +274,7 @@ export function UserMessage({
204
274
  ) : (
205
275
  <div className="-mr-1.5 flex items-center">
206
276
  {onUndo && <UndoButton onClick={onUndo} />}
277
+ <ExportFromHere itemId={item.id} />
207
278
  <BookmarkButton itemId={item.id} />
208
279
  </div>
209
280
  )}
@@ -211,7 +282,8 @@ export function UserMessage({
211
282
  );
212
283
  // Not in the chat yet: nothing to star.
213
284
  if (pending) return message;
214
- return <BookmarkPress itemId={item.id}>{message}</BookmarkPress>;
285
+ const row = <BookmarkPress itemId={item.id}>{message}</BookmarkPress>;
286
+ return turn ? <TurnTick turn={turn}>{row}</TurnTick> : row;
215
287
  }
216
288
 
217
289
  export function AssistantMessage({
@@ -221,6 +293,8 @@ export function AssistantMessage({
221
293
  item: Of<"assistant">;
222
294
  onQuote?: (text: string) => void;
223
295
  }) {
296
+ // A reply whose only content was a tool call AgentOS ran for it.
297
+ if (!item.streaming && !item.text.trim()) return null;
224
298
  return (
225
299
  <BookmarkPress itemId={item.id}>
226
300
  <div className="group">
@@ -300,18 +374,38 @@ export function Todos({ item }: { item: Of<"todos"> }) {
300
374
 
301
375
  export function TurnEnd({ item }: { item: Of<"turn_end"> }) {
302
376
  const secs = item.durationMs ? Math.round(item.durationMs / 1000) : null;
377
+ const early = !item.interrupted && item.ended;
303
378
  const label = item.interrupted
304
379
  ? "Stopped"
305
- : secs !== null
306
- ? `Done in ${secs}s`
307
- : "Done";
380
+ : early
381
+ ? secs !== null
382
+ ? `Ended early in ${secs}s`
383
+ : "Ended early"
384
+ : secs !== null
385
+ ? `Done in ${secs}s`
386
+ : "Done";
308
387
  return (
309
- <div className="group text-muted-foreground/60 flex items-center gap-3 font-mono text-[11px]">
310
- <span className="bg-foreground/[0.06] h-px flex-1" />
311
- {label}
312
- <TurnBookmark turnEndId={item.id} />
313
- <span className="bg-foreground/[0.06] h-px flex-1" />
314
- </div>
388
+ <>
389
+ {early && (
390
+ <div
391
+ role="status"
392
+ className="rounded-xl bg-amber-500/10 px-3.5 py-2.5 text-sm text-amber-700 dark:text-amber-300"
393
+ >
394
+ {early.notice}
395
+ {item.finishReason && (
396
+ <span className="mt-1 block font-mono text-[11px] opacity-80">
397
+ finish reason: {item.finishReason}
398
+ </span>
399
+ )}
400
+ </div>
401
+ )}
402
+ <div className="group text-muted-foreground/60 flex items-center gap-3 font-mono text-[11px]">
403
+ <span className="bg-foreground/[0.06] h-px flex-1" />
404
+ {label}
405
+ <TurnBookmark turnEndId={item.id} />
406
+ <span className="bg-foreground/[0.06] h-px flex-1" />
407
+ </div>
408
+ </>
315
409
  );
316
410
  }
317
411
 
@@ -0,0 +1,38 @@
1
+ "use client";
2
+
3
+ import { cn } from "@/lib/utils";
4
+
5
+ // The paths a claim touches, the ones another claim also touches marked.
6
+ export function ClaimFiles({
7
+ files,
8
+ more,
9
+ shared,
10
+ }: {
11
+ files: string[];
12
+ more: number;
13
+ shared: ReadonlySet<string>;
14
+ }) {
15
+ return (
16
+ <ul className="bg-muted/40 mt-1 max-h-48 overflow-y-auto rounded-lg p-2 font-mono text-[11px] leading-5">
17
+ {files.map((f) => (
18
+ <li
19
+ key={f}
20
+ className={cn(
21
+ "break-all",
22
+ shared.has(f)
23
+ ? "text-amber-700 dark:text-amber-400"
24
+ : "text-muted-foreground"
25
+ )}
26
+ >
27
+ {f}
28
+ </li>
29
+ ))}
30
+ {more > 0 && (
31
+ <li className="text-muted-foreground pt-1">and {more} more</li>
32
+ )}
33
+ {files.length === 0 && more === 0 && (
34
+ <li className="text-muted-foreground">No changed files yet</li>
35
+ )}
36
+ </ul>
37
+ );
38
+ }
@@ -0,0 +1,82 @@
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+ import { ChevronRight, ExternalLink, GitBranch } from "lucide-react";
5
+ import { cn } from "@/lib/utils";
6
+ import type { Claim } from "@/lib/claims/types";
7
+ import { ClaimFiles } from "./ClaimFiles";
8
+ import { startedText } from "./format";
9
+
10
+ // One piece of claimed work: what, where, since when; its files fold open.
11
+ export function ClaimRow({
12
+ claim,
13
+ here,
14
+ now,
15
+ shared,
16
+ }: {
17
+ claim: Claim;
18
+ here: boolean;
19
+ now: number;
20
+ shared: ReadonlySet<string>;
21
+ }) {
22
+ const [open, setOpen] = useState(false);
23
+ const count = claim.files.length + claim.filesMore;
24
+ return (
25
+ <li className="bg-muted/40 rounded-xl" data-claim={claim.id}>
26
+ <button
27
+ type="button"
28
+ onClick={() => setOpen((v) => !v)}
29
+ aria-expanded={open}
30
+ className="flex min-h-11 w-full items-start gap-2 rounded-xl p-3 text-left"
31
+ >
32
+ <ChevronRight
33
+ aria-hidden
34
+ className={cn(
35
+ "text-muted-foreground mt-0.5 h-4 w-4 shrink-0 transition-transform",
36
+ open && "rotate-90"
37
+ )}
38
+ />
39
+ <span className="min-w-0 flex-1">
40
+ <span className="block text-sm font-medium break-words">
41
+ {claim.goal}
42
+ </span>
43
+ <span className="text-muted-foreground mt-0.5 flex flex-wrap items-center gap-x-2 gap-y-0.5 text-xs">
44
+ <span>
45
+ {here ? `${claim.originName} (here)` : claim.originName}
46
+ </span>
47
+ {claim.branch && (
48
+ <span className="flex min-w-0 items-center gap-1">
49
+ <GitBranch aria-hidden className="h-3 w-3 shrink-0" />
50
+ <span className="truncate">{claim.branch}</span>
51
+ </span>
52
+ )}
53
+ <span>{startedText(claim, now)}</span>
54
+ <span>
55
+ {count} file{count === 1 ? "" : "s"}
56
+ </span>
57
+ </span>
58
+ </span>
59
+ </button>
60
+ {claim.prUrl && (
61
+ <a
62
+ href={claim.prUrl}
63
+ target="_blank"
64
+ rel="noreferrer"
65
+ className="text-primary flex min-h-11 items-center gap-1 px-3 pb-2 pl-9 text-xs hover:underline"
66
+ >
67
+ <ExternalLink aria-hidden className="h-3 w-3" />
68
+ Pull request
69
+ </a>
70
+ )}
71
+ {open && (
72
+ <div className="px-3 pb-3 pl-9">
73
+ <ClaimFiles
74
+ files={claim.files}
75
+ more={claim.filesMore}
76
+ shared={shared}
77
+ />
78
+ </div>
79
+ )}
80
+ </li>
81
+ );
82
+ }
@@ -0,0 +1,176 @@
1
+ "use client";
2
+
3
+ import { useMemo } from "react";
4
+ import { useSnapshot } from "valtio";
5
+ import { GitCompareArrows } from "lucide-react";
6
+ import { StatusPill } from "@/components/ui/status-pill";
7
+ import {
8
+ Dialog,
9
+ DialogContent,
10
+ DialogDescription,
11
+ DialogHeader,
12
+ DialogTitle,
13
+ } from "@/components/ui/dialog";
14
+ import { useNow } from "@/components/Chat/useNow";
15
+ import { useClaims } from "@/data/claims";
16
+ import { claimsUi, claimsUiActions } from "@/stores/claimsUi";
17
+ import { cn } from "@/lib/utils";
18
+ import { ClaimRow } from "./ClaimRow";
19
+ import { QUIET_MS, focusView, groupByRepo, lastHeardText } from "./format";
20
+
21
+ const Skeleton = () => (
22
+ <div className="space-y-2" aria-label="Loading claims">
23
+ {[0, 1, 2].map((i) => (
24
+ <div key={i} className="bg-muted/40 h-16 animate-pulse rounded-xl" />
25
+ ))}
26
+ </div>
27
+ );
28
+
29
+ // Work across machines: what each linked machine's sessions are on, the
30
+ // overlaps first, and when each machine was last heard from.
31
+ export function ClaimsSheet() {
32
+ const snap = useSnapshot(claimsUi);
33
+ const { data, isPending, isError, dataUpdatedAt } = useClaims({
34
+ enabled: snap.open,
35
+ });
36
+ const tick = useNow(snap.open);
37
+ const now = tick + (data ? data.now - dataUpdatedAt : 0);
38
+ const view = useMemo(
39
+ () => (data ? focusView(data, snap.focus ? [...snap.focus] : null) : null),
40
+ [data, snap.focus]
41
+ );
42
+ const byId = useMemo(
43
+ () => new Map((view?.claims ?? []).map((c) => [c.id, c])),
44
+ [view]
45
+ );
46
+ const sharedOf = (id: string) =>
47
+ new Set(
48
+ (view?.overlaps ?? [])
49
+ .filter((o) => o.a === id || o.b === id)
50
+ .flatMap((o) => o.shared)
51
+ );
52
+ const here = data?.machine.id;
53
+
54
+ return (
55
+ <Dialog open={snap.open} onOpenChange={claimsUiActions.setOpen}>
56
+ <DialogContent className="max-h-[90vh] max-w-xl overflow-y-auto [&>*]:min-w-0">
57
+ <DialogHeader>
58
+ <DialogTitle>Work across machines</DialogTitle>
59
+ <DialogDescription className="sr-only">
60
+ What sessions on this and linked machines are working on, and where
61
+ the work overlaps.
62
+ </DialogDescription>
63
+ </DialogHeader>
64
+ {isPending && <Skeleton />}
65
+ {isError && (
66
+ <p className="text-destructive text-sm">Couldn’t load claims.</p>
67
+ )}
68
+ {data && view && (
69
+ <div className="space-y-4">
70
+ <ul className="flex flex-wrap gap-2" aria-label="Machines">
71
+ {data.machines.map((m) => {
72
+ const quiet =
73
+ !m.self &&
74
+ (m.lastHeardAt === null || now - m.lastHeardAt > QUIET_MS);
75
+ return (
76
+ <li
77
+ key={m.id}
78
+ className="bg-muted/40 flex items-center gap-2 rounded-lg px-3 py-2 text-xs"
79
+ >
80
+ <span className="font-medium">{m.name}</span>
81
+ <span
82
+ className={cn(
83
+ "text-muted-foreground",
84
+ quiet && "text-amber-700 dark:text-amber-400"
85
+ )}
86
+ >
87
+ {lastHeardText(m.lastHeardAt, now)}
88
+ </span>
89
+ </li>
90
+ );
91
+ })}
92
+ </ul>
93
+ {snap.focus && (
94
+ <button
95
+ type="button"
96
+ onClick={claimsUiActions.showAll}
97
+ className="text-primary min-h-11 text-sm"
98
+ >
99
+ Showing one pair. Show all work
100
+ </button>
101
+ )}
102
+ {view.overlaps.length > 0 && (
103
+ <section className="space-y-2" aria-label="Overlaps">
104
+ <h3 className="text-muted-foreground text-xs font-semibold tracking-wide uppercase">
105
+ Overlaps
106
+ </h3>
107
+ <ul className="space-y-2">
108
+ {view.overlaps.map((o) => {
109
+ const a = byId.get(o.a);
110
+ const b = byId.get(o.b);
111
+ if (!a || !b) return null;
112
+ return (
113
+ <li
114
+ key={`${o.a}|${o.b}`}
115
+ className="bg-muted/40 flex items-start gap-2 rounded-xl p-3 text-sm"
116
+ >
117
+ <StatusPill
118
+ tone={o.strength === "strong" ? "amber" : "muted"}
119
+ >
120
+ <GitCompareArrows
121
+ aria-hidden
122
+ className="h-2.5 w-2.5"
123
+ />
124
+ {o.strength}
125
+ </StatusPill>
126
+ <span className="min-w-0 flex-1 break-words">
127
+ {a.goal} on {a.originName} and {b.goal} on{" "}
128
+ {b.originName}
129
+ <span className="text-muted-foreground block text-xs">
130
+ {[
131
+ o.kinds.join(" + "),
132
+ o.shared.length
133
+ ? `${o.shared.length} shared file${o.shared.length === 1 ? "" : "s"}`
134
+ : null,
135
+ ]
136
+ .filter(Boolean)
137
+ .join(" · ")}
138
+ </span>
139
+ </span>
140
+ </li>
141
+ );
142
+ })}
143
+ </ul>
144
+ </section>
145
+ )}
146
+ {view.claims.length === 0 ? (
147
+ <p className="text-muted-foreground py-6 text-center text-sm">
148
+ Nothing is claimed right now. Work shows up here while a session
149
+ has changes in a repository.
150
+ </p>
151
+ ) : (
152
+ groupByRepo(view.claims).map((g) => (
153
+ <section key={g.repo} className="space-y-2" aria-label={g.repo}>
154
+ <h3 className="text-muted-foreground truncate text-xs font-semibold">
155
+ {g.repo}
156
+ </h3>
157
+ <ul className="space-y-2">
158
+ {g.claims.map((c) => (
159
+ <ClaimRow
160
+ key={c.id}
161
+ claim={c}
162
+ here={c.origin === here}
163
+ now={now}
164
+ shared={sharedOf(c.id)}
165
+ />
166
+ ))}
167
+ </ul>
168
+ </section>
169
+ ))
170
+ )}
171
+ </div>
172
+ )}
173
+ </DialogContent>
174
+ </Dialog>
175
+ );
176
+ }
@@ -0,0 +1,31 @@
1
+ "use client";
2
+
3
+ import { GitCompareArrows } from "lucide-react";
4
+ import { StatusPill } from "@/components/ui/status-pill";
5
+ import type { Session } from "@/lib/db";
6
+ import { claimsUiActions } from "@/stores/claimsUi";
7
+
8
+ // "Overlaps: box" on a session row: amber when the overlap is strong, muted
9
+ // when weak. Tapping opens the claims sheet on that pair.
10
+ export function OverlapPill({
11
+ overlap,
12
+ }: {
13
+ overlap: NonNullable<Session["overlap"]>;
14
+ }) {
15
+ const strong = overlap.strength === "strong";
16
+ const files = overlap.shared
17
+ ? ` · ${overlap.shared} shared file${overlap.shared === 1 ? "" : "s"}`
18
+ : "";
19
+ return (
20
+ <StatusPill
21
+ tone={strong ? "amber" : "muted"}
22
+ data-tip={`${strong ? "Strong" : "Weak"} overlap with "${overlap.goal}" on ${overlap.machine}${files}. Tap to see both.`}
23
+ aria-label={`Overlaps with ${overlap.machine}`}
24
+ data-overlap={overlap.strength}
25
+ onClick={() => claimsUiActions.open([overlap.mineId, overlap.claimId])}
26
+ >
27
+ <GitCompareArrows aria-hidden className="h-2.5 w-2.5 shrink-0" />
28
+ <span className="truncate">Overlaps: {overlap.machine}</span>
29
+ </StatusPill>
30
+ );
31
+ }
@@ -0,0 +1,70 @@
1
+ "use client";
2
+
3
+ import { Button } from "@/components/ui/button";
4
+ import { Notice } from "@/components/ui/notice";
5
+ import type { StartWarning } from "@/lib/claims/startWarning";
6
+
7
+ // Above the composer when a person's task start overlaps claimed work. They
8
+ // choose: start anyway, queue it behind that work, or go back and edit.
9
+ export function StartClaimsNotice({
10
+ warnings,
11
+ busy,
12
+ onStart,
13
+ onDismiss,
14
+ }: {
15
+ warnings: StartWarning[];
16
+ busy: boolean;
17
+ onStart: (after?: string) => void;
18
+ onDismiss: () => void;
19
+ }) {
20
+ const top = warnings[0];
21
+ if (!top) return null;
22
+ const files = top.shared.slice(0, 3).join(", ");
23
+ return (
24
+ <Notice
25
+ tone="warning"
26
+ action={
27
+ <div className="flex flex-wrap gap-2">
28
+ <Button
29
+ size="sm"
30
+ variant="outline"
31
+ className="min-h-11 md:min-h-8"
32
+ disabled={busy}
33
+ onClick={() => onStart()}
34
+ >
35
+ Start anyway
36
+ </Button>
37
+ <Button
38
+ size="sm"
39
+ className="min-h-11 md:min-h-8"
40
+ disabled={busy}
41
+ onClick={() => onStart(`claim:${top.claim.id}`)}
42
+ >
43
+ Start after it
44
+ </Button>
45
+ <Button
46
+ size="sm"
47
+ variant="ghost"
48
+ className="min-h-11 md:min-h-8"
49
+ disabled={busy}
50
+ onClick={onDismiss}
51
+ >
52
+ Edit
53
+ </Button>
54
+ </div>
55
+ }
56
+ >
57
+ <p className="font-medium">
58
+ {top.strength === "strong"
59
+ ? "Someone is already on this"
60
+ : "This may overlap"}
61
+ {warnings.length > 1 ? ` (${warnings.length} claims)` : ""}
62
+ </p>
63
+ <p className="text-muted-foreground text-xs break-words">
64
+ “{top.claim.goal}” on {top.claim.machine}
65
+ {top.claim.branch ? ` (${top.claim.branch})` : ""}
66
+ {files ? ` touches ${files}` : ""}
67
+ </p>
68
+ </Notice>
69
+ );
70
+ }