@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.
- package/README.md +2 -0
- package/app/api/agent-roles/route.test.ts +67 -0
- package/app/api/agent-roles/route.ts +61 -0
- package/app/api/analyst/[id]/recommendations/[recId]/again/route.ts +15 -0
- package/app/api/analyst/[id]/recommendations/[recId]/dismiss/route.ts +13 -0
- package/app/api/analyst/[id]/recommendations/[recId]/send/route.ts +13 -0
- package/app/api/analyst/[id]/review/route.ts +22 -0
- package/app/api/analyst/[id]/route.ts +44 -0
- package/app/api/analyst/route.ts +10 -0
- package/app/api/bus/groups/route.ts +44 -0
- package/app/api/bus/spawn/route.ts +34 -2
- 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/models/availability/route.ts +9 -0
- package/app/api/models/choices/route.ts +9 -0
- 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]/analyst/route.ts +11 -0
- package/app/api/sessions/[id]/crew/route.ts +38 -0
- package/app/api/sessions/[id]/export/estimate/route.ts +23 -0
- package/app/api/sessions/[id]/export/route.test.ts +125 -0
- package/app/api/sessions/[id]/export/route.ts +34 -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]/model/route.ts +49 -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 +31 -14
- package/app/page.tsx +10 -0
- package/components/Chat/Messages.tsx +104 -10
- 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/DraftChips.tsx +6 -1
- package/components/Draft/DraftPanel.tsx +90 -3
- package/components/Draft/ModelUnavailableDialog.tsx +78 -0
- package/components/Draft/RoleChips.tsx +110 -0
- package/components/Export/ExportActions.tsx +136 -0
- package/components/Export/ExportSheet.tsx +217 -0
- package/components/Export/RangePicker.tsx +144 -0
- package/components/Export/Seg.tsx +38 -0
- package/components/Export/TurnSelectBar.tsx +45 -0
- 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/Models/AgentRoleCard.tsx +114 -0
- package/components/Models/AgentRolesSection.tsx +132 -0
- package/components/Models/ModelBadges.tsx +48 -0
- package/components/Models/ModelsSection.tsx +4 -2
- package/components/Models/RoleModelSelect.tsx +87 -0
- package/components/Pane/DesktopTabBar.tsx +2 -0
- package/components/Pane/MobileTabBar.tsx +12 -0
- package/components/Pane/PaneMenu.tsx +10 -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 +19 -7
- package/components/SessionList/SessionRowMenu.tsx +23 -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/Sessions/RolePill.tsx +196 -0
- package/components/Settings/SettingsDialog.tsx +9 -0
- package/components/Share/ShareDialog.tsx +13 -0
- package/components/ui/notice.tsx +46 -0
- package/components/ui/status-pill.tsx +39 -0
- package/data/analyst/index.ts +3 -0
- package/data/analyst/keys.ts +6 -0
- package/data/analyst/mutations.ts +116 -0
- package/data/analyst/queries.ts +35 -0
- package/data/claims/index.ts +2 -0
- package/data/claims/keys.ts +5 -0
- package/data/claims/queries.ts +47 -0
- package/data/exports/index.ts +2 -0
- package/data/exports/keys.ts +3 -0
- package/data/exports/queries.ts +51 -0
- package/data/models/index.ts +98 -0
- package/data/peers/keys.ts +1 -0
- package/data/peers/queries.ts +31 -0
- package/data/push/topics.ts +9 -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 +36 -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/brief.ts +1 -0
- package/lib/agents/spawn.ts +4 -0
- package/lib/analyst/__fixtures__/items.ts +57 -0
- package/lib/analyst/__fixtures__/mocks.ts +49 -0
- package/lib/analyst/brief.ts +17 -0
- package/lib/analyst/buildDigest.ts +204 -0
- package/lib/analyst/createLink.ts +98 -0
- package/lib/analyst/endLink.ts +31 -0
- package/lib/analyst/forward.ts +109 -0
- package/lib/analyst/index.ts +16 -0
- package/lib/analyst/linkView.ts +38 -0
- package/lib/analyst/onTurnEnd.ts +119 -0
- package/lib/analyst/parseReview.ts +41 -0
- package/lib/analyst/pushDigest.ts +143 -0
- package/lib/analyst/resume.ts +35 -0
- package/lib/analyst/route.ts +50 -0
- package/lib/analyst/signals.ts +121 -0
- package/lib/analyst/store.ts +303 -0
- package/lib/analyst/types.ts +140 -0
- package/lib/bus/condense.ts +5 -1
- package/lib/bus/index.ts +13 -0
- package/lib/bus/read-scope.ts +12 -0
- package/lib/bus/role-alias.ts +70 -0
- package/lib/bus/targets.ts +3 -0
- package/lib/chat/drivers/opencode-mapper.ts +55 -1
- package/lib/chat/drivers/pi-approvals.ts +7 -4
- package/lib/chat/drivers/pi-mapper.ts +38 -0
- package/lib/chat/drivers/pi-text-tools.ts +104 -0
- package/lib/chat/drivers/pi.ts +8 -3
- package/lib/chat/drivers/turn-items.ts +36 -11
- package/lib/chat/drivers/turn-outcome.ts +108 -0
- package/lib/chat/events.ts +8 -0
- package/lib/chat/registry.ts +13 -0
- package/lib/chat/store.ts +19 -0
- package/lib/chat/turns.ts +1 -0
- package/lib/chat/worker/extras.ts +4 -2
- package/lib/claims/collectLocal.ts +123 -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 +12 -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/migrations/20261021T1400_session_agent_roles.ts +20 -0
- package/lib/db/migrations/20261021T1430_analyst_links.ts +52 -0
- package/lib/db/queries.ts +0 -40
- package/lib/db/session-parts/columns.ts +3 -0
- package/lib/db/types.ts +19 -8
- package/lib/doctor/checks.ts +64 -1
- package/lib/done/archive.ts +2 -0
- package/lib/drafts.ts +4 -0
- package/lib/export/build.ts +470 -0
- package/lib/export/compact.ts +156 -0
- package/lib/export/estimate.ts +83 -0
- package/lib/export/fixtures.ts +45 -0
- package/lib/export/html.tsx +195 -0
- package/lib/export/json.ts +39 -0
- package/lib/export/load.ts +64 -0
- package/lib/export/markdown.ts +127 -0
- package/lib/export/request.ts +36 -0
- package/lib/export/service.ts +142 -0
- package/lib/export/summary.ts +86 -0
- package/lib/export/turns.ts +63 -0
- package/lib/export/view.ts +173 -0
- package/lib/git-remote.ts +43 -0
- package/lib/hosts/index.ts +10 -1
- package/lib/models/agent-binding.ts +102 -0
- package/lib/models/agent-roles-view.ts +157 -0
- package/lib/models/agent-roles.ts +87 -0
- package/lib/models/availability-refs.ts +17 -0
- package/lib/models/availability.ts +91 -0
- package/lib/models/catalog.ts +156 -0
- package/lib/models/config.ts +27 -0
- package/lib/models/locality.ts +33 -0
- package/lib/models/providers.ts +11 -0
- package/lib/models/resolve.ts +50 -12
- package/lib/models/roles.ts +12 -0
- package/lib/needs-you.ts +1 -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 +12 -2
- package/lib/orchestrator/read.ts +5 -1
- 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/untrusted.ts +5 -0
- 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 +6 -2
- package/lib/security/demo.ts +7 -0
- package/lib/session-artifacts/agentos-files.ts +94 -0
- package/lib/session-artifacts/collect.ts +17 -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/crew.ts +72 -0
- package/lib/sessions/launch.ts +54 -11
- package/lib/settings-url.ts +1 -0
- package/lib/share/markdown.ts +2 -2
- package/lib/share/redact.ts +16 -10
- package/lib/share/render.tsx +1 -1
- package/lib/share/snapshot.ts +2 -2
- package/lib/sidebar/rollup.ts +1 -0
- package/lib/sidebar/row-menu.ts +8 -1
- 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 +11 -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-deps/caches.ts +3 -2
- package/lib/worktree-deps/clone.ts +3 -1
- package/lib/worktree-deps/paths.ts +26 -0
- 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 +12 -1
- package/stores/claimsUi.ts +22 -0
- package/stores/exportUi.ts +87 -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
|
@@ -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
|
-
|
|
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
|
-
:
|
|
306
|
-
?
|
|
307
|
-
|
|
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
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
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
|
+
}
|