@saadnvd1/agent-os 0.3.700 → 0.3.723
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/groups/route.ts +44 -0
- package/app/api/bus/spawn/route.ts +34 -2
- package/app/api/claims/[id]/route.ts +20 -0
- package/app/api/claims/check/route.ts +23 -0
- package/app/api/claims/check.test.ts +113 -0
- package/app/api/claims/exchange/route.ts +42 -0
- package/app/api/claims/outside.test.ts +90 -0
- package/app/api/claims/route.test.ts +193 -0
- package/app/api/claims/route.ts +73 -0
- package/app/api/hosts/[id]/route.ts +9 -1
- package/app/api/models/availability/route.ts +9 -0
- package/app/api/models/choices/route.ts +9 -0
- package/app/api/peer/unlink/route.ts +17 -0
- package/app/api/peers/[hostId]/audit/route.ts +14 -0
- package/app/api/peers/[hostId]/route.ts +20 -0
- package/app/api/peers/gate.test.ts +64 -1
- package/app/api/peers/pending/route.test.ts +37 -0
- package/app/api/peers/pending/route.ts +11 -4
- package/app/api/session-groups/[id]/restore/route.ts +11 -0
- package/app/api/session-groups/[id]/route.ts +25 -0
- package/app/api/session-groups/order/route.ts +15 -0
- package/app/api/session-groups/route.ts +21 -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]/fork/route.ts +3 -0
- package/app/api/sessions/[id]/group/route.ts +18 -0
- package/app/api/sessions/[id]/model/route.ts +49 -0
- package/app/api/sessions/[id]/route.ts +14 -4
- package/app/api/sessions/[id]/summarize/route.ts +2 -0
- package/app/api/sessions/claims.test.ts +122 -0
- package/app/api/sessions/group/route.ts +17 -0
- package/app/api/sessions/route.ts +31 -14
- package/app/page.tsx +10 -0
- package/components/Chat/Messages.tsx +104 -10
- package/components/Claims/ClaimFiles.tsx +38 -0
- package/components/Claims/ClaimRow.tsx +82 -0
- package/components/Claims/ClaimsSheet.tsx +176 -0
- package/components/Claims/OverlapPill.tsx +31 -0
- package/components/Claims/StartClaimsNotice.tsx +70 -0
- package/components/Claims/claims-ui.test.ts +161 -0
- package/components/Claims/format.ts +57 -0
- package/components/Claims/index.ts +3 -0
- package/components/Draft/DraftChips.tsx +6 -1
- package/components/Draft/DraftPanel.tsx +90 -3
- 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/Hosts/AccountMachines.tsx +32 -18
- package/components/Hosts/HostRow.tsx +155 -14
- package/components/Hosts/IncomingLinkSheet.tsx +28 -10
- package/components/Hosts/PeerActivitySheet.tsx +111 -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 +2 -0
- package/components/Pane/MobileTabBar.tsx +12 -0
- package/components/Pane/PaneMenu.tsx +10 -0
- package/components/SessionList/GroupShelves.tsx +195 -0
- package/components/SessionList/MoveToGroupMenu.tsx +105 -0
- package/components/SessionList/RowSubtitle.tsx +7 -1
- package/components/SessionList/SelectionToolbar.tsx +52 -1
- package/components/SessionList/SessionRow.tsx +19 -7
- package/components/SessionList/SessionRowMenu.tsx +23 -0
- package/components/SessionList/SessionShelves.tsx +5 -0
- package/components/SessionList/SidebarBody.tsx +6 -0
- package/components/SessionList/SidebarContextMenu.test.ts +104 -0
- package/components/SessionList/SidebarContextMenu.tsx +59 -0
- package/components/SessionList/SidebarHeader.tsx +5 -0
- package/components/SessionList/WorkspaceSwitcher.tsx +23 -6
- package/components/SessionList/index.tsx +18 -14
- package/components/SessionList/useSidebarData.ts +10 -0
- package/components/Sessions/RolePill.tsx +196 -0
- package/components/Settings/SettingsDialog.tsx +9 -0
- package/components/Share/ShareDialog.tsx +13 -0
- package/components/ui/notice.tsx +46 -0
- package/components/ui/status-pill.tsx +39 -0
- 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/claims/index.ts +2 -0
- package/data/claims/keys.ts +5 -0
- package/data/claims/queries.ts +47 -0
- package/data/exports/index.ts +2 -0
- package/data/exports/keys.ts +3 -0
- package/data/exports/queries.ts +51 -0
- package/data/models/index.ts +98 -0
- package/data/peers/keys.ts +1 -0
- package/data/peers/queries.ts +31 -0
- package/data/push/topics.ts +9 -0
- package/data/session-groups/index.ts +156 -0
- package/data/sessions/index.ts +0 -1
- package/data/sessions/pin.ts +2 -6
- package/data/sessions/queries.ts +36 -29
- package/data/sessions/shelf.ts +2 -6
- package/data/tasks/index.ts +2 -0
- package/hooks/useSessions.ts +0 -12
- package/lib/__fixtures__/peer-rpc.ts +65 -33
- package/lib/agents/brief.ts +1 -0
- package/lib/agents/spawn.ts +4 -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 +13 -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 +123 -0
- package/lib/claims/describe.ts +41 -0
- package/lib/claims/exchange.ts +91 -0
- package/lib/claims/goal.ts +45 -0
- package/lib/claims/index.ts +10 -0
- package/lib/claims/loop.ts +51 -0
- package/lib/claims/machine.ts +21 -0
- package/lib/claims/mine.ts +43 -0
- package/lib/claims/notifyOverlaps.ts +101 -0
- package/lib/claims/onMerged.ts +85 -0
- package/lib/claims/outside.ts +36 -0
- package/lib/claims/overlap.ts +87 -0
- package/lib/claims/overlapCache.ts +86 -0
- package/lib/claims/overlapText.ts +36 -0
- package/lib/claims/peers.ts +56 -0
- package/lib/claims/startCheck.ts +100 -0
- package/lib/claims/startWarning.ts +29 -0
- package/lib/claims/store.ts +366 -0
- package/lib/claims/types.ts +102 -0
- package/lib/claims/view.ts +80 -0
- package/lib/claims/yield.ts +24 -0
- package/lib/db/changes.ts +12 -0
- package/lib/db/migrations/20261020T1400_work_claims.ts +35 -0
- package/lib/db/migrations/20261020T1515_peer_unlinked.ts +22 -0
- package/lib/db/migrations/20261020T1700_clean_connect_names.ts +18 -0
- package/lib/db/migrations/20261020T1800_session_groups.ts +49 -0
- package/lib/db/migrations/20261020T1900_task_queue_after_claim.ts +13 -0
- package/lib/db/migrations/20261020T2000_claim_notices.ts +20 -0
- package/lib/db/migrations/20261020T2100_claims_via_host.ts +21 -0
- package/lib/db/migrations/20261020T2200_work_claims_watch.ts +10 -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/queries.ts +0 -40
- package/lib/db/session-parts/columns.ts +3 -0
- package/lib/db/types.ts +19 -8
- package/lib/doctor/checks.ts +64 -1
- package/lib/done/archive.ts +2 -0
- package/lib/drafts.ts +4 -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/git-remote.ts +43 -0
- package/lib/hosts/index.ts +10 -1
- 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/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/orchestration.ts +2 -0
- package/lib/orchestrator/act.ts +54 -4
- package/lib/orchestrator/brief.ts +2 -1
- package/lib/orchestrator/claim-wait.ts +71 -0
- package/lib/orchestrator/conditions.ts +28 -1
- package/lib/orchestrator/describe.ts +6 -1
- package/lib/orchestrator/facts.ts +12 -2
- package/lib/orchestrator/read.ts +5 -1
- package/lib/orchestrator/serve.ts +37 -2
- package/lib/orchestrator/tool-names.ts +1 -0
- package/lib/orchestrator/tool-schemas.ts +31 -1
- package/lib/orchestrator/tools.ts +3 -1
- package/lib/orchestrator/untrusted.ts +5 -0
- package/lib/orchestrator/watcher.ts +19 -1
- package/lib/peer/account.ts +74 -2
- package/lib/peer/allowlist.ts +1 -1
- package/lib/peer/client.ts +26 -1
- package/lib/peer/link-cli.ts +19 -4
- package/lib/peer/links.ts +117 -5
- package/lib/peer/machines.ts +14 -2
- package/lib/peer/name.ts +32 -0
- package/lib/peer/pairing.ts +34 -5
- package/lib/peer/pin.ts +10 -3
- package/lib/peer/reasons.ts +7 -0
- package/lib/peer/revoke.ts +75 -0
- package/lib/peer/rpc-server.ts +53 -5
- package/lib/peer/unlink.ts +62 -0
- package/lib/peer/unlinked-proof.ts +44 -0
- package/lib/projects.ts +6 -2
- package/lib/security/demo.ts +7 -0
- package/lib/session-artifacts/agentos-files.ts +94 -0
- package/lib/session-artifacts/collect.ts +17 -0
- package/lib/session-groups/assignSessions.ts +81 -0
- package/lib/session-groups/cli.ts +61 -0
- package/lib/session-groups/createGroup.ts +28 -0
- package/lib/session-groups/index.ts +21 -0
- package/lib/session-groups/removeGroup.ts +36 -0
- package/lib/session-groups/reorderGroups.ts +31 -0
- package/lib/session-groups/resolveGroup.ts +19 -0
- package/lib/session-groups/route.ts +32 -0
- package/lib/session-groups/store.ts +85 -0
- package/lib/session-groups/types.ts +25 -0
- package/lib/session-groups/updateGroup.ts +35 -0
- package/lib/session-groups/workspace.ts +21 -0
- package/lib/sessions/crew.ts +72 -0
- package/lib/sessions/launch.ts +54 -11
- 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/rollup.ts +1 -0
- package/lib/sidebar/row-menu.ts +8 -1
- package/lib/sidebar/shelves.ts +90 -15
- package/lib/sidebar/visible-order.ts +5 -0
- package/lib/tasks/hold-checks.ts +3 -1
- package/lib/tasks/holds.ts +3 -0
- package/lib/tasks/index.ts +11 -0
- package/lib/tasks/queue-rows.ts +8 -1
- package/lib/tasks/queue-start.ts +1 -0
- package/lib/tasks/queue-types.ts +9 -1
- package/lib/tasks/queue.ts +15 -5
- package/lib/tasks/schedule-types.ts +1 -0
- package/lib/test-temp.ts +19 -3
- package/lib/workspaces.ts +11 -3
- 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/lib/worktree-sweep/testTemp.ts +23 -106
- package/package.json +1 -1
- package/scripts/agent-os +1 -1
- package/scripts/autodeploy +2 -0
- package/scripts/autodeploy.test.ts +31 -1
- package/scripts/claim-hook.sh +31 -0
- package/scripts/claim-hook.test.ts +230 -0
- package/scripts/lib/cli-links.sh +36 -0
- package/scripts/lib/commands.sh +3 -0
- package/scripts/lib/machine.sh +27 -2
- package/scripts/lib/machine.test.ts +25 -3
- package/scripts/sync-cli-links +7 -0
- package/scripts/sync-cli-links.test.ts +69 -0
- package/server.ts +12 -1
- package/stores/claimsUi.ts +22 -0
- package/stores/exportUi.ts +87 -0
- package/app/api/groups/[...path]/route.ts +0 -136
- package/app/api/groups/route.ts +0 -93
- package/data/groups/index.ts +0 -1
- package/data/groups/mutations.ts +0 -95
- package/hooks/useGroups.ts +0 -37
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The roles a person assigns models to: Developer, Analyst, Reviewer and
|
|
3
|
+
* Supervisor. Each binds one of the model roles in ./roles, so there is one
|
|
4
|
+
* routing system: Developer is `implement`, Supervisor is `orchestrator`,
|
|
5
|
+
* and Analyst and Reviewer have a model role of their own. Imported by the
|
|
6
|
+
* browser too, so no Node modules here.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { ModelRole } from "./roles";
|
|
10
|
+
|
|
11
|
+
export const AGENT_ROLES = [
|
|
12
|
+
"developer",
|
|
13
|
+
"analyst",
|
|
14
|
+
"reviewer",
|
|
15
|
+
"supervisor",
|
|
16
|
+
] as const;
|
|
17
|
+
|
|
18
|
+
export type AgentRole = (typeof AGENT_ROLES)[number];
|
|
19
|
+
|
|
20
|
+
export interface AgentRoleDef {
|
|
21
|
+
id: AgentRole;
|
|
22
|
+
label: string;
|
|
23
|
+
// The one line shown beside its model.
|
|
24
|
+
purpose: string;
|
|
25
|
+
modelRole: ModelRole;
|
|
26
|
+
// Appended to the instructions of a session started in the role.
|
|
27
|
+
brief: string;
|
|
28
|
+
// Whether it may change files.
|
|
29
|
+
edits: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const AGENT_ROLE_DEFS: Record<AgentRole, AgentRoleDef> = {
|
|
33
|
+
developer: {
|
|
34
|
+
id: "developer",
|
|
35
|
+
label: "Developer",
|
|
36
|
+
purpose: "Writes the code: tasks, and sessions started as Developer",
|
|
37
|
+
modelRole: "implement",
|
|
38
|
+
edits: true,
|
|
39
|
+
brief:
|
|
40
|
+
"You are the Developer in this crew. You write and change the code. When your work is ready, tell the Reviewer with `aos send @reviewer`; answer the Analyst's questions with `aos send @analyst`.",
|
|
41
|
+
},
|
|
42
|
+
analyst: {
|
|
43
|
+
id: "analyst",
|
|
44
|
+
label: "Analyst",
|
|
45
|
+
purpose:
|
|
46
|
+
"Reads code, logs and the Developer's work; explains and proposes; doesn't commit",
|
|
47
|
+
modelRole: "analyze",
|
|
48
|
+
edits: false,
|
|
49
|
+
brief:
|
|
50
|
+
"You are the Analyst in this crew. You read code, logs and data, and the Developer's transcript (`aos read @developer`), then explain what you find and propose changes. You don't edit files, commit or open pull requests; send proposals to the Developer with `aos send @developer`.",
|
|
51
|
+
},
|
|
52
|
+
reviewer: {
|
|
53
|
+
id: "reviewer",
|
|
54
|
+
label: "Reviewer",
|
|
55
|
+
purpose:
|
|
56
|
+
"Reviews another role's diff and suggests improvements. Advice only: not the merge gate",
|
|
57
|
+
modelRole: "advise",
|
|
58
|
+
edits: false,
|
|
59
|
+
brief:
|
|
60
|
+
"You are the Reviewer in this crew. Read the Developer's diff (`git diff` in its worktree, `aos read @developer`) and send concrete suggestions with `aos send @developer`. You don't edit files or open pull requests. Your review is advice; the merge gate is separate.",
|
|
61
|
+
},
|
|
62
|
+
supervisor: {
|
|
63
|
+
id: "supervisor",
|
|
64
|
+
label: "Supervisor",
|
|
65
|
+
purpose:
|
|
66
|
+
"Watches a crew, sequences work, unblocks and reports. The workspace orchestrator",
|
|
67
|
+
modelRole: "orchestrator",
|
|
68
|
+
edits: false,
|
|
69
|
+
brief:
|
|
70
|
+
"You are the Supervisor in this crew. Watch the other roles (`aos peers`, `aos read @developer`), unblock them, sequence the work and report to the user. You don't write the code yourself.",
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export const isAgentRole = (v: unknown): v is AgentRole =>
|
|
75
|
+
typeof v === "string" && (AGENT_ROLES as readonly string[]).includes(v);
|
|
76
|
+
|
|
77
|
+
export const agentRoleOf = (modelRole: ModelRole): AgentRole | undefined =>
|
|
78
|
+
AGENT_ROLES.find((r) => AGENT_ROLE_DEFS[r].modelRole === modelRole);
|
|
79
|
+
|
|
80
|
+
// The model roles under the four above, shown as "internal" roles.
|
|
81
|
+
export const INTERNAL_MODEL_ROLES = (all: readonly ModelRole[]) =>
|
|
82
|
+
all.filter((r) => !agentRoleOf(r));
|
|
83
|
+
|
|
84
|
+
// A session's brief for its role, or "" with none.
|
|
85
|
+
export function roleBrief(role: string | null | undefined): string {
|
|
86
|
+
return isAgentRole(role) ? AGENT_ROLE_DEFS[role].brief : "";
|
|
87
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { modelCatalog } from "./catalog";
|
|
2
|
+
import { modelAvailability as probeRef } from "./catalog";
|
|
3
|
+
import type { Availability } from "./availability";
|
|
4
|
+
|
|
5
|
+
// Every local model's availability by ref, probing the stale or all of them.
|
|
6
|
+
export async function modelAvailability(
|
|
7
|
+
refresh = false
|
|
8
|
+
): Promise<Record<string, Availability>> {
|
|
9
|
+
const local = modelCatalog().filter((c) => c.locality === "local");
|
|
10
|
+
const out: Record<string, Availability> = {};
|
|
11
|
+
await Promise.all(
|
|
12
|
+
local.map(async (c) => {
|
|
13
|
+
out[c.ref] = await probeRef(c.ref, undefined, refresh);
|
|
14
|
+
})
|
|
15
|
+
);
|
|
16
|
+
return out;
|
|
17
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a model's server answers. Only endpoints on the local networks
|
|
3
|
+
* are probed (a cloud provider's key isn't proof and a probe costs), one
|
|
4
|
+
* short GET, cached for a while. Any HTTP answer means the server is up;
|
|
5
|
+
* what's down is a refused connection or no answer in time.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export type AvailabilityState = "up" | "down" | "unknown";
|
|
9
|
+
|
|
10
|
+
export interface Availability {
|
|
11
|
+
state: AvailabilityState;
|
|
12
|
+
checkedAt: string | null;
|
|
13
|
+
error?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const TTL_MS = 30_000;
|
|
17
|
+
const TIMEOUT_MS = 1_500;
|
|
18
|
+
|
|
19
|
+
interface Entry extends Availability {
|
|
20
|
+
at: number;
|
|
21
|
+
}
|
|
22
|
+
const cache = new Map<string, Entry>();
|
|
23
|
+
const probing = new Map<string, Promise<Entry>>();
|
|
24
|
+
|
|
25
|
+
const UNKNOWN: Availability = { state: "unknown", checkedAt: null };
|
|
26
|
+
|
|
27
|
+
// What a server of that API lists its models at.
|
|
28
|
+
export const probeUrl = (baseUrl: string, api: "anthropic" | "openai") => {
|
|
29
|
+
const base = baseUrl.replace(/\/+$/, "");
|
|
30
|
+
return api === "anthropic" && !/\/v1$/.test(base)
|
|
31
|
+
? `${base}/v1/models`
|
|
32
|
+
: `${base}/models`;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
async function probe(url: string): Promise<Entry> {
|
|
36
|
+
const at = Date.now();
|
|
37
|
+
try {
|
|
38
|
+
await fetch(url, {
|
|
39
|
+
method: "GET",
|
|
40
|
+
signal: AbortSignal.timeout(TIMEOUT_MS),
|
|
41
|
+
redirect: "manual",
|
|
42
|
+
});
|
|
43
|
+
return { state: "up", checkedAt: new Date(at).toISOString(), at };
|
|
44
|
+
} catch (error) {
|
|
45
|
+
const cause = (error as { cause?: { code?: string } }).cause?.code;
|
|
46
|
+
const timedOut = (error as Error).name === "TimeoutError";
|
|
47
|
+
return {
|
|
48
|
+
state: "down",
|
|
49
|
+
checkedAt: new Date(at).toISOString(),
|
|
50
|
+
at,
|
|
51
|
+
error: timedOut
|
|
52
|
+
? `no answer in ${TIMEOUT_MS / 1000}s`
|
|
53
|
+
: (cause ?? (error as Error).message),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const strip = ({ at: _at, ...rest }: Entry): Availability => rest;
|
|
59
|
+
|
|
60
|
+
// The last check of a URL, probing in the background when it's stale: a
|
|
61
|
+
// request handler never waits on a probe.
|
|
62
|
+
export function cachedAvailability(url: string): Availability {
|
|
63
|
+
const hit = cache.get(url);
|
|
64
|
+
if (!hit || Date.now() - hit.at >= TTL_MS) void checkAvailability(url);
|
|
65
|
+
return hit ? strip(hit) : UNKNOWN;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// A fresh enough answer, waiting for the probe if there isn't one.
|
|
69
|
+
export async function checkAvailability(
|
|
70
|
+
url: string,
|
|
71
|
+
refresh = false
|
|
72
|
+
): Promise<Availability> {
|
|
73
|
+
const hit = cache.get(url);
|
|
74
|
+
if (!refresh && hit && Date.now() - hit.at < TTL_MS) return strip(hit);
|
|
75
|
+
let running = probing.get(url);
|
|
76
|
+
if (!running) {
|
|
77
|
+
running = probe(url)
|
|
78
|
+
.then((e) => {
|
|
79
|
+
cache.set(url, e);
|
|
80
|
+
return e;
|
|
81
|
+
})
|
|
82
|
+
.finally(() => probing.delete(url));
|
|
83
|
+
probing.set(url, running);
|
|
84
|
+
}
|
|
85
|
+
return strip(await running);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function clearAvailability(): void {
|
|
89
|
+
cache.clear();
|
|
90
|
+
probing.clear();
|
|
91
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one list of models every picker shows, built on the server: each
|
|
3
|
+
* enabled provider's models, and the models Pi reaches (local servers in its
|
|
4
|
+
* models.json). Nothing about a model, provider or its locality is decided
|
|
5
|
+
* in the browser.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import fs from "fs";
|
|
9
|
+
import os from "os";
|
|
10
|
+
import path from "path";
|
|
11
|
+
import { effectiveModels, type EffectiveModels } from "./config";
|
|
12
|
+
import { ANTHROPIC } from "./roles";
|
|
13
|
+
import { PI, type ModelProvider } from "./providers";
|
|
14
|
+
import { hostLocality, type Locality } from "./locality";
|
|
15
|
+
import {
|
|
16
|
+
cachedAvailability,
|
|
17
|
+
checkAvailability,
|
|
18
|
+
probeUrl,
|
|
19
|
+
type Availability,
|
|
20
|
+
} from "./availability";
|
|
21
|
+
|
|
22
|
+
export interface ModelChoice {
|
|
23
|
+
// "anthropic/claude-opus-5-5", "pi/local/qwen3-27b"
|
|
24
|
+
ref: string;
|
|
25
|
+
label: string;
|
|
26
|
+
// The provider's name: "Anthropic", "DeepSeek", "Pi · local".
|
|
27
|
+
group: string;
|
|
28
|
+
// What runs it: Claude Code, or the Pi agent.
|
|
29
|
+
agent: "claude" | "pi";
|
|
30
|
+
locality: Locality;
|
|
31
|
+
availability: Availability;
|
|
32
|
+
// May serve a merge gate: Claude Code can run it.
|
|
33
|
+
gateCapable: boolean;
|
|
34
|
+
// Where the server is (never a key).
|
|
35
|
+
endpoint?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface PiProvider {
|
|
39
|
+
id: string;
|
|
40
|
+
baseUrl: string;
|
|
41
|
+
models: { id: string; name?: string }[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const piModelsFile = () =>
|
|
45
|
+
process.env.AGENTOS_PI_MODELS_FILE ||
|
|
46
|
+
path.join(os.homedir(), ".pi", "agent", "models.json");
|
|
47
|
+
|
|
48
|
+
// The providers in Pi's own config. A missing or unreadable file means Pi
|
|
49
|
+
// has no servers of ours to list.
|
|
50
|
+
export function piProviders(file = piModelsFile()): PiProvider[] {
|
|
51
|
+
let raw: unknown;
|
|
52
|
+
try {
|
|
53
|
+
raw = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
54
|
+
} catch {
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
57
|
+
const providers = (raw as { providers?: Record<string, unknown> })?.providers;
|
|
58
|
+
if (!providers || typeof providers !== "object") return [];
|
|
59
|
+
const out: PiProvider[] = [];
|
|
60
|
+
for (const [id, p] of Object.entries(providers)) {
|
|
61
|
+
const q = p as { baseUrl?: unknown; models?: unknown };
|
|
62
|
+
if (typeof q?.baseUrl !== "string" || !Array.isArray(q.models)) continue;
|
|
63
|
+
const models = q.models
|
|
64
|
+
.map((m) =>
|
|
65
|
+
typeof m === "string"
|
|
66
|
+
? { id: m }
|
|
67
|
+
: {
|
|
68
|
+
id: (m as { id?: unknown })?.id,
|
|
69
|
+
name: (m as { name?: unknown })?.name,
|
|
70
|
+
}
|
|
71
|
+
)
|
|
72
|
+
.filter(
|
|
73
|
+
(m): m is { id: string; name?: string } => typeof m.id === "string"
|
|
74
|
+
)
|
|
75
|
+
.map((m) => ({
|
|
76
|
+
id: m.id,
|
|
77
|
+
...(typeof m.name === "string" ? { name: m.name } : {}),
|
|
78
|
+
}));
|
|
79
|
+
out.push({ id, baseUrl: q.baseUrl, models });
|
|
80
|
+
}
|
|
81
|
+
return out;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const providerEndpoint = (p: ModelProvider) =>
|
|
85
|
+
p.baseUrl ? probeUrl(p.baseUrl, p.api) : undefined;
|
|
86
|
+
|
|
87
|
+
export function modelCatalog(
|
|
88
|
+
effective: EffectiveModels = effectiveModels(),
|
|
89
|
+
pi: PiProvider[] = piProviders()
|
|
90
|
+
): ModelChoice[] {
|
|
91
|
+
const out: ModelChoice[] = [];
|
|
92
|
+
for (const p of effective.providers.filter((x) => x.enabled)) {
|
|
93
|
+
const endpoint = providerEndpoint(p);
|
|
94
|
+
const locality = hostLocality(p.baseUrl);
|
|
95
|
+
const availability: Availability =
|
|
96
|
+
p.id === ANTHROPIC
|
|
97
|
+
? { state: "up", checkedAt: null }
|
|
98
|
+
: locality === "local" && endpoint
|
|
99
|
+
? cachedAvailability(endpoint)
|
|
100
|
+
: { state: "unknown", checkedAt: null };
|
|
101
|
+
for (const m of p.models)
|
|
102
|
+
out.push({
|
|
103
|
+
ref: `${p.id}/${m}`,
|
|
104
|
+
label: m,
|
|
105
|
+
group: p.label,
|
|
106
|
+
agent: "claude",
|
|
107
|
+
locality,
|
|
108
|
+
availability,
|
|
109
|
+
gateCapable: p.api === "anthropic",
|
|
110
|
+
endpoint: p.baseUrl,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
for (const p of pi) {
|
|
114
|
+
const locality = hostLocality(p.baseUrl);
|
|
115
|
+
const endpoint = probeUrl(p.baseUrl, "openai");
|
|
116
|
+
const availability: Availability =
|
|
117
|
+
locality === "local"
|
|
118
|
+
? cachedAvailability(endpoint)
|
|
119
|
+
: { state: "unknown", checkedAt: null };
|
|
120
|
+
for (const m of p.models)
|
|
121
|
+
out.push({
|
|
122
|
+
ref: `${PI}/${p.id}/${m.id}`,
|
|
123
|
+
label: m.name ?? m.id,
|
|
124
|
+
group: `Pi · ${p.id}`,
|
|
125
|
+
agent: "pi",
|
|
126
|
+
locality,
|
|
127
|
+
availability,
|
|
128
|
+
gateCapable: false,
|
|
129
|
+
endpoint: p.baseUrl,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
return out;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export const choiceFor = (
|
|
136
|
+
ref: string,
|
|
137
|
+
effective?: EffectiveModels
|
|
138
|
+
): ModelChoice | undefined =>
|
|
139
|
+
modelCatalog(effective).find((c) => c.ref === ref);
|
|
140
|
+
|
|
141
|
+
// Whether the model's server answers now, waiting for a probe. Cloud and
|
|
142
|
+
// unlisted models aren't probed: they count as available.
|
|
143
|
+
export async function modelAvailability(
|
|
144
|
+
ref: string,
|
|
145
|
+
effective?: EffectiveModels,
|
|
146
|
+
refresh = false
|
|
147
|
+
): Promise<Availability> {
|
|
148
|
+
const choice = choiceFor(ref, effective);
|
|
149
|
+
if (!choice || choice.locality !== "local" || !choice.endpoint)
|
|
150
|
+
return { state: "unknown", checkedAt: null };
|
|
151
|
+
const url =
|
|
152
|
+
choice.agent === "pi"
|
|
153
|
+
? probeUrl(choice.endpoint, "openai")
|
|
154
|
+
: probeUrl(choice.endpoint, "anthropic");
|
|
155
|
+
return checkAvailability(url, refresh);
|
|
156
|
+
}
|
package/lib/models/config.ts
CHANGED
|
@@ -138,25 +138,35 @@ const subagentMap = z
|
|
|
138
138
|
});
|
|
139
139
|
});
|
|
140
140
|
|
|
141
|
+
// A role may name a model to start on when its own can't be reached.
|
|
142
|
+
const fallbackMap = z.partialRecord(z.enum(MODEL_ROLES), roleRef);
|
|
143
|
+
|
|
141
144
|
export const modelSettingsSchema = z.strictObject({
|
|
142
145
|
roles: roleMap.optional(),
|
|
143
146
|
subagents: subagentMap.optional(),
|
|
144
147
|
providers: z.array(providerSchema).optional(),
|
|
145
148
|
prices: z.record(z.string().regex(REF_PATTERN), price).optional(),
|
|
146
149
|
planStep: z.boolean().optional(),
|
|
150
|
+
fallbacks: fallbackMap.optional(),
|
|
147
151
|
});
|
|
148
152
|
|
|
149
153
|
export const workspaceModelsSchema = z.strictObject({
|
|
150
154
|
roles: roleMap.optional(),
|
|
151
155
|
});
|
|
152
156
|
|
|
157
|
+
export const projectModelsSchema = z.strictObject({
|
|
158
|
+
roles: roleMap.optional(),
|
|
159
|
+
});
|
|
160
|
+
|
|
153
161
|
export type ModelSettings = z.infer<typeof modelSettingsSchema>;
|
|
154
162
|
export type WorkspaceModels = z.infer<typeof workspaceModelsSchema>;
|
|
163
|
+
export type ProjectModels = z.infer<typeof projectModelsSchema>;
|
|
155
164
|
export type RoleMap = Partial<Record<ModelRole, string>>;
|
|
156
165
|
export const roleMapSchema = roleMap;
|
|
157
166
|
|
|
158
167
|
const GLOBAL_KEY = "models";
|
|
159
168
|
const workspaceKey = (id: string) => `models.workspace.${id}`;
|
|
169
|
+
const projectKey = (id: string) => `models.project.${id}`;
|
|
160
170
|
|
|
161
171
|
function read<T>(key: string, schema: z.ZodType<T>): T | null {
|
|
162
172
|
const row = db
|
|
@@ -221,6 +231,23 @@ export function setWorkspaceModels(
|
|
|
221
231
|
return saved;
|
|
222
232
|
}
|
|
223
233
|
|
|
234
|
+
// Set in Settings, never in the repo, so work can't choose its own models.
|
|
235
|
+
export const projectModels = (projectId: string): ProjectModels =>
|
|
236
|
+
read(projectKey(projectId), projectModelsSchema) ?? {};
|
|
237
|
+
|
|
238
|
+
export function setProjectModels(
|
|
239
|
+
projectId: string,
|
|
240
|
+
value: unknown
|
|
241
|
+
): ProjectModels {
|
|
242
|
+
const saved = write(projectKey(projectId), projectModelsSchema, value);
|
|
243
|
+
if (!Object.keys(saved.roles ?? {}).length) clearProjectModels(projectId);
|
|
244
|
+
return saved;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export const clearProjectModels = (projectId: string): void => {
|
|
248
|
+
db.prepare(`DELETE FROM settings WHERE key = ?`).run(projectKey(projectId));
|
|
249
|
+
};
|
|
250
|
+
|
|
224
251
|
// The settings in force: the presets with any saved edits laid over them
|
|
225
252
|
// (by id), saved providers that aren't presets, and every role filled.
|
|
226
253
|
export interface EffectiveModels {
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a model endpoint is on this machine's own networks (local) or
|
|
3
|
+
* out on the internet (cloud), read from its host, never from a name.
|
|
4
|
+
* Imported by the browser too.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export type Locality = "local" | "cloud";
|
|
8
|
+
|
|
9
|
+
const PRIVATE_V4 = [
|
|
10
|
+
/^127\./,
|
|
11
|
+
/^10\./,
|
|
12
|
+
/^192\.168\./,
|
|
13
|
+
/^172\.(1[6-9]|2\d|3[01])\./,
|
|
14
|
+
/^169\.254\./,
|
|
15
|
+
// The tailnet: 100.64.0.0/10.
|
|
16
|
+
/^100\.(6[4-9]|[7-9]\d|1[01]\d|12[0-7])\./,
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
export function hostLocality(url: string | null | undefined): Locality {
|
|
20
|
+
if (!url) return "cloud";
|
|
21
|
+
let host: string;
|
|
22
|
+
try {
|
|
23
|
+
host = new URL(url).hostname.toLowerCase();
|
|
24
|
+
} catch {
|
|
25
|
+
return "cloud";
|
|
26
|
+
}
|
|
27
|
+
host = host.replace(/^\[|\]$/g, "");
|
|
28
|
+
if (host === "localhost" || host === "::1" || host.endsWith(".localhost"))
|
|
29
|
+
return "local";
|
|
30
|
+
if (host.endsWith(".local") || host.endsWith(".ts.net")) return "local";
|
|
31
|
+
if (/^f[cd][0-9a-f]{2}:/.test(host)) return "local";
|
|
32
|
+
return PRIVATE_V4.some((re) => re.test(host)) ? "local" : "cloud";
|
|
33
|
+
}
|
package/lib/models/providers.ts
CHANGED
|
@@ -63,6 +63,17 @@ export const ANTHROPIC_PROVIDER: ModelProvider = {
|
|
|
63
63
|
enabled: true,
|
|
64
64
|
};
|
|
65
65
|
|
|
66
|
+
// Pi's own models ("pi/<its provider>/<model>"): local servers Pi reaches,
|
|
67
|
+
// run by the Pi agent rather than through Claude Code's endpoint variables.
|
|
68
|
+
export const PI = "pi";
|
|
69
|
+
export const PI_PROVIDER: ModelProvider = {
|
|
70
|
+
id: PI,
|
|
71
|
+
label: "Pi",
|
|
72
|
+
api: "openai",
|
|
73
|
+
models: [],
|
|
74
|
+
enabled: true,
|
|
75
|
+
};
|
|
76
|
+
|
|
66
77
|
// Checked against each provider's docs on 2026-10-11: DeepSeek
|
|
67
78
|
// (api-docs.deepseek.com/guides/anthropic_api) and Z.ai
|
|
68
79
|
// (docs.z.ai/devpack/tool/claude). Off until a key reference is set.
|
package/lib/models/resolve.ts
CHANGED
|
@@ -12,11 +12,17 @@ import {
|
|
|
12
12
|
effectiveModels,
|
|
13
13
|
roleMapSchema,
|
|
14
14
|
savedModelSettings,
|
|
15
|
+
projectModels,
|
|
15
16
|
workspaceModels,
|
|
16
17
|
type EffectiveModels,
|
|
17
18
|
type RoleMap,
|
|
18
19
|
} from "./config";
|
|
19
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
ANTHROPIC_PROVIDER,
|
|
22
|
+
PI,
|
|
23
|
+
PI_PROVIDER,
|
|
24
|
+
type ModelProvider,
|
|
25
|
+
} from "./providers";
|
|
20
26
|
import {
|
|
21
27
|
ANTHROPIC,
|
|
22
28
|
DEFAULT_ROLE_MODELS,
|
|
@@ -33,7 +39,14 @@ import {
|
|
|
33
39
|
type SecretCommand,
|
|
34
40
|
} from "./secrets";
|
|
35
41
|
|
|
36
|
-
export type RoleSource =
|
|
42
|
+
export type RoleSource =
|
|
43
|
+
| "task"
|
|
44
|
+
| "project"
|
|
45
|
+
| "workspace"
|
|
46
|
+
| "settings"
|
|
47
|
+
| "env"
|
|
48
|
+
| "inherited"
|
|
49
|
+
| "default";
|
|
37
50
|
|
|
38
51
|
// The server's own environment can still stand in for a role's default:
|
|
39
52
|
// what AgentOS read before model routing existed.
|
|
@@ -87,9 +100,15 @@ const overrideFor = (o: ModelOverride | null | undefined, role: ModelRole) =>
|
|
|
87
100
|
// The provider by id, or throws why it can't be used.
|
|
88
101
|
export function providerFor(
|
|
89
102
|
id: string,
|
|
90
|
-
models: EffectiveModels = effectiveModels()
|
|
103
|
+
models: EffectiveModels = effectiveModels(),
|
|
104
|
+
allowPi = false
|
|
91
105
|
): ModelProvider {
|
|
92
106
|
if (id === ANTHROPIC) return ANTHROPIC_PROVIDER;
|
|
107
|
+
if (id === PI) {
|
|
108
|
+
if (!allowPi)
|
|
109
|
+
throw new Error("Pi models run only in a session started in a role");
|
|
110
|
+
return PI_PROVIDER;
|
|
111
|
+
}
|
|
93
112
|
const p = models.providers.find((x) => x.id === id);
|
|
94
113
|
if (!p) throw new Error(`No model provider "${id}" (Settings > Models)`);
|
|
95
114
|
if (!p.enabled) throw new Error(`The ${p.label} provider is turned off`);
|
|
@@ -104,10 +123,19 @@ export function providerFor(
|
|
|
104
123
|
|
|
105
124
|
export function resolveRole(
|
|
106
125
|
role: ModelRole,
|
|
107
|
-
opts: {
|
|
126
|
+
opts: {
|
|
127
|
+
workspaceId?: string | null;
|
|
128
|
+
projectId?: string | null;
|
|
129
|
+
override?: ModelOverride | null;
|
|
130
|
+
// A role session runs Pi models; a task or gate can't.
|
|
131
|
+
allowPi?: boolean;
|
|
132
|
+
} = {},
|
|
108
133
|
models: EffectiveModels = effectiveModels()
|
|
109
134
|
): RoutedModel {
|
|
110
135
|
const task = overrideFor(opts.override, role);
|
|
136
|
+
const proj = opts.projectId
|
|
137
|
+
? projectModels(opts.projectId).roles?.[role]
|
|
138
|
+
: undefined;
|
|
111
139
|
const ws = opts.workspaceId
|
|
112
140
|
? workspaceModels(opts.workspaceId).roles?.[role]
|
|
113
141
|
: undefined;
|
|
@@ -117,20 +145,30 @@ export function resolveRole(
|
|
|
117
145
|
saved === DEFAULT_ROLE_MODELS[role] && !savedModelSettings().roles?.[role]
|
|
118
146
|
? envModel(role)
|
|
119
147
|
: undefined;
|
|
148
|
+
// The reviewer's advice follows the gate's model until it's set.
|
|
149
|
+
const named = !!savedModelSettings().roles?.[role];
|
|
150
|
+
const inherited =
|
|
151
|
+
role === "advise" && !env && !named
|
|
152
|
+
? resolveRole("review", { ...opts, override: null }, models)
|
|
153
|
+
: null;
|
|
120
154
|
const [ref, source]: [string, RoleSource] = task
|
|
121
155
|
? [task, "task"]
|
|
122
|
-
:
|
|
123
|
-
? [
|
|
124
|
-
:
|
|
125
|
-
? [
|
|
126
|
-
:
|
|
127
|
-
? [
|
|
128
|
-
:
|
|
156
|
+
: proj
|
|
157
|
+
? [proj, "project"]
|
|
158
|
+
: ws
|
|
159
|
+
? [ws, "workspace"]
|
|
160
|
+
: saved !== DEFAULT_ROLE_MODELS[role] || (role === "advise" && named)
|
|
161
|
+
? [saved, "settings"]
|
|
162
|
+
: env
|
|
163
|
+
? [env, "env"]
|
|
164
|
+
: inherited
|
|
165
|
+
? [inherited.ref, "inherited"]
|
|
166
|
+
: [saved, "default"];
|
|
129
167
|
const parsed = parseModelRef(ref);
|
|
130
168
|
return {
|
|
131
169
|
role,
|
|
132
170
|
ref: formatModelRef(parsed),
|
|
133
|
-
provider: providerFor(parsed.provider, models),
|
|
171
|
+
provider: providerFor(parsed.provider, models, opts.allowPi),
|
|
134
172
|
model: parsed.model,
|
|
135
173
|
source,
|
|
136
174
|
};
|
package/lib/models/roles.ts
CHANGED
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
export const MODEL_ROLES = [
|
|
10
10
|
"plan",
|
|
11
11
|
"implement",
|
|
12
|
+
"analyze",
|
|
13
|
+
"advise",
|
|
12
14
|
"orchestrator",
|
|
13
15
|
"review",
|
|
14
16
|
"scope",
|
|
@@ -28,6 +30,14 @@ export const ROLE_LABELS: Record<ModelRole, { label: string; hint: string }> = {
|
|
|
28
30
|
label: "Task implementation",
|
|
29
31
|
hint: "A task's agent, following the plan in its worktree",
|
|
30
32
|
},
|
|
33
|
+
analyze: {
|
|
34
|
+
label: "Analyst",
|
|
35
|
+
hint: "Reads code, logs and a developer's work; explains and proposes",
|
|
36
|
+
},
|
|
37
|
+
advise: {
|
|
38
|
+
label: "Reviewer",
|
|
39
|
+
hint: "Advises on another role's diff. Not the merge gate",
|
|
40
|
+
},
|
|
31
41
|
orchestrator: { label: "Orchestrator", hint: "A workspace's orchestrator" },
|
|
32
42
|
review: {
|
|
33
43
|
label: "Gate review",
|
|
@@ -58,6 +68,8 @@ export const DEFAULT_ROLE_MODELS: Record<ModelRole, string> = {
|
|
|
58
68
|
plan: OPUS,
|
|
59
69
|
review: OPUS,
|
|
60
70
|
implement: SONNET,
|
|
71
|
+
analyze: OPUS,
|
|
72
|
+
advise: OPUS,
|
|
61
73
|
orchestrator: OPUS,
|
|
62
74
|
scope: SONNET,
|
|
63
75
|
interactive: OPUS,
|
package/lib/needs-you.ts
CHANGED
|
@@ -17,7 +17,7 @@ export function markSeen(sessionId: string): void {
|
|
|
17
17
|
notifyStatusChanged();
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
function latestChatItem(sessionId: string): ChatItem | null {
|
|
20
|
+
export function latestChatItem(sessionId: string): ChatItem | null {
|
|
21
21
|
const row = getDb()
|
|
22
22
|
.prepare(
|
|
23
23
|
`SELECT data FROM chat_items WHERE session_id = ? ORDER BY seq DESC LIMIT 1`
|
package/lib/orchestration.ts
CHANGED
|
@@ -19,6 +19,7 @@ import { UNSET_SERVER_ENV } from "./agents/server-env";
|
|
|
19
19
|
import { runInBackground } from "./async-operations";
|
|
20
20
|
import { tmux } from "./tmux/exec";
|
|
21
21
|
import { endTmuxSession } from "./tmux/end";
|
|
22
|
+
import { inheritGroup } from "@/lib/session-groups/assignSessions";
|
|
22
23
|
|
|
23
24
|
export interface SpawnWorkerOptions {
|
|
24
25
|
conductorSessionId: string;
|
|
@@ -146,6 +147,7 @@ export async function spawnWorker(
|
|
|
146
147
|
agentType,
|
|
147
148
|
"uncategorized" // project_id
|
|
148
149
|
);
|
|
150
|
+
inheritGroup(conductorSessionId, sessionId);
|
|
149
151
|
|
|
150
152
|
// Update worktree info if created
|
|
151
153
|
if (worktreePath) {
|