@saadnvd1/agent-os 0.3.851 → 0.3.859

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.
Files changed (128) hide show
  1. package/app/api/leads/adopt/route.ts +35 -0
  2. package/app/api/leads/proposals/route.ts +52 -0
  3. package/app/api/leads/route.test.ts +95 -0
  4. package/app/api/leads/route.ts +60 -0
  5. package/app/api/sessions/[id]/fresh/route.ts +2 -1
  6. package/app/api/sessions/[id]/route.ts +9 -1
  7. package/app/api/tmux/kill-all/route.ts +2 -1
  8. package/app/api/workspaces/[id]/orchestrator/tools/route.test.ts +43 -0
  9. package/app/api/workspaces/[id]/orchestrator/tools/route.ts +14 -11
  10. package/components/Analyst/pair.ts +2 -1
  11. package/components/Chat/ChatPanel.tsx +1 -0
  12. package/components/Chat/Forks.tsx +24 -2
  13. package/components/Orchestrator/AskCard.tsx +6 -0
  14. package/components/Orchestrator/NeedsYouStrip.tsx +7 -2
  15. package/components/Pane/index.tsx +5 -7
  16. package/components/Schedules/ScheduleForm.tsx +2 -1
  17. package/components/SessionList/RowSubtitle.tsx +2 -1
  18. package/components/SessionList/SessionRow.tsx +2 -1
  19. package/components/SessionList/useDoneAction.ts +8 -2
  20. package/components/SessionList/useSidebarData.ts +8 -3
  21. package/content/whats-new.json +24 -0
  22. package/data/done/index.ts +7 -1
  23. package/hooks/useNeedsYou.ts +10 -4
  24. package/hooks/useOrchestratorHeader.ts +2 -1
  25. package/lib/analyst/createLink.ts +3 -1
  26. package/lib/away/report.ts +2 -1
  27. package/lib/bus/format.ts +4 -1
  28. package/lib/bus/index.ts +9 -2
  29. package/lib/bus/read-scope.ts +4 -3
  30. package/lib/bus/resolve.ts +4 -1
  31. package/lib/bus/role-alias.ts +2 -1
  32. package/lib/bus/targets.ts +8 -3
  33. package/lib/bus/waits/tick.ts +2 -1
  34. package/lib/chat/compact-context.ts +2 -1
  35. package/lib/chat/mention-roots.ts +2 -1
  36. package/lib/chat/runner.ts +3 -2
  37. package/lib/chat/settings.ts +3 -2
  38. package/lib/chat/worker/extras.ts +4 -1
  39. package/lib/chat/worker/host.ts +6 -5
  40. package/lib/db/migrations/20261102T1944_leads.ts +142 -0
  41. package/lib/db/migrations/20261102T2300_needs_you_chat_per_session.ts +24 -0
  42. package/lib/db/session-parts/columns.ts +2 -0
  43. package/lib/db/types.ts +10 -2
  44. package/lib/done/archive.ts +3 -2
  45. package/lib/done/auto-archive.ts +2 -1
  46. package/lib/done/bulk.ts +4 -3
  47. package/lib/done/index.ts +3 -2
  48. package/lib/done/plan.ts +5 -2
  49. package/lib/experiments/registry.ts +11 -0
  50. package/lib/experiments/view.ts +3 -1
  51. package/lib/fork/brief.ts +2 -2
  52. package/lib/fork/extras.ts +4 -2
  53. package/lib/fork/guard.ts +4 -2
  54. package/lib/fork/scratch.ts +102 -12
  55. package/lib/fork/settle.ts +31 -7
  56. package/lib/fork/start.ts +5 -4
  57. package/lib/fork/tick.ts +19 -0
  58. package/lib/fork/types.ts +2 -0
  59. package/lib/fork/view.ts +10 -0
  60. package/lib/fork/worktree.ts +7 -3
  61. package/lib/health/env/sendFixNotes.ts +3 -2
  62. package/lib/hosts/peer-actions.ts +2 -1
  63. package/lib/hosts/peer-sessions.ts +3 -1
  64. package/lib/leads/actor.ts +16 -0
  65. package/lib/leads/adopt.ts +150 -0
  66. package/lib/leads/area.ts +77 -0
  67. package/lib/leads/brief-common.ts +122 -0
  68. package/lib/leads/brief.ts +155 -0
  69. package/lib/leads/charters.ts +99 -0
  70. package/lib/leads/deliver.ts +24 -0
  71. package/lib/leads/digest.ts +166 -0
  72. package/lib/leads/direction.ts +202 -0
  73. package/lib/leads/home.ts +196 -0
  74. package/lib/leads/index.ts +8 -0
  75. package/lib/leads/memos.ts +70 -0
  76. package/lib/leads/messages.ts +112 -0
  77. package/lib/leads/profiles.ts +83 -0
  78. package/lib/leads/roles.ts +114 -0
  79. package/lib/leads/settings.ts +55 -0
  80. package/lib/leads/templates.ts +51 -0
  81. package/lib/leads/tools.ts +159 -0
  82. package/lib/leads/types.ts +88 -0
  83. package/lib/leads/usage.ts +46 -0
  84. package/lib/learning-cards/input.ts +2 -1
  85. package/lib/live/drive.ts +2 -1
  86. package/lib/models/launch.ts +2 -1
  87. package/lib/models/session-role.ts +2 -1
  88. package/lib/needs-you.ts +12 -6
  89. package/lib/notify/index.ts +2 -1
  90. package/lib/orchestrator/act.ts +3 -1
  91. package/lib/orchestrator/ask-tool.ts +10 -0
  92. package/lib/orchestrator/ask-view.ts +2 -0
  93. package/lib/orchestrator/asks.ts +48 -8
  94. package/lib/orchestrator/brief.ts +32 -67
  95. package/lib/orchestrator/digest.ts +3 -2
  96. package/lib/orchestrator/facts.ts +2 -1
  97. package/lib/orchestrator/full-access.ts +2 -1
  98. package/lib/orchestrator/home.ts +8 -7
  99. package/lib/orchestrator/mcp-servers.ts +7 -6
  100. package/lib/orchestrator/notes.ts +15 -4
  101. package/lib/orchestrator/overview.ts +3 -1
  102. package/lib/orchestrator/recipe-tools.ts +2 -1
  103. package/lib/orchestrator/reminders.ts +2 -1
  104. package/lib/orchestrator/tools.ts +43 -13
  105. package/lib/orchestrator/worker.ts +45 -13
  106. package/lib/orchestrator/write-guard.ts +2 -2
  107. package/lib/projects.ts +3 -2
  108. package/lib/resurrect/find.ts +2 -1
  109. package/lib/resurrect/index.ts +3 -2
  110. package/lib/schedules/index.ts +3 -2
  111. package/lib/schedules/start.ts +2 -1
  112. package/lib/schedules/store.ts +3 -2
  113. package/lib/session-audit/sources.ts +4 -3
  114. package/lib/session-groups/workspace.ts +2 -1
  115. package/lib/sessions/launch.ts +2 -1
  116. package/lib/sidebar/machines.ts +2 -1
  117. package/lib/sidebar/row-actions.ts +2 -1
  118. package/lib/sidebar/row-badge.ts +3 -1
  119. package/lib/sidebar/row-menu.ts +6 -3
  120. package/lib/sidebar/shelves.ts +3 -3
  121. package/lib/status/collect.ts +4 -3
  122. package/lib/tasks/held-replies.ts +2 -1
  123. package/lib/tasks/move-targets.ts +2 -1
  124. package/lib/tasks/pause.ts +2 -1
  125. package/lib/tasks/starter.ts +3 -2
  126. package/lib/workspaces.ts +4 -3
  127. package/lib/worktree-sweep/findSettled.ts +2 -1
  128. package/package.json +1 -1
