@saadnvd1/agent-os 0.3.839 → 0.3.848

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.
Files changed (100) hide show
  1. package/app/api/approvals/route.ts +5 -1
  2. package/app/api/health/config/route.ts +2 -0
  3. package/app/api/health/route.ts +4 -2
  4. package/app/globals.css +6 -0
  5. package/components/Audits/AuditsPanel.tsx +42 -35
  6. package/components/Chat/quote-selection.test.ts +6 -0
  7. package/components/Chat/quote-selection.ts +13 -10
  8. package/components/Devices/ConnectCard.tsx +52 -56
  9. package/components/Devices/DeviceGroupRow.tsx +65 -0
  10. package/components/Devices/DeviceRow.tsx +2 -2
  11. package/components/Devices/DevicesPanel.tsx +63 -49
  12. package/components/Devices/NetworkSection.tsx +29 -26
  13. package/components/Devices/TailscaleCard.tsx +9 -10
  14. package/components/Devices/group.test.ts +66 -0
  15. package/components/Devices/group.ts +67 -0
  16. package/components/Health/HealthPanel.tsx +5 -81
  17. package/components/Health/MachineChecks.tsx +20 -0
  18. package/components/Health/SweepRuns.tsx +46 -51
  19. package/components/Health/Thresholds.tsx +50 -0
  20. package/components/Merge/GlobalMergeSection.tsx +5 -6
  21. package/components/Merge/MergeApprovalToggle.tsx +20 -14
  22. package/components/Merge/MergeSettingsFields.tsx +0 -2
  23. package/components/Merge/WorktreeSweepSection.tsx +15 -34
  24. package/components/Models/AgentRoleCard.tsx +7 -7
  25. package/components/Models/ModelSelect.tsx +14 -3
  26. package/components/Models/ModelsSection.tsx +42 -37
  27. package/components/Models/ProviderCard.tsx +1 -1
  28. package/components/Models/RoleModelSelect.tsx +15 -3
  29. package/components/Models/SubagentModels.tsx +60 -45
  30. package/components/PhoneNotify/PhoneNotifyForm.tsx +6 -13
  31. package/components/Recipes/RecipesPanel.tsx +20 -9
  32. package/components/Recipes/RunHistory.tsx +1 -1
  33. package/components/Schedules/SchedulesPanel.tsx +12 -10
  34. package/components/SessionList/GroupShelves.tsx +8 -3
  35. package/components/SessionList/OrchestratorPinRow.tsx +2 -2
  36. package/components/SessionList/RowParts.tsx +39 -35
  37. package/components/SessionList/RowRename.test.ts +86 -17
  38. package/components/SessionList/RowShell.tsx +2 -5
  39. package/components/SessionList/SessionRow.tsx +0 -1
  40. package/components/Settings/AppWideLimits.tsx +2 -2
  41. package/components/Settings/{NoApprovalsSection.test.ts → ApprovalsSection.test.ts} +61 -22
  42. package/components/Settings/ApprovalsSection.tsx +93 -0
  43. package/components/Settings/ExperimentsSection.test.ts +20 -4
  44. package/components/Settings/ExperimentsSection.tsx +54 -34
  45. package/components/Settings/GeneralSection.tsx +39 -0
  46. package/components/Settings/ListSection.test.ts +85 -0
  47. package/components/Settings/ListSection.tsx +132 -0
  48. package/components/Settings/NotificationsSection.tsx +31 -29
  49. package/components/Settings/PickerSetting.tsx +5 -1
  50. package/components/Settings/SettingRow.tsx +91 -49
  51. package/components/Settings/SettingsDialog.test.ts +31 -4
  52. package/components/Settings/SettingsDialog.tsx +22 -17
  53. package/components/Settings/ValueLabel.tsx +31 -0
  54. package/components/Settings/settings-fields.test.ts +81 -2
  55. package/components/Settings/settings-fields.tsx +28 -27
  56. package/components/Shares/SharedLinksPanel.tsx +11 -14
  57. package/components/Updates/AutoUpdates.tsx +12 -8
  58. package/components/Updates/UpdatesSection.tsx +18 -17
  59. package/components/Workspaces/HqFolderForm.tsx +21 -19
  60. package/components/Workspaces/McpServersForm.tsx +20 -28
  61. package/components/Workspaces/ProjectTiersForm.tsx +25 -12
  62. package/components/Workspaces/RuleEditor.tsx +86 -0
  63. package/components/Workspaces/RulesForm.test.ts +58 -31
  64. package/components/Workspaces/RulesForm.tsx +129 -58
  65. package/components/Workspaces/WorkspaceModeForm.tsx +3 -3
  66. package/components/Workspaces/WorkspaceModelsForm.tsx +40 -41
  67. package/components/Workspaces/WorkspacePanel.tsx +73 -0
  68. package/components/Workspaces/WorkspacePicker.tsx +44 -0
  69. package/components/Workspaces/WorkspaceSettings.test.ts +153 -0
  70. package/components/Workspaces/WorkspaceSettings.tsx +51 -79
  71. package/components/Workspaces/useSettingsWorkspace.ts +47 -0
  72. package/components/ui/switch.tsx +1 -1
  73. package/content/whats-new.json +40 -0
  74. package/data/approvals/index.ts +2 -2
  75. package/data/health/index.ts +3 -0
  76. package/hooks/useAutoSave.ts +18 -5
  77. package/hooks/useSettingsUrl.ts +6 -3
  78. package/lib/approvals/describe.ts +64 -0
  79. package/lib/away/deploy.ts +34 -7
  80. package/lib/health/config.ts +70 -17
  81. package/lib/models/plan-fresh.ts +61 -0
  82. package/lib/models/plan-step.ts +40 -24
  83. package/lib/models/providers.ts +1 -1
  84. package/lib/orchestrator/scope.ts +10 -0
  85. package/lib/recipes/settle.ts +22 -0
  86. package/lib/settings-url.ts +14 -2
  87. package/lib/updates/banner.ts +2 -0
  88. package/lib/updates/run.ts +9 -0
  89. package/lib/updates/view.ts +28 -6
  90. package/lib/worktree-deps/paths.ts +9 -4
  91. package/lib/worktrees.ts +3 -0
  92. package/package.json +1 -1
  93. package/scripts/autodeploy +11 -0
  94. package/scripts/autodeploy.test.ts +30 -0
  95. package/scripts/lib/update-verify.mjs +11 -0
  96. package/scripts/update-run.mjs +29 -0
  97. package/scripts/update-run.test.ts +62 -4
  98. package/components/Settings/NoApprovalsSection.tsx +0 -103
  99. package/components/Settings/SettingToggle.tsx +0 -36
  100. package/components/Settings/SetupSection.tsx +0 -46
