@saadnvd1/agent-os 0.3.698 → 0.3.715
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/app/api/bus/groups/route.ts +44 -0
- package/app/api/bus/send/route.ts +11 -1
- package/app/api/bus/spawn/route.ts +12 -1
- package/app/api/bus/wait/route.ts +22 -0
- package/app/api/claims/[id]/route.ts +20 -0
- package/app/api/claims/check/route.ts +23 -0
- package/app/api/claims/check.test.ts +113 -0
- package/app/api/claims/exchange/route.ts +42 -0
- package/app/api/claims/outside.test.ts +90 -0
- package/app/api/claims/route.test.ts +193 -0
- package/app/api/claims/route.ts +73 -0
- package/app/api/hosts/[id]/route.ts +9 -1
- package/app/api/peer/hello/reveal/route.ts +6 -0
- package/app/api/peer/hello/route.ts +9 -0
- package/app/api/peer/hello/status/route.ts +6 -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/account/route.ts +9 -0
- package/app/api/peers/gate.test.ts +174 -0
- package/app/api/peers/link/[id]/confirm/route.ts +27 -0
- package/app/api/peers/link/[id]/route.ts +20 -0
- package/app/api/peers/link/route.ts +23 -0
- package/app/api/peers/pending/[id]/accept/route.ts +31 -0
- package/app/api/peers/pending/[id]/decline/route.ts +13 -0
- package/app/api/peers/pending/route.test.ts +37 -0
- package/app/api/peers/pending/route.ts +14 -0
- package/app/api/session-groups/[id]/restore/route.ts +11 -0
- package/app/api/session-groups/[id]/route.ts +25 -0
- package/app/api/session-groups/order/route.ts +15 -0
- package/app/api/session-groups/route.ts +21 -0
- package/app/api/sessions/[id]/fork/route.ts +3 -0
- package/app/api/sessions/[id]/group/route.ts +18 -0
- package/app/api/sessions/[id]/route.ts +14 -4
- package/app/api/sessions/[id]/summarize/route.ts +2 -0
- package/app/api/sessions/claims.test.ts +122 -0
- package/app/api/sessions/group/route.ts +17 -0
- package/app/api/sessions/route.ts +31 -13
- package/app/page.tsx +7 -0
- package/components/Claims/ClaimFiles.tsx +38 -0
- package/components/Claims/ClaimRow.tsx +82 -0
- package/components/Claims/ClaimsSheet.tsx +176 -0
- package/components/Claims/OverlapPill.tsx +31 -0
- package/components/Claims/StartClaimsNotice.tsx +70 -0
- package/components/Claims/claims-ui.test.ts +161 -0
- package/components/Claims/format.ts +57 -0
- package/components/Claims/index.ts +3 -0
- package/components/Draft/DraftPanel.tsx +40 -2
- package/components/Hosts/AccountMachines.tsx +194 -0
- package/components/Hosts/HostRow.tsx +195 -45
- package/components/Hosts/HostsDialog.tsx +49 -37
- package/components/Hosts/IncomingLinkSheet.tsx +246 -0
- package/components/Hosts/LinkCodeSheet.tsx +186 -0
- package/components/Hosts/PeerActivitySheet.tsx +111 -0
- package/components/Hosts/sheet.ts +3 -0
- package/components/SessionList/GroupShelves.tsx +195 -0
- package/components/SessionList/MoveToGroupMenu.tsx +105 -0
- package/components/SessionList/RowSubtitle.tsx +18 -2
- package/components/SessionList/SelectionToolbar.tsx +52 -1
- package/components/SessionList/SessionRow.tsx +1 -0
- package/components/SessionList/SessionRowMenu.tsx +17 -0
- package/components/SessionList/SessionShelves.tsx +5 -0
- package/components/SessionList/SidebarBody.tsx +6 -0
- package/components/SessionList/SidebarContextMenu.test.ts +104 -0
- package/components/SessionList/SidebarContextMenu.tsx +59 -0
- package/components/SessionList/SidebarHeader.tsx +5 -0
- package/components/SessionList/WorkspaceSwitcher.tsx +23 -6
- package/components/SessionList/index.tsx +18 -14
- package/components/SessionList/useSidebarData.ts +10 -0
- package/components/Welcome/ConnectBlock.tsx +1 -1
- package/components/ui/notice.tsx +46 -0
- package/components/ui/status-pill.tsx +39 -0
- package/data/claims/index.ts +2 -0
- package/data/claims/keys.ts +5 -0
- package/data/claims/queries.ts +47 -0
- package/data/peers/index.ts +2 -0
- package/data/peers/keys.ts +7 -0
- package/data/peers/queries.ts +172 -0
- package/data/push/topics.ts +4 -0
- package/data/session-groups/index.ts +156 -0
- package/data/sessions/index.ts +0 -1
- package/data/sessions/pin.ts +2 -6
- package/data/sessions/queries.ts +10 -29
- package/data/sessions/shelf.ts +2 -6
- package/data/tasks/index.ts +2 -0
- package/hooks/useSessions.ts +0 -12
- package/lib/__fixtures__/peer-rpc.ts +65 -33
- package/lib/agents/brief.ts +3 -1
- package/lib/agents/spawn.ts +4 -0
- package/lib/bus/format.ts +25 -4
- package/lib/bus/index.ts +121 -106
- package/lib/bus/resolve.ts +16 -2
- package/lib/bus/targets.ts +122 -0
- package/lib/bus/waits/kick.ts +12 -0
- package/lib/bus/waits/lines.ts +69 -0
- package/lib/bus/waits/register.ts +83 -0
- package/lib/bus/waits/store.ts +196 -0
- package/lib/bus/waits/tick.ts +192 -0
- package/lib/claims/collectLocal.ts +121 -0
- package/lib/claims/describe.ts +41 -0
- package/lib/claims/exchange.ts +91 -0
- package/lib/claims/goal.ts +45 -0
- package/lib/claims/index.ts +10 -0
- package/lib/claims/loop.ts +51 -0
- package/lib/claims/machine.ts +21 -0
- package/lib/claims/mine.ts +43 -0
- package/lib/claims/notifyOverlaps.ts +101 -0
- package/lib/claims/onMerged.ts +85 -0
- package/lib/claims/outside.ts +36 -0
- package/lib/claims/overlap.ts +87 -0
- package/lib/claims/overlapCache.ts +86 -0
- package/lib/claims/overlapText.ts +36 -0
- package/lib/claims/peers.ts +56 -0
- package/lib/claims/startCheck.ts +100 -0
- package/lib/claims/startWarning.ts +29 -0
- package/lib/claims/store.ts +366 -0
- package/lib/claims/types.ts +102 -0
- package/lib/claims/view.ts +80 -0
- package/lib/claims/yield.ts +24 -0
- package/lib/connect/control-api.ts +108 -2
- package/lib/connect/heartbeat.ts +11 -3
- package/lib/connect/serve.ts +2 -0
- package/lib/db/changes.ts +6 -0
- package/lib/db/migrations/20261020T1400_work_claims.ts +35 -0
- package/lib/db/migrations/20261020T1515_peer_unlinked.ts +22 -0
- package/lib/db/migrations/20261020T1600_bus_waits.ts +35 -0
- package/lib/db/migrations/20261020T1700_clean_connect_names.ts +18 -0
- package/lib/db/migrations/20261020T1800_session_groups.ts +49 -0
- package/lib/db/migrations/20261020T1900_task_queue_after_claim.ts +13 -0
- package/lib/db/migrations/20261020T2000_claim_notices.ts +20 -0
- package/lib/db/migrations/20261020T2100_claims_via_host.ts +21 -0
- package/lib/db/migrations/20261020T2200_work_claims_watch.ts +10 -0
- package/lib/db/queries.ts +0 -40
- package/lib/db/session-parts/columns.ts +1 -0
- package/lib/db/types.ts +20 -8
- package/lib/doctor/checks.ts +64 -1
- package/lib/done/archive.ts +2 -0
- package/lib/git-remote.ts +43 -0
- package/lib/hosts/index.ts +10 -1
- package/lib/orchestration.ts +2 -0
- package/lib/orchestrator/act.ts +54 -4
- package/lib/orchestrator/brief.ts +2 -1
- package/lib/orchestrator/claim-wait.ts +71 -0
- package/lib/orchestrator/conditions.ts +28 -1
- package/lib/orchestrator/describe.ts +19 -1
- package/lib/orchestrator/facts.ts +22 -0
- package/lib/orchestrator/serve.ts +37 -2
- package/lib/orchestrator/tool-names.ts +1 -0
- package/lib/orchestrator/tool-schemas.ts +31 -1
- package/lib/orchestrator/tools.ts +3 -1
- package/lib/orchestrator/watcher.ts +19 -1
- package/lib/peer/account.ts +270 -0
- package/lib/peer/allowlist.ts +1 -1
- package/lib/peer/cli.ts +24 -0
- package/lib/peer/client.ts +26 -1
- package/lib/peer/code.ts +3 -0
- package/lib/peer/hello-route.ts +32 -0
- package/lib/peer/hello.ts +346 -0
- package/lib/peer/link-cli.ts +248 -0
- package/lib/peer/links.ts +117 -5
- package/lib/peer/machines.ts +84 -0
- package/lib/peer/name.ts +32 -0
- package/lib/peer/notify.ts +32 -0
- package/lib/peer/pairing-out.ts +382 -0
- package/lib/peer/pairing.ts +432 -0
- package/lib/peer/pin.ts +62 -0
- package/lib/peer/reasons.ts +7 -0
- package/lib/peer/revoke.ts +75 -0
- package/lib/peer/rpc-intake.ts +37 -8
- package/lib/peer/rpc-server.ts +72 -6
- package/lib/peer/unlink.ts +62 -0
- package/lib/peer/unlinked-proof.ts +44 -0
- package/lib/projects.ts +5 -2
- package/lib/schedules/when.ts +1 -1
- package/lib/security/auth.ts +10 -2
- package/lib/security/demo.ts +7 -0
- package/lib/security/gate.ts +3 -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/end.ts +3 -0
- package/lib/sessions/launch.ts +5 -2
- package/lib/sidebar/rollup.ts +1 -0
- package/lib/sidebar/row-menu.ts +2 -0
- package/lib/sidebar/shelves.ts +90 -15
- package/lib/sidebar/visible-order.ts +5 -0
- package/lib/tasks/hold-checks.ts +3 -1
- package/lib/tasks/holds.ts +3 -0
- package/lib/tasks/index.ts +5 -0
- package/lib/tasks/queue-rows.ts +8 -1
- package/lib/tasks/queue-start.ts +1 -0
- package/lib/tasks/queue-types.ts +9 -1
- package/lib/tasks/queue.ts +15 -5
- package/lib/tasks/schedule-types.ts +1 -0
- package/lib/test-temp.ts +19 -3
- package/lib/workspaces.ts +11 -3
- package/lib/worktree-sweep/testTemp.ts +23 -106
- package/package.json +1 -1
- package/scripts/agent-os +3 -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 +5 -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 +6 -0
- package/stores/claimsUi.ts +22 -0
- package/app/api/groups/[...path]/route.ts +0 -136
- package/app/api/groups/route.ts +0 -93
- package/data/groups/index.ts +0 -1
- package/data/groups/mutations.ts +0 -95
- package/hooks/useGroups.ts +0 -37
|
@@ -19,6 +19,7 @@ import { statusDetector } from "@/lib/status-detector";
|
|
|
19
19
|
import { deleteItems } from "@/lib/chat/store";
|
|
20
20
|
import { clearQueue } from "@/lib/chat/queued";
|
|
21
21
|
import { recordPreviousName } from "@/lib/session-names";
|
|
22
|
+
import { getProject, moveSessionToProject } from "@/lib/projects";
|
|
22
23
|
import { generateBranchName, getCurrentBranch, renameBranch } from "@/lib/git";
|
|
23
24
|
import { runInBackground } from "@/lib/async-operations";
|
|
24
25
|
import {
|
|
@@ -206,9 +207,15 @@ export async function PATCH(request: NextRequest, { params }: RouteParams) {
|
|
|
206
207
|
updates.push("system_prompt = ?");
|
|
207
208
|
values.push(body.systemPrompt);
|
|
208
209
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
210
|
+
|
|
211
|
+
let clearedGroup: string | null = null;
|
|
212
|
+
if (typeof body.projectId === "string" && !existing.role) {
|
|
213
|
+
if (!getProject(body.projectId))
|
|
214
|
+
return NextResponse.json(
|
|
215
|
+
{ error: "Project not found" },
|
|
216
|
+
{ status: 404 }
|
|
217
|
+
);
|
|
218
|
+
clearedGroup = moveSessionToProject(id, body.projectId).clearedGroup;
|
|
212
219
|
}
|
|
213
220
|
|
|
214
221
|
if (updates.length > 0) {
|
|
@@ -221,7 +228,10 @@ export async function PATCH(request: NextRequest, { params }: RouteParams) {
|
|
|
221
228
|
}
|
|
222
229
|
|
|
223
230
|
const session = queries.getSession(db).get(id) as Session;
|
|
224
|
-
return NextResponse.json({
|
|
231
|
+
return NextResponse.json({
|
|
232
|
+
session,
|
|
233
|
+
...(clearedGroup && { clearedGroup }),
|
|
234
|
+
});
|
|
225
235
|
} catch (error) {
|
|
226
236
|
console.error("Error updating session:", error);
|
|
227
237
|
return NextResponse.json(
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { inheritGroup } from "@/lib/session-groups/assignSessions";
|
|
1
2
|
import { effectiveAutoApprove } from "@/lib/approvals/no-approvals";
|
|
2
3
|
import { routedRun } from "@/lib/models/launch";
|
|
3
4
|
import { NextRequest, NextResponse } from "next/server";
|
|
@@ -305,6 +306,7 @@ export async function POST(
|
|
|
305
306
|
session.host_id
|
|
306
307
|
);
|
|
307
308
|
|
|
309
|
+
inheritGroup(session.id, newId);
|
|
308
310
|
newSession = queries.getSession(db).get(newId) as Session;
|
|
309
311
|
const newTmuxSession = tmuxName;
|
|
310
312
|
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it } from "vitest";
|
|
2
|
+
import { seedSession, seedWorkspace } from "@/lib/orchestrator/testing";
|
|
3
|
+
|
|
4
|
+
const { db } = await import("@/lib/db");
|
|
5
|
+
const { machineId } = await import("@/lib/claims/machine");
|
|
6
|
+
const { upsertLocal, upsertRemote } = await import("@/lib/claims/store");
|
|
7
|
+
const { refreshOverlapCache, resetOverlapCache } =
|
|
8
|
+
await import("@/lib/claims/overlapCache");
|
|
9
|
+
const { GET } = await import("./route");
|
|
10
|
+
|
|
11
|
+
const REPO = "git.example.test/team/widgets";
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
db.prepare(`DELETE FROM work_claims`).run();
|
|
15
|
+
resetOverlapCache();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
function localClaim(sessionId: string, files: string[]) {
|
|
19
|
+
upsertLocal(
|
|
20
|
+
{ id: machineId(), name: "here" },
|
|
21
|
+
{
|
|
22
|
+
sessionId,
|
|
23
|
+
sessionName: "Tidy auth",
|
|
24
|
+
repo: REPO,
|
|
25
|
+
branch: "feature/tidy",
|
|
26
|
+
base: "main",
|
|
27
|
+
goal: "Tidy auth",
|
|
28
|
+
files,
|
|
29
|
+
filesMore: 0,
|
|
30
|
+
prUrl: null,
|
|
31
|
+
}
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const remote = (id: string, files: string[], branch = "feature/login") =>
|
|
36
|
+
upsertRemote(machineId(), "peer-1", {
|
|
37
|
+
id,
|
|
38
|
+
origin: "box-machine",
|
|
39
|
+
origin_name: "box",
|
|
40
|
+
hops: 0,
|
|
41
|
+
source: "session",
|
|
42
|
+
session_name: "Rework login sessions",
|
|
43
|
+
repo: REPO,
|
|
44
|
+
branch,
|
|
45
|
+
base: "main",
|
|
46
|
+
goal: "Rework login sessions",
|
|
47
|
+
files,
|
|
48
|
+
files_more: 0,
|
|
49
|
+
pr_url: null,
|
|
50
|
+
ttl_s: 600,
|
|
51
|
+
age_s: 60,
|
|
52
|
+
released: null,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const FILES = ["a/one.ts", "a/two.ts", "a/three.ts"];
|
|
56
|
+
|
|
57
|
+
const rows = async () => {
|
|
58
|
+
const body = await (await GET()).json();
|
|
59
|
+
return body as {
|
|
60
|
+
sessions: { id: string; overlap?: Record<string, unknown> | null }[];
|
|
61
|
+
claims: { id: string; fileCount: number; files?: string[] }[];
|
|
62
|
+
claimOverlaps: { a: string; b: string; strength: string }[];
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
describe("GET /api/sessions with claims", () => {
|
|
67
|
+
it("puts a strong overlap on the session that has it, and no other", async () => {
|
|
68
|
+
const t = seedWorkspace();
|
|
69
|
+
const mine = seedSession({ projectId: t.app.id, name: "Tidy auth" });
|
|
70
|
+
const other = seedSession({ projectId: t.app.id, name: "Unrelated" });
|
|
71
|
+
localClaim(mine, FILES);
|
|
72
|
+
remote("claim-box-1", FILES);
|
|
73
|
+
refreshOverlapCache();
|
|
74
|
+
const body = await rows();
|
|
75
|
+
expect(body.sessions.find((s) => s.id === mine)?.overlap).toEqual({
|
|
76
|
+
strength: "strong",
|
|
77
|
+
machine: "box",
|
|
78
|
+
goal: "Rework login sessions",
|
|
79
|
+
shared: 3,
|
|
80
|
+
claimId: "claim-box-1",
|
|
81
|
+
mineId: `s-${mine}`,
|
|
82
|
+
});
|
|
83
|
+
expect(body.sessions.find((s) => s.id === other)?.overlap).toBeUndefined();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("marks a small file overlap weak", async () => {
|
|
87
|
+
const t = seedWorkspace();
|
|
88
|
+
const mine = seedSession({ projectId: t.app.id, name: "Tidy auth" });
|
|
89
|
+
localClaim(mine, ["a/1.ts", "a/2.ts", "a/3.ts", "a/4.ts", "a/5.ts"]);
|
|
90
|
+
remote(
|
|
91
|
+
"claim-box-2",
|
|
92
|
+
["a/1.ts", "a/2.ts", "b/1.ts", "b/2.ts", "b/3.ts"],
|
|
93
|
+
"feature/other"
|
|
94
|
+
);
|
|
95
|
+
refreshOverlapCache();
|
|
96
|
+
const row = (await rows()).sessions.find((s) => s.id === mine);
|
|
97
|
+
expect(row?.overlap?.strength).toBe("weak");
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("lists claims without their paths, and the overlapping pairs", async () => {
|
|
101
|
+
const t = seedWorkspace();
|
|
102
|
+
const mine = seedSession({ projectId: t.app.id, name: "Tidy auth" });
|
|
103
|
+
localClaim(mine, FILES);
|
|
104
|
+
remote("claim-box-1", FILES);
|
|
105
|
+
refreshOverlapCache();
|
|
106
|
+
const body = await rows();
|
|
107
|
+
expect(body.claims.map((c) => c.id).sort()).toEqual(
|
|
108
|
+
[`s-${mine}`, "claim-box-1"].sort()
|
|
109
|
+
);
|
|
110
|
+
expect(body.claims.every((c) => c.fileCount === 3 && !c.files)).toBe(true);
|
|
111
|
+
expect(body.claimOverlaps).toHaveLength(1);
|
|
112
|
+
expect(body.claimOverlaps[0].strength).toBe("strong");
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("serves no claims before the loop has run", async () => {
|
|
116
|
+
const t = seedWorkspace();
|
|
117
|
+
seedSession({ projectId: t.app.id, name: "Tidy auth" });
|
|
118
|
+
const body = await rows();
|
|
119
|
+
expect(body.claims).toEqual([]);
|
|
120
|
+
expect(body.sessions.every((s) => !s.overlap)).toBe(true);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { assignSessions } from "@/lib/session-groups";
|
|
3
|
+
import { groupRoute } from "@/lib/session-groups/route";
|
|
4
|
+
|
|
5
|
+
const body = z.object({
|
|
6
|
+
ids: z.array(z.string()).min(1).max(200),
|
|
7
|
+
groupId: z.string().nullable(),
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
// POST /api/sessions/group { ids, groupId | null } - the multi-select move.
|
|
11
|
+
// All or none: one session of another workspace refuses the lot.
|
|
12
|
+
export async function POST(request: Request) {
|
|
13
|
+
return groupRoute(request, body, (b) => ({
|
|
14
|
+
ok: true,
|
|
15
|
+
...assignSessions(b.ids, b.groupId),
|
|
16
|
+
}));
|
|
17
|
+
}
|
|
@@ -1,28 +1,47 @@
|
|
|
1
1
|
import { NextRequest, NextResponse } from "next/server";
|
|
2
|
-
import { getDb, queries, type Session
|
|
2
|
+
import { getDb, queries, type Session } from "@/lib/db";
|
|
3
|
+
import { listGroups } from "@/lib/session-groups";
|
|
3
4
|
import { isValidAgentType } from "@/lib/providers";
|
|
4
5
|
import { clientSend } from "@/lib/chat/client-send";
|
|
5
6
|
import { CHAT_ACCESS } from "@/lib/chat/events";
|
|
6
7
|
import { launchSession } from "@/lib/sessions/launch";
|
|
8
|
+
import { namesLine, waitBadges } from "@/lib/bus/waits/store";
|
|
9
|
+
import { claimsSnapshot } from "@/lib/claims";
|
|
7
10
|
import { isProjectRef } from "@/lib/tasks/project-ref";
|
|
8
11
|
|
|
9
|
-
// GET /api/sessions - List
|
|
12
|
+
// GET /api/sessions - List the sidebar's sessions and the user's groups
|
|
10
13
|
export async function GET() {
|
|
11
14
|
try {
|
|
12
15
|
const db = getDb();
|
|
13
16
|
// Merged tasks are listed (the sidebar files them under Done). Dropped
|
|
14
17
|
// tasks live in the Tasks panel, a moved one on the machine it moved to,
|
|
15
18
|
// and archived sessions in the Archived view: none is in the sidebar.
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
19
|
+
const badges = waitBadges();
|
|
20
|
+
const claims = claimsSnapshot();
|
|
21
|
+
const sessions = (queries.getSidebarSessions(db).all() as Session[]).map(
|
|
22
|
+
(s) => {
|
|
23
|
+
const b = badges.get(s.id);
|
|
24
|
+
const overlap = claims.bySession.get(s.id);
|
|
25
|
+
const row = overlap ? { ...s, overlap } : s;
|
|
26
|
+
return b
|
|
27
|
+
? {
|
|
28
|
+
...row,
|
|
29
|
+
waiting_on: b.on.length
|
|
30
|
+
? namesLine(b.on.map((w) => w.name))
|
|
31
|
+
: null,
|
|
32
|
+
waited_by: b.by.length
|
|
33
|
+
? namesLine(b.by.map((w) => w.name))
|
|
34
|
+
: null,
|
|
35
|
+
}
|
|
36
|
+
: row;
|
|
37
|
+
}
|
|
38
|
+
);
|
|
39
|
+
return NextResponse.json({
|
|
40
|
+
sessions,
|
|
41
|
+
sessionGroups: listGroups(),
|
|
42
|
+
claims: claims.claims,
|
|
43
|
+
claimOverlaps: claims.overlaps,
|
|
44
|
+
});
|
|
26
45
|
} catch (error) {
|
|
27
46
|
console.error("Error fetching sessions:", error);
|
|
28
47
|
return NextResponse.json(
|
|
@@ -78,7 +97,6 @@ export async function POST(request: NextRequest) {
|
|
|
78
97
|
name: str(body.name),
|
|
79
98
|
view: body.view === "terminal" ? "terminal" : undefined,
|
|
80
99
|
parentSessionId: str(body.parentSessionId),
|
|
81
|
-
groupPath: str(body.groupPath) ?? undefined,
|
|
82
100
|
systemPrompt: str(body.systemPrompt),
|
|
83
101
|
});
|
|
84
102
|
const db = getDb();
|
package/app/page.tsx
CHANGED
|
@@ -34,6 +34,7 @@ if (typeof window !== "undefined") {
|
|
|
34
34
|
import { PaneProvider, usePanes } from "@/contexts/PaneContext";
|
|
35
35
|
import { Pane } from "@/components/Pane";
|
|
36
36
|
import { AwayReport } from "@/components/Away/AwayReport";
|
|
37
|
+
import { IncomingLinks } from "@/components/Hosts/IncomingLinkSheet";
|
|
37
38
|
import type { BrowserNotifications } from "@/components/Settings/NotificationsSection";
|
|
38
39
|
import { useNotifications } from "@/hooks/useNotifications";
|
|
39
40
|
import { useViewport } from "@/hooks/useViewport";
|
|
@@ -121,6 +122,10 @@ const LumifyHubDialogs = dynamic(
|
|
|
121
122
|
),
|
|
122
123
|
{ ssr: false }
|
|
123
124
|
);
|
|
125
|
+
const ClaimsSheet = dynamic(
|
|
126
|
+
() => import("@/components/Claims/ClaimsSheet").then((m) => m.ClaimsSheet),
|
|
127
|
+
{ ssr: false }
|
|
128
|
+
);
|
|
124
129
|
const UsageDialog = dynamic(
|
|
125
130
|
() => import("@/components/Usage/UsageDialog").then((m) => m.UsageDialog),
|
|
126
131
|
{ ssr: false }
|
|
@@ -158,9 +163,11 @@ const AppDialogs = memo(function AppDialogs({
|
|
|
158
163
|
<CleanupDialog />
|
|
159
164
|
<LumifyHubDialogs />
|
|
160
165
|
<UsageDialog />
|
|
166
|
+
<ClaimsSheet />
|
|
161
167
|
<MoveDialog />
|
|
162
168
|
<ShareDialog />
|
|
163
169
|
<AwayReport />
|
|
170
|
+
<IncomingLinks />
|
|
164
171
|
<CommandPalette />
|
|
165
172
|
</>
|
|
166
173
|
);
|
|
@@ -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
|
+
}
|