@huynle/hub 0.7.3 → 0.8.0
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/CHANGELOG.md +229 -0
- package/Dockerfile +7 -2
- package/README.md +153 -24
- package/package.json +5 -3
- package/packages/claude-code/package.json +1 -1
- package/packages/claude-code/src/daemon.ts +8 -1
- package/packages/claude-code/src/launcher.ts +29 -9
- package/packages/claude-code/src/profile.ts +173 -0
- package/packages/claude-code/src/sessions.ts +22 -4
- package/packages/cli/dist/hub-cli.js +6474 -3470
- package/packages/cli/package.json +1 -1
- package/packages/cli/src/artifact-jobs-mcp.ts +226 -0
- package/packages/cli/src/artifacts-mcp.ts +160 -0
- package/packages/cli/src/bootstrap/enrollment/enroll.ts +20 -8
- package/packages/cli/src/bootstrap/enrollment/policy.ts +125 -12
- package/packages/cli/src/bootstrap/enrollment/status.ts +3 -2
- package/packages/cli/src/cli.ts +95 -13
- package/packages/cli/src/format.ts +48 -10
- package/packages/cli/src/http.ts +1 -0
- package/packages/cli/src/intents.ts +23 -0
- package/packages/cli/src/login.ts +20 -6
- package/packages/cli/src/mcp.ts +380 -9
- package/packages/cli/src/messaging.ts +3 -1
- package/packages/cli/src/setup/commands.ts +3 -2
- package/packages/cli/src/setup/runtime.ts +3 -2
- package/packages/cli/src/team-intake-mcp.ts +158 -0
- package/packages/cli/src/teams-mcp.ts +403 -0
- package/packages/cli/src/watchlist.ts +135 -0
- package/packages/codex/package.json +1 -1
- package/packages/codex/src/profile.ts +163 -0
- package/packages/codex/src/sessions.ts +12 -0
- package/packages/codex/src/spoke.ts +19 -1
- package/packages/node/package.json +1 -1
- package/packages/node/src/config.ts +96 -2
- package/packages/node/src/opencode-warm.ts +325 -0
- package/packages/node/src/run.ts +27 -1
- package/packages/preset/package.json +1 -1
- package/packages/preset/src/profile.ts +34 -0
- package/packages/preset/src/spoke.ts +4 -0
- package/packages/protocol/package.json +1 -1
- package/packages/protocol/src/artifacts.ts +142 -0
- package/packages/protocol/src/concierge.ts +366 -0
- package/packages/protocol/src/contracts.ts +104 -0
- package/packages/protocol/src/graph.ts +70 -0
- package/packages/protocol/src/hub-workspace.ts +201 -0
- package/packages/protocol/src/index.ts +169 -6
- package/packages/protocol/src/launch.ts +22 -0
- package/packages/protocol/src/profiles.ts +301 -0
- package/packages/protocol/src/teams.ts +204 -0
- package/packages/runtime-openshell/package.json +1 -1
- package/packages/runtime-sdk/package.json +1 -1
- package/packages/sdk/package.json +1 -1
- package/packages/sdk/src/address.ts +50 -2
- package/packages/sdk/src/artifact-jobs.ts +194 -0
- package/packages/sdk/src/artifacts.ts +187 -0
- package/packages/sdk/src/client.ts +226 -5
- package/packages/sdk/src/concierge.ts +88 -0
- package/packages/sdk/src/errors.ts +3 -1
- package/packages/sdk/src/index.ts +5 -0
- package/packages/sdk/src/team-intake.ts +122 -0
- package/packages/sdk/src/teams.ts +346 -0
- package/packages/sdk/src/types.ts +548 -1
- package/packages/spoke-sdk/package.json +1 -1
- package/packages/spoke-sdk/src/index.ts +1 -0
- package/packages/spoke-sdk/src/profile-apply.ts +245 -0
- package/src/central/actions.ts +7 -0
- package/src/central/agent/acting-routes.ts +2 -1
- package/src/central/agent/acting-switch.ts +2 -1
- package/src/central/agent/acting.ts +215 -10
- package/src/central/agent/app-tokens.ts +3 -2
- package/src/central/agent/audit.ts +17 -0
- package/src/central/agent/body.ts +163 -0
- package/src/central/agent/decisions.ts +69 -2
- package/src/central/agent/delivery.ts +95 -15
- package/src/central/agent/directory-routes.ts +37 -7
- package/src/central/agent/directory.ts +70 -14
- package/src/central/agent/errors.ts +5 -0
- package/src/central/agent/events-routes.ts +21 -4
- package/src/central/agent/events.ts +11 -3
- package/src/central/agent/form-check.ts +85 -0
- package/src/central/agent/grant-requests.ts +2 -1
- package/src/central/agent/grant-routes.ts +4 -3
- package/src/central/agent/grants.ts +29 -2
- package/src/central/agent/idempotency.ts +133 -0
- package/src/central/agent/intent-routes.ts +116 -0
- package/src/central/agent/intents.ts +505 -0
- package/src/central/agent/interaction-graph-routes.ts +212 -0
- package/src/central/agent/interaction-graph.ts +865 -0
- package/src/central/agent/launch-engine.ts +16 -2
- package/src/central/agent/launch-routes.ts +6 -1
- package/src/central/agent/launch-spec.ts +14 -1
- package/src/central/agent/message-routes.ts +2 -1
- package/src/central/agent/messages.ts +27 -2
- package/src/central/agent/names.ts +12 -2
- package/src/central/agent/read-routes.ts +86 -19
- package/src/central/agent/routes.ts +176 -5
- package/src/central/agent/webhook-routes.ts +2 -1
- package/src/central/agent-data.ts +4 -0
- package/src/central/artifacts/access.ts +44 -0
- package/src/central/artifacts/blob-store.ts +113 -0
- package/src/central/artifacts/blob-support.ts +98 -0
- package/src/central/artifacts/events.ts +27 -0
- package/src/central/artifacts/host-caps.ts +162 -0
- package/src/central/artifacts/host.ts +315 -0
- package/src/central/artifacts/idempotency.ts +109 -0
- package/src/central/artifacts/jobs-routes.ts +773 -0
- package/src/central/artifacts/jobs.ts +344 -0
- package/src/central/artifacts/maintenance.ts +103 -0
- package/src/central/artifacts/routes.ts +360 -0
- package/src/central/artifacts/store.ts +776 -0
- package/src/central/artifacts/workspaces.ts +31 -0
- package/src/central/backend/file-blobs.ts +377 -0
- package/src/central/backend/index.ts +12 -3
- package/src/central/backend/memory-blobs.ts +116 -0
- package/src/central/backend/postgres-blobs.ts +513 -0
- package/src/central/backend/postgres-migrate.ts +33 -0
- package/src/central/backend/postgres-state.ts +19 -0
- package/src/central/backend/types.ts +8 -0
- package/src/central/concierge/activity.ts +142 -0
- package/src/central/concierge/arm.ts +63 -0
- package/src/central/concierge/board-render.ts +190 -0
- package/src/central/concierge/board-results.ts +169 -0
- package/src/central/concierge/board-routes.ts +339 -0
- package/src/central/concierge/board.ts +249 -0
- package/src/central/concierge/context.ts +221 -0
- package/src/central/concierge/entitlements.ts +13 -0
- package/src/central/concierge/guard.ts +120 -0
- package/src/central/concierge/inbound.ts +89 -0
- package/src/central/concierge/index.ts +84 -0
- package/src/central/concierge/notes.ts +41 -0
- package/src/central/concierge/notifier.ts +986 -0
- package/src/central/concierge/prompt.ts +130 -0
- package/src/central/concierge/proposals.ts +262 -0
- package/src/central/concierge/push-remap.ts +49 -0
- package/src/central/concierge/routes.ts +62 -0
- package/src/central/concierge/schedule.ts +113 -0
- package/src/central/concierge/service.ts +243 -0
- package/src/central/concierge/session-routes.ts +69 -0
- package/src/central/concierge/settings.ts +217 -0
- package/src/central/concierge/supervisor.ts +740 -0
- package/src/central/concierge/tiers.ts +260 -0
- package/src/central/concierge/watchlist-routes.ts +277 -0
- package/src/central/concierge/watchlist.ts +273 -0
- package/src/central/config.ts +7 -1
- package/src/central/http.ts +633 -50
- package/src/central/mcp-http.ts +10 -21
- package/src/central/profiles/apply.ts +333 -0
- package/src/central/profiles/content.ts +174 -0
- package/src/central/profiles/events.ts +40 -0
- package/src/central/profiles/idempotency.ts +14 -0
- package/src/central/profiles/import.ts +139 -0
- package/src/central/profiles/routes.ts +356 -0
- package/src/central/profiles/store.ts +597 -0
- package/src/central/profiles/usage-ports.ts +120 -0
- package/src/central/profiles/usage.ts +364 -0
- package/src/central/push.ts +7 -0
- package/src/central/registry.ts +43 -6
- package/src/central/store.ts +4 -0
- package/src/central/teams/collections.ts +34 -0
- package/src/central/teams/discovery.ts +346 -0
- package/src/central/teams/draft-edits.ts +46 -0
- package/src/central/teams/events.ts +46 -0
- package/src/central/teams/intake-routes.ts +100 -0
- package/src/central/teams/intake.ts +772 -0
- package/src/central/teams/policy.ts +231 -0
- package/src/central/teams/routes.ts +516 -0
- package/src/central/teams/seats.ts +272 -0
- package/src/central/teams/staffing.ts +888 -0
- package/src/central/teams/store.ts +667 -0
- package/src/central/teams/validation.ts +256 -0
- package/src/central/workspaces/board.ts +408 -0
- package/src/central/workspaces/checkpoints.ts +316 -0
- package/src/central/workspaces/coordinator.ts +841 -0
- package/src/central/workspaces/events.ts +85 -0
- package/src/central/workspaces/lifecycle.ts +93 -0
- package/src/central/workspaces/master.ts +490 -0
- package/src/central/workspaces/members.ts +632 -0
- package/src/central/workspaces/routes.ts +354 -0
- package/src/central/workspaces/sidechats-routes.ts +410 -0
- package/src/central/workspaces/sidechats.ts +482 -0
- package/src/central/workspaces/store.ts +571 -0
- package/src/central/workspaces/supervision.ts +355 -0
- package/src/index.ts +97 -15
- package/src/spoke/concierge-agent.ts +33 -0
- package/src/spoke/deliver.ts +22 -4
- package/src/spoke/fork-revert.ts +142 -0
- package/src/spoke/hub-rpc.ts +41 -5
- package/src/spoke/hub-tool.ts +383 -18
- package/src/spoke/listing.ts +47 -4
- package/src/spoke/profile-apply.ts +298 -0
- package/src/spoke/session-context.ts +176 -0
- package/src/spoke/spoke.ts +319 -20
- package/src/spoke/timeline.ts +6 -1
- package/src/spoke/tracker.ts +23 -1
- package/src/spoke/who-teams.ts +81 -0
- package/src/tui-messaging.ts +32 -18
- package/web/dist/assets/ArtifactPage-BS0gVkfu.js +1 -0
- package/web/dist/assets/ArtifactPane-PLYHNs6s.js +1 -0
- package/web/dist/assets/ArtifactWorkbench-9eTj1CeW.css +1 -0
- package/web/dist/assets/ArtifactWorkbench-LkDyJ5Dv.js +6 -0
- package/web/dist/assets/Library-CtxRqTjo.js +2 -0
- package/web/dist/assets/Library-DYsFj_5c.css +1 -0
- package/web/dist/assets/ProfilesDestination-BYiHKLmc.js +1 -0
- package/web/dist/assets/TeamsPage-BwjsAAUC.js +1 -0
- package/web/dist/assets/Watchtower-C4swrWVs.js +2 -0
- package/web/dist/assets/Watchtower-CTgqHk5g.css +1 -0
- package/web/dist/assets/index-DaH5GUdV.js +31 -0
- package/web/dist/assets/index-WWuKfWxL.css +1 -0
- package/web/dist/assets/live-BUofsQpz.css +1 -0
- package/web/dist/assets/live-YyTjE47g.js +5 -0
- package/web/dist/assets/pane-layout-CKC4I00k.js +1 -0
- package/web/dist/assets/plural-Bspvoqfq.js +1 -0
- package/web/dist/assets/renderer2d-CX5j3NcD.js +1 -0
- package/web/dist/assets/renderer3d-BXIVOvfi.js +5393 -0
- package/web/dist/assets/safe-area-DfPsOdWR.js +1 -0
- package/web/dist/assets/source-CvUHTc3O.js +1 -0
- package/web/dist/assets/ui-C9Igxzjm.js +1 -0
- package/web/dist/index.html +5 -2
- package/web/dist/sw.js +22 -2
- package/web/dist/assets/index-Dm8OmkaO.css +0 -1
- package/web/dist/assets/index-FUJbj_Du.js +0 -20
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
// The Watchtower's read API (HWA-31) over interaction-graph.ts, and the adapters that feed it from the hub's
|
|
2
|
+
// stores (one scan per source per request; no per-edge reads):
|
|
3
|
+
//
|
|
4
|
+
// GET /api/agent/graph?since=&until=&machine=&harness=&team=&tag=&project=&session=&workspace=&kind=&state=&limit=
|
|
5
|
+
// GET /api/agent/graph/edges/:id?since=&until=&cursor=&limit= message summaries of one edge (no text)
|
|
6
|
+
// GET /api/agent/graph/nodes/:id?since=&until=&cursor=&limit= message summaries of one node (no text)
|
|
7
|
+
//
|
|
8
|
+
// Users always; session tokens; app tokens with sessions:read (the route class) and messages:read (viewerOf).
|
|
9
|
+
// Full text stays behind GET /api/agent/messages/:id.
|
|
10
|
+
import { LIST_LIMIT_MAX } from "../../protocol"
|
|
11
|
+
import type { Store } from "../store"
|
|
12
|
+
import type { TeamStore } from "../teams/store"
|
|
13
|
+
import type { TeamSeatStore } from "../teams/seats"
|
|
14
|
+
import type { MembershipStore } from "../workspaces/members"
|
|
15
|
+
import type { WorkspaceStore } from "../workspaces/store"
|
|
16
|
+
import type { SupervisionStore } from "../workspaces/supervision"
|
|
17
|
+
import type { AgentAudit } from "./audit"
|
|
18
|
+
import { AUDIT_PAGE_MAX } from "./audit"
|
|
19
|
+
import type { Decisions } from "./decisions"
|
|
20
|
+
import type { Directory } from "./directory"
|
|
21
|
+
import { buildGraph, edgeDetail, type GraphDenial, type GraphLaunch, type GraphMembership, type GraphSeat, type GraphSources, type GraphTeam, nodeDetail, parseGraphQuery, viewerOf } from "./interaction-graph"
|
|
22
|
+
import type { Messages } from "./messages"
|
|
23
|
+
import type { ReadRegistry } from "./read-routes"
|
|
24
|
+
import { AGENT_PREFIX, type AgentRoute } from "./routes"
|
|
25
|
+
|
|
26
|
+
/** Pages read from a paged store list before giving up (bounded: the stores cap their records). */
|
|
27
|
+
const PAGES_MAX = 50
|
|
28
|
+
|
|
29
|
+
export interface GraphRouteDeps {
|
|
30
|
+
readonly store: Pick<Store, "machines" | "agentData">
|
|
31
|
+
readonly registry: Pick<ReadRegistry, "catalog" | "sessionTenant">
|
|
32
|
+
readonly directory: Pick<Directory, "who">
|
|
33
|
+
readonly messages: Pick<Messages, "all" | "retention">
|
|
34
|
+
readonly decisions?: Pick<Decisions, "list">
|
|
35
|
+
readonly launches?: { list(userID: string): Promise<readonly GraphLaunch[]> }
|
|
36
|
+
readonly teams?: { readonly teams: Pick<TeamStore, "list">; readonly seats: Pick<TeamSeatStore, "list"> }
|
|
37
|
+
readonly workspaces?: { readonly workspaces: Pick<WorkspaceStore, "list">; readonly members: Pick<MembershipStore, "members">; readonly supervision: Pick<SupervisionStore, "graph"> }
|
|
38
|
+
readonly audit?: Pick<AgentAudit, "list">
|
|
39
|
+
readonly now?: () => number
|
|
40
|
+
/** (CON-04) The user's Concierge sessions: their nodes carry `concierge: true`. */
|
|
41
|
+
readonly concierge?: (userID: string) => Promise<readonly string[]>
|
|
42
|
+
/** (CON-09a) The user's current Concierge session: labelled @@concierge; the others "@@concierge (previous)", ended. */
|
|
43
|
+
readonly conciergeCurrent?: (userID: string) => Promise<string | undefined>
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface ConciergeMarks {
|
|
47
|
+
readonly node: <N extends { id: string; kind: string }>(node: N) => N
|
|
48
|
+
/** (CON-10) Previous (retired) Concierge sessions: ended for good. */
|
|
49
|
+
readonly previous: ReadonlySet<string>
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Moves undelivered counts (pending, queued) to "dropped": they never reach an ended session. */
|
|
53
|
+
function dropUndelivered<D extends Partial<Record<string, number>>>(delivery: D | undefined): D | undefined {
|
|
54
|
+
if (!delivery) return delivery
|
|
55
|
+
const { pending = 0, queued = 0, ...rest } = delivery as Record<string, number>
|
|
56
|
+
const dropped = (rest.dropped ?? 0) + pending + queued
|
|
57
|
+
return (dropped ? { ...rest, dropped } : rest) as unknown as D
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* (CON-09a) How Watchtower shows a Concierge session: no auto @name, previous ones ended (not idle sessions).
|
|
62
|
+
* (CON-10) An ended one has no "last seen" (its spoke may still report it) and is not online, and messages to it
|
|
63
|
+
* that never reached it (queued, pending) are dropped, never waiting.
|
|
64
|
+
*/
|
|
65
|
+
async function conciergeMarker(deps: GraphRouteDeps, userID: string): Promise<ConciergeMarks> {
|
|
66
|
+
const concierge = new Set(deps.concierge ? await deps.concierge(userID) : [])
|
|
67
|
+
if (!concierge.size) return { node: (node) => node, previous: new Set() }
|
|
68
|
+
const current = deps.conciergeCurrent ? await deps.conciergeCurrent(userID) : undefined
|
|
69
|
+
const previous = new Set(deps.conciergeCurrent === undefined ? [] : [...concierge].filter((id) => id !== current))
|
|
70
|
+
return {
|
|
71
|
+
previous,
|
|
72
|
+
node: (node) => {
|
|
73
|
+
if (node.kind !== "session" || !concierge.has(node.id)) return node
|
|
74
|
+
if (deps.conciergeCurrent === undefined) return { ...node, concierge: true as const }
|
|
75
|
+
if (node.id === current) return { ...node, concierge: true as const, label: "@@concierge" }
|
|
76
|
+
const { lastSeen: _lastSeen, ...rest } = node as typeof node & { lastSeen?: number }
|
|
77
|
+
return { ...rest, concierge: true as const, label: "@@concierge (previous)", status: "ended", online: false } as unknown as typeof node
|
|
78
|
+
},
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** (CON-10) Edges into an ended Concierge session: their undelivered messages are dropped. */
|
|
83
|
+
const markEdge = (previous: ReadonlySet<string>) => <E extends { to: string; delivery?: Partial<Record<string, number>> }>(edge: E): E =>
|
|
84
|
+
previous.has(edge.to) && edge.delivery ? { ...edge, delivery: dropUndelivered(edge.delivery) } : edge
|
|
85
|
+
|
|
86
|
+
/** (CON-10) A message to an ended Concierge session that never reached it reads "dropped". */
|
|
87
|
+
const markMessage = (previous: ReadonlySet<string>) => <M extends { to: string; delivery?: string }>(m: M): M =>
|
|
88
|
+
previous.has(m.to) && (m.delivery === "pending" || m.delivery === "queued") ? { ...m, delivery: "dropped" } : m
|
|
89
|
+
|
|
90
|
+
/** Every page of a cursor-paged list. */
|
|
91
|
+
async function allPages<T>(list: (cursor?: string) => Promise<{ readonly items: readonly T[]; readonly cursor?: string }>): Promise<T[]> {
|
|
92
|
+
const out: T[] = []
|
|
93
|
+
let cursor: string | undefined
|
|
94
|
+
for (let i = 0; i < PAGES_MAX; i++) {
|
|
95
|
+
const page = await list(cursor)
|
|
96
|
+
out.push(...page.items)
|
|
97
|
+
if (!page.cursor) break
|
|
98
|
+
cursor = page.cursor
|
|
99
|
+
}
|
|
100
|
+
return out
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** The projection's sources over the hub's stores. */
|
|
104
|
+
export function graphSources(deps: GraphRouteDeps): GraphSources {
|
|
105
|
+
const { teams, workspaces, audit, launches } = deps
|
|
106
|
+
return {
|
|
107
|
+
now: deps.now ?? Date.now,
|
|
108
|
+
machines: (userID) => deps.store.machines().filter((m) => !m.revoked && m.ownerID === userID),
|
|
109
|
+
registry: deps.registry,
|
|
110
|
+
sessions: (userID) => deps.store.agentData(userID).sessions ?? [],
|
|
111
|
+
directory: deps.directory,
|
|
112
|
+
messages: deps.messages,
|
|
113
|
+
...(deps.decisions ? { decisions: deps.decisions } : {}),
|
|
114
|
+
...(launches ? { launches: (userID: string) => launches.list(userID) } : {}),
|
|
115
|
+
...(teams
|
|
116
|
+
? {
|
|
117
|
+
teams: async (userID: string) => {
|
|
118
|
+
const list = (await allPages((cursor) => teams.teams.list(userID, { state: "active", limit: LIST_LIMIT_MAX, ...(cursor ? { cursor } : {}) }))) as GraphTeam[]
|
|
119
|
+
const seats: GraphSeat[] = []
|
|
120
|
+
for (const team of list) {
|
|
121
|
+
try {
|
|
122
|
+
for (const seat of await teams.seats.list(userID, team.id, { current: true })) seats.push({ teamID: seat.teamID, role: seat.role, sessionID: seat.sessionID })
|
|
123
|
+
} catch {
|
|
124
|
+
// A team this build cannot read shows without seats.
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return { teams: list, seats }
|
|
128
|
+
},
|
|
129
|
+
}
|
|
130
|
+
: {}),
|
|
131
|
+
...(workspaces
|
|
132
|
+
? {
|
|
133
|
+
supervision: async (userID: string) => {
|
|
134
|
+
const out: GraphMembership[] = []
|
|
135
|
+
for (const workspace of await allPages((cursor) => workspaces.workspaces.list(userID, { state: "active", limit: LIST_LIMIT_MAX, ...(cursor ? { cursor } : {}) }))) {
|
|
136
|
+
const [members, graph] = await Promise.all([workspaces.members.members(userID, workspace.id), workspaces.supervision.graph(userID, workspace.id)])
|
|
137
|
+
const sessionOf = new Map(members.filter((m) => m.current).map((m) => [m.member.id, m.member.sessionID]))
|
|
138
|
+
const supervisorOf = new Map<string, string>()
|
|
139
|
+
for (const { link, member, supervisor } of graph.links) {
|
|
140
|
+
const sessionID = sessionOf.get(link.supervisorMemberID)
|
|
141
|
+
if (member === "current" && supervisor === "current" && sessionID !== undefined) supervisorOf.set(link.memberID, sessionID)
|
|
142
|
+
}
|
|
143
|
+
for (const [memberID, sessionID] of sessionOf) {
|
|
144
|
+
const supervisorSessionID = supervisorOf.get(memberID)
|
|
145
|
+
out.push({ workspaceID: workspace.id, sessionID, ...(supervisorSessionID !== undefined ? { supervisorSessionID } : {}) })
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return out
|
|
149
|
+
},
|
|
150
|
+
}
|
|
151
|
+
: {}),
|
|
152
|
+
...(audit
|
|
153
|
+
? {
|
|
154
|
+
denials: async (userID: string) => {
|
|
155
|
+
const out: GraphDenial[] = []
|
|
156
|
+
let before: number | undefined
|
|
157
|
+
for (let i = 0; i < PAGES_MAX; i++) {
|
|
158
|
+
const page = await audit.list(userID, { limit: AUDIT_PAGE_MAX, ...(before !== undefined ? { before } : {}) })
|
|
159
|
+
for (const entry of page.entries) {
|
|
160
|
+
if (entry.verb !== "team.denied" || entry.actor.kind !== "session") continue
|
|
161
|
+
out.push({ at: entry.at, senderID: entry.actor.sessionID, ...(entry.target.sessionID !== undefined ? { recipientID: entry.target.sessionID } : {}) })
|
|
162
|
+
}
|
|
163
|
+
if (page.next === undefined) break
|
|
164
|
+
before = page.next
|
|
165
|
+
}
|
|
166
|
+
return out
|
|
167
|
+
},
|
|
168
|
+
}
|
|
169
|
+
: {}),
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const NO_STORE = { "cache-control": "no-store" }
|
|
174
|
+
const ok = (body: unknown) => Response.json(body, { headers: NO_STORE })
|
|
175
|
+
const READ = { session: true, scope: "sessions:read" } as const
|
|
176
|
+
|
|
177
|
+
export function graphRoutes(deps: GraphRouteDeps): AgentRoute[] {
|
|
178
|
+
const sources = graphSources(deps)
|
|
179
|
+
return [
|
|
180
|
+
{
|
|
181
|
+
method: "GET",
|
|
182
|
+
path: `${AGENT_PREFIX}/graph`,
|
|
183
|
+
allow: READ,
|
|
184
|
+
handle: async ({ user, actor, url }) => {
|
|
185
|
+
const viewer = viewerOf(actor)
|
|
186
|
+
const graph = await buildGraph(sources, user.id, viewer, parseGraphQuery(url.searchParams, sources.now()))
|
|
187
|
+
const mark = await conciergeMarker(deps, user.id)
|
|
188
|
+
return ok({ ...graph, nodes: graph.nodes.map(mark.node), edges: graph.edges.map(markEdge(mark.previous)) })
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
method: "GET",
|
|
193
|
+
path: `${AGENT_PREFIX}/graph/edges/:id`,
|
|
194
|
+
allow: READ,
|
|
195
|
+
handle: async ({ user, actor, url, params }) => {
|
|
196
|
+
const detail = await edgeDetail(sources, user.id, viewerOf(actor), params.id!, url.searchParams, sources.now())
|
|
197
|
+
const { previous } = await conciergeMarker(deps, user.id)
|
|
198
|
+
return ok({ ...detail, edge: markEdge(previous)(detail.edge), messages: detail.messages.map(markMessage(previous)) })
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
method: "GET",
|
|
203
|
+
path: `${AGENT_PREFIX}/graph/nodes/:id`,
|
|
204
|
+
allow: READ,
|
|
205
|
+
handle: async ({ user, actor, url, params }) => {
|
|
206
|
+
const detail = await nodeDetail(sources, user.id, viewerOf(actor), params.id!, url.searchParams, sources.now())
|
|
207
|
+
const mark = await conciergeMarker(deps, user.id)
|
|
208
|
+
return ok({ ...detail, node: mark.node(detail.node), messages: detail.messages.map(markMessage(mark.previous)) })
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
]
|
|
212
|
+
}
|