@saadnvd1/agent-os 0.3.851 → 0.3.853

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.
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  import { useState } from "react";
4
- import { GitFork, Square } from "lucide-react";
4
+ import { CircleCheck, GitFork, Square } from "lucide-react";
5
5
  import { toast } from "sonner";
6
6
  import { useExperiment } from "@/data/experiments";
7
7
  import {
@@ -13,6 +13,7 @@ import {
13
13
  import { formatElapsed } from "@/lib/chat/elapsed";
14
14
  import { Button } from "@/components/ui/button";
15
15
  import { StatusPill } from "@/components/ui/status-pill";
16
+ import { useDoneAction } from "@/components/SessionList/useDoneAction";
16
17
  import { SessionAnchor } from "./SessionAnchor";
17
18
 
18
19
  type Tone = "amber" | "muted" | "green" | "primary" | "red";
@@ -73,12 +74,14 @@ export const runningNote = (c: ChildView, now = Date.now()): string | null => {
73
74
  function Row({
74
75
  c,
75
76
  onStop,
77
+ onDone,
76
78
  stopping,
77
79
  again,
78
80
  }: {
79
81
  c: ChildView;
80
82
  again: ReturnType<typeof useRunAgain>;
81
83
  onStop: () => void;
84
+ onDone: () => void;
82
85
  stopping: boolean;
83
86
  }) {
84
87
  const { text, tone } = stateLabel(c);
@@ -144,6 +147,16 @@ function Row({
144
147
  <Square aria-hidden className="size-4" />
145
148
  </Button>
146
149
  )}
150
+ <Button
151
+ size="sm"
152
+ variant="ghost"
153
+ aria-label={`Mark fork ${c.n} done`}
154
+ data-tip="Done"
155
+ className="size-11 shrink-0"
156
+ onClick={onDone}
157
+ >
158
+ <CircleCheck aria-hidden className="size-4" />
159
+ </Button>
147
160
  </li>
148
161
  );
149
162
  }
@@ -154,10 +167,11 @@ export function ForksStrip({ sessionId }: { sessionId: string }) {
154
167
  const { data } = useForkChildren(sessionId, on);
155
168
  const cancel = useCancelForks(sessionId);
156
169
  const again = useRunAgain(sessionId);
170
+ const markDone = useDoneAction();
157
171
  const [collapsed, setCollapsed] = useState(false);
158
172
  if (!on || !data) return null;
159
173
  if (data.asChild) return <ForkOfBanner info={data.asChild} />;
160
- const { children } = data;
174
+ const children = data.children.filter((c) => !c.done);
161
175
  if (!children.length) return null;
162
176
  const open = children.filter((c) => c.state !== "settled").length;
163
177
  const stop = (ref?: string) =>
@@ -206,6 +220,14 @@ export function ForksStrip({ sessionId }: { sessionId: string }) {
206
220
  stopping={cancel.isPending}
207
221
  again={again}
208
222
  onStop={() => stop(String(c.n))}
223
+ onDone={() =>
224
+ markDone({
225
+ id: c.id,
226
+ name: c.label,
227
+ task_status: null,
228
+ pr_status: null,
229
+ })
230
+ }
209
231
  />
210
232
  ))}
211
233
  </ul>
@@ -9,12 +9,18 @@ import {
9
9
  } from "@/data/done";
10
10
  import { useSettleSession } from "@/data/sessions/shelf";
11
11
 
12
+ export type DoneTarget = Pick<
13
+ Session,
14
+ "id" | "name" | "task_status" | "pr_status"
15
+ > &
16
+ Partial<Pick<Session, "settled_at">>;
17
+
12
18
  // Done from a session's menu: an open PR merges through the gates, after an
13
19
  // in-app confirmation; a failing gate comes back as an error toast naming it.
14
20
  export function useDoneAction() {
15
21
  const done = useDoneSession();
16
22
  const settle = useSettleSession();
17
- const run = (session: Session, merges: boolean) => {
23
+ const run = (session: DoneTarget, merges: boolean) => {
18
24
  const id = toast.loading(
19
25
  merges ? `Merging ${session.name}…` : `Finishing ${session.name}…`
20
26
  );
@@ -38,7 +44,7 @@ export function useDoneAction() {
38
44
  onError: (e) => toast.error(e.message, { id, duration: 12000 }),
39
45
  });
40
46
  };
41
- return (session: Session) => {
47
+ return (session: DoneTarget) => {
42
48
  const merges =
43
49
  session.task_status === "running" && session.pr_status === "open";
44
50
  if (!merges) return run(session, false);
@@ -2033,6 +2033,14 @@
2033
2033
  "area": "Updates",
2034
2034
  "text": "You can update AgentOS by hand again on machines that deploy themselves, and Update now skips the hourly wait.",
2035
2035
  "url": "https://github.com/saadnvd1/agent-os/pull/571"
2036
+ },
2037
+ {
2038
+ "id": 573,
2039
+ "date": "2026-11-02",
2040
+ "kind": "new",
2041
+ "area": "AgentOS",
2042
+ "text": "Mark a fork as done straight from the Forks list above the chat box.",
2043
+ "url": "https://github.com/saadnvd1/agent-os/pull/573"
2036
2044
  }
2037
2045
  ]
2038
2046
  }
@@ -5,6 +5,7 @@ import type { ArchivedView } from "@/lib/done/archive";
5
5
  import type { ResurrectOutcome } from "@/lib/resurrect";
6
6
  import { sessionKeys } from "../sessions/keys";
7
7
  import { taskKeys } from "../tasks";
8
+ import { forkKeys } from "../fork";
8
9
 
9
10
  export const archivedKeys = {
10
11
  all: ["archived-sessions"] as const,
@@ -52,7 +53,12 @@ function useDoneMutation<V, R>(fn: (v: V) => Promise<R>) {
52
53
  return useMutation({
53
54
  mutationFn: fn,
54
55
  onSettled: () => {
55
- for (const queryKey of [sessionKeys.all, taskKeys.all, archivedKeys.all])
56
+ for (const queryKey of [
57
+ sessionKeys.all,
58
+ taskKeys.all,
59
+ archivedKeys.all,
60
+ forkKeys.all,
61
+ ])
56
62
  queryClient.invalidateQueries({ queryKey });
57
63
  },
58
64
  });
package/lib/fork/types.ts CHANGED
@@ -65,6 +65,8 @@ export interface ChildView {
65
65
  excerpt: string | null;
66
66
  resultKind: ResultKind | null;
67
67
  worktree: boolean;
68
+ // Marked done or archived as a session: it leaves the strip.
69
+ done: boolean;
68
70
  fellBack: boolean;
69
71
  startedAt: number | null;
70
72
  settledAt: number | null;
package/lib/fork/view.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  /** What clients and `aos children` show, and the numbers behind them. */
2
2
 
3
3
  import { lastItems } from "../chat/store";
4
+ import { db } from "../db";
4
5
  import { childrenOf, getGroup } from "./ledger";
5
6
  import type { ChildRow, ChildView, GroupView } from "./types";
6
7
 
@@ -17,6 +18,7 @@ export const childView = (c: ChildRow): ChildView => ({
17
18
  excerpt: c.result ? c.result.slice(0, 600) : null,
18
19
  resultKind: c.result_kind,
19
20
  worktree: !!c.worktree,
21
+ done: false,
20
22
  fellBack: !!c.fell_back,
21
23
  startedAt: c.started_at,
22
24
  settledAt: c.settled_at,
@@ -32,6 +34,13 @@ const activityOf = (c: ChildRow): string | null => {
32
34
  return tool && tool.kind === "tool" ? tool.title : null;
33
35
  };
34
36
 
37
+ const isDone = (id: string): boolean =>
38
+ !!db
39
+ .prepare(
40
+ `SELECT 1 FROM sessions WHERE id = ? AND (settled_at IS NOT NULL OR archived_at IS NOT NULL)`
41
+ )
42
+ .get(id);
43
+
35
44
  export function childrenView(parentId: string): {
36
45
  children: ChildView[];
37
46
  groups: GroupView[];
@@ -57,6 +66,7 @@ export function childrenView(parentId: string): {
57
66
  children: rows.map((c, i) => ({
58
67
  ...childView(c),
59
68
  n: i + 1,
69
+ done: isDone(c.child_session_id),
60
70
  activity: activityOf(c),
61
71
  })),
62
72
  groups,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saadnvd1/agent-os",
3
- "version": "0.3.851",
3
+ "version": "0.3.853",
4
4
  "description": "Self-hosted web UI for managing Claude Code sessions",
5
5
  "bin": {
6
6
  "agent-os": "scripts/agent-os"