@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
package/scripts/lib/commands.sh
CHANGED
|
@@ -56,6 +56,7 @@ cmd_install() {
|
|
|
56
56
|
|
|
57
57
|
mkdir -p "$bin_dir"
|
|
58
58
|
ln -sf "$REPO_DIR/scripts/agent-os" "$bin_dir/agent-os"
|
|
59
|
+
"$REPO_DIR/scripts/sync-cli-links" create
|
|
59
60
|
|
|
60
61
|
# Check if ~/.local/bin is in PATH
|
|
61
62
|
if [[ ":$PATH:" != *":$bin_dir:"* ]]; then
|
|
@@ -304,6 +305,8 @@ cmd_update() {
|
|
|
304
305
|
log_success "Update complete!"
|
|
305
306
|
fi
|
|
306
307
|
|
|
308
|
+
scripts/sync-cli-links || log_warn "Could not sync the CLI links"
|
|
309
|
+
|
|
307
310
|
if [[ "$was_running" == true ]]; then
|
|
308
311
|
cmd_start
|
|
309
312
|
fi
|
|
@@ -558,6 +561,8 @@ cmd_help() {
|
|
|
558
561
|
echo " pair Add a phone, tablet or laptop (prints a QR code)"
|
|
559
562
|
echo " connect Turn on AgentOS Connect for this machine (in development)"
|
|
560
563
|
echo " disconnect Turn Connect off; the tunnel closes within seconds"
|
|
564
|
+
echo " link Link machines on your Connect account (link <name>, link accept <code>)"
|
|
565
|
+
echo " unlink Unlink a machine linked through Connect"
|
|
561
566
|
echo " passkeys reset Revoke every passkey (recovery; asks you to confirm)"
|
|
562
567
|
echo " logs Tail server logs"
|
|
563
568
|
echo " update Update to latest version"
|
package/scripts/lib/machine.sh
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# `agent-os machine`: make this Linux box (a WSL distro, a DGX Spark) linkable
|
|
3
|
-
# from another AgentOS
|
|
4
|
-
#
|
|
3
|
+
# from another AgentOS. Bare, it explains the Connect path (sign in to Connect,
|
|
4
|
+
# then `agent-os link`) and changes nothing. With --tailscale it sets up
|
|
5
|
+
# sshd for pairing, Tailscale for reaching it and the server kept running. It
|
|
6
|
+
# never edits sshd_config or authorized_keys.
|
|
5
7
|
|
|
6
8
|
# The ssh service's unit name differs by distro (Debian: ssh, others: sshd).
|
|
7
9
|
ssh_unit() {
|
|
@@ -21,7 +23,26 @@ tailscale_name() {
|
|
|
21
23
|
| head -1 | sed 's/\.$//'
|
|
22
24
|
}
|
|
23
25
|
|
|
26
|
+
machine_connect_help() {
|
|
27
|
+
log_info "Recommended: link through AgentOS Connect. It works from any network and needs no ssh or Tailscale (Pro)."
|
|
28
|
+
echo " 1. On this machine: agent-os connect (sign in with the code from runagentos.com)"
|
|
29
|
+
echo " 2. On the other one: open Machines and press Link next to this machine"
|
|
30
|
+
echo " (or run 'agent-os link <name>'). It shows a 6-digit code; type it here:"
|
|
31
|
+
echo " agent-os link accept <code>"
|
|
32
|
+
echo " 3. This machine then shows a second code. Type it back on the machine you're"
|
|
33
|
+
echo " linking from (in Machines, or at the 'agent-os link' prompt) to finish."
|
|
34
|
+
echo ""
|
|
35
|
+
log_info "No Connect? Use ssh over Tailscale instead: agent-os machine --tailscale"
|
|
36
|
+
}
|
|
37
|
+
|
|
24
38
|
cmd_machine() {
|
|
39
|
+
local tailscale=0 arg
|
|
40
|
+
for arg in "$@"; do
|
|
41
|
+
case "$arg" in
|
|
42
|
+
--tailscale) tailscale=1 ;;
|
|
43
|
+
*) log_error "Unknown option '$arg'. Usage: agent-os machine [--tailscale]"; exit 1 ;;
|
|
44
|
+
esac
|
|
45
|
+
done
|
|
25
46
|
if [[ "$OS" == "macos" ]]; then
|
|
26
47
|
log_info "A Mac is linked from the other side: on the machine you'll link from, open Machines and add this Mac."
|
|
27
48
|
return 0
|
|
@@ -30,6 +51,10 @@ cmd_machine() {
|
|
|
30
51
|
log_error "AgentOS is not installed. Run 'agent-os install' first."
|
|
31
52
|
exit 1
|
|
32
53
|
fi
|
|
54
|
+
if [[ "$tailscale" == 0 ]]; then
|
|
55
|
+
machine_connect_help
|
|
56
|
+
return 0
|
|
57
|
+
fi
|
|
33
58
|
if ! has_systemd; then
|
|
34
59
|
if [[ "${WSL:-0}" != 0 ]]; then
|
|
35
60
|
log_error "This needs systemd. Run 'agent-os enable', which offers to turn it on, then try again."
|
|
@@ -42,7 +42,11 @@ const log = () =>
|
|
|
42
42
|
const record = (name: string) =>
|
|
43
43
|
fake(name, `echo "${name} $*" >> "${dir}/calls.log"`);
|
|
44
44
|
|
|
45
|
-
function machine(
|
|
45
|
+
function machine(
|
|
46
|
+
env: Record<string, string> = {},
|
|
47
|
+
os = "debian",
|
|
48
|
+
args = "--tailscale"
|
|
49
|
+
) {
|
|
46
50
|
mkdirSync(join(dir, "repo"), { recursive: true });
|
|
47
51
|
return spawnSync(
|
|
48
52
|
"/bin/bash",
|
|
@@ -50,7 +54,7 @@ function machine(env: Record<string, string> = {}, os = "debian") {
|
|
|
50
54
|
"-euo",
|
|
51
55
|
"pipefail",
|
|
52
56
|
"-c",
|
|
53
|
-
`source "${LIB}/common.sh"; source "${LIB}/prerequisites.sh"; source "${LIB}/commands.sh"; source "${LIB}/machine.sh"; WSL=2 OS=${os} REPO_DIR="${dir}/repo" PORT=3011; has_systemd() { return 0; }; is_running() { return 0; }; cmd_enable() { echo "enable called"; }; cmd_machine`,
|
|
57
|
+
`source "${LIB}/common.sh"; source "${LIB}/prerequisites.sh"; source "${LIB}/commands.sh"; source "${LIB}/machine.sh"; WSL=2 OS=${os} REPO_DIR="${dir}/repo" PORT=3011; has_systemd() { return 0; }; is_running() { return 0; }; cmd_enable() { echo "enable called"; }; cmd_machine ${args}`,
|
|
54
58
|
join(dir, "agent-os"),
|
|
55
59
|
],
|
|
56
60
|
{
|
|
@@ -86,6 +90,24 @@ beforeEach(() => {
|
|
|
86
90
|
afterEach(() => rmSync(dir, { recursive: true, force: true }));
|
|
87
91
|
|
|
88
92
|
describe("agent-os machine", () => {
|
|
93
|
+
it("bare, points at Connect first, mentions --tailscale and changes nothing", () => {
|
|
94
|
+
const r = machine({}, "debian", "");
|
|
95
|
+
expect(r.status).toBe(0);
|
|
96
|
+
expect(r.stdout).toContain("agent-os connect");
|
|
97
|
+
expect(r.stdout).toContain("agent-os link accept");
|
|
98
|
+
expect(r.stdout).toContain("second code");
|
|
99
|
+
expect(r.stdout).toContain("agent-os machine --tailscale");
|
|
100
|
+
expect(r.stdout).not.toContain("enable called");
|
|
101
|
+
expect(log()).toBe("");
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("rejects an unknown option", () => {
|
|
105
|
+
const r = machine({}, "debian", "--bogus");
|
|
106
|
+
expect(r.status).toBe(1);
|
|
107
|
+
expect(r.stdout + r.stderr).toContain("Usage: agent-os machine");
|
|
108
|
+
expect(log()).toBe("");
|
|
109
|
+
});
|
|
110
|
+
|
|
89
111
|
it("stops with the install pointer when Tailscale is missing", () => {
|
|
90
112
|
const r = machine();
|
|
91
113
|
expect(r.status).toBe(1);
|
|
@@ -128,7 +150,7 @@ describe("agent-os machine", () => {
|
|
|
128
150
|
"/bin/bash",
|
|
129
151
|
[
|
|
130
152
|
"-c",
|
|
131
|
-
`source "${LIB}/common.sh"; source "${LIB}/machine.sh"; WSL=2 OS=debian REPO_DIR="${dir}/repo"; has_systemd() { return 1; }; cmd_machine`,
|
|
153
|
+
`source "${LIB}/common.sh"; source "${LIB}/machine.sh"; WSL=2 OS=debian REPO_DIR="${dir}/repo"; has_systemd() { return 1; }; cmd_machine --tailscale`,
|
|
132
154
|
],
|
|
133
155
|
{
|
|
134
156
|
encoding: "utf8",
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Point ~/.local/bin/{agent-os,aos} at the checkout the live server runs from.
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
here="$(cd "$(dirname "$0")/.." && pwd)"
|
|
5
|
+
# shellcheck source=lib/cli-links.sh
|
|
6
|
+
source "$here/scripts/lib/cli-links.sh"
|
|
7
|
+
sync_cli_links "$(live_checkout "$here")" "${1:-}"
|
|
@@ -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
|
@@ -14,12 +14,14 @@ import { setStartGate } from "./lib/stacks/tick";
|
|
|
14
14
|
import { stackStartGate } from "./lib/orchestrator/brakes";
|
|
15
15
|
import { buildId } from "./lib/build";
|
|
16
16
|
import { startOrchestratorWatcher } from "./lib/orchestrator/watcher";
|
|
17
|
+
import { startWaitWatcher } from "./lib/bus/waits/tick";
|
|
17
18
|
import { installErrorCounter } from "./lib/health/errors";
|
|
18
19
|
import { recordBoot } from "./lib/health/boots";
|
|
19
20
|
import { realDeps, schedulesEnabled, startScheduler } from "./lib/schedules";
|
|
20
21
|
import { startSettling } from "./lib/recipes";
|
|
21
22
|
import { startAutoArchive } from "./lib/done/auto-archive";
|
|
22
23
|
import { startWorktreeSweep } from "./lib/worktree-sweep/start";
|
|
24
|
+
import { startClaimsLoop } from "./lib/claims";
|
|
23
25
|
import { startUsageRollup, usageRollupEnabled } from "./lib/usage/nightly";
|
|
24
26
|
import { resumePhoneOutbox } from "./lib/notify";
|
|
25
27
|
import { resumePushOutbox } from "./lib/notify/push";
|
|
@@ -476,6 +478,8 @@ app.prepare().then(async () => {
|
|
|
476
478
|
if (process.env.AGENTOS_STACKS !== "off") startStackWatcher();
|
|
477
479
|
// Each workspace's orchestrator hears about its sessions as events.
|
|
478
480
|
if (process.env.AGENTOS_ORCHESTRATOR !== "off") startOrchestratorWatcher();
|
|
481
|
+
// Sessions waiting on each other (aos send --await, aos wait).
|
|
482
|
+
startWaitWatcher();
|
|
479
483
|
// Schedules tick here, once a minute, and nowhere else.
|
|
480
484
|
if (schedulesEnabled(process.env)) startScheduler(realDeps);
|
|
481
485
|
// Looking for a newer version, and running an update only when one is
|
|
@@ -494,6 +498,8 @@ app.prepare().then(async () => {
|
|
|
494
498
|
startAutoArchive();
|
|
495
499
|
// Settled worktrees older than the setting are removed; branches stay.
|
|
496
500
|
startWorktreeSweep();
|
|
501
|
+
// Who is working on what, across linked machines.
|
|
502
|
+
if (!process.env.AGENTOS_DEMO) startClaimsLoop();
|
|
497
503
|
resumePhoneOutbox();
|
|
498
504
|
resumePushOutbox();
|
|
499
505
|
// 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
|
+
};
|
|
@@ -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
|
-
}
|