@saadnvd1/agent-os 0.3.606 → 0.3.609
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/busy/route.ts +2 -1
- package/app/api/updates/route.test.ts +3 -1
- package/components/Chat/ContextMeter.tsx +2 -2
- package/components/Chat/composer/useCoarsePointer.ts +2 -14
- package/components/PrStageBadge.tsx +4 -4
- package/components/SessionList/LiveLine.tsx +0 -4
- package/components/SessionList/OrchestratorPinRow.tsx +3 -3
- package/components/SessionList/RowMenuParts.tsx +90 -0
- package/components/SessionList/RowShell.tsx +6 -2
- package/components/SessionList/SessionRowMenu.tsx +241 -377
- package/components/Stacks/StatusChip.tsx +9 -33
- package/components/Stacks/status-chip.test.ts +40 -0
- package/components/Stacks/status-chip.ts +44 -0
- package/components/ui/context-menu.tsx +4 -2
- package/components/ui/dropdown-menu.tsx +5 -2
- package/components/ui/menu-fit.test.ts +36 -0
- package/hooks/useCompactMenu.ts +9 -0
- package/hooks/useMediaQuery.ts +20 -0
- package/lib/busy.ts +9 -3
- package/lib/hosts/remote-menu.ts +4 -0
- package/lib/recipes/status-labels.ts +2 -2
- package/lib/resurrect/index.ts +2 -1
- package/lib/sidebar/live-status.ts +7 -14
- package/lib/sidebar/row-menu.ts +168 -0
- package/lib/simulator/index.ts +6 -1
- package/lib/tasks/brief.ts +16 -1
- package/lib/tasks/import.ts +6 -2
- package/lib/tasks/index.ts +2 -1
- package/lib/tasks/move.ts +2 -1
- package/package.json +1 -1
- package/scripts/autodeploy +28 -0
- package/scripts/autodeploy.test.ts +106 -0
- package/scripts/wait-quiet +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import { Fragment } from "react";
|
|
3
4
|
import {
|
|
4
5
|
CheckSquare,
|
|
5
6
|
AlarmClock,
|
|
@@ -26,8 +27,6 @@ import {
|
|
|
26
27
|
Sparkles,
|
|
27
28
|
Trash2,
|
|
28
29
|
} from "lucide-react";
|
|
29
|
-
import * as DM from "@/components/ui/dropdown-menu";
|
|
30
|
-
import * as CM from "@/components/ui/context-menu";
|
|
31
30
|
import type { Session } from "@/lib/db";
|
|
32
31
|
import { selectionActions } from "@/stores/sessionSelection";
|
|
33
32
|
import { schedulesUiActions } from "@/stores/schedulesUi";
|
|
@@ -53,90 +52,27 @@ import { MoveMenuItems } from "@/components/Tasks/MoveMenuItems";
|
|
|
53
52
|
import { shareUiActions } from "@/stores/shareUi";
|
|
54
53
|
import { copySessionLink } from "@/hooks/useCopyToClipboard";
|
|
55
54
|
import { useLinkedHostNames } from "@/data/hosts";
|
|
56
|
-
import { remoteBlocks } from "@/lib/hosts/remote-menu";
|
|
55
|
+
import { remoteBlocks, remoteNote } from "@/lib/hosts/remote-menu";
|
|
56
|
+
import { movableSession } from "@/lib/tasks/move-targets";
|
|
57
|
+
import { useMoveTargets } from "@/data/tasks/move";
|
|
58
|
+
import {
|
|
59
|
+
rowMenu,
|
|
60
|
+
groupLabel,
|
|
61
|
+
type RowMenuGroup,
|
|
62
|
+
type RowMenuItem,
|
|
63
|
+
} from "@/lib/sidebar/row-menu";
|
|
64
|
+
import { Expander, MenuItem, PARTS, icon, type MenuKind } from "./RowMenuParts";
|
|
57
65
|
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
SubContent: DM.DropdownMenuSubContent,
|
|
65
|
-
},
|
|
66
|
-
context: {
|
|
67
|
-
Item: CM.ContextMenuItem,
|
|
68
|
-
Separator: CM.ContextMenuSeparator,
|
|
69
|
-
Sub: CM.ContextMenuSub,
|
|
70
|
-
SubTrigger: CM.ContextMenuSubTrigger,
|
|
71
|
-
SubContent: CM.ContextMenuSubContent,
|
|
72
|
-
},
|
|
66
|
+
const GROUP_ICON: Record<RowMenuGroup, typeof Copy> = {
|
|
67
|
+
open: ExternalLink,
|
|
68
|
+
share: Share2,
|
|
69
|
+
restart: Copy,
|
|
70
|
+
move: FolderInput,
|
|
71
|
+
automate: Clock,
|
|
73
72
|
};
|
|
74
73
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
interface BlockedProps {
|
|
78
|
-
kind: keyof typeof PARTS;
|
|
79
|
-
icon: typeof Copy;
|
|
80
|
-
label: string;
|
|
81
|
-
why: string;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// An action that can't run here yet: shown, disabled, with why.
|
|
85
|
-
function BlockedItem({ kind, icon: Icon, label, why }: BlockedProps) {
|
|
86
|
-
const Item = PARTS[kind].Item;
|
|
87
|
-
return (
|
|
88
|
-
// Disabled, but the reason keeps full contrast: only the label dims.
|
|
89
|
-
<Item disabled className="items-start data-[disabled]:opacity-100">
|
|
90
|
-
<Icon className={`${icon} mt-0.5 opacity-50`} />
|
|
91
|
-
<span className="flex min-w-0 flex-col">
|
|
92
|
-
<span className="text-muted-foreground">{label}</span>
|
|
93
|
-
<span className="text-muted-foreground text-xs leading-tight">
|
|
94
|
-
{why}
|
|
95
|
-
</span>
|
|
96
|
-
</span>
|
|
97
|
-
</Item>
|
|
98
|
-
);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// Done and Archive, as the session's kind allows.
|
|
102
|
-
function FinishItems({
|
|
103
|
-
session,
|
|
104
|
-
plain,
|
|
105
|
-
Item,
|
|
106
|
-
}: {
|
|
107
|
-
session: Session;
|
|
108
|
-
plain: boolean;
|
|
109
|
-
Item: (typeof PARTS)[keyof typeof PARTS]["Item"];
|
|
110
|
-
}) {
|
|
111
|
-
const markDone = useDoneAction();
|
|
112
|
-
const archive = useArchiveAction();
|
|
113
|
-
const restore = useResurrectAction();
|
|
114
|
-
const actions = finishActions(session, plain);
|
|
115
|
-
return (
|
|
116
|
-
<>
|
|
117
|
-
{actions.includes("done") && (
|
|
118
|
-
<Item onClick={() => markDone(session)}>
|
|
119
|
-
<CircleCheck className={icon} />
|
|
120
|
-
Done
|
|
121
|
-
</Item>
|
|
122
|
-
)}
|
|
123
|
-
{actions.includes("restore") && (
|
|
124
|
-
<Item onClick={() => restore(session)}>
|
|
125
|
-
<RotateCcw className={icon} />
|
|
126
|
-
Restore
|
|
127
|
-
</Item>
|
|
128
|
-
)}
|
|
129
|
-
{actions.includes("archive") && (
|
|
130
|
-
<Item onClick={() => archive(session)}>
|
|
131
|
-
<Archive className={icon} />
|
|
132
|
-
Archive
|
|
133
|
-
</Item>
|
|
134
|
-
)}
|
|
135
|
-
</>
|
|
136
|
-
);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// The row's ⋯ menu (and right-click menu): everything a session can do.
|
|
74
|
+
// The row's ⋯ menu (and right-click menu): everything a session can do. What
|
|
75
|
+
// it offers is decided in lib/sidebar/row-menu; this only draws it.
|
|
140
76
|
export function SessionRowMenu({
|
|
141
77
|
session,
|
|
142
78
|
row,
|
|
@@ -146,16 +82,18 @@ export function SessionRowMenu({
|
|
|
146
82
|
session: Session;
|
|
147
83
|
// Where it sits, for Snooze, Mark done and Reopen (not on an orchestrator).
|
|
148
84
|
row?: SidebarRow;
|
|
149
|
-
kind:
|
|
85
|
+
kind: MenuKind;
|
|
150
86
|
onRename: () => void;
|
|
151
87
|
}) {
|
|
152
88
|
const ctx = useRowContext();
|
|
153
89
|
const shelf = useShelfActions();
|
|
154
|
-
const
|
|
155
|
-
const
|
|
90
|
+
const markDone = useDoneAction();
|
|
91
|
+
const archive = useArchiveAction();
|
|
92
|
+
const restore = useResurrectAction();
|
|
93
|
+
const { Separator } = PARTS[kind];
|
|
156
94
|
const linked = useLinkedHostNames();
|
|
157
|
-
|
|
158
|
-
const
|
|
95
|
+
const remote = remoteBlocks(session, (id) => linked[id]);
|
|
96
|
+
const targets = useMoveTargets(movableSession(session));
|
|
159
97
|
const summarizing = ctx.summarizingSessionId === session.id;
|
|
160
98
|
const cardUrl = ctx.cardUrl(session.id);
|
|
161
99
|
const others = ctx.projects.filter(
|
|
@@ -168,31 +106,203 @@ export function SessionRowMenu({
|
|
|
168
106
|
ctx.projects.find((p) => p.id === session.project_id)?.workspace_id ??
|
|
169
107
|
null;
|
|
170
108
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
109
|
+
const plan = rowMenu({
|
|
110
|
+
session,
|
|
111
|
+
row,
|
|
112
|
+
remote: !!remote,
|
|
113
|
+
canOpenInTab: !!ctx.onOpenInTab,
|
|
114
|
+
hasCard: !!cardUrl,
|
|
115
|
+
recipeRun: !!recipeRun,
|
|
116
|
+
hasWorkspace: !!workspaceId,
|
|
117
|
+
machineTargets: targets.length,
|
|
118
|
+
otherProjects: others.length,
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const item = (
|
|
122
|
+
label: string,
|
|
123
|
+
Icon: typeof Copy,
|
|
124
|
+
onClick: () => void,
|
|
125
|
+
extra?: { className?: string; disabled?: boolean; spin?: boolean }
|
|
126
|
+
) => (
|
|
127
|
+
<MenuItem
|
|
128
|
+
key={label}
|
|
129
|
+
kind={kind}
|
|
130
|
+
onClick={onClick}
|
|
131
|
+
disabled={extra?.disabled}
|
|
132
|
+
className={extra?.className}
|
|
133
|
+
>
|
|
134
|
+
<Icon className={`${icon}${extra?.spin ? "animate-spin" : ""}`} />
|
|
135
|
+
{label}
|
|
136
|
+
</MenuItem>
|
|
137
|
+
);
|
|
138
|
+
const open = (url: string) => () => window.open(url, "_blank");
|
|
139
|
+
const projects = others.map((p) => (
|
|
140
|
+
<MenuItem
|
|
141
|
+
key={p.id}
|
|
142
|
+
kind={kind}
|
|
143
|
+
onClick={() => ctx.onMoveToProject(session.id, p.id)}
|
|
144
|
+
>
|
|
145
|
+
{p.name}
|
|
146
|
+
</MenuItem>
|
|
147
|
+
));
|
|
148
|
+
const wake = (ms: number | null) =>
|
|
149
|
+
ms && (
|
|
150
|
+
<span className="text-muted-foreground ml-3 text-xs">
|
|
151
|
+
{wakeLabel(ms)}
|
|
152
|
+
</span>
|
|
191
153
|
);
|
|
192
154
|
|
|
155
|
+
const render = (it: RowMenuItem, inGroup: boolean) => {
|
|
156
|
+
switch (it) {
|
|
157
|
+
case "rename":
|
|
158
|
+
return item("Rename", Pencil, onRename);
|
|
159
|
+
case "pin":
|
|
160
|
+
return item("Pin", Pin, () => ctx.onPin(session.id, true));
|
|
161
|
+
case "unpin":
|
|
162
|
+
return item("Unpin", PinOff, () => ctx.onPin(session.id, false));
|
|
163
|
+
case "select":
|
|
164
|
+
return item("Select", CheckSquare, () =>
|
|
165
|
+
selectionActions.toggle(session.id, false, ctx.orderedIds())
|
|
166
|
+
);
|
|
167
|
+
case "open-tab":
|
|
168
|
+
return item("Open in new tab", ExternalLink, () =>
|
|
169
|
+
ctx.onOpenInTab?.(session.id)
|
|
170
|
+
);
|
|
171
|
+
case "open-pr":
|
|
172
|
+
return item(
|
|
173
|
+
`Open PR #${session.pr_number}`,
|
|
174
|
+
GitPullRequest,
|
|
175
|
+
open(session.pr_url!)
|
|
176
|
+
);
|
|
177
|
+
case "card":
|
|
178
|
+
return item("LumifyHub card", KanbanSquare, open(cardUrl!));
|
|
179
|
+
case "open-recipe":
|
|
180
|
+
return item("Open recipe", Repeat, () =>
|
|
181
|
+
recipesUiActions.openRecipe(recipeRun!.id)
|
|
182
|
+
);
|
|
183
|
+
case "copy-link":
|
|
184
|
+
return item("Copy link", Link, () => copySessionLink(session.id));
|
|
185
|
+
case "share":
|
|
186
|
+
return item("Share…", Share2, () =>
|
|
187
|
+
shareUiActions.open({
|
|
188
|
+
kind: "chat",
|
|
189
|
+
sessionId: session.id,
|
|
190
|
+
title: session.name,
|
|
191
|
+
})
|
|
192
|
+
);
|
|
193
|
+
case "fork":
|
|
194
|
+
return item("Fork session", Copy, () => ctx.onFork(session.id));
|
|
195
|
+
case "fresh-start":
|
|
196
|
+
return item(
|
|
197
|
+
summarizing ? "Summarizing..." : "Fresh start",
|
|
198
|
+
summarizing ? Loader2 : Sparkles,
|
|
199
|
+
() => ctx.onSummarize(session.id),
|
|
200
|
+
{ disabled: summarizing, spin: summarizing }
|
|
201
|
+
);
|
|
202
|
+
case "new-conversation":
|
|
203
|
+
return item("New conversation (keep brief)", RotateCcw, () =>
|
|
204
|
+
ctx.onFresh(session.id)
|
|
205
|
+
);
|
|
206
|
+
case "move-machine":
|
|
207
|
+
return (
|
|
208
|
+
<MoveMenuItems key={it} session={session} Item={PARTS[kind].Item} />
|
|
209
|
+
);
|
|
210
|
+
case "move-project":
|
|
211
|
+
return inGroup ? (
|
|
212
|
+
<div key={it}>
|
|
213
|
+
{targets.length > 0 && <Separator />}
|
|
214
|
+
{projects}
|
|
215
|
+
</div>
|
|
216
|
+
) : (
|
|
217
|
+
<Expander
|
|
218
|
+
key={it}
|
|
219
|
+
kind={kind}
|
|
220
|
+
icon={FolderInput}
|
|
221
|
+
label="Move to project"
|
|
222
|
+
>
|
|
223
|
+
{projects}
|
|
224
|
+
</Expander>
|
|
225
|
+
);
|
|
226
|
+
case "check-ins":
|
|
227
|
+
return item("Schedule check-ins", Clock, () =>
|
|
228
|
+
schedulesUiActions.openDraft(workspaceId!, {
|
|
229
|
+
kind: "message",
|
|
230
|
+
targetSessionId: session.id,
|
|
231
|
+
name: `Check-in: ${session.name}`.slice(0, 80),
|
|
232
|
+
})
|
|
233
|
+
);
|
|
234
|
+
case "save-recipe":
|
|
235
|
+
return item("Save as recipe", ChefHat, () =>
|
|
236
|
+
recipesUiActions.saveSessionAs(session.id, workspaceId)
|
|
237
|
+
);
|
|
238
|
+
case "run-again":
|
|
239
|
+
return item("Run again", Play, () =>
|
|
240
|
+
recipesUiActions.openRecipe(recipeRun!.id, true)
|
|
241
|
+
);
|
|
242
|
+
case "snooze":
|
|
243
|
+
return (
|
|
244
|
+
<Expander key={it} kind={kind} icon={AlarmClock} label="Snooze">
|
|
245
|
+
{SNOOZE_PRESETS.map((p) => (
|
|
246
|
+
<MenuItem
|
|
247
|
+
key={p.key}
|
|
248
|
+
kind={kind}
|
|
249
|
+
onClick={() => shelf.snooze(session, p.key)}
|
|
250
|
+
>
|
|
251
|
+
{p.label}
|
|
252
|
+
{wake(snoozePreset(p.key).getTime())}
|
|
253
|
+
</MenuItem>
|
|
254
|
+
))}
|
|
255
|
+
</Expander>
|
|
256
|
+
);
|
|
257
|
+
case "unsnooze":
|
|
258
|
+
return item("Unsnooze", AlarmClock, () => shelf.unsnooze(session));
|
|
259
|
+
case "pause":
|
|
260
|
+
return (
|
|
261
|
+
<Expander key={it} kind={kind} icon={Pause} label="Pause">
|
|
262
|
+
{PAUSE_PRESETS.map((p) => (
|
|
263
|
+
<MenuItem
|
|
264
|
+
key={p.key}
|
|
265
|
+
kind={kind}
|
|
266
|
+
onClick={() => shelf.pause(session, p.key)}
|
|
267
|
+
>
|
|
268
|
+
{p.label}
|
|
269
|
+
{wake(pauseUntil(p.key)?.getTime() ?? null)}
|
|
270
|
+
</MenuItem>
|
|
271
|
+
))}
|
|
272
|
+
<MenuItem
|
|
273
|
+
kind={kind}
|
|
274
|
+
onClick={() => shelf.pause(session, "until-resumed", true)}
|
|
275
|
+
>
|
|
276
|
+
Pause now (interrupt its step)
|
|
277
|
+
</MenuItem>
|
|
278
|
+
</Expander>
|
|
279
|
+
);
|
|
280
|
+
case "resume":
|
|
281
|
+
return item(
|
|
282
|
+
session.pause_state === "paused" ? "Resume now" : "Keep running",
|
|
283
|
+
Play,
|
|
284
|
+
() => shelf.resume(session)
|
|
285
|
+
);
|
|
286
|
+
case "settle":
|
|
287
|
+
return item("Mark done", CircleCheck, () => shelf.settle(session));
|
|
288
|
+
case "reopen":
|
|
289
|
+
return item("Reopen", RotateCcw, () => shelf.reopen(session));
|
|
290
|
+
case "done":
|
|
291
|
+
return item("Done", CircleCheck, () => markDone(session));
|
|
292
|
+
case "restore":
|
|
293
|
+
return item("Restore", RotateCcw, () => restore(session));
|
|
294
|
+
case "archive":
|
|
295
|
+
return item("Archive", Archive, () => archive(session));
|
|
296
|
+
case "delete":
|
|
297
|
+
return item("Delete session", Trash2, () => ctx.onDelete(session.id), {
|
|
298
|
+
className: "text-red-500 focus:text-red-500",
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
|
|
193
303
|
return (
|
|
194
304
|
<>
|
|
195
|
-
{session.branch_name && (
|
|
305
|
+
{session.branch_name && !session.quick_of && (
|
|
196
306
|
<>
|
|
197
307
|
<div className="text-muted-foreground flex flex-col gap-0.5 px-2 py-1.5 text-xs">
|
|
198
308
|
<span className="flex items-center gap-1.5">
|
|
@@ -208,278 +318,32 @@ export function SessionRowMenu({
|
|
|
208
318
|
<Separator />
|
|
209
319
|
</>
|
|
210
320
|
)}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
<Pin className={icon} />
|
|
216
|
-
)}
|
|
217
|
-
{session.pinned ? "Unpin" : "Pin"}
|
|
218
|
-
</Item>
|
|
219
|
-
{ctx.onOpenInTab && (
|
|
220
|
-
<Item onClick={() => ctx.onOpenInTab?.(session.id)}>
|
|
221
|
-
<ExternalLink className={icon} />
|
|
222
|
-
Open in new tab
|
|
223
|
-
</Item>
|
|
224
|
-
)}
|
|
225
|
-
<Item onClick={() => copySessionLink(session.id)}>
|
|
226
|
-
<Link className={icon} />
|
|
227
|
-
Copy link
|
|
228
|
-
</Item>
|
|
229
|
-
{session.view === "chat" && (
|
|
230
|
-
<Item
|
|
231
|
-
onClick={() =>
|
|
232
|
-
shareUiActions.open({
|
|
233
|
-
kind: "chat",
|
|
234
|
-
sessionId: session.id,
|
|
235
|
-
title: session.name,
|
|
236
|
-
})
|
|
237
|
-
}
|
|
238
|
-
>
|
|
239
|
-
<Share2 className={icon} />
|
|
240
|
-
Share…
|
|
241
|
-
</Item>
|
|
242
|
-
)}
|
|
243
|
-
<Item onClick={onRename}>
|
|
244
|
-
<Pencil className={icon} />
|
|
245
|
-
Rename
|
|
246
|
-
</Item>
|
|
247
|
-
{session.agent_type === "claude" && !session.role && blocked && (
|
|
248
|
-
<BlockedItem
|
|
249
|
-
kind={kind}
|
|
250
|
-
icon={Copy}
|
|
251
|
-
label="Fork session"
|
|
252
|
-
why={blocked.fork}
|
|
253
|
-
/>
|
|
321
|
+
{plan.remoteNote && remote && (
|
|
322
|
+
<div className="text-muted-foreground px-2 py-1.5 text-xs leading-tight">
|
|
323
|
+
{remoteNote(linked[session.host_id!] ?? "its machine")}
|
|
324
|
+
</div>
|
|
254
325
|
)}
|
|
255
|
-
{
|
|
256
|
-
<
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
</Item>
|
|
268
|
-
)}
|
|
269
|
-
{!session.role && !blocked && (
|
|
270
|
-
<Item
|
|
271
|
-
onClick={() => ctx.onSummarize(session.id)}
|
|
272
|
-
disabled={summarizing}
|
|
273
|
-
>
|
|
274
|
-
{summarizing ? (
|
|
275
|
-
<Loader2 className={`${icon} animate-spin`} />
|
|
276
|
-
) : (
|
|
277
|
-
<Sparkles className={icon} />
|
|
278
|
-
)}
|
|
279
|
-
{summarizing ? "Summarizing..." : "Fresh start"}
|
|
280
|
-
</Item>
|
|
281
|
-
)}
|
|
282
|
-
{(session.role === "orchestrator" || !!session.has_brief) &&
|
|
283
|
-
session.view === "chat" &&
|
|
284
|
-
!session.archived_at &&
|
|
285
|
-
!blocked && (
|
|
286
|
-
<Item onClick={() => ctx.onFresh(session.id)}>
|
|
287
|
-
<RotateCcw className={icon} />
|
|
288
|
-
New conversation (keep brief)
|
|
289
|
-
</Item>
|
|
290
|
-
)}
|
|
291
|
-
{session.pr_url && (
|
|
292
|
-
<Item onClick={() => window.open(session.pr_url!, "_blank")}>
|
|
293
|
-
<GitPullRequest className={icon} />
|
|
294
|
-
Open PR #{session.pr_number}
|
|
295
|
-
</Item>
|
|
296
|
-
)}
|
|
297
|
-
{cardUrl && (
|
|
298
|
-
<Item onClick={() => window.open(cardUrl, "_blank")}>
|
|
299
|
-
<KanbanSquare className={icon} />
|
|
300
|
-
LumifyHub card
|
|
301
|
-
</Item>
|
|
302
|
-
)}
|
|
303
|
-
<MoveMenuItems session={session} Item={Item} />
|
|
304
|
-
{!session.role && others.length > 0 && blocked && (
|
|
305
|
-
<BlockedItem
|
|
306
|
-
kind={kind}
|
|
307
|
-
icon={FolderInput}
|
|
308
|
-
label="Move to project"
|
|
309
|
-
why={blocked.moveToProject}
|
|
310
|
-
/>
|
|
311
|
-
)}
|
|
312
|
-
{!session.role && others.length > 0 && !blocked && (
|
|
313
|
-
<Sub>
|
|
314
|
-
<SubTrigger>
|
|
315
|
-
<FolderInput className={icon} />
|
|
316
|
-
Move to project
|
|
317
|
-
</SubTrigger>
|
|
318
|
-
<SubContent>
|
|
319
|
-
{others.map((p) => (
|
|
320
|
-
<Item
|
|
321
|
-
key={p.id}
|
|
322
|
-
onClick={() => ctx.onMoveToProject(session.id, p.id)}
|
|
326
|
+
{plan.sections.map((section, i) => (
|
|
327
|
+
<Fragment key={i}>
|
|
328
|
+
{i > 0 && <Separator />}
|
|
329
|
+
{section.map((entry) =>
|
|
330
|
+
typeof entry === "string" ? (
|
|
331
|
+
render(entry, false)
|
|
332
|
+
) : (
|
|
333
|
+
<Expander
|
|
334
|
+
key={entry.group}
|
|
335
|
+
kind={kind}
|
|
336
|
+
icon={GROUP_ICON[entry.group]}
|
|
337
|
+
label={groupLabel(entry.group)}
|
|
323
338
|
>
|
|
324
|
-
{
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
</Sub>
|
|
329
|
-
)}
|
|
330
|
-
{workspaceId &&
|
|
331
|
-
session.role !== "orchestrator" &&
|
|
332
|
-
(!session.task_status || session.task_status === "running") &&
|
|
333
|
-
blocked && (
|
|
334
|
-
<BlockedItem
|
|
335
|
-
kind={kind}
|
|
336
|
-
icon={Clock}
|
|
337
|
-
label="Schedule check-ins"
|
|
338
|
-
why={blocked.schedule}
|
|
339
|
-
/>
|
|
340
|
-
)}
|
|
341
|
-
{workspaceId &&
|
|
342
|
-
session.role !== "orchestrator" &&
|
|
343
|
-
(!session.task_status || session.task_status === "running") &&
|
|
344
|
-
!blocked && (
|
|
345
|
-
<Item
|
|
346
|
-
onClick={() =>
|
|
347
|
-
schedulesUiActions.openDraft(workspaceId, {
|
|
348
|
-
kind: "message",
|
|
349
|
-
targetSessionId: session.id,
|
|
350
|
-
name: `Check-in: ${session.name}`.slice(0, 80),
|
|
351
|
-
})
|
|
352
|
-
}
|
|
353
|
-
>
|
|
354
|
-
<Clock className={icon} />
|
|
355
|
-
Schedule check-ins
|
|
356
|
-
</Item>
|
|
357
|
-
)}
|
|
358
|
-
{recipeRun && (
|
|
359
|
-
<>
|
|
360
|
-
<Item onClick={() => recipesUiActions.openRecipe(recipeRun.id)}>
|
|
361
|
-
<Repeat className={icon} />
|
|
362
|
-
Open recipe
|
|
363
|
-
</Item>
|
|
364
|
-
<Item onClick={() => recipesUiActions.openRecipe(recipeRun.id, true)}>
|
|
365
|
-
<Play className={icon} />
|
|
366
|
-
Run again
|
|
367
|
-
</Item>
|
|
368
|
-
</>
|
|
369
|
-
)}
|
|
370
|
-
{!recipeRun && !session.role && !session.quick_of && (
|
|
371
|
-
<Item
|
|
372
|
-
onClick={() =>
|
|
373
|
-
recipesUiActions.saveSessionAs(session.id, workspaceId)
|
|
374
|
-
}
|
|
375
|
-
>
|
|
376
|
-
<ChefHat className={icon} />
|
|
377
|
-
Save as recipe
|
|
378
|
-
</Item>
|
|
379
|
-
)}
|
|
380
|
-
<Item
|
|
381
|
-
onClick={() =>
|
|
382
|
-
selectionActions.toggle(session.id, false, ctx.orderedIds())
|
|
383
|
-
}
|
|
384
|
-
>
|
|
385
|
-
<CheckSquare className={icon} />
|
|
386
|
-
Select
|
|
387
|
-
</Item>
|
|
388
|
-
{!session.role && (
|
|
389
|
-
<>
|
|
390
|
-
<Separator />
|
|
391
|
-
{row?.wakesAt ? (
|
|
392
|
-
<Item onClick={() => shelf.unsnooze(session)}>
|
|
393
|
-
<AlarmClock className={icon} />
|
|
394
|
-
Unsnooze
|
|
395
|
-
</Item>
|
|
396
|
-
) : (
|
|
397
|
-
!session.pinned &&
|
|
398
|
-
row && (
|
|
399
|
-
<Sub>
|
|
400
|
-
<SubTrigger>
|
|
401
|
-
<AlarmClock className={icon} />
|
|
402
|
-
Snooze
|
|
403
|
-
</SubTrigger>
|
|
404
|
-
<SubContent>
|
|
405
|
-
{SNOOZE_PRESETS.map((p) => (
|
|
406
|
-
<Item
|
|
407
|
-
key={p.key}
|
|
408
|
-
onClick={() => shelf.snooze(session, p.key)}
|
|
409
|
-
>
|
|
410
|
-
{p.label}
|
|
411
|
-
<span className="text-muted-foreground ml-3 text-xs">
|
|
412
|
-
{wakeLabel(snoozePreset(p.key).getTime())}
|
|
413
|
-
</span>
|
|
414
|
-
</Item>
|
|
415
|
-
))}
|
|
416
|
-
</SubContent>
|
|
417
|
-
</Sub>
|
|
339
|
+
{entry.items.map((it) => (
|
|
340
|
+
<Fragment key={it}>{render(it, true)}</Fragment>
|
|
341
|
+
))}
|
|
342
|
+
</Expander>
|
|
418
343
|
)
|
|
419
344
|
)}
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
session.pause_state === "pausing" ? (
|
|
423
|
-
<Item onClick={() => shelf.resume(session)}>
|
|
424
|
-
<Play className={icon} />
|
|
425
|
-
{session.pause_state === "paused"
|
|
426
|
-
? "Resume now"
|
|
427
|
-
: "Keep running"}
|
|
428
|
-
</Item>
|
|
429
|
-
) : session.pause_state ? null : (
|
|
430
|
-
<Sub>
|
|
431
|
-
<SubTrigger>
|
|
432
|
-
<Pause className={icon} />
|
|
433
|
-
Pause
|
|
434
|
-
</SubTrigger>
|
|
435
|
-
<SubContent>
|
|
436
|
-
{PAUSE_PRESETS.map((p) => {
|
|
437
|
-
const at = pauseUntil(p.key);
|
|
438
|
-
return (
|
|
439
|
-
<Item
|
|
440
|
-
key={p.key}
|
|
441
|
-
onClick={() => shelf.pause(session, p.key)}
|
|
442
|
-
>
|
|
443
|
-
{p.label}
|
|
444
|
-
{at && (
|
|
445
|
-
<span className="text-muted-foreground ml-3 text-xs">
|
|
446
|
-
{wakeLabel(at.getTime())}
|
|
447
|
-
</span>
|
|
448
|
-
)}
|
|
449
|
-
</Item>
|
|
450
|
-
);
|
|
451
|
-
})}
|
|
452
|
-
<Item
|
|
453
|
-
onClick={() => shelf.pause(session, "until-resumed", true)}
|
|
454
|
-
>
|
|
455
|
-
Pause now (interrupt its step)
|
|
456
|
-
</Item>
|
|
457
|
-
</SubContent>
|
|
458
|
-
</Sub>
|
|
459
|
-
)
|
|
460
|
-
) : null}
|
|
461
|
-
{plain &&
|
|
462
|
-
(row.settled ? (
|
|
463
|
-
<Item onClick={() => shelf.reopen(session)}>
|
|
464
|
-
<RotateCcw className={icon} />
|
|
465
|
-
Reopen
|
|
466
|
-
</Item>
|
|
467
|
-
) : (
|
|
468
|
-
<Item onClick={() => shelf.settle(session)}>
|
|
469
|
-
<CircleCheck className={icon} />
|
|
470
|
-
Mark done
|
|
471
|
-
</Item>
|
|
472
|
-
))}
|
|
473
|
-
<FinishItems session={session} plain={plain} Item={Item} />
|
|
474
|
-
<Item
|
|
475
|
-
onClick={() => ctx.onDelete(session.id)}
|
|
476
|
-
className="text-red-500 focus:text-red-500"
|
|
477
|
-
>
|
|
478
|
-
<Trash2 className={icon} />
|
|
479
|
-
Delete session
|
|
480
|
-
</Item>
|
|
481
|
-
</>
|
|
482
|
-
)}
|
|
345
|
+
</Fragment>
|
|
346
|
+
))}
|
|
483
347
|
</>
|
|
484
348
|
);
|
|
485
349
|
}
|