@saadnvd1/agent-os 0.3.781 → 0.3.786
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/hosts/[id]/agents/route.ts +6 -4
- package/app/api/machine/route.ts +5 -3
- package/app/api/whats-new/route.ts +47 -0
- package/components/Chat/ChatPanel.tsx +2 -0
- package/components/Chat/ModelPicker.tsx +82 -42
- package/components/Chat/composer/ComposerBody.tsx +3 -0
- package/components/Chat/composer/Toolbar.tsx +3 -0
- package/components/Draft/AgentModelChip.test.ts +1 -0
- package/components/Draft/AgentModelChip.tsx +36 -93
- package/components/Draft/Chip.tsx +27 -0
- package/components/Draft/DraftChips.tsx +5 -1
- package/components/Draft/DraftPanel.tsx +0 -21
- package/components/Draft/catalogBlocker.ts +19 -0
- package/components/Draft/picker/AgentModelPicker.tsx +152 -0
- package/components/Draft/picker/AgentRail.tsx +145 -0
- package/components/Draft/picker/ModelList.tsx +136 -0
- package/components/Draft/picker/ModelNote.tsx +15 -0
- package/components/Draft/picker/PickerSheet.tsx +61 -0
- package/components/Draft/picker/SameAsLast.tsx +52 -0
- package/components/Draft/picker/SearchField.tsx +48 -0
- package/components/Draft/picker/picker.test.ts +152 -0
- package/components/Draft/picker/sections.test.ts +115 -0
- package/components/Draft/picker/sections.ts +121 -0
- package/components/Draft/picker/useListKeys.ts +54 -0
- package/components/Draft/picker/useModelSearch.ts +66 -0
- package/components/SidebarFooter.tsx +3 -1
- package/components/Updates/UpdateBanner.test.ts +1 -1
- package/components/Updates/UpdateBanner.tsx +27 -13
- package/components/WhatsNew/WhatsNewButton.tsx +36 -0
- package/components/WhatsNew/WhatsNewDialog.tsx +169 -0
- package/components/WhatsNew/WhatsNewHost.tsx +17 -0
- package/components/WhatsNew/WhatsNewRows.tsx +62 -0
- package/components/WhatsNew/index.ts +2 -0
- package/components/ui/dialog.tsx +12 -1
- package/content/whats-new.json +1773 -0
- package/data/chat/useChat.ts +3 -0
- package/data/machines/index.ts +13 -4
- package/data/machines/keys.ts +2 -1
- package/data/whats-new/index.ts +44 -0
- package/hooks/useWhatsNewSeen.ts +50 -0
- package/lib/chat/drivers/copilot.ts +89 -11
- package/lib/chat/drivers/opencode.ts +1 -1
- package/lib/chat/drivers/pi.ts +1 -1
- package/lib/chat/events.ts +6 -0
- package/lib/chat/settings.ts +2 -0
- package/lib/machines/catalog.ts +5 -1
- package/lib/machines/recent.ts +31 -0
- package/lib/machines/remote.ts +28 -9
- package/lib/machines/report.ts +7 -2
- package/lib/machines/types.ts +12 -0
- package/lib/orchestrator/review-prompt.ts +2 -0
- package/lib/security/demo.ts +2 -0
- package/lib/tasks/brief.ts +1 -0
- package/lib/whats-new/ahead.ts +28 -0
- package/lib/whats-new/areas.ts +76 -0
- package/lib/whats-new/claude.ts +35 -0
- package/lib/whats-new/collect.ts +68 -0
- package/lib/whats-new/generate.ts +107 -0
- package/lib/whats-new/lint.ts +69 -0
- package/lib/whats-new/load.ts +59 -0
- package/lib/whats-new/rows.ts +79 -0
- package/lib/whats-new/schema.ts +41 -0
- package/lib/whats-new/summarize.ts +91 -0
- package/lib/whats-new/unread.ts +14 -0
- package/package.json +2 -1
- package/scripts/autodeploy +28 -0
- package/scripts/autodeploy.test.ts +53 -0
- package/scripts/whats-new.ts +118 -0
- package/stores/whatsNew.ts +16 -0
- package/components/Draft/draftModels.test.ts +0 -31
- package/components/Draft/draftModels.ts +0 -17
|
@@ -113,6 +113,59 @@ describe("autodeploy restart gap", () => {
|
|
|
113
113
|
});
|
|
114
114
|
});
|
|
115
115
|
|
|
116
|
+
describe("autodeploy changelog commits", () => {
|
|
117
|
+
const changelog = (t: ReturnType<typeof setup>, also?: string) => {
|
|
118
|
+
const work = path.join(t.root, "work");
|
|
119
|
+
fs.mkdirSync(path.join(work, "content"), { recursive: true });
|
|
120
|
+
fs.writeFileSync(path.join(work, "content/whats-new.json"), "{}\n");
|
|
121
|
+
if (also) fs.appendFileSync(path.join(work, "f"), also + "\n");
|
|
122
|
+
sh(work, "add", "-A");
|
|
123
|
+
sh(
|
|
124
|
+
work,
|
|
125
|
+
"-c",
|
|
126
|
+
"user.name=t",
|
|
127
|
+
"-c",
|
|
128
|
+
"user.email=t@t",
|
|
129
|
+
"commit",
|
|
130
|
+
"-qm",
|
|
131
|
+
"chore(whats-new): add 1 entries"
|
|
132
|
+
);
|
|
133
|
+
sh(work, "push", "-q", "origin", "main");
|
|
134
|
+
return sh(work, "rev-parse", "HEAD");
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
it("pulls a commit that touches only the changelog without a restart", () => {
|
|
138
|
+
const t = setup();
|
|
139
|
+
const a = t.commit("a");
|
|
140
|
+
t.run();
|
|
141
|
+
expect(t.restarts()).toBe(1);
|
|
142
|
+
|
|
143
|
+
const b = changelog(t);
|
|
144
|
+
expect(t.run()).toBe("");
|
|
145
|
+
expect(t.deployed()).toBe(b);
|
|
146
|
+
expect(t.deployed()).not.toBe(a);
|
|
147
|
+
expect(t.restarts()).toBe(1);
|
|
148
|
+
expect(fs.existsSync(path.join(t.clone, "content/whats-new.json"))).toBe(
|
|
149
|
+
true
|
|
150
|
+
);
|
|
151
|
+
expect(t.run()).toBe("");
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("still redeploys a commit that touches the changelog and something else", () => {
|
|
155
|
+
const t = setup();
|
|
156
|
+
t.commit("a");
|
|
157
|
+
t.run();
|
|
158
|
+
const c = changelog(t, "more");
|
|
159
|
+
fs.writeFileSync(
|
|
160
|
+
path.join(t.state, "restarted"),
|
|
161
|
+
`${Math.floor(Date.now() / 1000) - 3601}\n`
|
|
162
|
+
);
|
|
163
|
+
expect(t.run()).toContain("Deploying");
|
|
164
|
+
expect(t.deployed()).toBe(c);
|
|
165
|
+
expect(t.restarts()).toBe(2);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
116
169
|
describe("autodeploy CLI links", () => {
|
|
117
170
|
it("repoints a link into another clone at the live checkout after a deploy", () => {
|
|
118
171
|
const t = setup();
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// Adds the changelog entries for merges since the last run to
|
|
2
|
+
// content/whats-new.json. Run by scripts/autodeploy on the machine that
|
|
3
|
+
// publishes; installs only ever read the file.
|
|
4
|
+
// whats-new.ts [--since-days N] [--through-file F] [--commit] [--dry-run]
|
|
5
|
+
// --commit also commits just that file and pushes it to origin/main.
|
|
6
|
+
import { execFileSync } from "child_process";
|
|
7
|
+
import fs from "fs";
|
|
8
|
+
import path from "path";
|
|
9
|
+
import { askHaiku } from "@/lib/whats-new/claude";
|
|
10
|
+
import { generate, type PrInfo } from "@/lib/whats-new/generate";
|
|
11
|
+
import { WHATS_NEW_FILE } from "@/lib/whats-new/load";
|
|
12
|
+
import { readBlocklist } from "@/lib/whats-new/lint";
|
|
13
|
+
|
|
14
|
+
const root = process.cwd();
|
|
15
|
+
const args = process.argv.slice(2);
|
|
16
|
+
const flag = (name: string) => args.includes(name);
|
|
17
|
+
const value = (name: string) => {
|
|
18
|
+
const i = args.indexOf(name);
|
|
19
|
+
return i >= 0 ? args[i + 1] : undefined;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const run = (cmd: string, a: string[]) =>
|
|
23
|
+
execFileSync(cmd, a, { cwd: root, encoding: "utf-8", maxBuffer: 64 << 20 });
|
|
24
|
+
const git = (a: string[]) => run("git", a);
|
|
25
|
+
|
|
26
|
+
function prs(numbers: number[]): Map<number, PrInfo> {
|
|
27
|
+
const rows = JSON.parse(
|
|
28
|
+
run("gh", [
|
|
29
|
+
"pr",
|
|
30
|
+
"list",
|
|
31
|
+
"--state",
|
|
32
|
+
"merged",
|
|
33
|
+
"--limit",
|
|
34
|
+
"1000",
|
|
35
|
+
"--json",
|
|
36
|
+
"number,body,url",
|
|
37
|
+
])
|
|
38
|
+
) as { number: number; body: string; url: string }[];
|
|
39
|
+
const want = new Set(numbers);
|
|
40
|
+
return new Map(
|
|
41
|
+
rows.filter((r) => want.has(r.number)).map((r) => [r.number, r])
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function main() {
|
|
46
|
+
const throughFile = value("--through-file");
|
|
47
|
+
const commit = flag("--commit");
|
|
48
|
+
if (commit) {
|
|
49
|
+
git(["fetch", "-q", "origin", "main"]);
|
|
50
|
+
const onMain = git(["rev-parse", "--abbrev-ref", "HEAD"]).trim() === "main";
|
|
51
|
+
const level =
|
|
52
|
+
git(["rev-parse", "HEAD"]).trim() ===
|
|
53
|
+
git(["rev-parse", "origin/main"]).trim();
|
|
54
|
+
if (
|
|
55
|
+
!onMain ||
|
|
56
|
+
!level ||
|
|
57
|
+
git(["status", "--porcelain", "--untracked-files=no"]).trim()
|
|
58
|
+
) {
|
|
59
|
+
console.log("whats-new: checkout is not a clean, current main; skipped");
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
let through =
|
|
64
|
+
throughFile && fs.existsSync(throughFile)
|
|
65
|
+
? fs.readFileSync(throughFile, "utf-8").trim()
|
|
66
|
+
: null;
|
|
67
|
+
if (through) {
|
|
68
|
+
try {
|
|
69
|
+
git(["cat-file", "-e", `${through}^{commit}`]);
|
|
70
|
+
} catch {
|
|
71
|
+
through = null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const before = git(["rev-parse", "HEAD"]).trim();
|
|
75
|
+
const result = await generate(
|
|
76
|
+
root,
|
|
77
|
+
{
|
|
78
|
+
through,
|
|
79
|
+
sinceDays: Number(value("--since-days") ?? 30),
|
|
80
|
+
write: !flag("--dry-run"),
|
|
81
|
+
},
|
|
82
|
+
{ git, prs, ask: askHaiku, blocklist: readBlocklist() }
|
|
83
|
+
);
|
|
84
|
+
console.log(
|
|
85
|
+
`whats-new: ${result.added.length} added, ${result.skipped} skipped, ${result.dropped.length} dropped`
|
|
86
|
+
);
|
|
87
|
+
for (const d of result.dropped)
|
|
88
|
+
console.log(` dropped #${d.pr}: ${d.reason}`);
|
|
89
|
+
if (flag("--dry-run")) return;
|
|
90
|
+
|
|
91
|
+
if (commit && result.added.length) {
|
|
92
|
+
git(["add", WHATS_NEW_FILE]);
|
|
93
|
+
git([
|
|
94
|
+
"commit",
|
|
95
|
+
"-q",
|
|
96
|
+
"--no-verify",
|
|
97
|
+
"-m",
|
|
98
|
+
`chore(whats-new): add ${result.added.length} entries`,
|
|
99
|
+
"--",
|
|
100
|
+
WHATS_NEW_FILE,
|
|
101
|
+
]);
|
|
102
|
+
try {
|
|
103
|
+
git(["push", "-q", "origin", "HEAD:main"]);
|
|
104
|
+
} catch (e) {
|
|
105
|
+
git(["reset", "-q", "--hard", before]);
|
|
106
|
+
throw e;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (throughFile) {
|
|
110
|
+
fs.mkdirSync(path.dirname(throughFile), { recursive: true });
|
|
111
|
+
fs.writeFileSync(throughFile, `${before}\n`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
main().catch((e) => {
|
|
116
|
+
console.error(`whats-new: ${e instanceof Error ? e.message : e}`);
|
|
117
|
+
process.exit(1);
|
|
118
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { proxy } from "valtio";
|
|
2
|
+
|
|
3
|
+
// Whether the What's New dialog is open, so the footer row and the update
|
|
4
|
+
// banner can both open the one dialog. `preview` opens it showing what a
|
|
5
|
+
// waiting update would add.
|
|
6
|
+
export const whatsNewStore = proxy({ open: false, preview: false });
|
|
7
|
+
|
|
8
|
+
export const whatsNewActions = {
|
|
9
|
+
open: (preview = false) => {
|
|
10
|
+
whatsNewStore.preview = preview;
|
|
11
|
+
whatsNewStore.open = true;
|
|
12
|
+
},
|
|
13
|
+
close: () => {
|
|
14
|
+
whatsNewStore.open = false;
|
|
15
|
+
},
|
|
16
|
+
};
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { draftModels } from "./draftModels";
|
|
3
|
-
|
|
4
|
-
const qwen = { value: "ollama/qwen3", label: "Qwen 3" };
|
|
5
|
-
|
|
6
|
-
describe("draftModels", () => {
|
|
7
|
-
it("lists the default and then what OpenCode reports", () => {
|
|
8
|
-
const list = draftModels("opencode", {
|
|
9
|
-
data: [qwen],
|
|
10
|
-
isPending: false,
|
|
11
|
-
isError: false,
|
|
12
|
-
});
|
|
13
|
-
expect(list.map((m) => m.value)).toEqual(["default", "ollama/qwen3"]);
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
it("says so beside the default while loading and on failure", () => {
|
|
17
|
-
const loading = draftModels("opencode", {
|
|
18
|
-
isPending: true,
|
|
19
|
-
isError: false,
|
|
20
|
-
});
|
|
21
|
-
const failed = draftModels("opencode", { isPending: false, isError: true });
|
|
22
|
-
expect(loading).toHaveLength(1);
|
|
23
|
-
expect(loading[0].label).toContain("loading");
|
|
24
|
-
expect(failed[0].label).toContain("couldn't");
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it("keeps a fixed list as it is", () => {
|
|
28
|
-
const list = draftModels("claude", { isPending: true, isError: false });
|
|
29
|
-
expect(list.map((m) => m.value)).toContain("opus");
|
|
30
|
-
});
|
|
31
|
-
});
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import type { ChatModel } from "@/lib/chat/events";
|
|
2
|
-
import type { Draft } from "@/lib/drafts";
|
|
3
|
-
import { discoversModels, getModelOptions } from "@/lib/model-catalog";
|
|
4
|
-
|
|
5
|
-
// A fixed list, or the agent's default followed by what it reports; while it
|
|
6
|
-
// loads or if it fails, the default alone says so.
|
|
7
|
-
export function draftModels(
|
|
8
|
-
agentType: Draft["agentType"],
|
|
9
|
-
found: { data?: ChatModel[]; isPending: boolean; isError: boolean }
|
|
10
|
-
): ChatModel[] {
|
|
11
|
-
const options = getModelOptions(agentType);
|
|
12
|
-
if (!discoversModels(agentType)) return options;
|
|
13
|
-
if (found.data) return [...options, ...found.data];
|
|
14
|
-
const [first, ...rest] = options;
|
|
15
|
-
const note = found.isError ? "couldn't list models" : "loading models…";
|
|
16
|
-
return [{ ...first, label: `${first.label} (${note})` }, ...rest];
|
|
17
|
-
}
|