@saadnvd1/agent-os 0.3.782 → 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 +9 -6
- 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
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useMemo, useState } from "react";
|
|
4
|
+
import { Sparkles, X } from "lucide-react";
|
|
5
|
+
import { Button } from "@/components/ui/button";
|
|
6
|
+
import {
|
|
7
|
+
Dialog,
|
|
8
|
+
DialogClose,
|
|
9
|
+
DialogContent,
|
|
10
|
+
DialogDescription,
|
|
11
|
+
DialogTitle,
|
|
12
|
+
} from "@/components/ui/dialog";
|
|
13
|
+
import { ShimmeringLoader } from "@/components/ui/skeleton";
|
|
14
|
+
import {
|
|
15
|
+
useWhatsNewAhead,
|
|
16
|
+
useWhatsNewHead,
|
|
17
|
+
useWhatsNewList,
|
|
18
|
+
} from "@/data/whats-new";
|
|
19
|
+
import { useUpdates } from "@/data/updates";
|
|
20
|
+
import { useMediaQuery } from "@/hooks/useMediaQuery";
|
|
21
|
+
import { whatsNewStore } from "@/stores/whatsNew";
|
|
22
|
+
import { useWhatsNewSeen } from "@/hooks/useWhatsNewSeen";
|
|
23
|
+
import { buildRows, type Row } from "@/lib/whats-new/rows";
|
|
24
|
+
import { unreadCount } from "@/lib/whats-new/unread";
|
|
25
|
+
import { WhatsNewRow } from "./WhatsNewRows";
|
|
26
|
+
|
|
27
|
+
function Empty() {
|
|
28
|
+
return (
|
|
29
|
+
<div className="text-muted-foreground flex flex-1 flex-col items-center justify-center gap-2 px-8 py-12 text-center">
|
|
30
|
+
<span className="bg-muted grid size-11 place-items-center rounded-xl">
|
|
31
|
+
<Sparkles className="size-5" />
|
|
32
|
+
</span>
|
|
33
|
+
<h3 className="text-foreground text-[15px] font-semibold">
|
|
34
|
+
Nothing to show yet
|
|
35
|
+
</h3>
|
|
36
|
+
<p className="max-w-[17.5rem] text-[13px]">
|
|
37
|
+
Changes appear here after your next update.
|
|
38
|
+
</p>
|
|
39
|
+
</div>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function WhatsNewDialog({
|
|
44
|
+
open,
|
|
45
|
+
onOpenChange,
|
|
46
|
+
}: {
|
|
47
|
+
open: boolean;
|
|
48
|
+
onOpenChange: (open: boolean) => void;
|
|
49
|
+
}) {
|
|
50
|
+
const wide = useMediaQuery("(min-width: 768px)");
|
|
51
|
+
const { data: head } = useWhatsNewHead();
|
|
52
|
+
const { seen, markSeen } = useWhatsNewSeen(head);
|
|
53
|
+
const { data: updates } = useUpdates();
|
|
54
|
+
const waiting = !!updates?.available;
|
|
55
|
+
const list = useWhatsNewList(open);
|
|
56
|
+
const ahead = useWhatsNewAhead(open && waiting);
|
|
57
|
+
const [previewing, setPreviewing] = useState(whatsNewStore.preview);
|
|
58
|
+
// Where the last visit ended, read once as the dialog opens: opening marks
|
|
59
|
+
// everything seen, and the divider has to stay where it was.
|
|
60
|
+
const [baseline] = useState(seen);
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
markSeen();
|
|
63
|
+
// Once, on open; `seen` changes as a result of this.
|
|
64
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
65
|
+
}, []);
|
|
66
|
+
|
|
67
|
+
const entries = useMemo(
|
|
68
|
+
() => list.data?.pages.flatMap((p) => p.entries) ?? [],
|
|
69
|
+
[list.data]
|
|
70
|
+
);
|
|
71
|
+
const rows = useMemo<Row[]>(() => {
|
|
72
|
+
const own = buildRows(entries, baseline);
|
|
73
|
+
const coming = previewing && ahead.data?.length ? ahead.data : [];
|
|
74
|
+
return coming.length
|
|
75
|
+
? [
|
|
76
|
+
{ type: "heading", key: "ahead", label: "Arrives with the update" },
|
|
77
|
+
...buildRows(coming, null),
|
|
78
|
+
{ type: "heading", key: "installed", label: "Already installed" },
|
|
79
|
+
...own,
|
|
80
|
+
]
|
|
81
|
+
: own;
|
|
82
|
+
}, [entries, baseline, previewing, ahead.data]);
|
|
83
|
+
|
|
84
|
+
const unread =
|
|
85
|
+
head && baseline !== null ? unreadCount(head.recent, baseline) : 0;
|
|
86
|
+
const version = updates?.current?.version;
|
|
87
|
+
const aheadCount = ahead.data?.length ?? 0;
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
91
|
+
<DialogContent
|
|
92
|
+
showCloseButton={false}
|
|
93
|
+
placement={wide ? "center" : "sheet"}
|
|
94
|
+
className="flex max-h-[85dvh] flex-col gap-0 overflow-hidden p-0 max-md:h-[85dvh] sm:max-w-[34rem]"
|
|
95
|
+
>
|
|
96
|
+
<div className="bg-muted-foreground/40 mx-auto mt-2 h-1 w-9 rounded-full md:hidden" />
|
|
97
|
+
<div className="border-border/50 flex items-center gap-2 border-b px-4 py-3">
|
|
98
|
+
<div className="min-w-0 flex-1">
|
|
99
|
+
<DialogTitle>What's New</DialogTitle>
|
|
100
|
+
<DialogDescription className="mt-1 text-xs">
|
|
101
|
+
{[
|
|
102
|
+
version && `AgentOS ${version}`,
|
|
103
|
+
unread > 0
|
|
104
|
+
? `${unread} new since you last looked`
|
|
105
|
+
: "You're up to date",
|
|
106
|
+
]
|
|
107
|
+
.filter(Boolean)
|
|
108
|
+
.join(" · ")}
|
|
109
|
+
</DialogDescription>
|
|
110
|
+
</div>
|
|
111
|
+
<DialogClose asChild>
|
|
112
|
+
<Button
|
|
113
|
+
variant="ghost"
|
|
114
|
+
size="icon-sm"
|
|
115
|
+
aria-label="Close"
|
|
116
|
+
className="-mr-2 size-11 shrink-0 md:size-8"
|
|
117
|
+
>
|
|
118
|
+
<X className="size-4" />
|
|
119
|
+
</Button>
|
|
120
|
+
</DialogClose>
|
|
121
|
+
</div>
|
|
122
|
+
|
|
123
|
+
<div className="min-h-0 flex-1 overflow-y-auto pb-2">
|
|
124
|
+
{waiting && aheadCount > 0 && (
|
|
125
|
+
<div className="bg-primary/10 mx-3 mt-3 flex items-center gap-3 rounded-lg px-3 py-2 text-[12.5px]">
|
|
126
|
+
<span className="flex-1">
|
|
127
|
+
<b className="font-semibold">Update available.</b> {aheadCount}{" "}
|
|
128
|
+
more {aheadCount === 1 ? "change is" : "changes are"} waiting.
|
|
129
|
+
</span>
|
|
130
|
+
<button
|
|
131
|
+
type="button"
|
|
132
|
+
aria-pressed={previewing}
|
|
133
|
+
onClick={() => setPreviewing(!previewing)}
|
|
134
|
+
className="text-primary -my-1 min-h-11 px-2 font-semibold md:min-h-8"
|
|
135
|
+
>
|
|
136
|
+
{previewing ? "Hide" : "Preview"}
|
|
137
|
+
</button>
|
|
138
|
+
</div>
|
|
139
|
+
)}
|
|
140
|
+
{list.isPending ? (
|
|
141
|
+
<div className="space-y-3 p-4" aria-busy>
|
|
142
|
+
{[0, 1, 2, 3].map((i) => (
|
|
143
|
+
<ShimmeringLoader
|
|
144
|
+
key={i}
|
|
145
|
+
delayIndex={i}
|
|
146
|
+
className="h-9 w-full"
|
|
147
|
+
/>
|
|
148
|
+
))}
|
|
149
|
+
</div>
|
|
150
|
+
) : rows.length === 0 ? (
|
|
151
|
+
<Empty />
|
|
152
|
+
) : (
|
|
153
|
+
rows.map((row) => <WhatsNewRow key={row.key} row={row} />)
|
|
154
|
+
)}
|
|
155
|
+
{list.hasNextPage && (
|
|
156
|
+
<Button
|
|
157
|
+
variant="secondary"
|
|
158
|
+
className="mx-4 my-2 min-h-11 w-[calc(100%-2rem)]"
|
|
159
|
+
disabled={list.isFetchingNextPage}
|
|
160
|
+
onClick={() => list.fetchNextPage()}
|
|
161
|
+
>
|
|
162
|
+
Show older changes
|
|
163
|
+
</Button>
|
|
164
|
+
)}
|
|
165
|
+
</div>
|
|
166
|
+
</DialogContent>
|
|
167
|
+
</Dialog>
|
|
168
|
+
);
|
|
169
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useSnapshot } from "valtio";
|
|
4
|
+
import { whatsNewActions, whatsNewStore } from "@/stores/whatsNew";
|
|
5
|
+
import { WhatsNewDialog } from "./WhatsNewDialog";
|
|
6
|
+
|
|
7
|
+
/** Mounted once, near the top of the app: the footer row and the update banner both open it. */
|
|
8
|
+
export function WhatsNewHost() {
|
|
9
|
+
const { open } = useSnapshot(whatsNewStore);
|
|
10
|
+
if (!open) return null;
|
|
11
|
+
return (
|
|
12
|
+
<WhatsNewDialog
|
|
13
|
+
open
|
|
14
|
+
onOpenChange={(next) => !next && whatsNewActions.close()}
|
|
15
|
+
/>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { StatusPill } from "@/components/ui/status-pill";
|
|
2
|
+
import type { Row } from "@/lib/whats-new/rows";
|
|
3
|
+
import { KIND_LABEL, type Kind } from "@/lib/whats-new/schema";
|
|
4
|
+
|
|
5
|
+
const TONE = { new: "primary", improved: "muted", fixed: "green" } as const;
|
|
6
|
+
|
|
7
|
+
export function KindChip({ kind }: { kind: Kind }) {
|
|
8
|
+
return (
|
|
9
|
+
<StatusPill
|
|
10
|
+
tone={TONE[kind]}
|
|
11
|
+
className="label-mono mt-0.5 w-[4.5rem] shrink-0 justify-center py-1 tracking-wider"
|
|
12
|
+
>
|
|
13
|
+
{KIND_LABEL[kind]}
|
|
14
|
+
</StatusPill>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function WhatsNewRow({ row }: { row: Row }) {
|
|
19
|
+
if (row.type === "day")
|
|
20
|
+
return (
|
|
21
|
+
<h3 className="label-mono text-muted-foreground bg-popover sticky top-0 z-10 px-4 pt-4 pb-1.5">
|
|
22
|
+
{row.label}
|
|
23
|
+
</h3>
|
|
24
|
+
);
|
|
25
|
+
if (row.type === "heading")
|
|
26
|
+
return (
|
|
27
|
+
<h3 className="label-mono text-primary px-4 pt-4 pb-1.5">{row.label}</h3>
|
|
28
|
+
);
|
|
29
|
+
if (row.type === "divider")
|
|
30
|
+
return (
|
|
31
|
+
<div className="text-primary my-2 flex items-center gap-3 px-4">
|
|
32
|
+
<span className="bg-primary/30 h-px flex-1" />
|
|
33
|
+
<span className="label-mono">{row.label}</span>
|
|
34
|
+
<span className="bg-primary/30 h-px flex-1" />
|
|
35
|
+
</div>
|
|
36
|
+
);
|
|
37
|
+
const { entry } = row;
|
|
38
|
+
return (
|
|
39
|
+
<div className="flex items-start gap-3 px-4 py-2">
|
|
40
|
+
<KindChip kind={entry.kind} />
|
|
41
|
+
<div className="min-w-0 flex-1">
|
|
42
|
+
<p className="text-[13.5px] leading-snug">{entry.text}</p>
|
|
43
|
+
{entry.note && (
|
|
44
|
+
<p className="text-muted-foreground mt-0.5 text-xs">{entry.note}</p>
|
|
45
|
+
)}
|
|
46
|
+
</div>
|
|
47
|
+
{entry.url ? (
|
|
48
|
+
<a
|
|
49
|
+
href={entry.url}
|
|
50
|
+
target="_blank"
|
|
51
|
+
rel="noreferrer noopener"
|
|
52
|
+
aria-label={`Pull request ${entry.id}`}
|
|
53
|
+
className="text-muted-foreground hover:text-foreground -my-2 -mr-2 inline-flex min-h-11 min-w-11 items-center justify-center text-[11px] md:min-h-8 md:min-w-8"
|
|
54
|
+
>
|
|
55
|
+
#{entry.id}
|
|
56
|
+
</a>
|
|
57
|
+
) : (
|
|
58
|
+
<span className="text-muted-foreground text-[11px]">#{entry.id}</span>
|
|
59
|
+
)}
|
|
60
|
+
</div>
|
|
61
|
+
);
|
|
62
|
+
}
|
package/components/ui/dialog.tsx
CHANGED
|
@@ -46,13 +46,23 @@ function DialogOverlay({
|
|
|
46
46
|
);
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
const PLACEMENT = {
|
|
50
|
+
center:
|
|
51
|
+
"top-[50%] left-[50%] grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-2xl p-6 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 sm:max-w-lg",
|
|
52
|
+
// Rises from the bottom edge on a phone; the caller sets its height.
|
|
53
|
+
sheet:
|
|
54
|
+
"inset-x-0 bottom-0 flex w-full flex-col rounded-t-2xl pb-[env(safe-area-inset-bottom)] data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
|
|
55
|
+
} as const;
|
|
56
|
+
|
|
49
57
|
function DialogContent({
|
|
50
58
|
className,
|
|
51
59
|
children,
|
|
52
60
|
showCloseButton = true,
|
|
61
|
+
placement = "center",
|
|
53
62
|
...props
|
|
54
63
|
}: React.ComponentProps<typeof DialogPrimitive.Content> & {
|
|
55
64
|
showCloseButton?: boolean;
|
|
65
|
+
placement?: keyof typeof PLACEMENT;
|
|
56
66
|
}) {
|
|
57
67
|
return (
|
|
58
68
|
<DialogPortal data-slot="dialog-portal">
|
|
@@ -60,7 +70,8 @@ function DialogContent({
|
|
|
60
70
|
<DialogPrimitive.Content
|
|
61
71
|
data-slot="dialog-content"
|
|
62
72
|
className={cn(
|
|
63
|
-
"popover-surface data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0
|
|
73
|
+
"popover-surface data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed z-50 duration-200 outline-none",
|
|
74
|
+
PLACEMENT[placement],
|
|
64
75
|
className
|
|
65
76
|
)}
|
|
66
77
|
{...props}
|