@saadnvd1/agent-os 0.3.715 → 0.3.728
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/README.md +2 -0
- package/app/api/agent-roles/route.test.ts +67 -0
- package/app/api/agent-roles/route.ts +61 -0
- package/app/api/analyst/[id]/recommendations/[recId]/again/route.ts +15 -0
- package/app/api/analyst/[id]/recommendations/[recId]/dismiss/route.ts +13 -0
- package/app/api/analyst/[id]/recommendations/[recId]/send/route.ts +13 -0
- package/app/api/analyst/[id]/review/route.ts +22 -0
- package/app/api/analyst/[id]/route.ts +44 -0
- package/app/api/analyst/route.ts +10 -0
- package/app/api/bus/spawn/route.ts +22 -1
- package/app/api/loops/route.ts +17 -0
- package/app/api/models/availability/route.ts +9 -0
- package/app/api/models/choices/route.ts +9 -0
- package/app/api/sessions/[id]/analyst/route.ts +11 -0
- package/app/api/sessions/[id]/crew/route.ts +38 -0
- package/app/api/sessions/[id]/export/estimate/route.ts +23 -0
- package/app/api/sessions/[id]/export/route.test.ts +125 -0
- package/app/api/sessions/[id]/export/route.ts +34 -0
- package/app/api/sessions/[id]/model/route.ts +49 -0
- package/app/api/sessions/route.ts +16 -0
- package/app/api/tasks/[id]/loop/cycles/[n]/log/route.ts +29 -0
- package/app/api/tasks/[id]/loop/draft-criteria/route.ts +17 -0
- package/app/api/tasks/[id]/loop/next/route.ts +25 -0
- package/app/api/tasks/[id]/loop/route.ts +72 -0
- package/app/api/tasks/[id]/loop/run/route.ts +12 -0
- package/app/api/tasks/[id]/loop/stop/route.ts +12 -0
- package/app/api/tasks/[id]/mode/route.ts +29 -0
- package/app/api/tasks/[id]/report/draft/route.ts +54 -0
- package/app/api/tasks/[id]/report/evidence/[evId]/route.ts +18 -0
- package/app/api/tasks/[id]/report/route.ts +39 -0
- package/app/api/workspaces/[id]/mode/route.ts +23 -0
- package/app/api/workspaces/[id]/reports/route.ts +17 -0
- package/app/page.tsx +13 -0
- package/components/Analyst/PairChip.tsx +131 -0
- package/components/Analyst/PairSwitch.tsx +76 -0
- package/components/Analyst/ReviewCard.tsx +272 -0
- package/components/Analyst/SignalChips.tsx +39 -0
- package/components/Analyst/StartAnalystSheet.tsx +109 -0
- package/components/Analyst/cardState.test.ts +109 -0
- package/components/Analyst/cardState.ts +95 -0
- package/components/Analyst/pair.ts +81 -0
- package/components/Chat/Code.tsx +3 -0
- package/components/Chat/Messages.tsx +110 -11
- package/components/Chat/itemContext.ts +14 -0
- package/components/Draft/DraftChips.tsx +16 -1
- package/components/Draft/DraftPanel.tsx +58 -2
- package/components/Draft/ModelUnavailableDialog.tsx +78 -0
- package/components/Draft/RoleChips.tsx +110 -0
- package/components/Export/ExportActions.tsx +136 -0
- package/components/Export/ExportSheet.tsx +217 -0
- package/components/Export/RangePicker.tsx +144 -0
- package/components/Export/Seg.tsx +38 -0
- package/components/Export/TurnSelectBar.tsx +45 -0
- package/components/Models/AgentRoleCard.tsx +114 -0
- package/components/Models/AgentRolesSection.tsx +132 -0
- package/components/Models/ModelBadges.tsx +48 -0
- package/components/Models/ModelsSection.tsx +4 -2
- package/components/Models/RoleModelSelect.tsx +87 -0
- package/components/Pane/DesktopTabBar.tsx +8 -0
- package/components/Pane/MobileTabBar.tsx +29 -0
- package/components/Pane/MobileViewPicker.tsx +4 -0
- package/components/Pane/PaneMenu.test.ts +21 -0
- package/components/Pane/PaneMenu.tsx +26 -0
- package/components/Pane/PaneViewToggle.tsx +12 -0
- package/components/Pane/index.tsx +19 -0
- package/components/Report/DraftCard.tsx +89 -0
- package/components/Report/EstimateBar.tsx +51 -0
- package/components/Report/EvidenceSheet.tsx +98 -0
- package/components/Report/ModeChooser.tsx +43 -0
- package/components/Report/ReportView.tsx +289 -0
- package/components/SessionList/SessionRow.tsx +18 -7
- package/components/SessionList/SessionRowMenu.tsx +12 -0
- package/components/Sessions/RolePill.tsx +203 -0
- package/components/Settings/SettingsDialog.tsx +9 -0
- package/components/Share/ShareDialog.tsx +13 -0
- package/components/Tasks/Loop/LoopButton.tsx +74 -0
- package/components/Tasks/Loop/LoopSetup.tsx +146 -0
- package/components/Tasks/Loop/LoopView.tsx +350 -0
- package/components/Tasks/Loop/labels.ts +17 -0
- package/components/Tasks/TaskRow.tsx +2 -0
- package/components/Workspaces/WorkspaceModeForm.tsx +22 -0
- package/components/Workspaces/WorkspaceSettings.tsx +2 -0
- package/components/ui/segmented.tsx +55 -0
- package/components/ui/status-pill.tsx +3 -0
- package/contexts/PaneContext.tsx +21 -1
- package/data/analyst/index.ts +3 -0
- package/data/analyst/keys.ts +6 -0
- package/data/analyst/mutations.ts +116 -0
- package/data/analyst/queries.ts +35 -0
- package/data/exports/index.ts +2 -0
- package/data/exports/keys.ts +3 -0
- package/data/exports/queries.ts +51 -0
- package/data/loops/index.ts +3 -0
- package/data/loops/keys.ts +4 -0
- package/data/loops/mutations.ts +68 -0
- package/data/loops/queries.ts +40 -0
- package/data/models/index.ts +98 -0
- package/data/push/topics.ts +11 -0
- package/data/reports/index.ts +115 -0
- package/data/sessions/queries.ts +26 -0
- package/lib/agents/brief.ts +1 -0
- package/lib/analyst/__fixtures__/items.ts +57 -0
- package/lib/analyst/__fixtures__/mocks.ts +49 -0
- package/lib/analyst/brief.ts +17 -0
- package/lib/analyst/buildDigest.ts +204 -0
- package/lib/analyst/createLink.ts +98 -0
- package/lib/analyst/endLink.ts +31 -0
- package/lib/analyst/forward.ts +109 -0
- package/lib/analyst/index.ts +16 -0
- package/lib/analyst/linkView.ts +38 -0
- package/lib/analyst/onTurnEnd.ts +119 -0
- package/lib/analyst/parseReview.ts +41 -0
- package/lib/analyst/pushDigest.ts +143 -0
- package/lib/analyst/resume.ts +35 -0
- package/lib/analyst/route.ts +50 -0
- package/lib/analyst/signals.ts +121 -0
- package/lib/analyst/store.ts +303 -0
- package/lib/analyst/types.ts +140 -0
- package/lib/bus/condense.ts +5 -1
- package/lib/bus/index.ts +5 -0
- package/lib/bus/read-scope.ts +12 -0
- package/lib/bus/role-alias.ts +70 -0
- package/lib/bus/targets.ts +3 -0
- package/lib/chat/drivers/opencode-mapper.ts +55 -1
- package/lib/chat/drivers/pi-approvals.ts +7 -4
- package/lib/chat/drivers/pi-mapper.ts +38 -0
- package/lib/chat/drivers/pi-text-tools.ts +104 -0
- package/lib/chat/drivers/pi.ts +8 -3
- package/lib/chat/drivers/turn-items.ts +36 -11
- package/lib/chat/drivers/turn-outcome.ts +108 -0
- package/lib/chat/events.ts +8 -0
- package/lib/chat/registry.ts +13 -0
- package/lib/chat/store.ts +19 -0
- package/lib/chat/turns.ts +1 -0
- package/lib/chat/worker/extras.ts +4 -2
- package/lib/claims/collectLocal.ts +3 -1
- package/lib/db/changes.ts +13 -0
- package/lib/db/migrations/20261021T1400_session_agent_roles.ts +20 -0
- package/lib/db/migrations/20261021T1430_analyst_links.ts +52 -0
- package/lib/db/migrations/20261021T1600_task_loops.ts +81 -0
- package/lib/db/migrations/20261021T1800_health_reports.ts +38 -0
- package/lib/db/session-parts/columns.ts +2 -0
- package/lib/db/types.ts +3 -0
- package/lib/done/archive.ts +2 -0
- package/lib/done/index.ts +2 -0
- package/lib/drafts.ts +6 -0
- package/lib/export/build.ts +470 -0
- package/lib/export/compact.ts +156 -0
- package/lib/export/estimate.ts +83 -0
- package/lib/export/fixtures.ts +45 -0
- package/lib/export/html.tsx +195 -0
- package/lib/export/json.ts +39 -0
- package/lib/export/load.ts +64 -0
- package/lib/export/markdown.ts +127 -0
- package/lib/export/request.ts +36 -0
- package/lib/export/service.ts +142 -0
- package/lib/export/summary.ts +86 -0
- package/lib/export/turns.ts +63 -0
- package/lib/export/view.ts +173 -0
- package/lib/loops/analyse.ts +46 -0
- package/lib/loops/analyst-prompt.ts +203 -0
- package/lib/loops/checkCriteria.ts +114 -0
- package/lib/loops/compactEvidence.ts +140 -0
- package/lib/loops/controls.ts +317 -0
- package/lib/loops/decideStop.ts +113 -0
- package/lib/loops/deps.ts +128 -0
- package/lib/loops/diffMap.ts +39 -0
- package/lib/loops/draftCriteria.ts +71 -0
- package/lib/loops/gatherEvidence.ts +265 -0
- package/lib/loops/hooks.ts +40 -0
- package/lib/loops/index.ts +7 -0
- package/lib/loops/route.ts +83 -0
- package/lib/loops/runCycle.ts +323 -0
- package/lib/loops/screenInstruction.ts +59 -0
- package/lib/loops/sendInstruction.ts +83 -0
- package/lib/loops/snapshot.ts +38 -0
- package/lib/loops/stopLoop.ts +86 -0
- package/lib/loops/store.ts +481 -0
- package/lib/loops/summary.ts +27 -0
- package/lib/loops/tickLoops.ts +141 -0
- package/lib/loops/types.ts +223 -0
- package/lib/loops/world.test-helpers.ts +223 -0
- package/lib/models/agent-binding.ts +102 -0
- package/lib/models/agent-roles-view.ts +157 -0
- package/lib/models/agent-roles.ts +87 -0
- package/lib/models/availability-refs.ts +17 -0
- package/lib/models/availability.ts +91 -0
- package/lib/models/catalog.ts +156 -0
- package/lib/models/config.ts +27 -0
- package/lib/models/locality.ts +33 -0
- package/lib/models/plan-step.ts +4 -1
- package/lib/models/providers.ts +11 -0
- package/lib/models/resolve.ts +50 -12
- package/lib/models/roles.ts +12 -0
- package/lib/needs-you.ts +1 -1
- package/lib/orchestrator/digest.ts +2 -1
- package/lib/orchestrator/facts.ts +7 -2
- package/lib/orchestrator/loop-guard.ts +5 -2
- package/lib/orchestrator/pr-wake.ts +4 -0
- package/lib/orchestrator/read.ts +5 -1
- package/lib/orchestrator/untrusted.ts +5 -0
- package/lib/orchestrator/watcher.ts +8 -1
- package/lib/panes.ts +52 -0
- package/lib/plans/acceptance.ts +23 -0
- package/lib/projects.ts +1 -0
- package/lib/reports/drafts.ts +87 -0
- package/lib/reports/end-hook.ts +17 -0
- package/lib/reports/estimate.ts +107 -0
- package/lib/reports/evidence/build.ts +193 -0
- package/lib/reports/evidence/gather.ts +229 -0
- package/lib/reports/fixtures.ts +17 -0
- package/lib/reports/generate.ts +159 -0
- package/lib/reports/hooks.ts +30 -0
- package/lib/reports/mode.ts +65 -0
- package/lib/reports/model.ts +115 -0
- package/lib/reports/read.ts +26 -0
- package/lib/reports/store.ts +180 -0
- package/lib/reports/template.ts +117 -0
- package/lib/reports/types.ts +135 -0
- package/lib/reports/validate.ts +142 -0
- package/lib/session-artifacts/agentos-files.ts +94 -0
- package/lib/session-artifacts/collect.ts +17 -0
- package/lib/sessions/crew.ts +72 -0
- package/lib/sessions/launch.ts +49 -9
- package/lib/settings-url.ts +1 -0
- package/lib/share/markdown.ts +2 -2
- package/lib/share/redact.ts +16 -10
- package/lib/share/render.tsx +1 -1
- package/lib/share/snapshot.ts +2 -2
- package/lib/sidebar/row-menu.ts +15 -1
- package/lib/tasks/contract/routes.ts +5 -0
- package/lib/tasks/contract/types.ts +3 -0
- package/lib/tasks/finish.ts +2 -0
- package/lib/tasks/index.ts +6 -0
- package/lib/tasks/move-carry.ts +1 -0
- package/lib/worktree-deps/caches.ts +3 -2
- package/lib/worktree-deps/clone.ts +3 -1
- package/lib/worktree-deps/paths.ts +26 -0
- package/package.json +2 -2
- package/server.ts +9 -1
- package/stores/analystUi.ts +19 -0
- package/stores/exportUi.ts +87 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { useAgentRolesQuery, useSaveAgentRoles } from "@/data/models";
|
|
5
|
+
import { useProjectsQuery } from "@/data/projects/queries";
|
|
6
|
+
import { useWorkspacesQuery } from "@/data/workspaces";
|
|
7
|
+
import type { RoleMap } from "@/lib/models/config";
|
|
8
|
+
import type { RoleScope } from "@/lib/models/agent-roles-view";
|
|
9
|
+
import { cn } from "@/lib/utils";
|
|
10
|
+
import { AgentRoleCard } from "./AgentRoleCard";
|
|
11
|
+
|
|
12
|
+
const SCOPES: { id: RoleScope; label: string }[] = [
|
|
13
|
+
{ id: "settings", label: "Default" },
|
|
14
|
+
{ id: "workspace", label: "Workspace" },
|
|
15
|
+
{ id: "project", label: "Project" },
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
// Settings > Agent roles: the model each of Developer, Analyst, Reviewer and
|
|
19
|
+
// Supervisor runs on, for everything or for one workspace or project.
|
|
20
|
+
export function AgentRolesSection() {
|
|
21
|
+
const [scope, setScope] = useState<RoleScope>("settings");
|
|
22
|
+
const [target, setTarget] = useState<string | null>(null);
|
|
23
|
+
const workspaces = useWorkspacesQuery();
|
|
24
|
+
const projects = useProjectsQuery();
|
|
25
|
+
const options =
|
|
26
|
+
scope === "workspace"
|
|
27
|
+
? (workspaces.data ?? []).map((w) => ({ id: w.id, name: w.name }))
|
|
28
|
+
: scope === "project"
|
|
29
|
+
? (projects.data ?? [])
|
|
30
|
+
.filter((p) => !p.is_uncategorized)
|
|
31
|
+
.map((p) => ({ id: p.id, name: p.name }))
|
|
32
|
+
: [];
|
|
33
|
+
const id = scope === "settings" ? null : (target ?? options[0]?.id ?? null);
|
|
34
|
+
const { data, isPending, isError, error } = useAgentRolesQuery(scope, id);
|
|
35
|
+
const save = useSaveAgentRoles(scope, id);
|
|
36
|
+
|
|
37
|
+
const saved = (key: "value" | "fallback"): RoleMap =>
|
|
38
|
+
Object.fromEntries(
|
|
39
|
+
(data?.roles ?? [])
|
|
40
|
+
.filter((r) => r[key])
|
|
41
|
+
.map((r) => [r.modelRole, r[key] as string])
|
|
42
|
+
);
|
|
43
|
+
const setRole = (modelRole: string, ref: string | null) => {
|
|
44
|
+
const roles: RoleMap = { ...saved("value") };
|
|
45
|
+
if (ref) roles[modelRole as keyof RoleMap] = ref;
|
|
46
|
+
else delete roles[modelRole as keyof RoleMap];
|
|
47
|
+
save.mutate({ roles });
|
|
48
|
+
};
|
|
49
|
+
const setFallback = (modelRole: string, ref: string | null) => {
|
|
50
|
+
const fallbacks: RoleMap = { ...saved("fallback") };
|
|
51
|
+
if (ref) fallbacks[modelRole as keyof RoleMap] = ref;
|
|
52
|
+
else delete fallbacks[modelRole as keyof RoleMap];
|
|
53
|
+
save.mutate({ roles: saved("value"), fallbacks });
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<div className="space-y-4">
|
|
58
|
+
<div
|
|
59
|
+
role="tablist"
|
|
60
|
+
className="bg-muted inline-flex max-w-full gap-0.5 overflow-x-auto rounded-xl p-0.5"
|
|
61
|
+
>
|
|
62
|
+
{SCOPES.map((s) => (
|
|
63
|
+
<button
|
|
64
|
+
key={s.id}
|
|
65
|
+
role="tab"
|
|
66
|
+
aria-selected={scope === s.id}
|
|
67
|
+
type="button"
|
|
68
|
+
onClick={() => {
|
|
69
|
+
setScope(s.id);
|
|
70
|
+
setTarget(null);
|
|
71
|
+
}}
|
|
72
|
+
className={cn(
|
|
73
|
+
"h-11 rounded-lg px-3 text-sm whitespace-nowrap md:h-8",
|
|
74
|
+
scope === s.id
|
|
75
|
+
? "bg-background text-foreground ring-border ring-1"
|
|
76
|
+
: "text-muted-foreground hover:text-foreground"
|
|
77
|
+
)}
|
|
78
|
+
>
|
|
79
|
+
{s.label}
|
|
80
|
+
</button>
|
|
81
|
+
))}
|
|
82
|
+
</div>
|
|
83
|
+
{scope !== "settings" && (
|
|
84
|
+
<select
|
|
85
|
+
aria-label={scope === "workspace" ? "Workspace" : "Project"}
|
|
86
|
+
value={id ?? ""}
|
|
87
|
+
onChange={(e) => setTarget(e.target.value)}
|
|
88
|
+
className="border-input bg-background h-11 w-full rounded-md border px-3 text-sm md:h-9"
|
|
89
|
+
>
|
|
90
|
+
{options.length === 0 && <option value="">None yet</option>}
|
|
91
|
+
{options.map((o) => (
|
|
92
|
+
<option key={o.id} value={o.id}>
|
|
93
|
+
{o.name}
|
|
94
|
+
</option>
|
|
95
|
+
))}
|
|
96
|
+
</select>
|
|
97
|
+
)}
|
|
98
|
+
<p className="text-muted-foreground text-xs">
|
|
99
|
+
The model each role runs on. A workspace or project can override these,
|
|
100
|
+
and a session can when it starts. A Pi model (a server you run) runs
|
|
101
|
+
sessions started in the role; tasks and the merge gate stay on Claude
|
|
102
|
+
Code models. Planning, gate review, scope check, helpers and watchers
|
|
103
|
+
are in Models.
|
|
104
|
+
</p>
|
|
105
|
+
{scope !== "settings" && !id ? (
|
|
106
|
+
<p className="text-muted-foreground text-sm">
|
|
107
|
+
There is no {scope} to set roles for yet.
|
|
108
|
+
</p>
|
|
109
|
+
) : isPending ? (
|
|
110
|
+
<div className="bg-muted/40 h-64 animate-pulse rounded-lg" />
|
|
111
|
+
) : isError ? (
|
|
112
|
+
<p className="text-destructive text-sm">{error.message}</p>
|
|
113
|
+
) : data ? (
|
|
114
|
+
<ul className="space-y-2.5">
|
|
115
|
+
{data.roles.map((r) => (
|
|
116
|
+
<AgentRoleCard
|
|
117
|
+
key={r.id}
|
|
118
|
+
role={r}
|
|
119
|
+
scope={scope}
|
|
120
|
+
pending={save.isPending}
|
|
121
|
+
onChange={(ref) => setRole(r.modelRole, ref)}
|
|
122
|
+
onFallback={(ref) => setFallback(r.modelRole, ref)}
|
|
123
|
+
/>
|
|
124
|
+
))}
|
|
125
|
+
</ul>
|
|
126
|
+
) : null}
|
|
127
|
+
{save.isError && (
|
|
128
|
+
<p className="text-destructive text-xs">{save.error.message}</p>
|
|
129
|
+
)}
|
|
130
|
+
</div>
|
|
131
|
+
);
|
|
132
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { Availability } from "@/lib/models/availability";
|
|
2
|
+
import type { Locality } from "@/lib/models/locality";
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
|
|
5
|
+
// Whether a model runs on this machine's own networks or in the cloud.
|
|
6
|
+
export function LocalityBadge({ locality }: { locality: Locality }) {
|
|
7
|
+
return (
|
|
8
|
+
<span
|
|
9
|
+
className={cn(
|
|
10
|
+
"inline-flex h-5 shrink-0 items-center rounded-full px-2 text-[11px] font-medium",
|
|
11
|
+
locality === "local"
|
|
12
|
+
? "bg-emerald-500/15 text-emerald-700 dark:text-emerald-400"
|
|
13
|
+
: "bg-muted text-muted-foreground ring-border ring-1 ring-inset"
|
|
14
|
+
)}
|
|
15
|
+
>
|
|
16
|
+
{locality === "local" ? "Local" : "Cloud"}
|
|
17
|
+
</span>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const DOT = {
|
|
22
|
+
up: "bg-emerald-500",
|
|
23
|
+
down: "bg-destructive",
|
|
24
|
+
unknown: "bg-muted-foreground/50",
|
|
25
|
+
} as const;
|
|
26
|
+
|
|
27
|
+
const TITLE = {
|
|
28
|
+
up: "Answering",
|
|
29
|
+
down: "Not answering",
|
|
30
|
+
unknown: "Not checked",
|
|
31
|
+
} as const;
|
|
32
|
+
|
|
33
|
+
// The last availability check: green answering, red not, grey not checked.
|
|
34
|
+
export function AvailabilityDot({
|
|
35
|
+
availability,
|
|
36
|
+
}: {
|
|
37
|
+
availability: Pick<Availability, "state"> | undefined;
|
|
38
|
+
}) {
|
|
39
|
+
const state = availability?.state ?? "unknown";
|
|
40
|
+
return (
|
|
41
|
+
<span
|
|
42
|
+
role="img"
|
|
43
|
+
aria-label={TITLE[state]}
|
|
44
|
+
title={TITLE[state]}
|
|
45
|
+
className={cn("inline-block h-2 w-2 shrink-0 rounded-full", DOT[state])}
|
|
46
|
+
/>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
type ModelProvider,
|
|
12
12
|
} from "@/lib/models/providers";
|
|
13
13
|
import { SubagentModels } from "./SubagentModels";
|
|
14
|
+
import { INTERNAL_MODEL_ROLES } from "@/lib/models/agent-roles";
|
|
14
15
|
import { MODEL_ROLES, ROLE_LABELS } from "@/lib/models/roles";
|
|
15
16
|
import { ModelSelect } from "./ModelSelect";
|
|
16
17
|
import { ProviderCard } from "./ProviderCard";
|
|
@@ -58,11 +59,12 @@ export function ModelsSection() {
|
|
|
58
59
|
<div className="space-y-6">
|
|
59
60
|
<section className="space-y-2">
|
|
60
61
|
<p className="text-muted-foreground text-sm">
|
|
61
|
-
Which model each kind of work runs on.
|
|
62
|
+
Which model each kind of work runs on. Developer, Analyst, Reviewer
|
|
63
|
+
and Supervisor are in Agent roles. A workspace can set its own
|
|
62
64
|
(Settings › Workspaces), and a task its own when it starts or resumes.
|
|
63
65
|
</p>
|
|
64
66
|
<ul className="space-y-2">
|
|
65
|
-
{MODEL_ROLES.map((role) => (
|
|
67
|
+
{INTERNAL_MODEL_ROLES(MODEL_ROLES).map((role) => (
|
|
66
68
|
<li
|
|
67
69
|
key={role}
|
|
68
70
|
className="bg-muted/40 space-y-1.5 rounded-lg px-3 py-2.5"
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Select,
|
|
5
|
+
SelectContent,
|
|
6
|
+
SelectGroup,
|
|
7
|
+
SelectItem,
|
|
8
|
+
SelectLabel,
|
|
9
|
+
SelectTrigger,
|
|
10
|
+
SelectValue,
|
|
11
|
+
} from "@/components/ui/select";
|
|
12
|
+
import { useModelChoices } from "@/data/models";
|
|
13
|
+
import type { ModelChoice } from "@/lib/models/catalog";
|
|
14
|
+
import { AvailabilityDot, LocalityBadge } from "./ModelBadges";
|
|
15
|
+
|
|
16
|
+
const INHERIT = "__inherit";
|
|
17
|
+
|
|
18
|
+
function Row({ choice }: { choice: ModelChoice }) {
|
|
19
|
+
return (
|
|
20
|
+
<span className="flex min-w-0 flex-1 items-center gap-2">
|
|
21
|
+
<AvailabilityDot availability={choice.availability} />
|
|
22
|
+
<span className="truncate">{choice.label}</span>
|
|
23
|
+
<LocalityBadge locality={choice.locality} />
|
|
24
|
+
</span>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// A model picker over the server's list: grouped by provider, each model
|
|
29
|
+
// labelled Local or Cloud with whether its server answers. `claudeOnly`
|
|
30
|
+
// leaves out the models that run through Pi.
|
|
31
|
+
export function RoleModelSelect({
|
|
32
|
+
id,
|
|
33
|
+
value,
|
|
34
|
+
inheritLabel,
|
|
35
|
+
claudeOnly,
|
|
36
|
+
gateOnly,
|
|
37
|
+
disabled,
|
|
38
|
+
onChange,
|
|
39
|
+
}: {
|
|
40
|
+
id?: string;
|
|
41
|
+
value: string | null;
|
|
42
|
+
inheritLabel: string;
|
|
43
|
+
claudeOnly?: boolean;
|
|
44
|
+
// Only models that can serve a merge gate are selectable.
|
|
45
|
+
gateOnly?: boolean;
|
|
46
|
+
disabled?: boolean;
|
|
47
|
+
onChange: (ref: string | null) => void;
|
|
48
|
+
}) {
|
|
49
|
+
const { data } = useModelChoices();
|
|
50
|
+
const choices = (data ?? []).filter((c) => !claudeOnly || c.agent !== "pi");
|
|
51
|
+
const groups = [...new Set(choices.map((c) => c.group))];
|
|
52
|
+
const current = value ? choices.find((c) => c.ref === value) : undefined;
|
|
53
|
+
return (
|
|
54
|
+
<Select
|
|
55
|
+
value={value ?? INHERIT}
|
|
56
|
+
onValueChange={(v) => onChange(v === INHERIT ? null : v)}
|
|
57
|
+
disabled={disabled}
|
|
58
|
+
>
|
|
59
|
+
<SelectTrigger id={id} className="h-11 md:h-9">
|
|
60
|
+
<SelectValue>
|
|
61
|
+
{current ? <Row choice={current} /> : (value ?? inheritLabel)}
|
|
62
|
+
</SelectValue>
|
|
63
|
+
</SelectTrigger>
|
|
64
|
+
<SelectContent>
|
|
65
|
+
<SelectItem value={INHERIT}>{inheritLabel}</SelectItem>
|
|
66
|
+
{value && !current && <SelectItem value={value}>{value}</SelectItem>}
|
|
67
|
+
{groups.map((g) => (
|
|
68
|
+
<SelectGroup key={g}>
|
|
69
|
+
<SelectLabel>{g}</SelectLabel>
|
|
70
|
+
{choices
|
|
71
|
+
.filter((c) => c.group === g)
|
|
72
|
+
.map((c) => (
|
|
73
|
+
<SelectItem
|
|
74
|
+
key={c.ref}
|
|
75
|
+
value={c.ref}
|
|
76
|
+
disabled={gateOnly && !c.gateCapable}
|
|
77
|
+
className="min-h-11 md:min-h-0"
|
|
78
|
+
>
|
|
79
|
+
<Row choice={c} />
|
|
80
|
+
</SelectItem>
|
|
81
|
+
))}
|
|
82
|
+
</SelectGroup>
|
|
83
|
+
))}
|
|
84
|
+
</SelectContent>
|
|
85
|
+
</Select>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
@@ -15,6 +15,8 @@ import type { Session } from "@/lib/db";
|
|
|
15
15
|
import { PaneViewToggle } from "./PaneViewToggle";
|
|
16
16
|
import { PaneMenu } from "./PaneMenu";
|
|
17
17
|
import { HostBadge } from "@/components/Hosts/HostBadge";
|
|
18
|
+
import { RolePill } from "@/components/Sessions/RolePill";
|
|
19
|
+
import { PairChip } from "@/components/Analyst/PairChip";
|
|
18
20
|
import { usePaneBarSlots } from "./PaneBarSlots";
|
|
19
21
|
import { ChatBookmarksButton } from "@/components/Bookmarks/ChatBookmarksButton";
|
|
20
22
|
import type { ViewMode } from "@/contexts/PaneContext";
|
|
@@ -36,6 +38,7 @@ interface DesktopTabBarProps {
|
|
|
36
38
|
isFocused: boolean;
|
|
37
39
|
isConductor: boolean;
|
|
38
40
|
hasLog?: boolean;
|
|
41
|
+
hasReport?: boolean;
|
|
39
42
|
workerCount: number;
|
|
40
43
|
canSplit: boolean;
|
|
41
44
|
canClose: boolean;
|
|
@@ -67,6 +70,7 @@ export function DesktopTabBar({
|
|
|
67
70
|
isFocused,
|
|
68
71
|
isConductor,
|
|
69
72
|
hasLog,
|
|
73
|
+
hasReport,
|
|
70
74
|
workerCount,
|
|
71
75
|
canSplit,
|
|
72
76
|
canClose,
|
|
@@ -161,6 +165,8 @@ export function DesktopTabBar({
|
|
|
161
165
|
</div>
|
|
162
166
|
|
|
163
167
|
<div className="flex shrink-0 items-center gap-1.5">
|
|
168
|
+
<RolePill session={session} paneId={paneId} />
|
|
169
|
+
<PairChip session={session} paneId={paneId} />
|
|
164
170
|
<HostBadge hostId={session?.host_id} />
|
|
165
171
|
<ContextMeter sessionId={session?.id} />
|
|
166
172
|
<SessionPrStage sessionId={session?.id} />
|
|
@@ -180,6 +186,7 @@ export function DesktopTabBar({
|
|
|
180
186
|
isLocalSession={isLocalSession}
|
|
181
187
|
isConductor={isConductor}
|
|
182
188
|
hasLog={hasLog}
|
|
189
|
+
hasReport={hasReport}
|
|
183
190
|
workerCount={workerCount}
|
|
184
191
|
showGit={showGit}
|
|
185
192
|
gitDrawerOpen={gitDrawerOpen}
|
|
@@ -192,6 +199,7 @@ export function DesktopTabBar({
|
|
|
192
199
|
<div className="flex items-center">
|
|
193
200
|
<PaneMenu
|
|
194
201
|
session={session}
|
|
202
|
+
paneId={paneId}
|
|
195
203
|
bookmarksSessionId={bookmarksSessionId}
|
|
196
204
|
canSplit={canSplit}
|
|
197
205
|
canClose={canClose}
|
|
@@ -24,15 +24,21 @@ import {
|
|
|
24
24
|
Search,
|
|
25
25
|
Star,
|
|
26
26
|
Share2,
|
|
27
|
+
Download,
|
|
27
28
|
TextSearch,
|
|
29
|
+
Eye,
|
|
28
30
|
} from "lucide-react";
|
|
29
31
|
import { cn } from "@/lib/utils";
|
|
30
32
|
import { useAwayQuery } from "@/data/away";
|
|
31
33
|
import { awayUntil } from "@/components/Away/time";
|
|
32
34
|
import type { Session, Project } from "@/lib/db";
|
|
33
35
|
import { HostBadge } from "@/components/Hosts/HostBadge";
|
|
36
|
+
import { RolePill } from "@/components/Sessions/RolePill";
|
|
34
37
|
import { bookmarksUiActions } from "@/stores/bookmarksUi";
|
|
35
38
|
import { shareUiActions } from "@/stores/shareUi";
|
|
39
|
+
import { exportUiActions } from "@/stores/exportUi";
|
|
40
|
+
import { analystUiActions } from "@/stores/analystUi";
|
|
41
|
+
import { useCanAddAnalyst } from "@/components/Analyst/pair";
|
|
36
42
|
import { MoveMenuItems } from "@/components/Tasks/MoveMenuItems";
|
|
37
43
|
import { ChatBookmarksButton } from "@/components/Bookmarks/ChatBookmarksButton";
|
|
38
44
|
import type { ViewMode } from "@/contexts/PaneContext";
|
|
@@ -47,6 +53,7 @@ interface MobileTabBarProps {
|
|
|
47
53
|
viewMode: ViewMode;
|
|
48
54
|
isConductor: boolean;
|
|
49
55
|
hasLog?: boolean;
|
|
56
|
+
hasReport?: boolean;
|
|
50
57
|
workerCount: number;
|
|
51
58
|
// Local, and the folder is not known to be outside a git repository.
|
|
52
59
|
showGit: boolean;
|
|
@@ -67,6 +74,7 @@ export function MobileTabBar({
|
|
|
67
74
|
viewMode,
|
|
68
75
|
isConductor,
|
|
69
76
|
hasLog,
|
|
77
|
+
hasReport,
|
|
70
78
|
workerCount,
|
|
71
79
|
showGit,
|
|
72
80
|
onMenuClick,
|
|
@@ -125,6 +133,7 @@ export function MobileTabBar({
|
|
|
125
133
|
};
|
|
126
134
|
|
|
127
135
|
const isLocalSession = !session?.host_id || session.host_id === "local";
|
|
136
|
+
const canAddAnalyst = useCanAddAnalyst(session);
|
|
128
137
|
|
|
129
138
|
return (
|
|
130
139
|
<div
|
|
@@ -219,6 +228,24 @@ export function MobileTabBar({
|
|
|
219
228
|
<Share2 className="h-4 w-4" />
|
|
220
229
|
Share…
|
|
221
230
|
</DropdownMenuItem>
|
|
231
|
+
<DropdownMenuItem
|
|
232
|
+
onSelect={() =>
|
|
233
|
+
exportUiActions.open(session.id, session.name)
|
|
234
|
+
}
|
|
235
|
+
>
|
|
236
|
+
<Download className="h-4 w-4" />
|
|
237
|
+
Export for AI…
|
|
238
|
+
</DropdownMenuItem>
|
|
239
|
+
{canAddAnalyst && (
|
|
240
|
+
<DropdownMenuItem
|
|
241
|
+
onSelect={() =>
|
|
242
|
+
analystUiActions.open(session.id, session.name)
|
|
243
|
+
}
|
|
244
|
+
>
|
|
245
|
+
<Eye className="h-4 w-4" />
|
|
246
|
+
Add analyst…
|
|
247
|
+
</DropdownMenuItem>
|
|
248
|
+
)}
|
|
222
249
|
<DropdownMenuSeparator />
|
|
223
250
|
</>
|
|
224
251
|
)}
|
|
@@ -270,6 +297,7 @@ export function MobileTabBar({
|
|
|
270
297
|
</button>
|
|
271
298
|
</div>
|
|
272
299
|
|
|
300
|
+
<RolePill session={session} compact />
|
|
273
301
|
{bookmarksSessionId && (
|
|
274
302
|
<ChatBookmarksButton
|
|
275
303
|
sessionId={bookmarksSessionId}
|
|
@@ -363,6 +391,7 @@ export function MobileTabBar({
|
|
|
363
391
|
isLocalSession={isLocalSession}
|
|
364
392
|
showGit={showGit}
|
|
365
393
|
hasLog={hasLog}
|
|
394
|
+
hasReport={hasReport}
|
|
366
395
|
isConductor={isConductor}
|
|
367
396
|
workerCount={workerCount}
|
|
368
397
|
/>
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
GitBranch,
|
|
8
8
|
Package,
|
|
9
9
|
ScrollText,
|
|
10
|
+
ClipboardList,
|
|
10
11
|
Terminal as TerminalIcon,
|
|
11
12
|
Users,
|
|
12
13
|
type LucideIcon,
|
|
@@ -31,6 +32,7 @@ export interface MobileViewAvailability {
|
|
|
31
32
|
// Local, and the folder is not known to be outside a git repository.
|
|
32
33
|
showGit: boolean;
|
|
33
34
|
hasLog?: boolean;
|
|
35
|
+
hasReport?: boolean;
|
|
34
36
|
isConductor: boolean;
|
|
35
37
|
workerCount: number;
|
|
36
38
|
}
|
|
@@ -40,6 +42,7 @@ export function mobileViews({
|
|
|
40
42
|
isLocalSession,
|
|
41
43
|
showGit,
|
|
42
44
|
hasLog,
|
|
45
|
+
hasReport,
|
|
43
46
|
isConductor,
|
|
44
47
|
workerCount,
|
|
45
48
|
}: MobileViewAvailability): MobileView[] {
|
|
@@ -48,6 +51,7 @@ export function mobileViews({
|
|
|
48
51
|
isLocalSession && { mode: "files", label: "Files", icon: FolderOpen },
|
|
49
52
|
showGit && { mode: "git", label: "Git", icon: GitBranch },
|
|
50
53
|
{ mode: "artifacts", label: "Artifacts", icon: Package },
|
|
54
|
+
!!hasReport && { mode: "report", label: "Report", icon: ClipboardList },
|
|
51
55
|
!!hasLog && { mode: "log", label: "Log", icon: ScrollText },
|
|
52
56
|
isConductor && {
|
|
53
57
|
mode: "workers",
|
|
@@ -6,10 +6,12 @@ import type { Session } from "@/lib/db/types";
|
|
|
6
6
|
import { bookmarksUi } from "@/stores/bookmarksUi";
|
|
7
7
|
import { docsUi } from "@/stores/docsUi";
|
|
8
8
|
import { paletteUi } from "@/stores/palette";
|
|
9
|
+
import { analystUi } from "@/stores/analystUi";
|
|
9
10
|
|
|
10
11
|
const data = vi.hoisted(() => ({
|
|
11
12
|
bookmarks: [] as unknown[],
|
|
12
13
|
docs: null as { id: string } | null,
|
|
14
|
+
canAddAnalyst: false,
|
|
13
15
|
}));
|
|
14
16
|
vi.mock("@/data/bookmarks", () => ({
|
|
15
17
|
useSessionBookmarks: () => ({ data: data.bookmarks }),
|
|
@@ -17,6 +19,9 @@ vi.mock("@/data/bookmarks", () => ({
|
|
|
17
19
|
vi.mock("@/data/lumifyhub/docs", () => ({
|
|
18
20
|
useDocsWorkspace: () => data.docs,
|
|
19
21
|
}));
|
|
22
|
+
vi.mock("@/components/Analyst/pair", () => ({
|
|
23
|
+
useCanAddAnalyst: () => data.canAddAnalyst,
|
|
24
|
+
}));
|
|
20
25
|
vi.mock("@/components/Tasks/MoveMenuItems", () => ({
|
|
21
26
|
MoveMenuItems: () => null,
|
|
22
27
|
}));
|
|
@@ -60,6 +65,8 @@ describe("PaneMenu", () => {
|
|
|
60
65
|
root = createRoot(host);
|
|
61
66
|
data.bookmarks = [];
|
|
62
67
|
data.docs = null;
|
|
68
|
+
data.canAddAnalyst = false;
|
|
69
|
+
analystUi.open = false;
|
|
63
70
|
paletteUi.open = false;
|
|
64
71
|
bookmarksUi.chatSessionId = null;
|
|
65
72
|
docsUi.workspaceId = null;
|
|
@@ -73,6 +80,7 @@ describe("PaneMenu", () => {
|
|
|
73
80
|
root.render(
|
|
74
81
|
createElement(PaneMenu, {
|
|
75
82
|
session,
|
|
83
|
+
paneId: "pane-1",
|
|
76
84
|
bookmarksSessionId,
|
|
77
85
|
canSplit: true,
|
|
78
86
|
canClose: true,
|
|
@@ -115,4 +123,17 @@ describe("PaneMenu", () => {
|
|
|
115
123
|
act(() => item("Docs")?.click());
|
|
116
124
|
expect(docsUi.workspaceId).toBe("w1");
|
|
117
125
|
});
|
|
126
|
+
|
|
127
|
+
it("offers Add analyst only when the chat can have one, beside this pane", () => {
|
|
128
|
+
render();
|
|
129
|
+
expect(item("Add analyst")).toBeUndefined();
|
|
130
|
+
data.canAddAnalyst = true;
|
|
131
|
+
render();
|
|
132
|
+
act(() => item("Add analyst")?.click());
|
|
133
|
+
expect(analystUi).toMatchObject({
|
|
134
|
+
open: true,
|
|
135
|
+
sessionId: "s1",
|
|
136
|
+
paneId: "pane-1",
|
|
137
|
+
});
|
|
138
|
+
});
|
|
118
139
|
});
|
|
@@ -8,9 +8,11 @@ import {
|
|
|
8
8
|
Link,
|
|
9
9
|
MoreHorizontal,
|
|
10
10
|
Share2,
|
|
11
|
+
Download,
|
|
11
12
|
SplitSquareHorizontal,
|
|
12
13
|
SplitSquareVertical,
|
|
13
14
|
Star,
|
|
15
|
+
Eye,
|
|
14
16
|
Unplug,
|
|
15
17
|
X,
|
|
16
18
|
} from "lucide-react";
|
|
@@ -27,6 +29,9 @@ import {
|
|
|
27
29
|
import { copySessionLink, writeClipboard } from "@/hooks/useCopyToClipboard";
|
|
28
30
|
import type { Session } from "@/lib/db";
|
|
29
31
|
import { shareUiActions } from "@/stores/shareUi";
|
|
32
|
+
import { exportUiActions } from "@/stores/exportUi";
|
|
33
|
+
import { analystUiActions } from "@/stores/analystUi";
|
|
34
|
+
import { useCanAddAnalyst } from "@/components/Analyst/pair";
|
|
30
35
|
import { useSessionBookmarks } from "@/data/bookmarks";
|
|
31
36
|
import { useDocsWorkspace } from "@/data/lumifyhub/docs";
|
|
32
37
|
import { bookmarksUiActions } from "@/stores/bookmarksUi";
|
|
@@ -36,6 +41,7 @@ import { MoveMenuItems } from "@/components/Tasks/MoveMenuItems";
|
|
|
36
41
|
|
|
37
42
|
interface PaneMenuProps {
|
|
38
43
|
session: Session | null | undefined;
|
|
44
|
+
paneId?: string;
|
|
39
45
|
// The chat on show, for its bookmarks; null for a terminal or another view.
|
|
40
46
|
bookmarksSessionId?: string | null;
|
|
41
47
|
canSplit: boolean;
|
|
@@ -51,6 +57,7 @@ interface PaneMenuProps {
|
|
|
51
57
|
// the bar.
|
|
52
58
|
export function PaneMenu({
|
|
53
59
|
session,
|
|
60
|
+
paneId,
|
|
54
61
|
bookmarksSessionId,
|
|
55
62
|
canSplit,
|
|
56
63
|
canClose,
|
|
@@ -62,6 +69,7 @@ export function PaneMenu({
|
|
|
62
69
|
}: PaneMenuProps) {
|
|
63
70
|
const { data: bookmarks = [] } = useSessionBookmarks(bookmarksSessionId);
|
|
64
71
|
const docs = useDocsWorkspace(session?.project_id);
|
|
72
|
+
const canAddAnalyst = useCanAddAnalyst(session);
|
|
65
73
|
const copyId = async (id: string) => {
|
|
66
74
|
try {
|
|
67
75
|
await writeClipboard(id);
|
|
@@ -117,6 +125,24 @@ export function PaneMenu({
|
|
|
117
125
|
Share…
|
|
118
126
|
</DropdownMenuItem>
|
|
119
127
|
)}
|
|
128
|
+
{session.view === "chat" && (
|
|
129
|
+
<DropdownMenuItem
|
|
130
|
+
onSelect={() => exportUiActions.open(session.id, session.name)}
|
|
131
|
+
>
|
|
132
|
+
<Download className="h-4 w-4" />
|
|
133
|
+
Export for AI…
|
|
134
|
+
</DropdownMenuItem>
|
|
135
|
+
)}
|
|
136
|
+
{canAddAnalyst && session && (
|
|
137
|
+
<DropdownMenuItem
|
|
138
|
+
onSelect={() =>
|
|
139
|
+
analystUiActions.open(session.id, session.name, paneId)
|
|
140
|
+
}
|
|
141
|
+
>
|
|
142
|
+
<Eye className="h-4 w-4" />
|
|
143
|
+
Add analyst…
|
|
144
|
+
</DropdownMenuItem>
|
|
145
|
+
)}
|
|
120
146
|
<DropdownMenuSeparator />
|
|
121
147
|
<MoveMenuItems
|
|
122
148
|
session={session}
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
Home,
|
|
7
7
|
Package,
|
|
8
8
|
ScrollText,
|
|
9
|
+
ClipboardList,
|
|
9
10
|
Users,
|
|
10
11
|
} from "lucide-react";
|
|
11
12
|
import {
|
|
@@ -27,6 +28,7 @@ interface PaneViewToggleProps {
|
|
|
27
28
|
isConductor: boolean;
|
|
28
29
|
// The task merged and has a log.
|
|
29
30
|
hasLog?: boolean;
|
|
31
|
+
hasReport?: boolean;
|
|
30
32
|
workerCount: number;
|
|
31
33
|
gitDrawerOpen: boolean;
|
|
32
34
|
shellDrawerOpen: boolean;
|
|
@@ -85,6 +87,7 @@ export function PaneViewToggle({
|
|
|
85
87
|
showGit,
|
|
86
88
|
isConductor,
|
|
87
89
|
hasLog,
|
|
90
|
+
hasReport,
|
|
88
91
|
workerCount,
|
|
89
92
|
gitDrawerOpen,
|
|
90
93
|
shellDrawerOpen,
|
|
@@ -138,6 +141,15 @@ export function PaneViewToggle({
|
|
|
138
141
|
<Package className="h-3.5 w-3.5" />
|
|
139
142
|
</ToggleButton>
|
|
140
143
|
)}
|
|
144
|
+
{hasReport && (
|
|
145
|
+
<ToggleButton
|
|
146
|
+
label="Report"
|
|
147
|
+
active={viewMode === "report"}
|
|
148
|
+
onClick={() => onViewModeChange("report")}
|
|
149
|
+
>
|
|
150
|
+
<ClipboardList className="h-3.5 w-3.5" />
|
|
151
|
+
</ToggleButton>
|
|
152
|
+
)}
|
|
141
153
|
{hasLog && (
|
|
142
154
|
<ToggleButton
|
|
143
155
|
label="Log"
|