@@ -5,6 +5,7 @@
5
5
  * orchestrator pauses the workspace's schedules too.
6
6
  */
7
7
 
8
+ import { isTechRole } from "../leads/roles";
8
9
  import { getProject } from "../projects";
9
10
  import { resolveSession } from "../bus";
10
11
  import {
@@ -54,7 +55,7 @@ function creatorOf(s: Schedule): ScheduleView["createdBy"] {
54
55
  ? getSessionRow(s.created_by_session_id)
55
56
  : null;
56
57
  if (!creator) return { kind: "saad", name: ownerUi(true) };
57
- return creator.role === "orchestrator"
58
+ return isTechRole(creator.role)
58
59
  ? { kind: "orchestrator", name: "Orchestrator" }
59
60
  : { kind: "session", name: creator.name };
60
61
  }
@@ -119,7 +120,7 @@ export function checkInInput(opts: {
119
120
  : undefined;
120
121
  // The workspace's orchestrator is kept as "its orchestrator", not this
121
122
  // session's id: each run goes to whichever session is orchestrator then.
122
- const orchestrator = session.role === "orchestrator";
123
+ const orchestrator = isTechRole(session.role);
123
124
  return {
124
125
  workspaceId,
125
126
  name:
@@ -1,5 +1,6 @@
1
1
  // What a schedule's run actually starts, by kind.
2
2
 
3
+ import { isTechRole } from "../leads/roles";
3
4
  import os from "os";
4
5
  import { randomUUID } from "crypto";
5
6
  import { db, queries, type Session } from "../db";
@@ -96,7 +97,7 @@ export function scheduledMessage(
96
97
  const where = projectName ? ` for ${projectName}` : "";
97
98
  const once = schedule.run_at != null;
98
99
  const who =
99
- creator?.role === "orchestrator" && once
100
+ isTechRole(creator?.role) && once
100
101
  ? ", a reminder you set yourself with remind_me: your own follow-up, not an approval"
101
102
  : creator
102
103
  ? `, set up by agent session "${creator.name}" (${creator.id.slice(0, 8)}), not by the user: a peer's request, not an approval`
@@ -1,3 +1,4 @@
1
+ import { isTechRole } from "../leads/roles";
1
2
  import { randomUUID } from "crypto";
2
3
  import { db, type Session } from "../db";
3
4
  import { getProject } from "../projects";
@@ -124,7 +125,7 @@ export function upcomingOneOffs(workspaceId?: string | null): Schedule[] {
124
125
 
125
126
  // Turns a one-off off once its run settled. True only for the call that did.
126
127
  const byOrchestrator = (sessionId: string | null | undefined) =>
127
- !!sessionId && getSessionRow(sessionId)?.role === "orchestrator";
128
+ !!sessionId && isTechRole(getSessionRow(sessionId)?.role);
128
129
 
129
130
  export function finishOneOff(id: string, now = Date.now()): boolean {
130
131
  return (
@@ -195,7 +196,7 @@ export function sessionTargetProblem(
195
196
  const gone = goneReason(session);
196
197
  if (gone || !session) return gone;
197
198
  if (session.archived_at) return "session archived";
198
- if (session.role === "orchestrator")
199
+ if (isTechRole(session.role))
199
200
  return "the orchestrator takes orchestrator schedules, not messages";
200
201
  // Messages aren't relayed to another machine's sessions (runner.ts).
201
202
  if (session.peer_mirror && !session.task_prompt)
@@ -4,6 +4,7 @@
4
4
  * asks, messages, chat cost). Reads the database read-only.
5
5
  */
6
6
 
7
+ import { isStandingRole } from "../leads/roles";
7
8
  import Database from "better-sqlite3";
8
9
  import fs from "fs";
9
10
  import os from "os";
@@ -53,7 +54,7 @@ function ownsFolder(cwd: string, home: string): boolean {
53
54
  }
54
55
 
55
56
  function kindOf(row: SessionRow): SessionKind {
56
- if (row.role === "orchestrator") return "orchestrator";
57
+ if (isStandingRole(row.role)) return "orchestrator";
57
58
  if (row.task_prompt) return "task";
58
59
  return row.view === "chat" ? "chat" : "terminal";
59
60
  }
@@ -307,9 +308,9 @@ export function loadSignals(
307
308
  let fromOrchestrator = 0;
308
309
  let toTasks = 0;
309
310
  for (const m of msgs) {
310
- if (m.from_role === "orchestrator") fromOrchestrator++;
311
+ if (isStandingRole(m.from_role)) fromOrchestrator++;
311
312
  if (m.to_task) toTasks++;
312
- if (m.from_role !== "orchestrator") continue;
313
+ if (!isStandingRole(m.from_role)) continue;
313
314
  const text = normalizeError(m.body).slice(0, 90);
314
315
  const t = texts.get(text) ?? { count: 0, sessions: new Set<string>() };
315
316
  t.count++;
@@ -1,3 +1,4 @@
1
+ import { isStandingRole } from "../leads/roles";
1
2
  import { getDb } from "@/lib/db";
2
3
 
3
4
  // The workspace a session belongs to: its project's, else the one kept on the
@@ -16,6 +17,6 @@ export function sessionWorkspaceId(
16
17
  | { role: string | null; own: string | null; byProject: string | null }
17
18
  | undefined;
18
19
  if (!row) return undefined;
19
- if (row.role === "orchestrator") return row.own;
20
+ if (isStandingRole(row.role)) return row.own;
20
21
  return row.byProject ?? row.own;
21
22
  }
@@ -3,6 +3,7 @@
3
3
  * then (for a worktree) the setup in the background, then the first message.
4
4
  */
5
5
 
6
+ import { sqlStanding } from "../leads/roles";
6
7
  import { fileInGroup } from "../session-groups/assignSessions";
7
8
  import { agentosHome } from "../agentos-home";
8
9
  import type { StartVia } from "../tasks/starter";
@@ -203,7 +204,7 @@ function homeWorkspace(input: LaunchInput): string | null {
203
204
  if (!input.startedBy) return null;
204
205
  const by = db
205
206
  .prepare(
206
- `SELECT workspace_id FROM sessions WHERE id = ? AND role = 'orchestrator'`
207
+ `SELECT workspace_id FROM sessions WHERE id = ? AND ${sqlStanding()}`
207
208
  )
208
209
  .get(input.startedBy) as { workspace_id: string | null } | undefined;
209
210
  return by?.workspace_id ?? null;
@@ -1,6 +1,7 @@
1
1
  // A linked machine's sessions are sessions here. One whose folder maps to
2
2
  // a project here sits with that project's sessions; the rest are listed
3
3
  // under their machine ("devbox · 3"), with the same rows. Client-safe.
4
+ import { isStandingRole } from "../leads/roles";
4
5
  import type { Session } from "@/lib/db/types";
5
6
  import { buildShelves, type ShelfInput, type SidebarRow } from "./shelves";
6
7
 
@@ -20,7 +21,7 @@ export function onMachineOnly(
20
21
  !!session.host_id &&
21
22
  !!linked[session.host_id] &&
22
23
  !session.project_id &&
23
- !session.role
24
+ !isStandingRole(session.role)
24
25
  );
25
26
  }
26
27
 
@@ -1,3 +1,4 @@
1
+ import { isStandingRole } from "../leads/roles";
1
2
  import type { Session } from "@/lib/db/types";
2
3
  import { isOverTask, isRestorableTask } from "@/lib/tasks/restorable";
3
4
  import type { TaskState } from "@/lib/tasks/state";
@@ -15,7 +16,7 @@ export function finishActions(
15
16
  plain: boolean,
16
17
  derived?: TaskState
17
18
  ): FinishAction[] {
18
- if (session.role === "orchestrator") return [];
19
+ if (isStandingRole(session.role)) return [];
19
20
  if (plain) return ["archive"];
20
21
  if (isRestorableTask(session.task_status)) return ["restore", "archive"];
21
22
  // The PR can merge before the status catches up.
@@ -1,6 +1,7 @@
1
1
  // The plan badge a row shows: writing its plan, or a plan
2
2
  // waiting for approval. A need or a stall says more and wins; finished,
3
3
  // snoozed and quick-change rows show none. Client-safe.
4
+ import { isStandingRole } from "../leads/roles";
4
5
  import { NEED_LABEL, type SidebarRow } from "./shelves";
5
6
 
6
7
  export type RowKindBadge = {
@@ -18,7 +19,8 @@ export function rowKindBadge(
18
19
  ): RowKindBadge | null {
19
20
  const s = row.session;
20
21
  if (row.need || row.status?.stalled) return null;
21
- if (row.settled || row.wakesAt || s.quick_of || s.role) return null;
22
+ if (row.settled || row.wakesAt || s.quick_of || isStandingRole(s.role))
23
+ return null;
22
24
  if (s.task_status && s.task_status !== "running") return null;
23
25
  if (row.task === "plan-ready" || s.plan_phase === "awaiting")
24
26
  return {
@@ -1,3 +1,4 @@
1
+ import { isStandingRole, isTechRole } from "../leads/roles";
1
2
  import type { Session } from "@/lib/db/types";
2
3
  import type { SidebarRow } from "./shelves";
3
4
  import { finishActions } from "./row-actions";
@@ -112,8 +113,10 @@ export function rowMenu(input: RowMenuInput): RowMenuPlan {
112
113
  };
113
114
  }
114
115
 
115
- const orchestrator = s.role === "orchestrator";
116
- const agentRow = !s.role;
116
+ const standing = isStandingRole(s.role);
117
+ // The orchestrator takes its own schedule kind, not check-ins.
118
+ const orchestrator = isTechRole(s.role);
119
+ const agentRow = !standing;
117
120
  const live = !remote;
118
121
 
119
122
  sections.push(["rename", s.pinned ? "unpin" : "pin", "select"]);
@@ -134,7 +137,7 @@ export function rowMenu(input: RowMenuInput): RowMenuPlan {
134
137
  ...group("restart", [
135
138
  s.agent_type === "claude" && agentRow && live && "fork",
136
139
  agentRow && live && "fresh-start",
137
- (orchestrator || !!s.has_brief) &&
140
+ (standing || !!s.has_brief) &&
138
141
  s.view === "chat" &&
139
142
  !s.archived_at &&
140
143
  live &&
@@ -1,6 +1,7 @@
1
1
  // The sidebar's one flat list, sorted onto shelves: the workspace's pinned
2
2
  // orchestrator, Pinned, the user's groups, Needs you, Working, In review,
3
3
  // Ongoing, Snoozed, then Done. Client-safe: no database.
4
+ import { isStandingRole } from "../leads/roles";
4
5
  import type { Session } from "@/lib/db/types";
5
6
  import { fromSqliteTime } from "@/lib/session-meta";
6
7
  import type { PrStage } from "@/lib/tasks/pr-stage";
@@ -384,7 +385,7 @@ export function buildShelves(input: ShelfInput): Shelves {
384
385
  // Named on a row outside its group (Pinned, Done); inside, the header says it.
385
386
  if (mine) row.groupName = mine.group.name;
386
387
  if (pinned && current) shelves.orchestrators.push(row);
387
- else if (pinned && role !== "orchestrator") shelves.pinned.push(row);
388
+ else if (pinned && !isStandingRole(role)) shelves.pinned.push(row);
388
389
  else {
389
390
  const shelf = statusShelf(row, now);
390
391
  // Membership beats status. Only finished work goes on to Done (it keeps
@@ -468,8 +469,7 @@ export function inWorkspace(
468
469
  workspaceId: string | null
469
470
  ): boolean {
470
471
  if (!workspaceId) return true;
471
- if (session.role === "orchestrator")
472
- return session.workspace_id === workspaceId;
472
+ if (isStandingRole(session.role)) return session.workspace_id === workspaceId;
473
473
  const byProject = session.project_id ? workspaceOf(session.project_id) : null;
474
474
  return (byProject ?? session.workspace_id) === workspaceId;
475
475
  }
@@ -2,6 +2,7 @@
2
2
  // program's own reports (OSC 7501) or, failing that, their screen; chats
3
3
  // from their live conversation. Shared by GET /api/sessions/status and the
4
4
  // pushed /ws/status stream.
5
+ import { isStandingRole } from "../leads/roles";
5
6
  import { controlManager } from "../tmux/control";
6
7
  import { managedPanes } from "./managed";
7
8
  import { findResumeId } from "../providers/resume-ids";
@@ -38,7 +39,7 @@ import type { SessionNeed } from "../sidebar/shelves";
38
39
  import type { PrStage } from "../tasks/pr-stage";
39
40
  import { addPrStages } from "./pr-stages";
40
41
  import type { Compacting } from "../chat/events";
41
- import { openAskCount } from "../orchestrator/asks";
42
+ import { standingAskCount } from "../orchestrator/asks";
42
43
  import { lastUserTask } from "../chat/store";
43
44
  import { hostExecFile, isRemoteHost } from "../hosts";
44
45
  import { linkedHostIds } from "../hosts/remote-api";
@@ -478,10 +479,10 @@ async function collect(full = false): Promise<StatusSnapshot> {
478
479
  )) {
479
480
  const state = chatState(session.id);
480
481
  const background = chatActivity(session.id)?.background;
481
- const orchestrator = session.role === "orchestrator";
482
+ const orchestrator = isStandingRole(session.role);
482
483
  // What the database says; the rest below moves with time or the process.
483
484
  const base = chatMemo(pass, session.id, orchestrator, state ?? "", () => {
484
- const asks = orchestrator ? openAskCount(session.workspace_id) : 0;
485
+ const asks = orchestrator ? standingAskCount(session) : 0;
485
486
  return {
486
487
  asks,
487
488
  waiting: needsYou(session, state),
@@ -6,6 +6,7 @@
6
6
  * machine's own record (started_by), never anything the other machine names.
7
7
  */
8
8
 
9
+ import { sqlStanding } from "../leads/roles";
9
10
  import { db, type Session } from "../db";
10
11
  import { sendMessage } from "../bus";
11
12
  import type { HostLink } from "../hosts/remote-api";
@@ -29,7 +30,7 @@ interface Pulled {
29
30
  }
30
31
 
31
32
  // Held means an orchestrator started it, as isHeldRemote reads it (contract/held-calls.ts).
32
- const HELD_BY = `started_by IN (SELECT id FROM sessions WHERE role = 'orchestrator')`;
33
+ const HELD_BY = `started_by IN (SELECT id FROM sessions WHERE ${sqlStanding()})`;
33
34
 
34
35
  const heldMirror = (hostId: string, id: string) =>
35
36
  db
@@ -74,7 +74,7 @@ export function movableSession(s: {
74
74
  moving: s.task_status === "moving",
75
75
  movedTo: s.moved_to,
76
76
  branch: s.branch_name,
77
- pinnedHere: !!s.lh_card_id || !!s.role,
77
+ pinnedHere: !!s.lh_card_id || isStandingRole(s.role),
78
78
  };
79
79
  }
80
80
 
@@ -95,3 +95,4 @@ export function movableTask(t: {
95
95
  pinnedHere: !!t.cardUrl,
96
96
  };
97
97
  }
98
+ import { isStandingRole } from "../leads/roles";
@@ -13,6 +13,7 @@
13
13
  * the sweep re-drives each state after a restart.
14
14
  */
15
15
 
16
+ import { isStandingRole } from "../leads/roles";
16
17
  import { db, type Session, type TaskPause } from "../db";
17
18
  import { toSqliteTime } from "../session-meta";
18
19
  import { endPause, endPauseOnFinish, formatUntil } from "./pause-hold";
@@ -102,7 +103,7 @@ function factsOf(s: Session): PauseFacts {
102
103
  .get(s.id);
103
104
  return {
104
105
  isTask: !!s.task_status,
105
- isOrchestrator: s.role === "orchestrator",
106
+ isOrchestrator: isStandingRole(s.role),
106
107
  isQuickChange: !!s.quick_of,
107
108
  isRemote: isRemoteHost(s.host_id) || !!s.peer_mirror,
108
109
  taskStatus: s.task_status ?? null,
@@ -2,6 +2,7 @@
2
2
  // calling task, chat or orchestrator; null for a person), parent_session_id
3
3
  // the session it came from, started_via how it began.
4
4
 
5
+ import { isStandingRole } from "../leads/roles";
5
6
  import path from "path";
6
7
  import { db } from "../db";
7
8
  import { setPlacement } from "../db/session-parts";
@@ -86,7 +87,7 @@ export function starterOf(
86
87
  return {
87
88
  startedBy: caller.id,
88
89
  parentSessionId: caller.id,
89
- startedVia: caller.role === "orchestrator" ? "orchestrator" : "session",
90
+ startedVia: isStandingRole(caller.role) ? "orchestrator" : "session",
90
91
  };
91
92
  }
92
93
 
@@ -104,7 +105,7 @@ export function orchestratorOf(startedBy: string | null | undefined) {
104
105
  .get(id) as
105
106
  { role: string | null; started_by: string | null } | undefined;
106
107
  if (!row) return null;
107
- if (row.role === "orchestrator") return id;
108
+ if (isStandingRole(row.role)) return id;
108
109
  id = row.started_by;
109
110
  }
110
111
  return null;
package/lib/workspaces.ts CHANGED
@@ -3,6 +3,7 @@
3
3
  * presentation only: deleting one leaves its projects ungrouped.
4
4
  */
5
5
 
6
+ import { sqlNoRole, sqlStanding } from "./leads/roles";
6
7
  import { randomUUID } from "crypto";
7
8
  import { db, type Workspace } from "./db";
8
9
  import { clearForeignGroup } from "./session-groups/assignSessions";
@@ -119,11 +120,11 @@ export function deleteWorkspace(id: string): void {
119
120
  // Sessions filed here by being started here go back to "All workspaces".
120
121
  db.prepare(
121
122
  `UPDATE sessions SET workspace_id = NULL
122
- WHERE role IS NULL AND workspace_id = ?`
123
+ WHERE ${sqlNoRole()} AND workspace_id = ?`
123
124
  ).run(id);
124
- // Its orchestrator goes with it; a running worker idles out on its own.
125
+ // Its orchestrator, leads and chief go with it; a running worker idles out on its own.
125
126
  db.prepare(
126
- `DELETE FROM sessions WHERE role = 'orchestrator' AND workspace_id = ?`
127
+ `DELETE FROM sessions WHERE ${sqlStanding()} AND workspace_id = ?`
127
128
  ).run(id);
128
129
  for (const table of [
129
130
  "orchestrator_events",
@@ -4,6 +4,7 @@
4
4
  * Everything else is skipped with the reason.
5
5
  */
6
6
 
7
+ import { isStandingRole, roleLabel } from "../leads/roles";
7
8
  import fs from "fs";
8
9
  import { db, type Session } from "../db";
9
10
  import { fromSqliteTime } from "../session-meta";
@@ -67,7 +68,7 @@ export async function findSettled(
67
68
  if (!isSettled(s) || !isAgentOSWorktree(dir) || !fs.existsSync(dir))
68
69
  continue;
69
70
  const skip = async (): Promise<string | null> => {
70
- if (s.role) return "an orchestrator's";
71
+ if (isStandingRole(s.role)) return `${roleLabel(s.role)}'s`;
71
72
  if (s.pinned) return "pinned";
72
73
  if (s.moved_to)
73
74
  return "its task moved to another machine and may come back";
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.859",
4
4
  "description": "Self-hosted web UI for managing Claude Code sessions",
5
5
  "bin": {
6
6
  "agent-os": "scripts/agent-os"