@saadnvd1/agent-os 0.3.606 → 0.3.609
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/app/api/busy/route.ts +2 -1
- package/app/api/updates/route.test.ts +3 -1
- package/components/Chat/ContextMeter.tsx +2 -2
- package/components/Chat/composer/useCoarsePointer.ts +2 -14
- package/components/PrStageBadge.tsx +4 -4
- package/components/SessionList/LiveLine.tsx +0 -4
- package/components/SessionList/OrchestratorPinRow.tsx +3 -3
- package/components/SessionList/RowMenuParts.tsx +90 -0
- package/components/SessionList/RowShell.tsx +6 -2
- package/components/SessionList/SessionRowMenu.tsx +241 -377
- package/components/Stacks/StatusChip.tsx +9 -33
- package/components/Stacks/status-chip.test.ts +40 -0
- package/components/Stacks/status-chip.ts +44 -0
- package/components/ui/context-menu.tsx +4 -2
- package/components/ui/dropdown-menu.tsx +5 -2
- package/components/ui/menu-fit.test.ts +36 -0
- package/hooks/useCompactMenu.ts +9 -0
- package/hooks/useMediaQuery.ts +20 -0
- package/lib/busy.ts +9 -3
- package/lib/hosts/remote-menu.ts +4 -0
- package/lib/recipes/status-labels.ts +2 -2
- package/lib/resurrect/index.ts +2 -1
- package/lib/sidebar/live-status.ts +7 -14
- package/lib/sidebar/row-menu.ts +168 -0
- package/lib/simulator/index.ts +6 -1
- package/lib/tasks/brief.ts +16 -1
- package/lib/tasks/import.ts +6 -2
- package/lib/tasks/index.ts +2 -1
- package/lib/tasks/move.ts +2 -1
- package/package.json +1 -1
- package/scripts/autodeploy +28 -0
- package/scripts/autodeploy.test.ts +106 -0
- package/scripts/wait-quiet +1 -1
package/lib/tasks/move.ts
CHANGED
|
@@ -13,7 +13,7 @@ import { db, type Session } from "../db";
|
|
|
13
13
|
import { getProject } from "../projects";
|
|
14
14
|
import { endTmuxSession } from "../tmux/end";
|
|
15
15
|
import { launchClaude } from "../agents/launch";
|
|
16
|
-
import { buildTaskBrief } from "./brief";
|
|
16
|
+
import { buildTaskBrief, declaredTest } from "./brief";
|
|
17
17
|
import { git as runGit } from "../git-exec";
|
|
18
18
|
import { getTaskSession, isoUTC } from "./session";
|
|
19
19
|
import { projectRef } from "./project-ref";
|
|
@@ -308,6 +308,7 @@ async function relaunch(session: Session, note?: string): Promise<void> {
|
|
|
308
308
|
? buildTaskBrief({
|
|
309
309
|
branch: session.branch_name,
|
|
310
310
|
baseBranch: session.base_branch ?? "main",
|
|
311
|
+
test: declaredTest(session.working_directory),
|
|
311
312
|
})
|
|
312
313
|
: undefined,
|
|
313
314
|
});
|
package/package.json
CHANGED
package/scripts/autodeploy
CHANGED
|
@@ -4,6 +4,11 @@
|
|
|
4
4
|
# background. Chat turns live in workers and outlive the restart; reviews
|
|
5
5
|
# and task setups don't, so the restart waits up to 10 minutes for them.
|
|
6
6
|
# sm add agentos-autodeploy "scripts/autodeploy" --cron "* * * * *"
|
|
7
|
+
# Restarts are spaced AGENTOS_MIN_GAP seconds apart (10 minutes; 0 turns it
|
|
8
|
+
# off): a head that lands inside the gap waits and is deployed, whatever it
|
|
9
|
+
# has become by then, when the gap ends. A commit message with [deploy-now]
|
|
10
|
+
# (in any commit the deploy would take) skips the gap. Each hold logs one line:
|
|
11
|
+
# HELD <sha> <seconds> left in the restart gap (since <last restart sha>)
|
|
7
12
|
# Another machine (a server) runs it with AGENTOS_PUBLISH=0: deploy
|
|
8
13
|
# only, and its own AGENTOS_RESTART. Only one machine publishes to npm.
|
|
9
14
|
set -euo pipefail
|
|
@@ -44,6 +49,28 @@ if [ "$head" != "$deployed" ] && [ "$settle" -gt 0 ] &&
|
|
|
44
49
|
exit 0
|
|
45
50
|
fi
|
|
46
51
|
|
|
52
|
+
# Restarts kill simulator starts and reviews in flight, so they are spaced
|
|
53
|
+
# apart. The head isn't recorded as deployed while it waits, so it (or a
|
|
54
|
+
# newer one) goes out as soon as the gap is over; nothing is dropped.
|
|
55
|
+
gap="${AGENTOS_MIN_GAP:-600}"
|
|
56
|
+
if [ "$head" != "$deployed" ] && [ "$gap" -gt 0 ]; then
|
|
57
|
+
last=$(cat "$state/restarted" 2>/dev/null || echo 0)
|
|
58
|
+
left=$(( last + gap - $(date +%s) ))
|
|
59
|
+
if [ "$left" -gt 0 ]; then
|
|
60
|
+
msgs=$(git log --format=%B "${deployed:+$deployed..}$head" 2>/dev/null || git log -1 --format=%B "$head")
|
|
61
|
+
if printf '%s' "$msgs" | grep -qF '[deploy-now]'; then
|
|
62
|
+
log "Restart gap skipped for ${head:0:7}: [deploy-now]"
|
|
63
|
+
else
|
|
64
|
+
# Once per head, not once per minute, so the log counts holds.
|
|
65
|
+
if [ "$(cat "$state/held" 2>/dev/null)" != "$head" ]; then
|
|
66
|
+
echo "$head" > "$state/held"
|
|
67
|
+
log "HELD ${head:0:7} ${left}s left in the ${gap}s restart gap"
|
|
68
|
+
fi
|
|
69
|
+
exit 0
|
|
70
|
+
fi
|
|
71
|
+
fi
|
|
72
|
+
fi
|
|
73
|
+
|
|
47
74
|
# A deploy that can't land says so once per commit, rather than retrying
|
|
48
75
|
# quietly every minute (uncommitted edits in this checkout block the pull).
|
|
49
76
|
alert() {
|
|
@@ -70,6 +97,7 @@ if [ "$head" != "$deployed" ]; then
|
|
|
70
97
|
fi
|
|
71
98
|
# The pull takes what landed while it waited, too.
|
|
72
99
|
git rev-parse HEAD > "$state/deployed"
|
|
100
|
+
date +%s > "$state/restarted"
|
|
73
101
|
fi
|
|
74
102
|
|
|
75
103
|
# Publishing waits minutes for npm to serve a version, so it runs on its own
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { execFileSync } from "child_process";
|
|
2
|
+
import * as fs from "fs";
|
|
3
|
+
import * as os from "os";
|
|
4
|
+
import * as path from "path";
|
|
5
|
+
import { describe, expect, it } from "vitest";
|
|
6
|
+
|
|
7
|
+
// Runs the real scripts/autodeploy in a scratch clone with a stub redeploy
|
|
8
|
+
// (a pull) and wait-quiet, against a scratch origin and HOME.
|
|
9
|
+
const script = path.resolve(__dirname, "autodeploy");
|
|
10
|
+
const sh = (cwd: string, ...args: string[]) =>
|
|
11
|
+
execFileSync("git", args, { cwd, encoding: "utf-8" }).trim();
|
|
12
|
+
|
|
13
|
+
function setup() {
|
|
14
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), "aos-autodeploy-"));
|
|
15
|
+
const origin = path.join(root, "origin.git");
|
|
16
|
+
const work = path.join(root, "work");
|
|
17
|
+
const clone = path.join(root, "live");
|
|
18
|
+
const home = path.join(root, "home");
|
|
19
|
+
fs.mkdirSync(home);
|
|
20
|
+
sh(root, "init", "-q", "--bare", "-b", "main", origin);
|
|
21
|
+
sh(root, "clone", "-q", origin, work);
|
|
22
|
+
fs.mkdirSync(path.join(work, "scripts"));
|
|
23
|
+
fs.copyFileSync(script, path.join(work, "scripts/autodeploy"));
|
|
24
|
+
fs.writeFileSync(path.join(work, "scripts/wait-quiet"), "#!/bin/sh\n");
|
|
25
|
+
fs.writeFileSync(
|
|
26
|
+
path.join(work, "scripts/redeploy"),
|
|
27
|
+
"#!/bin/sh\ngit pull -q origin main && echo x >> ../restarts\n"
|
|
28
|
+
);
|
|
29
|
+
fs.chmodSync(path.join(work, "scripts/autodeploy"), 0o755);
|
|
30
|
+
fs.chmodSync(path.join(work, "scripts/wait-quiet"), 0o755);
|
|
31
|
+
fs.chmodSync(path.join(work, "scripts/redeploy"), 0o755);
|
|
32
|
+
const commit = (msg: string) => {
|
|
33
|
+
fs.appendFileSync(path.join(work, "f"), msg + "\n");
|
|
34
|
+
sh(work, "add", "-A");
|
|
35
|
+
sh(work, "-c", "user.name=t", "-c", "user.email=t@t", "commit", "-qm", msg);
|
|
36
|
+
sh(work, "push", "-q", "origin", "main");
|
|
37
|
+
return sh(work, "rev-parse", "HEAD");
|
|
38
|
+
};
|
|
39
|
+
commit("first");
|
|
40
|
+
sh(root, "clone", "-q", origin, clone);
|
|
41
|
+
const state = path.join(home, ".agent-os/autodeploy");
|
|
42
|
+
const run = () =>
|
|
43
|
+
execFileSync(path.join(clone, "scripts/autodeploy"), [], {
|
|
44
|
+
cwd: clone,
|
|
45
|
+
encoding: "utf-8",
|
|
46
|
+
env: {
|
|
47
|
+
...process.env,
|
|
48
|
+
HOME: home,
|
|
49
|
+
AGENTOS_PUBLISH: "0",
|
|
50
|
+
AGENTOS_SETTLE: "0",
|
|
51
|
+
AGENTOS_RESTART: "true",
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
const restarts = () => {
|
|
55
|
+
try {
|
|
56
|
+
return (
|
|
57
|
+
fs.readFileSync(path.join(root, "restarts"), "utf-8").split("\n")
|
|
58
|
+
.length - 1
|
|
59
|
+
);
|
|
60
|
+
} catch {
|
|
61
|
+
return 0;
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
const deployed = () =>
|
|
65
|
+
fs.readFileSync(path.join(state, "deployed"), "utf-8").trim();
|
|
66
|
+
return { commit, run, restarts, deployed, state };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
describe("autodeploy restart gap", () => {
|
|
70
|
+
it("holds a second deploy inside the gap, then deploys the newest head once it ends", () => {
|
|
71
|
+
const t = setup();
|
|
72
|
+
const a = t.commit("a");
|
|
73
|
+
expect(t.run()).toContain("Deploying");
|
|
74
|
+
expect(t.deployed()).toBe(a);
|
|
75
|
+
expect(t.restarts()).toBe(1);
|
|
76
|
+
|
|
77
|
+
t.commit("b");
|
|
78
|
+
const c = t.commit("c");
|
|
79
|
+
const held = t.run();
|
|
80
|
+
expect(held).toMatch(/HELD [0-9a-f]{7} \d+s left in the 600s restart gap/);
|
|
81
|
+
expect(t.deployed()).toBe(a);
|
|
82
|
+
expect(t.restarts()).toBe(1);
|
|
83
|
+
// Still waiting a minute later: no second log line for the same head.
|
|
84
|
+
expect(t.run()).toBe("");
|
|
85
|
+
|
|
86
|
+
// The gap ends: the head that was waiting goes out, once.
|
|
87
|
+
fs.writeFileSync(
|
|
88
|
+
path.join(t.state, "restarted"),
|
|
89
|
+
`${Math.floor(Date.now() / 1000) - 601}\n`
|
|
90
|
+
);
|
|
91
|
+
expect(t.run()).toContain("Deploying");
|
|
92
|
+
expect(t.deployed()).toBe(c);
|
|
93
|
+
expect(t.restarts()).toBe(2);
|
|
94
|
+
expect(t.run()).toBe("");
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("skips the gap for a [deploy-now] commit", () => {
|
|
98
|
+
const t = setup();
|
|
99
|
+
t.commit("a");
|
|
100
|
+
t.run();
|
|
101
|
+
const urgent = t.commit("fix: login loop [deploy-now]");
|
|
102
|
+
expect(t.run()).toContain("[deploy-now]");
|
|
103
|
+
expect(t.deployed()).toBe(urgent);
|
|
104
|
+
expect(t.restarts()).toBe(2);
|
|
105
|
+
});
|
|
106
|
+
});
|
package/scripts/wait-quiet
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# Waits until the AgentOS server on $PORT has nothing in flight that a
|
|
3
|
-
# restart would cut off (reviews, task setups: GET /api/busy), for at most
|
|
3
|
+
# restart would cut off (reviews, task setups, simulator starts: GET /api/busy), for at most
|
|
4
4
|
# AGENTOS_QUIET_WAIT seconds; then returns, so a deploy always lands. A
|
|
5
5
|
# server that doesn't answer (down, or a build without /api/busy) is quiet.
|
|
6
6
|
set -uo pipefail
|