@@ -78,20 +78,27 @@ describe("scripts/update-run.mjs", () => {
78
78
 
79
79
  // A stand-in for the restarted server: its build comes from a file the fake
80
80
  // redeploy writes, and its startedAt can be made to change (a crash loop).
81
- async function fakeServer(opts: { restarts?: boolean } = {}) {
81
+ async function fakeServer(
82
+ opts: { restarts?: boolean; switchAt?: number } = {}
83
+ ) {
82
84
  let n = 0;
85
+ let calls = 0;
83
86
  const server = http.createServer((req, res) => {
84
- const build = fs.readFileSync(path.join(dir, "build"), "utf8").trim();
87
+ let build = fs.readFileSync(path.join(dir, "build"), "utf8").trim();
85
88
  res.setHeader("content-type", "application/json");
86
- if (req.url === "/api/updates/health")
89
+ if (req.url === "/api/updates/health") {
90
+ // Something else (a deploy job) restarts it into another build.
91
+ const other = opts.switchAt !== undefined && calls++ >= opts.switchAt;
92
+ if (other) build = "other";
87
93
  return res.end(
88
94
  JSON.stringify({
89
95
  ok: true,
90
96
  build,
91
- startedAt: opts.restarts ? `s${n++}` : "s",
97
+ startedAt: opts.restarts ? `s${n++}` : other ? "s-other" : "s",
92
98
  migrations: { known: 3, applied: 3 },
93
99
  })
94
100
  );
101
+ }
95
102
  res.end("{}");
96
103
  });
97
104
  await new Promise<void>((r) => server.listen(0, "127.0.0.1", r));
@@ -170,6 +177,57 @@ describe("scripts/update-run.mjs checks the new server", () => {
170
177
  }
171
178
  });
172
179
 
180
+ it("a restart into a different build is left alone: no rollback, nothing marked", async () => {
181
+ fs.writeFileSync(path.join(dir, "next-build"), "new");
182
+ fs.writeFileSync(path.join(dir, "rollback-works"), "");
183
+ const srv = await fakeServer({ switchAt: 2 });
184
+ try {
185
+ const s = await go(REDEPLOY, "x", verifying(srv.port, "new"));
186
+ expect(s).toMatchObject({ phase: "failed", outcome: "interrupted" });
187
+ expect(s.error).toContain("something else restarted it");
188
+ expect(rolled()).toBeNull();
189
+ } finally {
190
+ await srv.close();
191
+ }
192
+ });
193
+
194
+ it("a rollback records the build it went back to as autodeploy's deployed marker", async () => {
195
+ fs.writeFileSync(path.join(dir, "next-build"), "stale");
196
+ fs.writeFileSync(path.join(dir, "rollback-works"), "");
197
+ const state = path.join(dir, "autodeploy");
198
+ fs.mkdirSync(state);
199
+ fs.writeFileSync(path.join(state, "deployed"), "f5054a0\n");
200
+ const srv = await fakeServer();
201
+ try {
202
+ const s = await go(REDEPLOY, "x", {
203
+ ...verifying(srv.port, "new"),
204
+ AGENTOS_AUTODEPLOY_DIR: state,
205
+ });
206
+ expect(s.outcome).toBe("rolled_back");
207
+ expect(fs.readFileSync(path.join(state, "deployed"), "utf8").trim()).toBe(
208
+ "a"
209
+ );
210
+ } finally {
211
+ await srv.close();
212
+ }
213
+ });
214
+
215
+ it("a rollback on a machine without autodeploy creates no marker", async () => {
216
+ fs.writeFileSync(path.join(dir, "next-build"), "stale");
217
+ fs.writeFileSync(path.join(dir, "rollback-works"), "");
218
+ const state = path.join(dir, "autodeploy");
219
+ const srv = await fakeServer();
220
+ try {
221
+ await go(REDEPLOY, "x", {
222
+ ...verifying(srv.port, "new"),
223
+ AGENTOS_AUTODEPLOY_DIR: state,
224
+ });
225
+ expect(fs.existsSync(state)).toBe(false);
226
+ } finally {
227
+ await srv.close();
228
+ }
229
+ });
230
+
173
231
  it("when the rollback doesn't come up either, it ends broken", async () => {
174
232
  fs.writeFileSync(path.join(dir, "next-build"), "stale");
175
233
  const srv = await fakeServer();
@@ -1,103 +0,0 @@
1
- "use client";
2
-
3
- import { useState } from "react";
4
- import { Button } from "@/components/ui/button";
5
- import { SettingToggle } from "@/components/Settings/SettingToggle";
6
- import { useApprovalsModeQuery, useSetApprovalsMode } from "@/data/approvals";
7
-
8
- // What it switches off, said plainly before it can be switched on.
9
- export const NO_APPROVALS_WARNING = [
10
- "Every ask is approved the moment it is raised, hard lines included: public posts and messages, money, irreversible or destructive changes, credentials.",
11
- "A PR stuck failing its gates, with no CI, or too big to review still merges.",
12
- "Brakes stop holding new starts. Plan-only tasks still wait for approve_plan.",
13
- "Every agent AgentOS starts runs with its permission prompts off, and prompts already waiting are allowed.",
14
- ];
15
-
16
- export function NoApprovalsSection() {
17
- const { data, isPending, isError, error } = useApprovalsModeQuery();
18
- const set = useSetApprovalsMode();
19
- const [confirming, setConfirming] = useState(false);
20
-
21
- if (isPending)
22
- return <div className="bg-muted/40 h-16 animate-pulse rounded-lg" />;
23
- if (isError)
24
- return <p className="text-destructive text-sm">{error.message}</p>;
25
-
26
- return (
27
- <div className="space-y-3">
28
- <SettingToggle
29
- title="No approvals"
30
- detail="Shuts off every approval AgentOS would otherwise wait on, and every agent's permission prompts. Off by default."
31
- checked={data.on}
32
- onChange={(on) => {
33
- if (on) setConfirming(true);
34
- else set.mutate(false);
35
- }}
36
- />
37
- {confirming && !data.on && (
38
- <div
39
- role="alertdialog"
40
- aria-label="Turn on No approvals"
41
- className="rounded-lg border border-red-500/20 bg-red-500/10 p-3"
42
- >
43
- <p className="mb-2 text-sm font-medium text-red-400">
44
- Turn on No approvals?
45
- </p>
46
- <ul className="text-muted-foreground mb-3 list-disc space-y-1 pl-5 text-xs">
47
- {NO_APPROVALS_WARNING.map((line) => (
48
- <li key={line}>{line}</li>
49
- ))}
50
- <li>
51
- Terminals already running in an agent other than Claude keep
52
- asking until they are restarted. Each approval made for you is
53
- logged below.
54
- </li>
55
- </ul>
56
- <div className="flex gap-2">
57
- <Button
58
- size="sm"
59
- variant="destructive"
60
- disabled={set.isPending}
61
- onClick={() =>
62
- set.mutate(true, { onSuccess: () => setConfirming(false) })
63
- }
64
- >
65
- {set.isPending ? "Turning on..." : "Turn on No approvals"}
66
- </Button>
67
- <Button
68
- size="sm"
69
- variant="ghost"
70
- disabled={set.isPending}
71
- onClick={() => setConfirming(false)}
72
- >
73
- Cancel
74
- </Button>
75
- </div>
76
- </div>
77
- )}
78
- {set.error && (
79
- <p className="text-destructive text-xs">{set.error.message}</p>
80
- )}
81
- <div>
82
- <p className="mb-1 text-sm font-medium">Approved for you</p>
83
- {data.audit.length === 0 ? (
84
- <p className="text-muted-foreground text-xs">Nothing yet.</p>
85
- ) : (
86
- <ul className="space-y-1">
87
- {data.audit.map((row) => (
88
- <li key={row.id} className="text-xs">
89
- <span className="text-muted-foreground">
90
- {row.at} · {row.source} ·{" "}
91
- </span>
92
- {row.subject}
93
- {row.detail ? (
94
- <span className="text-muted-foreground"> ({row.detail})</span>
95
- ) : null}
96
- </li>
97
- ))}
98
- </ul>
99
- )}
100
- </div>
101
- </div>
102
- );
103
- }
@@ -1,36 +0,0 @@
1
- "use client";
2
-
3
- import { Switch } from "@/components/ui/switch";
4
-
5
- // One labelled switch on a Settings page; a locked one says so.
6
- export function SettingToggle({
7
- title,
8
- detail,
9
- checked,
10
- locked,
11
- onChange,
12
- }: {
13
- title: string;
14
- detail: string;
15
- checked: boolean;
16
- locked?: boolean;
17
- onChange: (on: boolean) => void;
18
- }) {
19
- return (
20
- <label className="bg-muted/40 flex items-start gap-3 rounded-lg px-3 py-2.5">
21
- <div className="min-w-0 flex-1">
22
- <p className="text-sm font-medium">{title}</p>
23
- <p className="text-muted-foreground text-xs">
24
- {detail}
25
- {locked ? " Can't be changed from here." : ""}
26
- </p>
27
- </div>
28
- <Switch
29
- checked={checked}
30
- disabled={locked}
31
- onCheckedChange={onChange}
32
- className="mt-0.5"
33
- />
34
- </label>
35
- );
36
- }
@@ -1,46 +0,0 @@
1
- "use client";
2
-
3
- import { Button } from "@/components/ui/button";
4
- import { HealthList } from "@/components/Welcome/HealthList";
5
- import { AgentList } from "@/components/Welcome/AgentList";
6
- import { ReachRow } from "@/components/Reach/ReachRow";
7
- import { OwnerNameRow } from "./OwnerNameRow";
8
- import { settingsUiActions } from "@/stores/settingsUi";
9
- import { welcomeUiActions } from "@/stores/welcomeUi";
10
-
11
- // What this machine has for running AgentOS and agents, and the first-run setup again.
12
- export function SetupSection() {
13
- return (
14
- <div className="flex flex-col gap-4">
15
- <OwnerNameRow />
16
- <div className="bg-muted/40 flex items-center gap-3 rounded-lg px-3 py-2.5">
17
- <div className="min-w-0 flex-1">
18
- <p className="text-sm font-medium">First-run setup</p>
19
- <p className="text-muted-foreground text-xs">
20
- Agents, your first project, and a first session.
21
- </p>
22
- </div>
23
- <Button
24
- size="sm"
25
- variant="outline"
26
- className="min-h-11 sm:min-h-8"
27
- onClick={() => {
28
- settingsUiActions.close();
29
- welcomeUiActions.open("agents");
30
- }}
31
- >
32
- Run setup again
33
- </Button>
34
- </div>
35
- <div className="flex flex-col gap-2">
36
- <h3 className="text-sm font-medium">Health</h3>
37
- <HealthList />
38
- <ReachRow />
39
- </div>
40
- <div className="flex flex-col gap-2">
41
- <h3 className="text-sm font-medium">Agents on this machine</h3>
42
- <AgentList />
43
- </div>
44
- </div>
45
- );
46
- }