@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,303 @@
|
|
|
1
|
+
import { randomUUID } from "crypto";
|
|
2
|
+
import { db } from "../db";
|
|
3
|
+
import {
|
|
4
|
+
AnalystError,
|
|
5
|
+
type AnalystLink,
|
|
6
|
+
type DigestSignals,
|
|
7
|
+
type Issue,
|
|
8
|
+
type Recommendation,
|
|
9
|
+
type RecommendationState,
|
|
10
|
+
type Verdict,
|
|
11
|
+
} from "./types";
|
|
12
|
+
|
|
13
|
+
interface LinkRow {
|
|
14
|
+
id: string;
|
|
15
|
+
coder_session_id: string;
|
|
16
|
+
analyst_session_id: string;
|
|
17
|
+
paused: number;
|
|
18
|
+
reviewed_through: string | null;
|
|
19
|
+
pending_digest_id: string | null;
|
|
20
|
+
pending_through: string | null;
|
|
21
|
+
digest_signals: string | null;
|
|
22
|
+
created_at: number;
|
|
23
|
+
ended_at: number | null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface RecRow {
|
|
27
|
+
id: string;
|
|
28
|
+
link_id: string;
|
|
29
|
+
analyst_item_id: string;
|
|
30
|
+
coder_through: string | null;
|
|
31
|
+
verdict: string | null;
|
|
32
|
+
summary: string;
|
|
33
|
+
issues: string;
|
|
34
|
+
signals: string | null;
|
|
35
|
+
prompt: string | null;
|
|
36
|
+
state: string;
|
|
37
|
+
error: string | null;
|
|
38
|
+
sent_text: string | null;
|
|
39
|
+
sent_item_id: string | null;
|
|
40
|
+
edited: number;
|
|
41
|
+
created_at: number;
|
|
42
|
+
decided_at: number | null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const parse = <T>(s: string | null, fallback: T): T => {
|
|
46
|
+
if (!s) return fallback;
|
|
47
|
+
try {
|
|
48
|
+
return JSON.parse(s) as T;
|
|
49
|
+
} catch {
|
|
50
|
+
return fallback;
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const toLink = (r: LinkRow): AnalystLink => ({
|
|
55
|
+
id: r.id,
|
|
56
|
+
coderSessionId: r.coder_session_id,
|
|
57
|
+
analystSessionId: r.analyst_session_id,
|
|
58
|
+
paused: !!r.paused,
|
|
59
|
+
reviewedThrough: r.reviewed_through,
|
|
60
|
+
pendingDigestId: r.pending_digest_id,
|
|
61
|
+
pendingThrough: r.pending_through,
|
|
62
|
+
digestSignals: parse<DigestSignals | null>(r.digest_signals, null),
|
|
63
|
+
createdAt: r.created_at,
|
|
64
|
+
endedAt: r.ended_at,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const toRec = (r: RecRow): Recommendation => ({
|
|
68
|
+
id: r.id,
|
|
69
|
+
linkId: r.link_id,
|
|
70
|
+
analystItemId: r.analyst_item_id,
|
|
71
|
+
coderThrough: r.coder_through,
|
|
72
|
+
verdict: r.verdict as Verdict | null,
|
|
73
|
+
summary: r.summary,
|
|
74
|
+
issues: parse<Issue[]>(r.issues, []),
|
|
75
|
+
signals: parse<DigestSignals | null>(r.signals, null),
|
|
76
|
+
prompt: r.prompt,
|
|
77
|
+
state: r.state as RecommendationState,
|
|
78
|
+
error: r.error,
|
|
79
|
+
sentText: r.sent_text,
|
|
80
|
+
sentItemId: r.sent_item_id,
|
|
81
|
+
edited: !!r.edited,
|
|
82
|
+
createdAt: r.created_at,
|
|
83
|
+
decidedAt: r.decided_at,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
export function getLink(id: string): AnalystLink | null {
|
|
87
|
+
const r = db.prepare(`SELECT * FROM analyst_links WHERE id = ?`).get(id) as
|
|
88
|
+
LinkRow | undefined;
|
|
89
|
+
return r ? toLink(r) : null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function getLinkOrThrow(id: string): AnalystLink {
|
|
93
|
+
const link = getLink(id);
|
|
94
|
+
if (!link) throw new AnalystError("Pairing not found", 404);
|
|
95
|
+
return link;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// The live link a session is in, on either side.
|
|
99
|
+
export function liveLinkFor(sessionId: string): AnalystLink | null {
|
|
100
|
+
const r = db
|
|
101
|
+
.prepare(
|
|
102
|
+
`SELECT * FROM analyst_links WHERE ended_at IS NULL
|
|
103
|
+
AND (coder_session_id = ? OR analyst_session_id = ?)`
|
|
104
|
+
)
|
|
105
|
+
.get(sessionId, sessionId) as LinkRow | undefined;
|
|
106
|
+
return r ? toLink(r) : null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Any link, live or ended, the session was the analyst of.
|
|
110
|
+
export function linkOfAnalyst(sessionId: string): AnalystLink | null {
|
|
111
|
+
const r = db
|
|
112
|
+
.prepare(`SELECT * FROM analyst_links WHERE analyst_session_id = ?`)
|
|
113
|
+
.get(sessionId) as LinkRow | undefined;
|
|
114
|
+
return r ? toLink(r) : null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function liveLinks(): AnalystLink[] {
|
|
118
|
+
return (
|
|
119
|
+
db
|
|
120
|
+
.prepare(`SELECT * FROM analyst_links WHERE ended_at IS NULL`)
|
|
121
|
+
.all() as LinkRow[]
|
|
122
|
+
).map(toLink);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function insertLink(
|
|
126
|
+
coderSessionId: string,
|
|
127
|
+
analystSessionId: string,
|
|
128
|
+
reviewedThrough: string | null
|
|
129
|
+
): AnalystLink {
|
|
130
|
+
const id = randomUUID();
|
|
131
|
+
db.prepare(
|
|
132
|
+
`INSERT INTO analyst_links (id, coder_session_id, analyst_session_id,
|
|
133
|
+
reviewed_through, created_at) VALUES (?, ?, ?, ?, ?)`
|
|
134
|
+
).run(id, coderSessionId, analystSessionId, reviewedThrough, Date.now());
|
|
135
|
+
return getLink(id)!;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function setPausedRow(id: string, paused: boolean): void {
|
|
139
|
+
db.prepare(`UPDATE analyst_links SET paused = ? WHERE id = ?`).run(
|
|
140
|
+
paused ? 1 : 0,
|
|
141
|
+
id
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function endLinkRow(id: string): boolean {
|
|
146
|
+
return (
|
|
147
|
+
db
|
|
148
|
+
.prepare(
|
|
149
|
+
`UPDATE analyst_links SET ended_at = ? WHERE id = ? AND ended_at IS NULL`
|
|
150
|
+
)
|
|
151
|
+
.run(Date.now(), id).changes > 0
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Recorded before the digest is sent, so a restart can send it again.
|
|
156
|
+
export function markDigestPending(
|
|
157
|
+
id: string,
|
|
158
|
+
digestId: string,
|
|
159
|
+
through: string,
|
|
160
|
+
signals: DigestSignals
|
|
161
|
+
): void {
|
|
162
|
+
db.prepare(
|
|
163
|
+
`UPDATE analyst_links SET pending_digest_id = ?, pending_through = ?,
|
|
164
|
+
digest_signals = ? WHERE id = ?`
|
|
165
|
+
).run(digestId, through, JSON.stringify(signals), id);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// The analyst answered the digest: its turns count as reviewed.
|
|
169
|
+
export function settleDigest(id: string, digestId: string): boolean {
|
|
170
|
+
return (
|
|
171
|
+
db
|
|
172
|
+
.prepare(
|
|
173
|
+
`UPDATE analyst_links SET reviewed_through = pending_through,
|
|
174
|
+
pending_digest_id = NULL, pending_through = NULL
|
|
175
|
+
WHERE id = ? AND pending_digest_id = ?`
|
|
176
|
+
)
|
|
177
|
+
.run(id, digestId).changes > 0
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// A digest that never arrived: dropped, so the next one covers its turns.
|
|
182
|
+
export function dropPendingDigest(id: string): void {
|
|
183
|
+
db.prepare(
|
|
184
|
+
`UPDATE analyst_links SET pending_digest_id = NULL, pending_through = NULL
|
|
185
|
+
WHERE id = ?`
|
|
186
|
+
).run(id);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function listRecommendations(
|
|
190
|
+
linkId: string,
|
|
191
|
+
limit = 20
|
|
192
|
+
): Recommendation[] {
|
|
193
|
+
return (
|
|
194
|
+
db
|
|
195
|
+
.prepare(
|
|
196
|
+
`SELECT * FROM analyst_recommendations WHERE link_id = ?
|
|
197
|
+
ORDER BY created_at DESC, rowid DESC LIMIT ?`
|
|
198
|
+
)
|
|
199
|
+
.all(linkId, limit) as RecRow[]
|
|
200
|
+
).map(toRec);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export function getRecommendation(id: string): Recommendation | null {
|
|
204
|
+
const r = db
|
|
205
|
+
.prepare(`SELECT * FROM analyst_recommendations WHERE id = ?`)
|
|
206
|
+
.get(id) as RecRow | undefined;
|
|
207
|
+
return r ? toRec(r) : null;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export function recommendationFor(
|
|
211
|
+
linkId: string,
|
|
212
|
+
analystItemId: string
|
|
213
|
+
): Recommendation | null {
|
|
214
|
+
const r = db
|
|
215
|
+
.prepare(
|
|
216
|
+
`SELECT * FROM analyst_recommendations WHERE link_id = ? AND analyst_item_id = ?`
|
|
217
|
+
)
|
|
218
|
+
.get(linkId, analystItemId) as RecRow | undefined;
|
|
219
|
+
return r ? toRec(r) : null;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export interface NewRecommendation {
|
|
223
|
+
linkId: string;
|
|
224
|
+
analystItemId: string;
|
|
225
|
+
coderThrough: string | null;
|
|
226
|
+
verdict: Verdict | null;
|
|
227
|
+
summary: string;
|
|
228
|
+
issues: Issue[];
|
|
229
|
+
signals: DigestSignals | null;
|
|
230
|
+
prompt: string | null;
|
|
231
|
+
state: "open" | "invalid";
|
|
232
|
+
error?: string | null;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Once per analyst reply: a repeat is a no-op. A new one retires the
|
|
236
|
+
// cards still open, so only one is ever actionable.
|
|
237
|
+
export function insertRecommendation(
|
|
238
|
+
r: NewRecommendation
|
|
239
|
+
): Recommendation | null {
|
|
240
|
+
const id = randomUUID();
|
|
241
|
+
const inserted = db.transaction(() => {
|
|
242
|
+
const changes = db
|
|
243
|
+
.prepare(
|
|
244
|
+
`INSERT OR IGNORE INTO analyst_recommendations (id, link_id,
|
|
245
|
+
analyst_item_id, coder_through, verdict, summary, issues, signals,
|
|
246
|
+
prompt, state, error, created_at)
|
|
247
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
248
|
+
)
|
|
249
|
+
.run(
|
|
250
|
+
id,
|
|
251
|
+
r.linkId,
|
|
252
|
+
r.analystItemId,
|
|
253
|
+
r.coderThrough,
|
|
254
|
+
r.verdict,
|
|
255
|
+
r.summary,
|
|
256
|
+
JSON.stringify(r.issues),
|
|
257
|
+
r.signals ? JSON.stringify(r.signals) : null,
|
|
258
|
+
r.prompt,
|
|
259
|
+
r.state,
|
|
260
|
+
r.error ?? null,
|
|
261
|
+
Date.now()
|
|
262
|
+
).changes;
|
|
263
|
+
if (!changes) return false;
|
|
264
|
+
db.prepare(
|
|
265
|
+
`UPDATE analyst_recommendations SET state = 'superseded', decided_at = ?
|
|
266
|
+
WHERE link_id = ? AND id != ? AND state IN ('open', 'invalid')`
|
|
267
|
+
).run(Date.now(), r.linkId, id);
|
|
268
|
+
return true;
|
|
269
|
+
})();
|
|
270
|
+
return inserted ? getRecommendation(id) : null;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Moves an open card to its decision, once: false when it was decided.
|
|
274
|
+
export function decideRecommendation(
|
|
275
|
+
id: string,
|
|
276
|
+
state: "sent" | "dismissed",
|
|
277
|
+
sent?: { text: string; itemId: string; edited: boolean }
|
|
278
|
+
): boolean {
|
|
279
|
+
return (
|
|
280
|
+
db
|
|
281
|
+
.prepare(
|
|
282
|
+
`UPDATE analyst_recommendations SET state = ?, decided_at = ?,
|
|
283
|
+
sent_text = ?, sent_item_id = ?, edited = ?
|
|
284
|
+
WHERE id = ? AND state IN ('open', 'invalid')`
|
|
285
|
+
)
|
|
286
|
+
.run(
|
|
287
|
+
state,
|
|
288
|
+
Date.now(),
|
|
289
|
+
sent?.text ?? null,
|
|
290
|
+
sent?.itemId ?? null,
|
|
291
|
+
sent?.edited ? 1 : 0,
|
|
292
|
+
id
|
|
293
|
+
).changes > 0
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Back to open: a send that failed after the card was claimed.
|
|
298
|
+
export function reopenRecommendation(id: string): void {
|
|
299
|
+
db.prepare(
|
|
300
|
+
`UPDATE analyst_recommendations SET state = 'open', decided_at = NULL,
|
|
301
|
+
sent_text = NULL, sent_item_id = NULL, edited = 0 WHERE id = ?`
|
|
302
|
+
).run(id);
|
|
303
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
// What the analyst says the coding agent should do next.
|
|
4
|
+
export const VERDICTS = ["continue", "done", "blocked", "needs_you"] as const;
|
|
5
|
+
export type Verdict = (typeof VERDICTS)[number];
|
|
6
|
+
|
|
7
|
+
export const RECOMMENDATION_STATES = [
|
|
8
|
+
"open",
|
|
9
|
+
"sent",
|
|
10
|
+
"dismissed",
|
|
11
|
+
"superseded",
|
|
12
|
+
"invalid",
|
|
13
|
+
] as const;
|
|
14
|
+
export type RecommendationState = (typeof RECOMMENDATION_STATES)[number];
|
|
15
|
+
|
|
16
|
+
export const issueSchema = z.object({
|
|
17
|
+
severity: z.enum(["high", "medium", "low"]),
|
|
18
|
+
title: z.string().trim().min(1).max(200),
|
|
19
|
+
detail: z.string().trim().max(2_000).default(""),
|
|
20
|
+
where: z.string().trim().max(300).optional(),
|
|
21
|
+
});
|
|
22
|
+
export type Issue = z.infer<typeof issueSchema>;
|
|
23
|
+
|
|
24
|
+
export const MAX_ISSUES = 8;
|
|
25
|
+
export const MAX_PROMPT = 4_000;
|
|
26
|
+
|
|
27
|
+
// The fenced `agentos-review` block the analyst ends each review with.
|
|
28
|
+
export const reviewSchema = z.object({
|
|
29
|
+
verdict: z.enum(VERDICTS),
|
|
30
|
+
summary: z.string().trim().min(1).max(1_000),
|
|
31
|
+
issues: z.array(issueSchema).max(MAX_ISSUES).default([]),
|
|
32
|
+
prompt: z.string().trim().max(MAX_PROMPT).nullable().optional(),
|
|
33
|
+
});
|
|
34
|
+
export type Review = z.infer<typeof reviewSchema>;
|
|
35
|
+
|
|
36
|
+
// What a batch of the coding agent's turns did, worked out without a model.
|
|
37
|
+
export interface DigestSignals {
|
|
38
|
+
turns: number;
|
|
39
|
+
toolCalls: number;
|
|
40
|
+
failedTools: number;
|
|
41
|
+
// Test, build, typecheck or lint commands, and whether each passed.
|
|
42
|
+
checks: { command: string; passed: boolean }[];
|
|
43
|
+
filesChanged: string[];
|
|
44
|
+
durationMs: number;
|
|
45
|
+
costUsd: number;
|
|
46
|
+
interrupted: boolean;
|
|
47
|
+
errors: number;
|
|
48
|
+
// Turns that ended without a single tool call.
|
|
49
|
+
noToolTurns: number;
|
|
50
|
+
// Fetched web content: a prompt built on it shouldn't be sent unseen.
|
|
51
|
+
readWeb: boolean;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface AnalystLink {
|
|
55
|
+
id: string;
|
|
56
|
+
coderSessionId: string;
|
|
57
|
+
analystSessionId: string;
|
|
58
|
+
paused: boolean;
|
|
59
|
+
// The coding agent's last turn_end the analyst has answered for.
|
|
60
|
+
reviewedThrough: string | null;
|
|
61
|
+
// A digest sent and not yet answered, the turn_end it runs to, and what
|
|
62
|
+
// its turns did.
|
|
63
|
+
pendingDigestId: string | null;
|
|
64
|
+
pendingThrough: string | null;
|
|
65
|
+
digestSignals: DigestSignals | null;
|
|
66
|
+
createdAt: number;
|
|
67
|
+
endedAt: number | null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface Recommendation {
|
|
71
|
+
id: string;
|
|
72
|
+
linkId: string;
|
|
73
|
+
analystItemId: string;
|
|
74
|
+
coderThrough: string | null;
|
|
75
|
+
verdict: Verdict | null;
|
|
76
|
+
summary: string;
|
|
77
|
+
issues: Issue[];
|
|
78
|
+
signals: DigestSignals | null;
|
|
79
|
+
prompt: string | null;
|
|
80
|
+
state: RecommendationState;
|
|
81
|
+
// Why it's invalid.
|
|
82
|
+
error: string | null;
|
|
83
|
+
sentText: string | null;
|
|
84
|
+
sentItemId: string | null;
|
|
85
|
+
edited: boolean;
|
|
86
|
+
createdAt: number;
|
|
87
|
+
decidedAt: number | null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface PaneSession {
|
|
91
|
+
id: string;
|
|
92
|
+
name: string;
|
|
93
|
+
agentType: string;
|
|
94
|
+
model: string | null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// A link as the panes and the native app read it.
|
|
98
|
+
export interface AnalystLinkView extends AnalystLink {
|
|
99
|
+
coder: PaneSession;
|
|
100
|
+
analyst: PaneSession;
|
|
101
|
+
// The coding agent's latest finished turn, to tell a card it has moved on.
|
|
102
|
+
coderLastTurn: string | null;
|
|
103
|
+
recommendations: Recommendation[];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export class AnalystError extends Error {
|
|
107
|
+
constructor(
|
|
108
|
+
message: string,
|
|
109
|
+
readonly status: 400 | 404 | 409 = 400
|
|
110
|
+
) {
|
|
111
|
+
super(message);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export const createLinkSchema = z
|
|
116
|
+
.object({
|
|
117
|
+
coderSessionId: z.string().min(1).optional(),
|
|
118
|
+
newCoder: z
|
|
119
|
+
.object({
|
|
120
|
+
projectId: z.string().min(1),
|
|
121
|
+
agentType: z.string().min(1),
|
|
122
|
+
model: z.string().trim().min(1).optional(),
|
|
123
|
+
prompt: z.string().trim().max(20_000).optional(),
|
|
124
|
+
useWorktree: z.boolean().optional(),
|
|
125
|
+
})
|
|
126
|
+
.optional(),
|
|
127
|
+
// The analyst's model for this pair, "provider/model"; else its role's.
|
|
128
|
+
analystModel: z.string().trim().min(1).max(200).optional(),
|
|
129
|
+
reviewLast: z.boolean().optional(),
|
|
130
|
+
})
|
|
131
|
+
.refine((b) => !!b.coderSessionId !== !!b.newCoder, {
|
|
132
|
+
message: "Give coderSessionId or newCoder, not both",
|
|
133
|
+
});
|
|
134
|
+
export type CreateLinkInput = z.infer<typeof createLinkSchema>;
|
|
135
|
+
|
|
136
|
+
export const patchLinkSchema = z.object({ paused: z.boolean() });
|
|
137
|
+
|
|
138
|
+
export const sendSchema = z.object({
|
|
139
|
+
text: z.string().trim().min(1).max(20_000).optional(),
|
|
140
|
+
});
|
package/lib/bus/condense.ts
CHANGED
|
@@ -102,7 +102,11 @@ function lineOf(i: ChatItem): string | null {
|
|
|
102
102
|
case "compacted":
|
|
103
103
|
return "[conversation compacted]";
|
|
104
104
|
case "turn_end":
|
|
105
|
-
return i.interrupted
|
|
105
|
+
return i.interrupted
|
|
106
|
+
? "[turn stopped]"
|
|
107
|
+
: i.ended
|
|
108
|
+
? `[turn ended early: ${i.ended.reason}]`
|
|
109
|
+
: null;
|
|
106
110
|
default:
|
|
107
111
|
return null;
|
|
108
112
|
}
|
package/lib/bus/index.ts
CHANGED
|
@@ -38,6 +38,7 @@ import {
|
|
|
38
38
|
waitBadges,
|
|
39
39
|
} from "./waits/store";
|
|
40
40
|
import { HUMAN, overRateLimit, wakeLine, type BusMessageView } from "./format";
|
|
41
|
+
import { groupNames } from "../session-groups/store";
|
|
41
42
|
|
|
42
43
|
export * from "./format";
|
|
43
44
|
export {
|
|
@@ -64,6 +65,11 @@ export interface Peer {
|
|
|
64
65
|
// Names of the sessions it waits on, and of those waiting on it.
|
|
65
66
|
waitingOn: string[];
|
|
66
67
|
waitedBy: string[];
|
|
68
|
+
// The user's group it sits in, when it is in one.
|
|
69
|
+
group?: string;
|
|
70
|
+
// The agent role it was started in (lib/models/agent-roles), and its model.
|
|
71
|
+
agentRole?: string;
|
|
72
|
+
model?: string;
|
|
67
73
|
}
|
|
68
74
|
|
|
69
75
|
// A chat has no pane: its worker is the state, and it can always take a
|
|
@@ -99,6 +105,7 @@ export async function listPeers(callerId?: string): Promise<Peer[]> {
|
|
|
99
105
|
const caller = sessionById(callerId);
|
|
100
106
|
const orchestrator = heldOrchestrator(caller);
|
|
101
107
|
const badges = waitBadges();
|
|
108
|
+
const groups = groupNames();
|
|
102
109
|
const listed = await Promise.all(
|
|
103
110
|
sessions.map(async (s, i) => {
|
|
104
111
|
const info = await peerState(s);
|
|
@@ -114,6 +121,12 @@ export async function listPeers(callerId?: string): Promise<Peer[]> {
|
|
|
114
121
|
orchestratorOf: orchestratorOf(s),
|
|
115
122
|
waitingOn: badges.get(s.id)?.on.map((w) => w.name) ?? [],
|
|
116
123
|
waitedBy: badges.get(s.id)?.by.map((w) => w.name) ?? [],
|
|
124
|
+
...(s.agent_role && { agentRole: s.agent_role }),
|
|
125
|
+
...(s.agent_role && s.model && { model: s.model }),
|
|
126
|
+
...(s.session_group_id &&
|
|
127
|
+
groups.has(s.session_group_id) && {
|
|
128
|
+
group: groups.get(s.session_group_id),
|
|
129
|
+
}),
|
|
117
130
|
...(caller?.workspace_id &&
|
|
118
131
|
s.role === "orchestrator" &&
|
|
119
132
|
s.workspace_id === caller.workspace_id && { yours: true }),
|
package/lib/bus/read-scope.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { getProject } from "../projects";
|
|
|
4
4
|
import { getWorkspace } from "../workspaces";
|
|
5
5
|
import { previousNames } from "../session-names";
|
|
6
6
|
import { resolveRef, type Candidate } from "./resolve";
|
|
7
|
+
import { resolveRoleAlias, roleAliasOf } from "./role-alias";
|
|
7
8
|
|
|
8
9
|
export type ReadableSession = Session & { project_name: string | null };
|
|
9
10
|
|
|
@@ -79,6 +80,17 @@ export function resolveReadable(callerId: string, ref: string): Readable {
|
|
|
79
80
|
a.updated_at < b.updated_at ? 1 : -1
|
|
80
81
|
);
|
|
81
82
|
const mine = all.filter((s) => inScope(s, scope));
|
|
83
|
+
const alias = roleAliasOf(ref);
|
|
84
|
+
if (alias) {
|
|
85
|
+
const found = resolveRoleAlias(
|
|
86
|
+
alias,
|
|
87
|
+
mine.filter(isLive),
|
|
88
|
+
all.find((s) => s.id === callerId)
|
|
89
|
+
);
|
|
90
|
+
return found.ok
|
|
91
|
+
? { ok: true, session: found.session }
|
|
92
|
+
: { ok: false, external: found.none, error: found.error };
|
|
93
|
+
}
|
|
82
94
|
let none = "";
|
|
83
95
|
for (const group of [mine.filter(isLive), mine.filter((s) => !isLive(s))]) {
|
|
84
96
|
const r = resolveRef(ref, candidates(group));
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* "@reviewer", "@developer", "@analyst", "@supervisor": the live session in
|
|
3
|
+
* that agent role. The caller's crew first, then its project; a supervisor
|
|
4
|
+
* nobody started is the workspace's orchestrator. More than one match
|
|
5
|
+
* refuses and lists them, like any other name.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Session } from "../db";
|
|
9
|
+
|
|
10
|
+
type RoleSession = Pick<
|
|
11
|
+
Session,
|
|
12
|
+
| "id"
|
|
13
|
+
| "name"
|
|
14
|
+
| "role"
|
|
15
|
+
| "agent_role"
|
|
16
|
+
| "crew_id"
|
|
17
|
+
| "project_id"
|
|
18
|
+
| "workspace_id"
|
|
19
|
+
>;
|
|
20
|
+
import {
|
|
21
|
+
AGENT_ROLES,
|
|
22
|
+
isAgentRole,
|
|
23
|
+
type AgentRole,
|
|
24
|
+
} from "../models/agent-roles";
|
|
25
|
+
|
|
26
|
+
export type RoleAlias<T extends RoleSession = Session> =
|
|
27
|
+
{ ok: true; session: T } | { ok: false; none: boolean; error: string };
|
|
28
|
+
|
|
29
|
+
export const roleAliasOf = (ref: string): AgentRole | null => {
|
|
30
|
+
const m = /^@([a-z]+)$/i.exec(ref.trim());
|
|
31
|
+
const role = m?.[1].toLowerCase();
|
|
32
|
+
return isAgentRole(role) ? role : null;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const label = (s: RoleSession) => `${s.name} (id ${s.id.slice(0, 8)})`;
|
|
36
|
+
|
|
37
|
+
export function resolveRoleAlias<T extends RoleSession>(
|
|
38
|
+
role: AgentRole,
|
|
39
|
+
sessions: T[],
|
|
40
|
+
caller: RoleSession | undefined
|
|
41
|
+
): RoleAlias<T> {
|
|
42
|
+
const others = sessions.filter((s) => s.id !== caller?.id);
|
|
43
|
+
const inRole = others.filter((s) => s.agent_role === role);
|
|
44
|
+
const scopes = [
|
|
45
|
+
caller?.crew_id ? inRole.filter((s) => s.crew_id === caller.crew_id) : [],
|
|
46
|
+
caller?.project_id && caller.project_id !== "uncategorized"
|
|
47
|
+
? inRole.filter((s) => s.project_id === caller.project_id)
|
|
48
|
+
: [],
|
|
49
|
+
];
|
|
50
|
+
for (const found of scopes) {
|
|
51
|
+
if (found.length === 1) return { ok: true, session: found[0] };
|
|
52
|
+
if (found.length > 1)
|
|
53
|
+
return {
|
|
54
|
+
ok: false,
|
|
55
|
+
none: false,
|
|
56
|
+
error: `"@${role}" matches ${found.length} sessions: ${found.map(label).join(", ")}. Use the name or the id`,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
if (role === "supervisor" && caller?.workspace_id) {
|
|
60
|
+
const orchestrator = others.find(
|
|
61
|
+
(s) => s.role === "orchestrator" && s.workspace_id === caller.workspace_id
|
|
62
|
+
);
|
|
63
|
+
if (orchestrator) return { ok: true, session: orchestrator };
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
ok: false,
|
|
67
|
+
none: true,
|
|
68
|
+
error: `No ${role} is running in your crew or project. Roles: ${AGENT_ROLES.map((r) => `@${r}`).join(", ")}. Run: aos peers`,
|
|
69
|
+
};
|
|
70
|
+
}
|
package/lib/bus/targets.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { previousNames } from "../session-names";
|
|
|
9
9
|
import { sessionWorkspace } from "../schedules/store";
|
|
10
10
|
import { heldTarget } from "./held-reply";
|
|
11
11
|
import { resolveRef, type Candidate } from "./resolve";
|
|
12
|
+
import { resolveRoleAlias, roleAliasOf } from "./role-alias";
|
|
12
13
|
|
|
13
14
|
export function liveSessions(): Session[] {
|
|
14
15
|
return db
|
|
@@ -86,6 +87,8 @@ export function tryResolveTarget(
|
|
|
86
87
|
| { ok: true; session: Session; note?: string }
|
|
87
88
|
| { ok: false; none: boolean; error: string } {
|
|
88
89
|
const sessions = liveSessions();
|
|
90
|
+
const alias = roleAliasOf(ref);
|
|
91
|
+
if (alias) return resolveRoleAlias(alias, sessions, sessionById(callerId));
|
|
89
92
|
const own = ownOrchestrator(ref, sessions, callerId);
|
|
90
93
|
if (own) return { ok: true, session: own };
|
|
91
94
|
const r = resolveRef(ref, candidates(sessions));
|