@saadnvd1/agent-os 0.3.549 → 0.3.550
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.
|
@@ -4,6 +4,8 @@ import { GitPullRequest } from "lucide-react";
|
|
|
4
4
|
import type { SidebarRow } from "@/lib/sidebar/shelves";
|
|
5
5
|
import { TASK_STATE } from "@/components/Tasks/task-state-labels";
|
|
6
6
|
import { cn } from "@/lib/utils";
|
|
7
|
+
import { RowShell } from "./RowShell";
|
|
8
|
+
import { SessionRowMenu } from "./SessionRowMenu";
|
|
7
9
|
|
|
8
10
|
// One of an orchestrator's quick changes, under it: its name, its PR and
|
|
9
11
|
// where CI stands. It has no agent to open, so the row opens its PR.
|
|
@@ -14,45 +16,50 @@ export function QuickChangeRow({ row }: { row: SidebarRow }) {
|
|
|
14
16
|
const label = !session.pr_number
|
|
15
17
|
? "Editing, no PR yet"
|
|
16
18
|
: `PR #${session.pr_number}${state ? ` · ${state.label}` : ""}`;
|
|
19
|
+
const leading = (
|
|
20
|
+
<GitPullRequest
|
|
21
|
+
aria-hidden
|
|
22
|
+
className={cn(
|
|
23
|
+
"h-3.5 w-3.5 shrink-0",
|
|
24
|
+
state?.tone ?? "text-muted-foreground"
|
|
25
|
+
)}
|
|
26
|
+
/>
|
|
27
|
+
);
|
|
17
28
|
const body = (
|
|
18
29
|
<>
|
|
19
|
-
<
|
|
20
|
-
|
|
30
|
+
<span className="text-foreground truncate text-[13px]">
|
|
31
|
+
{session.name}
|
|
32
|
+
</span>
|
|
33
|
+
<span
|
|
21
34
|
className={cn(
|
|
22
|
-
"
|
|
23
|
-
state
|
|
35
|
+
"mt-0.5 truncate text-xs",
|
|
36
|
+
session.pr_number && state ? state.tone : "text-muted-foreground"
|
|
24
37
|
)}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
<span className="text-foreground truncate text-[13px]">
|
|
28
|
-
{session.name}
|
|
29
|
-
</span>
|
|
30
|
-
<span
|
|
31
|
-
className={cn(
|
|
32
|
-
"mt-0.5 truncate text-xs",
|
|
33
|
-
session.pr_number && state ? state.tone : "text-muted-foreground"
|
|
34
|
-
)}
|
|
35
|
-
>
|
|
36
|
-
{label}
|
|
37
|
-
</span>
|
|
38
|
+
>
|
|
39
|
+
{label}
|
|
38
40
|
</span>
|
|
39
41
|
</>
|
|
40
42
|
);
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
43
|
+
const openPr = pr ? () => window.open(pr, "_blank") : undefined;
|
|
44
|
+
return (
|
|
45
|
+
<div className="ml-5">
|
|
46
|
+
<RowShell
|
|
47
|
+
title={`Quick change: ${session.name}\n${label}`}
|
|
48
|
+
onActivate={openPr}
|
|
49
|
+
onClick={openPr}
|
|
50
|
+
leading={leading}
|
|
51
|
+
menu={(kind) => (
|
|
52
|
+
<SessionRowMenu
|
|
53
|
+
session={session}
|
|
54
|
+
row={row}
|
|
55
|
+
kind={kind}
|
|
56
|
+
onRename={() => {}}
|
|
57
|
+
/>
|
|
58
|
+
)}
|
|
59
|
+
menuLabel="Quick change actions"
|
|
60
|
+
>
|
|
61
|
+
{body}
|
|
62
|
+
</RowShell>
|
|
56
63
|
</div>
|
|
57
64
|
);
|
|
58
65
|
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import "@/components/Chat/composer/test-dom";
|
|
2
|
+
import { act, createElement } from "react";
|
|
3
|
+
import { createRoot, type Root } from "react-dom/client";
|
|
4
|
+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
6
|
+
import type { Session } from "@/lib/db/types";
|
|
7
|
+
import type { SidebarRow } from "@/lib/sidebar/shelves";
|
|
8
|
+
import { RowProvider, type RowContextValue } from "./RowContext";
|
|
9
|
+
import { SessionRowMenu } from "./SessionRowMenu";
|
|
10
|
+
|
|
11
|
+
// Radix's focus handling needs a real browser; plain elements show what the
|
|
12
|
+
// menu offers.
|
|
13
|
+
vi.mock("@/components/ui/dropdown-menu", async (importOriginal) => {
|
|
14
|
+
const { createElement: h } = await import("react");
|
|
15
|
+
const part = (props: { children?: unknown; onClick?: () => void }) =>
|
|
16
|
+
h("div", { onClick: props.onClick }, props.children as never);
|
|
17
|
+
return {
|
|
18
|
+
...(await importOriginal<object>()),
|
|
19
|
+
DropdownMenuItem: part,
|
|
20
|
+
DropdownMenuSeparator: part,
|
|
21
|
+
DropdownMenuSub: part,
|
|
22
|
+
DropdownMenuSubTrigger: part,
|
|
23
|
+
DropdownMenuSubContent: part,
|
|
24
|
+
};
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
(
|
|
28
|
+
globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }
|
|
29
|
+
).IS_REACT_ACT_ENVIRONMENT = true;
|
|
30
|
+
|
|
31
|
+
let root: Root;
|
|
32
|
+
let host: HTMLElement;
|
|
33
|
+
beforeEach(() => {
|
|
34
|
+
host = document.createElement("div");
|
|
35
|
+
document.body.append(host);
|
|
36
|
+
root = createRoot(host);
|
|
37
|
+
});
|
|
38
|
+
afterEach(() => {
|
|
39
|
+
act(() => root.unmount());
|
|
40
|
+
host.remove();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const ctx: RowContextValue = {
|
|
44
|
+
summarizingSessionId: null,
|
|
45
|
+
projects: [],
|
|
46
|
+
projectNames: new Map(),
|
|
47
|
+
workspaceNames: new Map(),
|
|
48
|
+
orchestrators: [],
|
|
49
|
+
runningByWorkspace: new Map(),
|
|
50
|
+
queuedByWorkspace: new Map(),
|
|
51
|
+
rollups: new Map(),
|
|
52
|
+
orchestratorNames: new Map(),
|
|
53
|
+
orderedIds: () => [],
|
|
54
|
+
cardUrl: () => null,
|
|
55
|
+
onSelect: vi.fn(),
|
|
56
|
+
onRename: vi.fn(),
|
|
57
|
+
onFork: vi.fn(),
|
|
58
|
+
onSummarize: vi.fn(),
|
|
59
|
+
onDelete: vi.fn(),
|
|
60
|
+
onMoveToProject: vi.fn(),
|
|
61
|
+
onPin: vi.fn(),
|
|
62
|
+
onFresh: vi.fn(),
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
async function menuText(session: Partial<Session>) {
|
|
66
|
+
const full = { id: "s1", name: "Fix typo", ...session } as Session;
|
|
67
|
+
const row: SidebarRow = {
|
|
68
|
+
session: full,
|
|
69
|
+
status: undefined,
|
|
70
|
+
need: null,
|
|
71
|
+
unread: false,
|
|
72
|
+
working: false,
|
|
73
|
+
activeAt: 0,
|
|
74
|
+
workers: [],
|
|
75
|
+
};
|
|
76
|
+
await act(async () =>
|
|
77
|
+
root.render(
|
|
78
|
+
createElement(
|
|
79
|
+
QueryClientProvider,
|
|
80
|
+
{ client: new QueryClient() },
|
|
81
|
+
createElement(
|
|
82
|
+
RowProvider,
|
|
83
|
+
{ value: ctx },
|
|
84
|
+
createElement(SessionRowMenu, {
|
|
85
|
+
session: full,
|
|
86
|
+
row,
|
|
87
|
+
kind: "dropdown",
|
|
88
|
+
onRename: () => {},
|
|
89
|
+
})
|
|
90
|
+
)
|
|
91
|
+
)
|
|
92
|
+
)
|
|
93
|
+
);
|
|
94
|
+
return document.body.textContent ?? "";
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
describe("SessionRowMenu", () => {
|
|
98
|
+
it("gives a quick change Done, Archive and Delete", async () => {
|
|
99
|
+
const text = await menuText({
|
|
100
|
+
quick_of: "o1",
|
|
101
|
+
task_status: "running",
|
|
102
|
+
role: null,
|
|
103
|
+
});
|
|
104
|
+
expect(text).toContain("Done");
|
|
105
|
+
expect(text).toContain("Archive");
|
|
106
|
+
expect(text).toContain("Delete session");
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("gives an orchestrator neither Done nor Archive", async () => {
|
|
110
|
+
const text = await menuText({ role: "orchestrator" });
|
|
111
|
+
expect(text).not.toContain("Done");
|
|
112
|
+
expect(text).not.toContain("Archive");
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
describe("QuickChangeRow", () => {
|
|
117
|
+
it("has a row menu to finish it from", async () => {
|
|
118
|
+
const { QuickChangeRow } = await import("./QuickChangeRow");
|
|
119
|
+
const session = {
|
|
120
|
+
id: "q1",
|
|
121
|
+
name: "Fix typo",
|
|
122
|
+
quick_of: "o1",
|
|
123
|
+
task_status: "running",
|
|
124
|
+
} as Session;
|
|
125
|
+
await act(async () =>
|
|
126
|
+
root.render(
|
|
127
|
+
createElement(
|
|
128
|
+
QueryClientProvider,
|
|
129
|
+
{ client: new QueryClient() },
|
|
130
|
+
createElement(
|
|
131
|
+
RowProvider,
|
|
132
|
+
{ value: ctx },
|
|
133
|
+
createElement(QuickChangeRow, {
|
|
134
|
+
row: {
|
|
135
|
+
session,
|
|
136
|
+
status: undefined,
|
|
137
|
+
need: null,
|
|
138
|
+
unread: false,
|
|
139
|
+
working: false,
|
|
140
|
+
activeAt: 0,
|
|
141
|
+
workers: [],
|
|
142
|
+
},
|
|
143
|
+
})
|
|
144
|
+
)
|
|
145
|
+
)
|
|
146
|
+
)
|
|
147
|
+
);
|
|
148
|
+
expect(
|
|
149
|
+
host.querySelector('[aria-label="Quick change actions"]')
|
|
150
|
+
).not.toBeNull();
|
|
151
|
+
});
|
|
152
|
+
});
|
|
@@ -148,6 +148,28 @@ export function SessionRowMenu({
|
|
|
148
148
|
ctx.projects.find((p) => p.id === session.project_id)?.workspace_id ??
|
|
149
149
|
null;
|
|
150
150
|
|
|
151
|
+
// A quick change has no agent: no fork, fresh start, snooze or move, just
|
|
152
|
+
// its PR and the ways to finish it.
|
|
153
|
+
if (session.quick_of)
|
|
154
|
+
return (
|
|
155
|
+
<>
|
|
156
|
+
{session.pr_url && (
|
|
157
|
+
<Item onClick={() => window.open(session.pr_url!, "_blank")}>
|
|
158
|
+
<GitPullRequest className={icon} />
|
|
159
|
+
Open PR #{session.pr_number}
|
|
160
|
+
</Item>
|
|
161
|
+
)}
|
|
162
|
+
<FinishItems session={session} plain={plain} Item={Item} />
|
|
163
|
+
<Item
|
|
164
|
+
onClick={() => ctx.onDelete(session.id)}
|
|
165
|
+
className="text-red-500 focus:text-red-500"
|
|
166
|
+
>
|
|
167
|
+
<Trash2 className={icon} />
|
|
168
|
+
Delete session
|
|
169
|
+
</Item>
|
|
170
|
+
</>
|
|
171
|
+
);
|
|
172
|
+
|
|
151
173
|
return (
|
|
152
174
|
<>
|
|
153
175
|
{session.branch_name && (
|