@saadnvd1/agent-os 0.3.694 → 0.3.697
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/components/Settings/NoApprovalsSection.tsx +1 -1
- package/lib/approvals/enable.ts +1 -21
- package/lib/approvals/no-approvals.ts +1 -1
- package/lib/models/plan-step.ts +5 -13
- package/lib/tasks/contract/plan.ts +1 -14
- package/lib/worktree-sweep/testTemp.ts +1 -0
- package/package.json +1 -1
- package/scripts/install-deps +6 -1
- package/scripts/lib/commands.sh +1 -1
- package/scripts/lib/wsl.test.ts +51 -1
|
@@ -9,7 +9,7 @@ import { useApprovalsModeQuery, useSetApprovalsMode } from "@/data/approvals";
|
|
|
9
9
|
export const NO_APPROVALS_WARNING = [
|
|
10
10
|
"Every ask is approved the moment it is raised, hard lines included: public posts and messages, money, irreversible or destructive changes, credentials.",
|
|
11
11
|
"A PR stuck failing its gates, with no CI, or too big to review still merges.",
|
|
12
|
-
"Brakes stop holding new starts
|
|
12
|
+
"Brakes stop holding new starts. Plan-only tasks still wait for approve_plan.",
|
|
13
13
|
"Every agent AgentOS starts runs with its permission prompts off, and prompts already waiting are allowed.",
|
|
14
14
|
];
|
|
15
15
|
|
package/lib/approvals/enable.ts
CHANGED
|
@@ -2,9 +2,7 @@
|
|
|
2
2
|
// brings what already exists in line with it.
|
|
3
3
|
|
|
4
4
|
import { db } from "../db";
|
|
5
|
-
import { isRemoteHost } from "../hosts";
|
|
6
5
|
import { reapplyChatModes } from "../chat/settings";
|
|
7
|
-
import { approvePlanNow } from "../models/plan-step";
|
|
8
6
|
import { autoApproveAsk, type AskRow } from "../orchestrator/asks";
|
|
9
7
|
import { addNote } from "../orchestrator/notes";
|
|
10
8
|
import { listOrchestrators } from "../orchestrator/home";
|
|
@@ -15,7 +13,7 @@ import {
|
|
|
15
13
|
} from "./no-approvals";
|
|
16
14
|
|
|
17
15
|
// Everything already waiting when the mode goes on is approved: open asks,
|
|
18
|
-
//
|
|
16
|
+
// tool cards in live chats.
|
|
19
17
|
async function sweep(): Promise<void> {
|
|
20
18
|
const asks = db
|
|
21
19
|
.prepare(`SELECT * FROM orchestrator_asks WHERE status = 'open'`)
|
|
@@ -27,24 +25,6 @@ async function sweep(): Promise<void> {
|
|
|
27
25
|
console.error(`auto-approving ask ${ask.id} failed:`, e);
|
|
28
26
|
}
|
|
29
27
|
}
|
|
30
|
-
const plans = db
|
|
31
|
-
.prepare(
|
|
32
|
-
`SELECT id, host_id, plan_waits_for_caller FROM session_rows WHERE plan_phase = 'awaiting' AND task_status = 'running'`
|
|
33
|
-
)
|
|
34
|
-
.all() as {
|
|
35
|
-
id: string;
|
|
36
|
-
host_id: string;
|
|
37
|
-
plan_waits_for_caller: number | null;
|
|
38
|
-
}[];
|
|
39
|
-
for (const p of plans) {
|
|
40
|
-
// A plan placed here by another machine is that machine's to decide.
|
|
41
|
-
if (p.plan_waits_for_caller && !isRemoteHost(p.host_id)) continue;
|
|
42
|
-
try {
|
|
43
|
-
await approvePlanNow(p.id, undefined, "no-approvals");
|
|
44
|
-
} catch (e) {
|
|
45
|
-
console.error(`auto-approving plan of ${p.id} failed:`, e);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
28
|
}
|
|
49
29
|
|
|
50
30
|
export async function switchNoApprovals(on: boolean): Promise<void> {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* No approvals mode: while it's on, nothing AgentOS starts or runs waits on
|
|
3
3
|
* a person. Asks are approved as they're raised, brakes let starts through,
|
|
4
|
-
*
|
|
4
|
+
* and agents launch with their harness's
|
|
5
5
|
* permission prompts off. Off by default; app-wide; every point that honours
|
|
6
6
|
* it reads it here and records what it did with recordAutoApproval.
|
|
7
7
|
*/
|
package/lib/models/plan-step.ts
CHANGED
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
} from "./resolve";
|
|
20
20
|
import { ANTHROPIC } from "./roles";
|
|
21
21
|
import { queueEvent } from "../orchestrator/events";
|
|
22
|
-
import {
|
|
22
|
+
import { recordAutoApproval } from "../approvals/no-approvals";
|
|
23
23
|
import { addNote } from "../orchestrator/notes";
|
|
24
24
|
import { amendBrief, scopeChangesOf } from "../orchestrator/amendments";
|
|
25
25
|
import { PIECES_PROMPT, foldedBrief, pieceBrief } from "../plans/pieces";
|
|
@@ -451,15 +451,15 @@ export async function handoffAfterPlan(
|
|
|
451
451
|
deleteQueued(sessionId, planRetryMessageId(sessionId));
|
|
452
452
|
const retried = waiting.get(sessionId)?.retried ?? false;
|
|
453
453
|
forget(sessionId);
|
|
454
|
-
//
|
|
455
|
-
//
|
|
456
|
-
|
|
454
|
+
// A plan-only task never goes on by itself, in No approvals mode too: it
|
|
455
|
+
// was asked to stop and show the plan. Pieces that couldn't be registered
|
|
456
|
+
// always hold a plan-first one: building on a guess is the bug.
|
|
457
457
|
const planText = readPlan(s);
|
|
458
458
|
const pieceErrors =
|
|
459
459
|
(planText && !s.plan_waits_for_caller
|
|
460
460
|
? splitOf(s, planText)?.errors
|
|
461
461
|
: null) ?? [];
|
|
462
|
-
if (
|
|
462
|
+
if (!s.plan_only && !pieceErrors.length) {
|
|
463
463
|
const ref = await startImplementing(
|
|
464
464
|
s,
|
|
465
465
|
"planning",
|
|
@@ -467,14 +467,6 @@ export async function handoffAfterPlan(
|
|
|
467
467
|
undefined,
|
|
468
468
|
retried
|
|
469
469
|
);
|
|
470
|
-
if (ref && auto)
|
|
471
|
-
recordAutoApproval({
|
|
472
|
-
source: "plan",
|
|
473
|
-
workspaceId: sessionWorkspace(s),
|
|
474
|
-
sessionId,
|
|
475
|
-
subject: `plan for ${s.name}`,
|
|
476
|
-
detail: "implementing without waiting for approve_plan",
|
|
477
|
-
});
|
|
478
470
|
return ref !== null;
|
|
479
471
|
}
|
|
480
472
|
if (!advancePlan(db, sessionId, "planning", "awaiting")) return false;
|
|
@@ -16,10 +16,6 @@ import { isRemoteHost } from "../../hosts";
|
|
|
16
16
|
import { peerSource } from "../../hosts/peer-load";
|
|
17
17
|
import { hostLink, requireHostLink } from "../../hosts/remote-api";
|
|
18
18
|
import { LOCAL, machineCapabilities } from "../../load/machines";
|
|
19
|
-
import {
|
|
20
|
-
noApprovalsOn,
|
|
21
|
-
recordAutoApproval,
|
|
22
|
-
} from "../../approvals/no-approvals";
|
|
23
19
|
import { implementArrived, planNoteFor } from "../../models/plan-step";
|
|
24
20
|
import { sessionWorkspace } from "../../models/resolve";
|
|
25
21
|
import { queueEvent } from "../../orchestrator/events";
|
|
@@ -112,8 +108,7 @@ export async function planWritten(
|
|
|
112
108
|
// As for a task planned here: a plan-first task goes on by itself, a
|
|
113
109
|
// plan-only one waits unless no-approvals is on, and pieces that couldn't
|
|
114
110
|
// be registered always hold it.
|
|
115
|
-
const
|
|
116
|
-
const goOn = (!fresh.plan_only || auto) && !errors.length;
|
|
111
|
+
const goOn = !fresh.plan_only && !errors.length;
|
|
117
112
|
const target = goOn ? implementTarget(fresh, split) : null;
|
|
118
113
|
const next =
|
|
119
114
|
target === "there"
|
|
@@ -148,14 +143,6 @@ export async function planWritten(
|
|
|
148
143
|
return;
|
|
149
144
|
}
|
|
150
145
|
await continuePlanThere(fresh);
|
|
151
|
-
if (auto)
|
|
152
|
-
recordAutoApproval({
|
|
153
|
-
source: "plan",
|
|
154
|
-
workspaceId,
|
|
155
|
-
sessionId: s.id,
|
|
156
|
-
subject: `plan for ${s.name}`,
|
|
157
|
-
detail: "implementing without waiting for approve_plan",
|
|
158
|
-
});
|
|
159
146
|
}
|
|
160
147
|
|
|
161
148
|
/**
|
package/package.json
CHANGED
package/scripts/install-deps
CHANGED
|
@@ -13,7 +13,12 @@ source scripts/lib/pnpm.sh
|
|
|
13
13
|
if [ -s "$HOME/.nvm/nvm.sh" ]; then
|
|
14
14
|
# shellcheck disable=SC1091
|
|
15
15
|
source "$HOME/.nvm/nvm.sh" >/dev/null
|
|
16
|
-
nvm
|
|
16
|
+
# A box with nvm but without .nvmrc's version (a CI runner) keeps the node on PATH.
|
|
17
|
+
nvm use >/dev/null 2>&1 || true
|
|
18
|
+
fi
|
|
19
|
+
if [ "$(node -p 'process.versions.node.split(".")[0]')" -lt 22 ]; then
|
|
20
|
+
echo "Node 22 or newer is required (found $(node -v)); run: nvm install" >&2
|
|
21
|
+
exit 1
|
|
17
22
|
fi
|
|
18
23
|
use_pnpm
|
|
19
24
|
|
package/scripts/lib/commands.sh
CHANGED
package/scripts/lib/wsl.test.ts
CHANGED
|
@@ -33,6 +33,12 @@ const TOOLS = [
|
|
|
33
33
|
"id",
|
|
34
34
|
];
|
|
35
35
|
|
|
36
|
+
// Bash 4+ (Linux, CI) fails `set -e` on `((n++))` at 0; macOS's 3.2 doesn't.
|
|
37
|
+
const BASH =
|
|
38
|
+
["/opt/homebrew/bin/bash", "/usr/local/bin/bash", "/bin/bash"].find(
|
|
39
|
+
existsSync
|
|
40
|
+
) ?? "/bin/bash";
|
|
41
|
+
|
|
36
42
|
let dir: string;
|
|
37
43
|
|
|
38
44
|
function fake(name: string, body: string) {
|
|
@@ -48,7 +54,7 @@ function run(
|
|
|
48
54
|
) {
|
|
49
55
|
writeFileSync(join(dir, "osrelease"), osrelease);
|
|
50
56
|
return spawnSync(
|
|
51
|
-
|
|
57
|
+
BASH,
|
|
52
58
|
[
|
|
53
59
|
"-euo",
|
|
54
60
|
"pipefail",
|
|
@@ -313,3 +319,47 @@ describe("installing prerequisites", () => {
|
|
|
313
319
|
expect(r.stdout).not.toContain("Prerequisites installed");
|
|
314
320
|
});
|
|
315
321
|
});
|
|
322
|
+
|
|
323
|
+
describe("cmd_stop", () => {
|
|
324
|
+
// `ps` reports the server alive for the first `alive` checks, then gone.
|
|
325
|
+
// A server that takes a moment to exit used to trip `((count++))` (status 1
|
|
326
|
+
// at 0) under `set -e`, so stop exited 1 after the server had stopped.
|
|
327
|
+
const stop = (alive: number) => {
|
|
328
|
+
mkdirSync(join(dir, "home"), { recursive: true });
|
|
329
|
+
fake(
|
|
330
|
+
"ps",
|
|
331
|
+
`n=$(cat "${dir}/ps-count" 2>/dev/null || echo 0); echo $((n + 1)) > "${dir}/ps-count"; [ "$n" -lt ${alive} ]`
|
|
332
|
+
);
|
|
333
|
+
fake("sleep", "exit 0");
|
|
334
|
+
return run(
|
|
335
|
+
`AGENT_OS_HOME="${dir}/home"; PID_FILE="$AGENT_OS_HOME/agent-os.pid"; echo $$ > "$PID_FILE"; kill() { return 0; }; cmd_stop`,
|
|
336
|
+
""
|
|
337
|
+
);
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
it("returns 0 when the server exits after a few checks", () => {
|
|
341
|
+
const r = stop(3);
|
|
342
|
+
expect(r.status).toBe(0);
|
|
343
|
+
expect(r.stdout).toContain("AgentOS stopped");
|
|
344
|
+
expect(existsSync(join(dir, "home", "agent-os.pid"))).toBe(false);
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
it("force kills and still returns 0 when it outlives the timeout", () => {
|
|
348
|
+
const r = stop(1000);
|
|
349
|
+
expect(r.status).toBe(0);
|
|
350
|
+
expect(r.stdout).toContain("Force killing");
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
it("has no bare ((x++)) in scripts/lib: it returns 1 at 0 under set -e", () => {
|
|
354
|
+
for (const f of [
|
|
355
|
+
"commands.sh",
|
|
356
|
+
"common.sh",
|
|
357
|
+
"machine.sh",
|
|
358
|
+
"prerequisites.sh",
|
|
359
|
+
]) {
|
|
360
|
+
expect(readFileSync(join(LIB, f), "utf8"), f).not.toMatch(
|
|
361
|
+
/^\s*\(\(\s*\w+(\+\+|--)\s*\)\)\s*$/m
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
});
|