@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
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { execFileSync } from "child_process";
|
|
2
|
+
import * as fs from "fs";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
import { describe, expect, it } from "vitest";
|
|
5
|
+
import { tempDir } from "@/lib/test-temp";
|
|
6
|
+
|
|
7
|
+
const script = path.resolve(__dirname, "sync-cli-links");
|
|
8
|
+
|
|
9
|
+
function setup() {
|
|
10
|
+
const root = tempDir("aos-synclinks-");
|
|
11
|
+
const home = path.join(root, "home");
|
|
12
|
+
const bin = path.join(root, "bin");
|
|
13
|
+
fs.mkdirSync(home);
|
|
14
|
+
fs.mkdirSync(bin);
|
|
15
|
+
const clone = (name: string) => {
|
|
16
|
+
const dir = path.join(root, name);
|
|
17
|
+
fs.mkdirSync(path.join(dir, "bin"), { recursive: true });
|
|
18
|
+
fs.mkdirSync(path.join(dir, "scripts"), { recursive: true });
|
|
19
|
+
fs.writeFileSync(path.join(dir, "bin/aos"), "");
|
|
20
|
+
fs.writeFileSync(path.join(dir, "scripts/agent-os"), "");
|
|
21
|
+
return dir;
|
|
22
|
+
};
|
|
23
|
+
const live = clone("live");
|
|
24
|
+
const old = clone("old");
|
|
25
|
+
fs.writeFileSync(
|
|
26
|
+
path.join(home, "live-server.json"),
|
|
27
|
+
JSON.stringify({ cwd: live })
|
|
28
|
+
);
|
|
29
|
+
const run = (...args: string[]) =>
|
|
30
|
+
execFileSync(script, args, {
|
|
31
|
+
encoding: "utf-8",
|
|
32
|
+
env: { ...process.env, AOS_BIN_DIR: bin, AGENT_OS_HOME: home },
|
|
33
|
+
});
|
|
34
|
+
return { bin, home, live, old, run };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
describe("sync-cli-links", () => {
|
|
38
|
+
it("repoints a link into another clone and logs it", () => {
|
|
39
|
+
const t = setup();
|
|
40
|
+
fs.symlinkSync(path.join(t.old, "bin/aos"), path.join(t.bin, "aos"));
|
|
41
|
+
const out = t.run();
|
|
42
|
+
expect(fs.readlinkSync(path.join(t.bin, "aos"))).toBe(
|
|
43
|
+
path.join(t.live, "bin/aos")
|
|
44
|
+
);
|
|
45
|
+
expect(out).toContain("repointed");
|
|
46
|
+
expect(out).toContain(t.old);
|
|
47
|
+
expect(t.run()).toBe("");
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("leaves absent links alone unless asked to create them", () => {
|
|
51
|
+
const t = setup();
|
|
52
|
+
t.run();
|
|
53
|
+
expect(fs.existsSync(path.join(t.bin, "aos"))).toBe(false);
|
|
54
|
+
t.run("create");
|
|
55
|
+
expect(fs.readlinkSync(path.join(t.bin, "aos"))).toBe(
|
|
56
|
+
path.join(t.live, "bin/aos")
|
|
57
|
+
);
|
|
58
|
+
expect(fs.readlinkSync(path.join(t.bin, "agent-os"))).toBe(
|
|
59
|
+
path.join(t.live, "scripts/agent-os")
|
|
60
|
+
);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("never replaces a real file", () => {
|
|
64
|
+
const t = setup();
|
|
65
|
+
fs.writeFileSync(path.join(t.bin, "aos"), "mine");
|
|
66
|
+
t.run("create");
|
|
67
|
+
expect(fs.readFileSync(path.join(t.bin, "aos"), "utf-8")).toBe("mine");
|
|
68
|
+
});
|
|
69
|
+
});
|
package/server.ts
CHANGED
|
@@ -6,6 +6,8 @@ import next from "next";
|
|
|
6
6
|
import { WebSocketServer, WebSocket } from "ws";
|
|
7
7
|
import { ensureBusBrief } from "./lib/agents/launch";
|
|
8
8
|
import { reattachChats } from "./lib/chat/runner";
|
|
9
|
+
import { setTurnEndHook } from "./lib/chat/registry";
|
|
10
|
+
import { analystTurnEndHook, resumeAnalystLinks } from "./lib/analyst";
|
|
9
11
|
import { serveChatSocket } from "./lib/chat/socket";
|
|
10
12
|
import { startStackWatcher } from "./lib/stacks";
|
|
11
13
|
import { startPauseWatcher } from "./lib/tasks/pause";
|
|
@@ -21,6 +23,7 @@ import { realDeps, schedulesEnabled, startScheduler } from "./lib/schedules";
|
|
|
21
23
|
import { startSettling } from "./lib/recipes";
|
|
22
24
|
import { startAutoArchive } from "./lib/done/auto-archive";
|
|
23
25
|
import { startWorktreeSweep } from "./lib/worktree-sweep/start";
|
|
26
|
+
import { startClaimsLoop } from "./lib/claims";
|
|
24
27
|
import { startUsageRollup, usageRollupEnabled } from "./lib/usage/nightly";
|
|
25
28
|
import { resumePhoneOutbox } from "./lib/notify";
|
|
26
29
|
import { resumePushOutbox } from "./lib/notify/push";
|
|
@@ -223,6 +226,8 @@ app.prepare().then(async () => {
|
|
|
223
226
|
const folder = sessionFolder(id);
|
|
224
227
|
if (folder) refreshGitSoon(folder);
|
|
225
228
|
});
|
|
229
|
+
// An analyst hears each of its coding agent's turns as they end.
|
|
230
|
+
setTurnEndHook(analystTurnEndHook);
|
|
226
231
|
// Terminal sessions are watched through tmux control mode, or tapped with
|
|
227
232
|
// pipe-pane where tmux is too old for it.
|
|
228
233
|
const control = await startTmuxControl();
|
|
@@ -452,7 +457,11 @@ app.prepare().then(async () => {
|
|
|
452
457
|
void sweepSims();
|
|
453
458
|
setInterval(sweepSims, 5 * 60_000);
|
|
454
459
|
// Chat turns that kept running through a restart.
|
|
455
|
-
void reattachChats().finally(() =>
|
|
460
|
+
void reattachChats().finally(() => {
|
|
461
|
+
startPauseWatcher();
|
|
462
|
+
// Digests a restart cut off, and turns that ended while it was down.
|
|
463
|
+
void resumeAnalystLinks();
|
|
464
|
+
});
|
|
456
465
|
// Older chats get into the search index in the background, a batch at a time.
|
|
457
466
|
if (process.env.AGENTOS_SEARCH_INDEX !== "off") startConversationBackfill();
|
|
458
467
|
// Reviews this restart cut off run again and deliver their verdicts.
|
|
@@ -497,6 +506,8 @@ app.prepare().then(async () => {
|
|
|
497
506
|
startAutoArchive();
|
|
498
507
|
// Settled worktrees older than the setting are removed; branches stay.
|
|
499
508
|
startWorktreeSweep();
|
|
509
|
+
// Who is working on what, across linked machines.
|
|
510
|
+
if (!process.env.AGENTOS_DEMO) startClaimsLoop();
|
|
500
511
|
resumePhoneOutbox();
|
|
501
512
|
resumePushOutbox();
|
|
502
513
|
// The usage ledger's daily rollup and its ratchet alerts.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { proxy } from "valtio";
|
|
2
|
+
|
|
3
|
+
// The claims sheet ("Work across machines"). `focus` narrows it to the claims
|
|
4
|
+
// of one overlapping pair, from a row's pill.
|
|
5
|
+
export const claimsUi = proxy<{ open: boolean; focus: string[] | null }>({
|
|
6
|
+
open: false,
|
|
7
|
+
focus: null,
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
export const claimsUiActions = {
|
|
11
|
+
open: (focus: string[] | null = null) => {
|
|
12
|
+
claimsUi.focus = focus;
|
|
13
|
+
claimsUi.open = true;
|
|
14
|
+
},
|
|
15
|
+
setOpen: (open: boolean) => {
|
|
16
|
+
claimsUi.open = open;
|
|
17
|
+
if (!open) claimsUi.focus = null;
|
|
18
|
+
},
|
|
19
|
+
showAll: () => {
|
|
20
|
+
claimsUi.focus = null;
|
|
21
|
+
},
|
|
22
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { proxy } from "valtio";
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_REQUEST,
|
|
4
|
+
type ExportRequest,
|
|
5
|
+
type ExportTurnInfo,
|
|
6
|
+
} from "@/lib/export/view";
|
|
7
|
+
|
|
8
|
+
// The export sheet, and picking turns in the chat for it. The request lives
|
|
9
|
+
// here so it survives closing the sheet to select turns.
|
|
10
|
+
export const exportUi = proxy<{
|
|
11
|
+
open: boolean;
|
|
12
|
+
sessionId: string | null;
|
|
13
|
+
title: string;
|
|
14
|
+
req: ExportRequest;
|
|
15
|
+
/** Open at the turn that began with this message, to the end. */
|
|
16
|
+
fromItemId: string | null;
|
|
17
|
+
/** The session's turns, as the last estimate listed them. */
|
|
18
|
+
turns: ExportTurnInfo[];
|
|
19
|
+
selecting: boolean;
|
|
20
|
+
pick: { from: number | null; to: number | null };
|
|
21
|
+
}>({
|
|
22
|
+
open: false,
|
|
23
|
+
sessionId: null,
|
|
24
|
+
title: "",
|
|
25
|
+
req: { ...DEFAULT_REQUEST },
|
|
26
|
+
fromItemId: null,
|
|
27
|
+
turns: [],
|
|
28
|
+
selecting: false,
|
|
29
|
+
pick: { from: null, to: null },
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
export const exportUiActions = {
|
|
33
|
+
open(sessionId: string, title: string, fromItemId: string | null = null) {
|
|
34
|
+
if (exportUi.sessionId !== sessionId) {
|
|
35
|
+
exportUi.turns = [];
|
|
36
|
+
exportUi.req = { ...exportUi.req, range: undefined };
|
|
37
|
+
}
|
|
38
|
+
exportUi.sessionId = sessionId;
|
|
39
|
+
exportUi.title = title;
|
|
40
|
+
exportUi.fromItemId = fromItemId;
|
|
41
|
+
exportUi.open = true;
|
|
42
|
+
},
|
|
43
|
+
close() {
|
|
44
|
+
exportUi.open = false;
|
|
45
|
+
exportUi.selecting = false;
|
|
46
|
+
exportUi.fromItemId = null;
|
|
47
|
+
},
|
|
48
|
+
setRequest(patch: Partial<ExportRequest>) {
|
|
49
|
+
exportUi.req = { ...exportUi.req, ...patch };
|
|
50
|
+
},
|
|
51
|
+
/** Close the sheet and let the chat's user messages be ticked. */
|
|
52
|
+
startSelect(from: number | null = null) {
|
|
53
|
+
exportUi.open = false;
|
|
54
|
+
exportUi.selecting = true;
|
|
55
|
+
exportUi.pick = { from, to: null };
|
|
56
|
+
},
|
|
57
|
+
/** A tap on a turn: the first sets the start, the second the end. */
|
|
58
|
+
tap(n: number) {
|
|
59
|
+
const { from, to } = exportUi.pick;
|
|
60
|
+
exportUi.pick =
|
|
61
|
+
from === null || to !== null ? { from: n, to: null } : { from, to: n };
|
|
62
|
+
},
|
|
63
|
+
/** Use the ticked turns as the range and go back to the sheet. */
|
|
64
|
+
finishSelect() {
|
|
65
|
+
const { from, to } = exportUi.pick;
|
|
66
|
+
if (from !== null) {
|
|
67
|
+
const end = to ?? from;
|
|
68
|
+
exportUi.req = {
|
|
69
|
+
...exportUi.req,
|
|
70
|
+
range: { from: Math.min(from, end), to: Math.max(from, end) },
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
exportUi.selecting = false;
|
|
74
|
+
exportUi.open = true;
|
|
75
|
+
},
|
|
76
|
+
cancelSelect() {
|
|
77
|
+
exportUi.selecting = false;
|
|
78
|
+
exportUi.open = true;
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/** The ticked turns, ordered: null until something is ticked. */
|
|
83
|
+
export function pickedRange(pick: { from: number | null; to: number | null }) {
|
|
84
|
+
if (pick.from === null) return null;
|
|
85
|
+
const end = pick.to ?? pick.from;
|
|
86
|
+
return { from: Math.min(pick.from, end), to: Math.max(pick.from, end) };
|
|
87
|
+
}
|
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
import { NextResponse } from "next/server";
|
|
2
|
-
import { db, queries, type Group } from "@/lib/db";
|
|
3
|
-
|
|
4
|
-
// GET /api/groups/[...path] - Get a single group
|
|
5
|
-
export async function GET(
|
|
6
|
-
request: Request,
|
|
7
|
-
{ params }: { params: Promise<{ path: string[] }> }
|
|
8
|
-
) {
|
|
9
|
-
const { path: pathParts } = await params;
|
|
10
|
-
const path = pathParts.join("/");
|
|
11
|
-
|
|
12
|
-
try {
|
|
13
|
-
const group = queries.getGroup(db).get(path) as Group | undefined;
|
|
14
|
-
|
|
15
|
-
if (!group) {
|
|
16
|
-
return NextResponse.json({ error: "Group not found" }, { status: 404 });
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
return NextResponse.json({
|
|
20
|
-
group: { ...group, expanded: Boolean(group.expanded) },
|
|
21
|
-
});
|
|
22
|
-
} catch (error) {
|
|
23
|
-
console.error("Error fetching group:", error);
|
|
24
|
-
return NextResponse.json(
|
|
25
|
-
{ error: "Failed to fetch group" },
|
|
26
|
-
{ status: 500 }
|
|
27
|
-
);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// PATCH /api/groups/[...path] - Update group (name, expanded, order)
|
|
32
|
-
export async function PATCH(
|
|
33
|
-
request: Request,
|
|
34
|
-
{ params }: { params: Promise<{ path: string[] }> }
|
|
35
|
-
) {
|
|
36
|
-
const { path: pathParts } = await params;
|
|
37
|
-
const path = pathParts.join("/");
|
|
38
|
-
|
|
39
|
-
try {
|
|
40
|
-
const body = await request.json();
|
|
41
|
-
const { name, expanded, sort_order } = body;
|
|
42
|
-
|
|
43
|
-
// Check if group exists
|
|
44
|
-
const group = queries.getGroup(db).get(path) as Group | undefined;
|
|
45
|
-
if (!group) {
|
|
46
|
-
return NextResponse.json({ error: "Group not found" }, { status: 404 });
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Protect default group from being renamed
|
|
50
|
-
if (path === "sessions" && name !== undefined && name !== "Sessions") {
|
|
51
|
-
return NextResponse.json(
|
|
52
|
-
{ error: "Cannot rename the default group" },
|
|
53
|
-
{ status: 400 }
|
|
54
|
-
);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Update name
|
|
58
|
-
if (name !== undefined) {
|
|
59
|
-
queries.updateGroupName(db).run(name, path);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Update expanded state
|
|
63
|
-
if (expanded !== undefined) {
|
|
64
|
-
queries.updateGroupExpanded(db).run(expanded ? 1 : 0, path);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Update sort order
|
|
68
|
-
if (sort_order !== undefined) {
|
|
69
|
-
queries.updateGroupOrder(db).run(sort_order, path);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const updatedGroup = queries.getGroup(db).get(path) as Group;
|
|
73
|
-
return NextResponse.json({
|
|
74
|
-
group: { ...updatedGroup, expanded: Boolean(updatedGroup.expanded) },
|
|
75
|
-
});
|
|
76
|
-
} catch (error) {
|
|
77
|
-
console.error("Error updating group:", error);
|
|
78
|
-
return NextResponse.json(
|
|
79
|
-
{ error: "Failed to update group" },
|
|
80
|
-
{ status: 500 }
|
|
81
|
-
);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// DELETE /api/groups/[...path] - Delete group (moves sessions to parent or default)
|
|
86
|
-
export async function DELETE(
|
|
87
|
-
request: Request,
|
|
88
|
-
{ params }: { params: Promise<{ path: string[] }> }
|
|
89
|
-
) {
|
|
90
|
-
const { path: pathParts } = await params;
|
|
91
|
-
const path = pathParts.join("/");
|
|
92
|
-
|
|
93
|
-
try {
|
|
94
|
-
// Protect default group
|
|
95
|
-
if (path === "sessions") {
|
|
96
|
-
return NextResponse.json(
|
|
97
|
-
{ error: "Cannot delete the default group" },
|
|
98
|
-
{ status: 400 }
|
|
99
|
-
);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Check if group exists
|
|
103
|
-
const group = queries.getGroup(db).get(path) as Group | undefined;
|
|
104
|
-
if (!group) {
|
|
105
|
-
return NextResponse.json({ error: "Group not found" }, { status: 404 });
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// Find parent group or use default
|
|
109
|
-
const pathParts2 = path.split("/");
|
|
110
|
-
pathParts2.pop();
|
|
111
|
-
const parentPath =
|
|
112
|
-
pathParts2.length > 0 ? pathParts2.join("/") : "sessions";
|
|
113
|
-
|
|
114
|
-
// Move all sessions in this group to parent
|
|
115
|
-
queries.moveSessionsToGroup(db).run(parentPath, path);
|
|
116
|
-
|
|
117
|
-
// Also move sessions from any subgroups
|
|
118
|
-
const allGroups = queries.getAllGroups(db).all() as Group[];
|
|
119
|
-
const subgroups = allGroups.filter((g) => g.path.startsWith(path + "/"));
|
|
120
|
-
for (const subgroup of subgroups) {
|
|
121
|
-
queries.moveSessionsToGroup(db).run(parentPath, subgroup.path);
|
|
122
|
-
queries.deleteGroup(db).run(subgroup.path);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// Delete the group
|
|
126
|
-
queries.deleteGroup(db).run(path);
|
|
127
|
-
|
|
128
|
-
return NextResponse.json({ success: true, movedTo: parentPath });
|
|
129
|
-
} catch (error) {
|
|
130
|
-
console.error("Error deleting group:", error);
|
|
131
|
-
return NextResponse.json(
|
|
132
|
-
{ error: "Failed to delete group" },
|
|
133
|
-
{ status: 500 }
|
|
134
|
-
);
|
|
135
|
-
}
|
|
136
|
-
}
|
package/app/api/groups/route.ts
DELETED
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
import { NextResponse } from "next/server";
|
|
2
|
-
import { db, queries, type Group } from "@/lib/db";
|
|
3
|
-
|
|
4
|
-
// GET /api/groups - List all groups
|
|
5
|
-
export async function GET() {
|
|
6
|
-
try {
|
|
7
|
-
const groups = queries.getAllGroups(db).all() as Group[];
|
|
8
|
-
|
|
9
|
-
// Convert expanded from 0/1 to boolean
|
|
10
|
-
const formattedGroups = groups.map((g) => ({
|
|
11
|
-
...g,
|
|
12
|
-
expanded: Boolean(g.expanded),
|
|
13
|
-
}));
|
|
14
|
-
|
|
15
|
-
return NextResponse.json({ groups: formattedGroups });
|
|
16
|
-
} catch (error) {
|
|
17
|
-
console.error("Error fetching groups:", error);
|
|
18
|
-
return NextResponse.json(
|
|
19
|
-
{ error: "Failed to fetch groups" },
|
|
20
|
-
{ status: 500 }
|
|
21
|
-
);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// POST /api/groups - Create a new group
|
|
26
|
-
export async function POST(request: Request) {
|
|
27
|
-
try {
|
|
28
|
-
const body = await request.json();
|
|
29
|
-
const { name, parentPath } = body;
|
|
30
|
-
|
|
31
|
-
if (!name || typeof name !== "string") {
|
|
32
|
-
return NextResponse.json({ error: "Name is required" }, { status: 400 });
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Sanitize name to create path
|
|
36
|
-
const sanitizedName = name
|
|
37
|
-
.toLowerCase()
|
|
38
|
-
.replace(/[^a-z0-9-]/g, "-")
|
|
39
|
-
.replace(/-+/g, "-")
|
|
40
|
-
.replace(/^-|-$/g, "");
|
|
41
|
-
|
|
42
|
-
if (!sanitizedName) {
|
|
43
|
-
return NextResponse.json(
|
|
44
|
-
{ error: "Invalid group name" },
|
|
45
|
-
{ status: 400 }
|
|
46
|
-
);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Build full path
|
|
50
|
-
const path = parentPath ? `${parentPath}/${sanitizedName}` : sanitizedName;
|
|
51
|
-
|
|
52
|
-
// Check if group already exists
|
|
53
|
-
const existing = queries.getGroup(db).get(path) as Group | undefined;
|
|
54
|
-
if (existing) {
|
|
55
|
-
return NextResponse.json(
|
|
56
|
-
{ error: "Group already exists", group: existing },
|
|
57
|
-
{ status: 409 }
|
|
58
|
-
);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// If parent path specified, ensure parent exists
|
|
62
|
-
if (parentPath) {
|
|
63
|
-
const parent = queries.getGroup(db).get(parentPath) as Group | undefined;
|
|
64
|
-
if (!parent) {
|
|
65
|
-
return NextResponse.json(
|
|
66
|
-
{ error: "Parent group does not exist" },
|
|
67
|
-
{ status: 400 }
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Get max sort order for new group
|
|
73
|
-
const groups = queries.getAllGroups(db).all() as Group[];
|
|
74
|
-
const maxOrder = groups.reduce((max, g) => Math.max(max, g.sort_order), 0);
|
|
75
|
-
|
|
76
|
-
// Create the group
|
|
77
|
-
queries.createGroup(db).run(path, name, maxOrder + 1);
|
|
78
|
-
|
|
79
|
-
const newGroup = queries.getGroup(db).get(path) as Group;
|
|
80
|
-
return NextResponse.json(
|
|
81
|
-
{
|
|
82
|
-
group: { ...newGroup, expanded: Boolean(newGroup.expanded) },
|
|
83
|
-
},
|
|
84
|
-
{ status: 201 }
|
|
85
|
-
);
|
|
86
|
-
} catch (error) {
|
|
87
|
-
console.error("Error creating group:", error);
|
|
88
|
-
return NextResponse.json(
|
|
89
|
-
{ error: "Failed to create group" },
|
|
90
|
-
{ status: 500 }
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
}
|
package/data/groups/index.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { useToggleGroup, useCreateGroup, useDeleteGroup } from "./mutations";
|
package/data/groups/mutations.ts
DELETED
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
2
|
-
import { sessionKeys } from "../sessions/keys";
|
|
3
|
-
|
|
4
|
-
export function useToggleGroup() {
|
|
5
|
-
const queryClient = useQueryClient();
|
|
6
|
-
|
|
7
|
-
return useMutation({
|
|
8
|
-
mutationFn: async ({
|
|
9
|
-
path,
|
|
10
|
-
expanded,
|
|
11
|
-
}: {
|
|
12
|
-
path: string;
|
|
13
|
-
expanded: boolean;
|
|
14
|
-
}) => {
|
|
15
|
-
const res = await fetch(`/api/groups/${encodeURIComponent(path)}`, {
|
|
16
|
-
method: "PATCH",
|
|
17
|
-
headers: { "Content-Type": "application/json" },
|
|
18
|
-
body: JSON.stringify({ expanded }),
|
|
19
|
-
});
|
|
20
|
-
if (!res.ok) throw new Error("Failed to toggle group");
|
|
21
|
-
return res.json();
|
|
22
|
-
},
|
|
23
|
-
onMutate: async ({ path, expanded }) => {
|
|
24
|
-
await queryClient.cancelQueries({ queryKey: sessionKeys.list() });
|
|
25
|
-
const previous = queryClient.getQueryData(sessionKeys.list());
|
|
26
|
-
queryClient.setQueryData(
|
|
27
|
-
sessionKeys.list(),
|
|
28
|
-
(
|
|
29
|
-
old:
|
|
30
|
-
| {
|
|
31
|
-
sessions: unknown[];
|
|
32
|
-
groups: Array<{ path: string; expanded: boolean }>;
|
|
33
|
-
}
|
|
34
|
-
| undefined
|
|
35
|
-
) =>
|
|
36
|
-
old
|
|
37
|
-
? {
|
|
38
|
-
...old,
|
|
39
|
-
groups: old.groups.map((g) =>
|
|
40
|
-
g.path === path ? { ...g, expanded } : g
|
|
41
|
-
),
|
|
42
|
-
}
|
|
43
|
-
: old
|
|
44
|
-
);
|
|
45
|
-
return { previous };
|
|
46
|
-
},
|
|
47
|
-
onError: (_, __, context) => {
|
|
48
|
-
if (context?.previous) {
|
|
49
|
-
queryClient.setQueryData(sessionKeys.list(), context.previous);
|
|
50
|
-
}
|
|
51
|
-
},
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export function useCreateGroup() {
|
|
56
|
-
const queryClient = useQueryClient();
|
|
57
|
-
|
|
58
|
-
return useMutation({
|
|
59
|
-
mutationFn: async ({
|
|
60
|
-
name,
|
|
61
|
-
parentPath,
|
|
62
|
-
}: {
|
|
63
|
-
name: string;
|
|
64
|
-
parentPath?: string;
|
|
65
|
-
}) => {
|
|
66
|
-
const res = await fetch("/api/groups", {
|
|
67
|
-
method: "POST",
|
|
68
|
-
headers: { "Content-Type": "application/json" },
|
|
69
|
-
body: JSON.stringify({ name, parentPath }),
|
|
70
|
-
});
|
|
71
|
-
if (!res.ok) throw new Error("Failed to create group");
|
|
72
|
-
return res.json();
|
|
73
|
-
},
|
|
74
|
-
onSuccess: () => {
|
|
75
|
-
queryClient.invalidateQueries({ queryKey: sessionKeys.list() });
|
|
76
|
-
},
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export function useDeleteGroup() {
|
|
81
|
-
const queryClient = useQueryClient();
|
|
82
|
-
|
|
83
|
-
return useMutation({
|
|
84
|
-
mutationFn: async (path: string) => {
|
|
85
|
-
const res = await fetch(`/api/groups/${encodeURIComponent(path)}`, {
|
|
86
|
-
method: "DELETE",
|
|
87
|
-
});
|
|
88
|
-
if (!res.ok) throw new Error("Failed to delete group");
|
|
89
|
-
return res.json();
|
|
90
|
-
},
|
|
91
|
-
onSuccess: () => {
|
|
92
|
-
queryClient.invalidateQueries({ queryKey: sessionKeys.list() });
|
|
93
|
-
},
|
|
94
|
-
});
|
|
95
|
-
}
|
package/hooks/useGroups.ts
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { useCallback } from "react";
|
|
2
|
-
import { useToggleGroup, useCreateGroup, useDeleteGroup } from "@/data/groups";
|
|
3
|
-
|
|
4
|
-
export function useGroups() {
|
|
5
|
-
const toggleMutation = useToggleGroup();
|
|
6
|
-
const createMutation = useCreateGroup();
|
|
7
|
-
const deleteMutation = useDeleteGroup();
|
|
8
|
-
|
|
9
|
-
const toggleGroup = useCallback(
|
|
10
|
-
async (path: string, expanded: boolean) => {
|
|
11
|
-
await toggleMutation.mutateAsync({ path, expanded });
|
|
12
|
-
},
|
|
13
|
-
[toggleMutation]
|
|
14
|
-
);
|
|
15
|
-
|
|
16
|
-
const createGroup = useCallback(
|
|
17
|
-
async (name: string, parentPath?: string) => {
|
|
18
|
-
await createMutation.mutateAsync({ name, parentPath });
|
|
19
|
-
},
|
|
20
|
-
[createMutation]
|
|
21
|
-
);
|
|
22
|
-
|
|
23
|
-
const deleteGroup = useCallback(
|
|
24
|
-
async (path: string) => {
|
|
25
|
-
if (!confirm("Delete this group? Sessions will be moved to parent."))
|
|
26
|
-
return;
|
|
27
|
-
await deleteMutation.mutateAsync(path);
|
|
28
|
-
},
|
|
29
|
-
[deleteMutation]
|
|
30
|
-
);
|
|
31
|
-
|
|
32
|
-
return {
|
|
33
|
-
toggleGroup,
|
|
34
|
-
createGroup,
|
|
35
|
-
deleteGroup,
|
|
36
|
-
};
|
|
37
|
-
}
|