@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.
Files changed (221) hide show
  1. package/CHANGELOG.md +229 -0
  2. package/Dockerfile +7 -2
  3. package/README.md +153 -24
  4. package/package.json +5 -3
  5. package/packages/claude-code/package.json +1 -1
  6. package/packages/claude-code/src/daemon.ts +8 -1
  7. package/packages/claude-code/src/launcher.ts +29 -9
  8. package/packages/claude-code/src/profile.ts +173 -0
  9. package/packages/claude-code/src/sessions.ts +22 -4
  10. package/packages/cli/dist/hub-cli.js +6474 -3470
  11. package/packages/cli/package.json +1 -1
  12. package/packages/cli/src/artifact-jobs-mcp.ts +226 -0
  13. package/packages/cli/src/artifacts-mcp.ts +160 -0
  14. package/packages/cli/src/bootstrap/enrollment/enroll.ts +20 -8
  15. package/packages/cli/src/bootstrap/enrollment/policy.ts +125 -12
  16. package/packages/cli/src/bootstrap/enrollment/status.ts +3 -2
  17. package/packages/cli/src/cli.ts +95 -13
  18. package/packages/cli/src/format.ts +48 -10
  19. package/packages/cli/src/http.ts +1 -0
  20. package/packages/cli/src/intents.ts +23 -0
  21. package/packages/cli/src/login.ts +20 -6
  22. package/packages/cli/src/mcp.ts +380 -9
  23. package/packages/cli/src/messaging.ts +3 -1
  24. package/packages/cli/src/setup/commands.ts +3 -2
  25. package/packages/cli/src/setup/runtime.ts +3 -2
  26. package/packages/cli/src/team-intake-mcp.ts +158 -0
  27. package/packages/cli/src/teams-mcp.ts +403 -0
  28. package/packages/cli/src/watchlist.ts +135 -0
  29. package/packages/codex/package.json +1 -1
  30. package/packages/codex/src/profile.ts +163 -0
  31. package/packages/codex/src/sessions.ts +12 -0
  32. package/packages/codex/src/spoke.ts +19 -1
  33. package/packages/node/package.json +1 -1
  34. package/packages/node/src/config.ts +96 -2
  35. package/packages/node/src/opencode-warm.ts +325 -0
  36. package/packages/node/src/run.ts +27 -1
  37. package/packages/preset/package.json +1 -1
  38. package/packages/preset/src/profile.ts +34 -0
  39. package/packages/preset/src/spoke.ts +4 -0
  40. package/packages/protocol/package.json +1 -1
  41. package/packages/protocol/src/artifacts.ts +142 -0
  42. package/packages/protocol/src/concierge.ts +366 -0
  43. package/packages/protocol/src/contracts.ts +104 -0
  44. package/packages/protocol/src/graph.ts +70 -0
  45. package/packages/protocol/src/hub-workspace.ts +201 -0
  46. package/packages/protocol/src/index.ts +169 -6
  47. package/packages/protocol/src/launch.ts +22 -0
  48. package/packages/protocol/src/profiles.ts +301 -0
  49. package/packages/protocol/src/teams.ts +204 -0
  50. package/packages/runtime-openshell/package.json +1 -1
  51. package/packages/runtime-sdk/package.json +1 -1
  52. package/packages/sdk/package.json +1 -1
  53. package/packages/sdk/src/address.ts +50 -2
  54. package/packages/sdk/src/artifact-jobs.ts +194 -0
  55. package/packages/sdk/src/artifacts.ts +187 -0
  56. package/packages/sdk/src/client.ts +226 -5
  57. package/packages/sdk/src/concierge.ts +88 -0
  58. package/packages/sdk/src/errors.ts +3 -1
  59. package/packages/sdk/src/index.ts +5 -0
  60. package/packages/sdk/src/team-intake.ts +122 -0
  61. package/packages/sdk/src/teams.ts +346 -0
  62. package/packages/sdk/src/types.ts +548 -1
  63. package/packages/spoke-sdk/package.json +1 -1
  64. package/packages/spoke-sdk/src/index.ts +1 -0
  65. package/packages/spoke-sdk/src/profile-apply.ts +245 -0
  66. package/src/central/actions.ts +7 -0
  67. package/src/central/agent/acting-routes.ts +2 -1
  68. package/src/central/agent/acting-switch.ts +2 -1
  69. package/src/central/agent/acting.ts +215 -10
  70. package/src/central/agent/app-tokens.ts +3 -2
  71. package/src/central/agent/audit.ts +17 -0
  72. package/src/central/agent/body.ts +163 -0
  73. package/src/central/agent/decisions.ts +69 -2
  74. package/src/central/agent/delivery.ts +95 -15
  75. package/src/central/agent/directory-routes.ts +37 -7
  76. package/src/central/agent/directory.ts +70 -14
  77. package/src/central/agent/errors.ts +5 -0
  78. package/src/central/agent/events-routes.ts +21 -4
  79. package/src/central/agent/events.ts +11 -3
  80. package/src/central/agent/form-check.ts +85 -0
  81. package/src/central/agent/grant-requests.ts +2 -1
  82. package/src/central/agent/grant-routes.ts +4 -3
  83. package/src/central/agent/grants.ts +29 -2
  84. package/src/central/agent/idempotency.ts +133 -0
  85. package/src/central/agent/intent-routes.ts +116 -0
  86. package/src/central/agent/intents.ts +505 -0
  87. package/src/central/agent/interaction-graph-routes.ts +212 -0
  88. package/src/central/agent/interaction-graph.ts +865 -0
  89. package/src/central/agent/launch-engine.ts +16 -2
  90. package/src/central/agent/launch-routes.ts +6 -1
  91. package/src/central/agent/launch-spec.ts +14 -1
  92. package/src/central/agent/message-routes.ts +2 -1
  93. package/src/central/agent/messages.ts +27 -2
  94. package/src/central/agent/names.ts +12 -2
  95. package/src/central/agent/read-routes.ts +86 -19
  96. package/src/central/agent/routes.ts +176 -5
  97. package/src/central/agent/webhook-routes.ts +2 -1
  98. package/src/central/agent-data.ts +4 -0
  99. package/src/central/artifacts/access.ts +44 -0
  100. package/src/central/artifacts/blob-store.ts +113 -0
  101. package/src/central/artifacts/blob-support.ts +98 -0
  102. package/src/central/artifacts/events.ts +27 -0
  103. package/src/central/artifacts/host-caps.ts +162 -0
  104. package/src/central/artifacts/host.ts +315 -0
  105. package/src/central/artifacts/idempotency.ts +109 -0
  106. package/src/central/artifacts/jobs-routes.ts +773 -0
  107. package/src/central/artifacts/jobs.ts +344 -0
  108. package/src/central/artifacts/maintenance.ts +103 -0
  109. package/src/central/artifacts/routes.ts +360 -0
  110. package/src/central/artifacts/store.ts +776 -0
  111. package/src/central/artifacts/workspaces.ts +31 -0
  112. package/src/central/backend/file-blobs.ts +377 -0
  113. package/src/central/backend/index.ts +12 -3
  114. package/src/central/backend/memory-blobs.ts +116 -0
  115. package/src/central/backend/postgres-blobs.ts +513 -0
  116. package/src/central/backend/postgres-migrate.ts +33 -0
  117. package/src/central/backend/postgres-state.ts +19 -0
  118. package/src/central/backend/types.ts +8 -0
  119. package/src/central/concierge/activity.ts +142 -0
  120. package/src/central/concierge/arm.ts +63 -0
  121. package/src/central/concierge/board-render.ts +190 -0
  122. package/src/central/concierge/board-results.ts +169 -0
  123. package/src/central/concierge/board-routes.ts +339 -0
  124. package/src/central/concierge/board.ts +249 -0
  125. package/src/central/concierge/context.ts +221 -0
  126. package/src/central/concierge/entitlements.ts +13 -0
  127. package/src/central/concierge/guard.ts +120 -0
  128. package/src/central/concierge/inbound.ts +89 -0
  129. package/src/central/concierge/index.ts +84 -0
  130. package/src/central/concierge/notes.ts +41 -0
  131. package/src/central/concierge/notifier.ts +986 -0
  132. package/src/central/concierge/prompt.ts +130 -0
  133. package/src/central/concierge/proposals.ts +262 -0
  134. package/src/central/concierge/push-remap.ts +49 -0
  135. package/src/central/concierge/routes.ts +62 -0
  136. package/src/central/concierge/schedule.ts +113 -0
  137. package/src/central/concierge/service.ts +243 -0
  138. package/src/central/concierge/session-routes.ts +69 -0
  139. package/src/central/concierge/settings.ts +217 -0
  140. package/src/central/concierge/supervisor.ts +740 -0
  141. package/src/central/concierge/tiers.ts +260 -0
  142. package/src/central/concierge/watchlist-routes.ts +277 -0
  143. package/src/central/concierge/watchlist.ts +273 -0
  144. package/src/central/config.ts +7 -1
  145. package/src/central/http.ts +633 -50
  146. package/src/central/mcp-http.ts +10 -21
  147. package/src/central/profiles/apply.ts +333 -0
  148. package/src/central/profiles/content.ts +174 -0
  149. package/src/central/profiles/events.ts +40 -0
  150. package/src/central/profiles/idempotency.ts +14 -0
  151. package/src/central/profiles/import.ts +139 -0
  152. package/src/central/profiles/routes.ts +356 -0
  153. package/src/central/profiles/store.ts +597 -0
  154. package/src/central/profiles/usage-ports.ts +120 -0
  155. package/src/central/profiles/usage.ts +364 -0
  156. package/src/central/push.ts +7 -0
  157. package/src/central/registry.ts +43 -6
  158. package/src/central/store.ts +4 -0
  159. package/src/central/teams/collections.ts +34 -0
  160. package/src/central/teams/discovery.ts +346 -0
  161. package/src/central/teams/draft-edits.ts +46 -0
  162. package/src/central/teams/events.ts +46 -0
  163. package/src/central/teams/intake-routes.ts +100 -0
  164. package/src/central/teams/intake.ts +772 -0
  165. package/src/central/teams/policy.ts +231 -0
  166. package/src/central/teams/routes.ts +516 -0
  167. package/src/central/teams/seats.ts +272 -0
  168. package/src/central/teams/staffing.ts +888 -0
  169. package/src/central/teams/store.ts +667 -0
  170. package/src/central/teams/validation.ts +256 -0
  171. package/src/central/workspaces/board.ts +408 -0
  172. package/src/central/workspaces/checkpoints.ts +316 -0
  173. package/src/central/workspaces/coordinator.ts +841 -0
  174. package/src/central/workspaces/events.ts +85 -0
  175. package/src/central/workspaces/lifecycle.ts +93 -0
  176. package/src/central/workspaces/master.ts +490 -0
  177. package/src/central/workspaces/members.ts +632 -0
  178. package/src/central/workspaces/routes.ts +354 -0
  179. package/src/central/workspaces/sidechats-routes.ts +410 -0
  180. package/src/central/workspaces/sidechats.ts +482 -0
  181. package/src/central/workspaces/store.ts +571 -0
  182. package/src/central/workspaces/supervision.ts +355 -0
  183. package/src/index.ts +97 -15
  184. package/src/spoke/concierge-agent.ts +33 -0
  185. package/src/spoke/deliver.ts +22 -4
  186. package/src/spoke/fork-revert.ts +142 -0
  187. package/src/spoke/hub-rpc.ts +41 -5
  188. package/src/spoke/hub-tool.ts +383 -18
  189. package/src/spoke/listing.ts +47 -4
  190. package/src/spoke/profile-apply.ts +298 -0
  191. package/src/spoke/session-context.ts +176 -0
  192. package/src/spoke/spoke.ts +319 -20
  193. package/src/spoke/timeline.ts +6 -1
  194. package/src/spoke/tracker.ts +23 -1
  195. package/src/spoke/who-teams.ts +81 -0
  196. package/src/tui-messaging.ts +32 -18
  197. package/web/dist/assets/ArtifactPage-BS0gVkfu.js +1 -0
  198. package/web/dist/assets/ArtifactPane-PLYHNs6s.js +1 -0
  199. package/web/dist/assets/ArtifactWorkbench-9eTj1CeW.css +1 -0
  200. package/web/dist/assets/ArtifactWorkbench-LkDyJ5Dv.js +6 -0
  201. package/web/dist/assets/Library-CtxRqTjo.js +2 -0
  202. package/web/dist/assets/Library-DYsFj_5c.css +1 -0
  203. package/web/dist/assets/ProfilesDestination-BYiHKLmc.js +1 -0
  204. package/web/dist/assets/TeamsPage-BwjsAAUC.js +1 -0
  205. package/web/dist/assets/Watchtower-C4swrWVs.js +2 -0
  206. package/web/dist/assets/Watchtower-CTgqHk5g.css +1 -0
  207. package/web/dist/assets/index-DaH5GUdV.js +31 -0
  208. package/web/dist/assets/index-WWuKfWxL.css +1 -0
  209. package/web/dist/assets/live-BUofsQpz.css +1 -0
  210. package/web/dist/assets/live-YyTjE47g.js +5 -0
  211. package/web/dist/assets/pane-layout-CKC4I00k.js +1 -0
  212. package/web/dist/assets/plural-Bspvoqfq.js +1 -0
  213. package/web/dist/assets/renderer2d-CX5j3NcD.js +1 -0
  214. package/web/dist/assets/renderer3d-BXIVOvfi.js +5393 -0
  215. package/web/dist/assets/safe-area-DfPsOdWR.js +1 -0
  216. package/web/dist/assets/source-CvUHTc3O.js +1 -0
  217. package/web/dist/assets/ui-C9Igxzjm.js +1 -0
  218. package/web/dist/index.html +5 -2
  219. package/web/dist/sw.js +22 -2
  220. package/web/dist/assets/index-Dm8OmkaO.css +0 -1
  221. package/web/dist/assets/index-FUJbj_Du.js +0 -20
@@ -0,0 +1,346 @@
1
+ // hub.teams (HWA-37) over /api/agent/teams: assemble and operate persistent teams — definitions (create, list with
2
+ // tag/project/metadata filters, show, draft edits incl. role and policy-edge editing, publish, history and diff,
3
+ // pause/resume, archive/restore), staffing (attach a session, launch a member, replace, unstaff) and status (roster
4
+ // plus intake counts). The intake verbs (submit, intake, intakes, setIntakeStatus) are HWA-36's (team-intake.ts)
5
+ // and part of the same object. `team` is the team's ID (team_…) or its name.
6
+ //
7
+ // Who: users; app tokens with teams:read (reads) or teams:manage (changes) — seats, roster, status and staffing also
8
+ // need sessions:read; staffing by an app or session still needs the user's grants (needs_user otherwise). Session
9
+ // tokens read only the teams they are seated in and never change definitions. Team roles and labels confer no
10
+ // authority. Every change of an existing record sends the revision you read (expectedRevision); creates take an
11
+ // Idempotency-Key. The wire types mirror the hub's records (the SDK imports nothing outside its package; the hub's
12
+ // tests keep them in step).
13
+ import { type TeamIntakeApi, type TeamIntakeTransport, teamIntakeApi } from "./team-intake.ts"
14
+ import type { LaunchSpec, LaunchView } from "./types.ts"
15
+
16
+ interface Signal {
17
+ readonly signal?: AbortSignal
18
+ }
19
+ interface Keyed extends Signal {
20
+ readonly idempotencyKey?: string
21
+ }
22
+
23
+ export type TeamMode = "open" | "restricted"
24
+ export type TeamMessageKind = "msg" | "ask"
25
+
26
+ export interface TeamRole {
27
+ readonly name: string
28
+ readonly label?: string
29
+ readonly description?: string
30
+ /** Holders may contact sessions outside all their teams. */
31
+ readonly externalContact?: boolean
32
+ readonly responsibilities?: readonly string[]
33
+ /** The profile its members should run (staffing reports whether they do; it never pins it). */
34
+ readonly profile?: { readonly profileID: string; readonly version?: number }
35
+ /** Placement preferences for staffing launches (advisory). */
36
+ readonly prefs?: { readonly harness?: string; readonly model?: string; readonly machineID?: string }
37
+ readonly minMembers?: number
38
+ readonly maxMembers?: number
39
+ }
40
+
41
+ /** A communication path: `from` may contact `to` with these kinds (two-way: also back). Restricted mode only. */
42
+ export interface TeamEdge {
43
+ readonly from: string
44
+ readonly to: string
45
+ readonly kinds: readonly TeamMessageKind[]
46
+ readonly direction: "one-way" | "two-way"
47
+ }
48
+
49
+ /** The publishable content of a team. */
50
+ export interface TeamSpec {
51
+ /** Slug used in addresses (`@@role#name`); never changes. */
52
+ readonly name: string
53
+ readonly title: string
54
+ readonly description?: string
55
+ readonly mode: TeamMode
56
+ readonly roles: readonly TeamRole[]
57
+ /** The intake role (e.g. the project manager): receives submitted work and supervises members. */
58
+ readonly entryRole: string
59
+ readonly edges: readonly TeamEdge[]
60
+ readonly tags?: readonly string[]
61
+ readonly metadata?: Readonly<Record<string, string>>
62
+ readonly projects?: readonly string[]
63
+ }
64
+
65
+ /** A team: the published spec plus its record fields. */
66
+ export interface Team extends TeamSpec {
67
+ readonly v: 1
68
+ readonly id: string
69
+ readonly state: "active" | "archived"
70
+ /** Present while paused: intake and new staffing stop; the policy still applies. */
71
+ readonly paused?: true
72
+ /** The published version (1 on create, +1 per publish). */
73
+ readonly version: number
74
+ readonly revision: number
75
+ /** A pending draft (edits are published explicitly). */
76
+ readonly draft?: { readonly baseVersion: number; readonly digest: string; readonly savedAt: number }
77
+ readonly createdAt: number
78
+ readonly publishedAt: number
79
+ readonly updatedAt: number
80
+ }
81
+
82
+ export interface TeamDraft {
83
+ readonly baseVersion: number
84
+ readonly spec: TeamSpec
85
+ readonly digest: string
86
+ readonly savedAt: number
87
+ }
88
+
89
+ export interface TeamVersionEntry {
90
+ readonly version: number
91
+ readonly digest: string
92
+ readonly publishedAt: number
93
+ }
94
+
95
+ export interface TeamVersion extends TeamVersionEntry {
96
+ readonly v: 1
97
+ readonly teamID: string
98
+ readonly spec: TeamSpec
99
+ }
100
+
101
+ /** What changed (names and keys only). Edge keys are `from>to` as drawn. */
102
+ export interface TeamDiff {
103
+ readonly fields: readonly string[]
104
+ readonly roles: { readonly added: readonly string[]; readonly removed: readonly string[]; readonly changed: readonly string[] }
105
+ readonly edges: { readonly added: readonly string[]; readonly removed: readonly string[]; readonly changed: readonly string[] }
106
+ readonly tags: { readonly added: readonly string[]; readonly removed: readonly string[] }
107
+ readonly metadata: { readonly added: readonly string[]; readonly removed: readonly string[]; readonly changed: readonly string[] }
108
+ readonly projects: { readonly added: readonly string[]; readonly removed: readonly string[] }
109
+ }
110
+
111
+ /** A session staffed in a role; a replacement is the slot's next generation. */
112
+ export interface TeamStaffing {
113
+ readonly v: 1
114
+ readonly id: string
115
+ readonly teamID: string
116
+ readonly role: string
117
+ readonly sessionID: string
118
+ readonly generation: number
119
+ readonly since: number
120
+ readonly until?: number
121
+ readonly revision: number
122
+ /** The published version it was staffed under. */
123
+ readonly teamVersion: number
124
+ }
125
+
126
+ export type TeamSessionPresence = "online" | "offline" | "archived" | "deleted" | "unknown"
127
+
128
+ export interface TeamStaffingRequest {
129
+ readonly id: string
130
+ readonly teamID: string
131
+ readonly role: string
132
+ readonly state: "launching" | "staffed" | "failed"
133
+ readonly by: string
134
+ readonly launchID?: string
135
+ readonly replaces?: { readonly staffingID: string; readonly generation: number }
136
+ readonly sessionID?: string
137
+ readonly machineID?: string
138
+ readonly staffingID?: string
139
+ readonly reason?: string
140
+ readonly createdAt: number
141
+ readonly updatedAt: number
142
+ }
143
+
144
+ export interface TeamWorkspaceLink {
145
+ readonly id: string
146
+ readonly state: "active" | "archived" | "missing"
147
+ }
148
+
149
+ export interface TeamRosterSeat {
150
+ readonly staffing: TeamStaffing
151
+ readonly presence: TeamSessionPresence
152
+ readonly memberID?: string
153
+ readonly supervisorMemberID?: string
154
+ readonly profile?: {
155
+ readonly requested: { readonly profileID: string; readonly version?: number }
156
+ readonly assigned?: { readonly profileID: string; readonly version: number }
157
+ readonly status: string
158
+ readonly matches: boolean
159
+ }
160
+ }
161
+
162
+ export interface TeamRosterRole {
163
+ readonly role: string
164
+ readonly entry: boolean
165
+ readonly minMembers: number
166
+ readonly maxMembers: number
167
+ readonly seats: readonly TeamRosterSeat[]
168
+ readonly launching: number
169
+ readonly online: number
170
+ readonly offline: number
171
+ readonly vacant: boolean
172
+ readonly understaffed: boolean
173
+ }
174
+
175
+ export interface TeamRoster {
176
+ readonly teamID: string
177
+ readonly version: number
178
+ readonly state: "active" | "archived"
179
+ readonly paused: boolean
180
+ readonly workspace?: TeamWorkspaceLink
181
+ readonly roles: readonly TeamRosterRole[]
182
+ readonly requests: readonly TeamStaffingRequest[]
183
+ }
184
+
185
+ /** Intake counts (no text): by status, and routed items by progress. Closed items count while retained. */
186
+ export interface TeamIntakeSummary {
187
+ readonly queued: number
188
+ readonly routed: number
189
+ readonly done: number
190
+ readonly cancelled: number
191
+ readonly failed: number
192
+ readonly open: number
193
+ readonly inProgress: number
194
+ readonly blocked: number
195
+ readonly oldestQueuedAt?: number
196
+ }
197
+
198
+ export interface TeamStatus {
199
+ readonly team: Team
200
+ readonly roster: TeamRoster
201
+ readonly intake?: TeamIntakeSummary
202
+ }
203
+
204
+ /** The owner's team settings (users only; app and session tokens are refused). */
205
+ export interface TeamSettings {
206
+ /** On: team members see only the sessions of their teams (non-team sessions are hidden from them). */
207
+ readonly teamIsolation: boolean
208
+ /** 0 while never set; send it back as expectedRevision. */
209
+ readonly revision: number
210
+ readonly updatedAt?: number
211
+ }
212
+
213
+ export interface TeamListFilter extends Signal {
214
+ /** active (default), archived or all. */
215
+ readonly state?: "active" | "archived" | "all"
216
+ /** Teams carrying every one of these tags. */
217
+ readonly tags?: readonly string[]
218
+ readonly project?: string
219
+ /** Teams whose metadata has every one of these pairs. */
220
+ readonly metadata?: Readonly<Record<string, string>>
221
+ readonly paused?: boolean
222
+ readonly limit?: number
223
+ readonly cursor?: string
224
+ }
225
+
226
+ /** Spec fields a draft edit may change (the name never changes; null clears an optional field). */
227
+ export type TeamPatch = { readonly [K in Exclude<keyof TeamSpec, "name">]?: TeamSpec[K] | null }
228
+
229
+ export interface Page<T> {
230
+ readonly items: readonly T[]
231
+ readonly cursor?: string
232
+ }
233
+
234
+ export interface Teams extends TeamIntakeApi {
235
+ create(spec: TeamSpec, options?: Keyed): Promise<{ team: Team; created: boolean }>
236
+ list(filter?: TeamListFilter): Promise<Page<Team>>
237
+ get(team: string, options?: Signal): Promise<{ team: Team; draft?: TeamDraft }>
238
+ /** Saves edits as the draft; with `publish: true` publishes it at once. */
239
+ update(team: string, patch: TeamPatch, expectedRevision: number, options?: Signal & { readonly publish?: boolean }): Promise<{ team: Team; published?: boolean }>
240
+ draft(team: string, options?: Signal): Promise<{ draft: TeamDraft }>
241
+ discardDraft(team: string, expectedRevision: number, options?: Signal): Promise<{ team: Team }>
242
+ /** Adds the role to the draft, or replaces the role of that name. */
243
+ setRole(team: string, role: TeamRole, expectedRevision: number, options?: Signal): Promise<{ team: Team; draft: TeamDraft }>
244
+ /** Removes a role (not the entry role) and its edges from the draft. */
245
+ removeRole(team: string, role: string, expectedRevision: number, options?: Signal): Promise<{ team: Team; draft: TeamDraft }>
246
+ /** Draws (or replaces) the edge from → to in the draft. */
247
+ setEdge(team: string, edge: TeamEdge, expectedRevision: number, options?: Signal): Promise<{ team: Team; draft: TeamDraft }>
248
+ removeEdge(team: string, from: string, to: string, expectedRevision: number, options?: Signal): Promise<{ team: Team; draft: TeamDraft }>
249
+ /** Publishes the draft as the next version (published false: it equalled the published spec). */
250
+ publish(team: string, expectedRevision: number, options?: Signal): Promise<{ team: Team; published: boolean }>
251
+ pause(team: string, expectedRevision: number, options?: Signal): Promise<{ team: Team }>
252
+ resume(team: string, expectedRevision: number, options?: Signal): Promise<{ team: Team }>
253
+ archive(team: string, expectedRevision: number, options?: Signal): Promise<{ team: Team }>
254
+ restore(team: string, expectedRevision: number, options?: Signal): Promise<{ team: Team }>
255
+ versions(team: string, options?: Signal & { readonly limit?: number; readonly cursor?: string }): Promise<Page<TeamVersionEntry>>
256
+ version(team: string, version: number, options?: Signal): Promise<{ version: TeamVersion }>
257
+ /** From a version (default: the published one) to another version or the draft (default). */
258
+ diff(team: string, options?: Signal & { readonly from?: number; readonly to?: number | "draft" }): Promise<{ from: number; to: number | "draft"; diff: TeamDiff }>
259
+ /** Current seats (with ended ones on `ended`). */
260
+ staffing(team: string, options?: Signal & { readonly ended?: boolean }): Promise<{ items: readonly TeamStaffing[] }>
261
+ /** Staffs an existing session in a role (the same role and session again: its seat, created false). */
262
+ staff(team: string, input: { readonly role: string; readonly sessionID: string }, options?: Keyed): Promise<{ staffing: TeamStaffing; workspace?: TeamWorkspaceLink; created: boolean }>
263
+ /** Launches a new member for a role through the launch API; the running session is staffed. */
264
+ launchMember(team: string, input: { readonly role: string; readonly launch: LaunchSpec }, options?: Keyed): Promise<{ request: TeamStaffingRequest; launch: LaunchView; created: boolean }>
265
+ /** Replaces a seat by an existing session or a launch; the old seat ends at once. */
266
+ replace(
267
+ team: string,
268
+ staffingID: string,
269
+ input: { readonly expectedRevision: number } & ({ readonly sessionID: string; readonly launch?: never } | { readonly launch: LaunchSpec; readonly sessionID?: never }),
270
+ options?: Signal,
271
+ ): Promise<{ ended: TeamStaffing; staffing?: TeamStaffing; request?: TeamStaffingRequest; launch?: LaunchView }>
272
+ unstaff(team: string, staffingID: string, expectedRevision: number, options?: Signal): Promise<{ staffing: TeamStaffing }>
273
+ roster(team: string, options?: Signal): Promise<{ roster: TeamRoster }>
274
+ /** The team, its roster and intake counts. */
275
+ status(team: string, options?: Signal): Promise<TeamStatus>
276
+ /** The owner's team settings (GET /api/agent/team-settings; users only). */
277
+ settings(options?: Signal): Promise<{ settings: TeamSettings }>
278
+ /** Changes them under CAS (expectedRevision 0 when never set). */
279
+ setSettings(input: { readonly teamIsolation: boolean }, expectedRevision: number, options?: Signal): Promise<{ settings: TeamSettings }>
280
+ }
281
+
282
+ type Query = Readonly<Record<string, string | number | readonly string[] | undefined>>
283
+
284
+ /** What hub.teams needs from the client (HubClient's request and call); queries may repeat a key (arrays). */
285
+ export interface TeamsTransport extends TeamIntakeTransport {
286
+ request(method: string, pathname: string, options?: { readonly query?: Query; readonly body?: unknown; readonly headers?: Readonly<Record<string, string>>; readonly signal?: AbortSignal }): Promise<Response>
287
+ call<T>(method: string, pathname: string, options?: { readonly query?: Query; readonly body?: unknown; readonly signal?: AbortSignal }): Promise<T>
288
+ }
289
+
290
+ const seg = (value: string) => encodeURIComponent(value)
291
+ const sig = (options: Signal = {}) => (options.signal ? { signal: options.signal } : {})
292
+ const key = (options: Keyed = {}) => (options.idempotencyKey ? { headers: { "idempotency-key": options.idempotencyKey } } : {})
293
+ const base = (team: string) => `/api/agent/teams/${seg(team)}`
294
+
295
+ export function teamsApi(hub: TeamsTransport): Teams {
296
+ const created = async <T>(method: string, pathname: string, body: unknown, options: Keyed = {}): Promise<T & { created: boolean }> => {
297
+ const response = await hub.request(method, pathname, { body, ...key(options), ...sig(options) })
298
+ return { ...((await response.json()) as T), created: response.status === 201 }
299
+ }
300
+ const revised = (verb: string) => (team: string, expectedRevision: number, options?: Signal) => hub.call<{ team: Team }>("POST", `${base(team)}/${verb}`, { body: { expectedRevision }, ...sig(options) })
301
+ return {
302
+ ...teamIntakeApi(hub),
303
+ create: (spec, options) => created("POST", "/api/agent/teams", spec, options),
304
+ list: (filter = {}) =>
305
+ hub.call("GET", "/api/agent/teams", {
306
+ query: {
307
+ state: filter.state,
308
+ tag: filter.tags,
309
+ project: filter.project,
310
+ metadata: filter.metadata ? Object.entries(filter.metadata).map(([k, v]) => `${k}=${v}`) : undefined,
311
+ paused: filter.paused === undefined ? undefined : String(filter.paused),
312
+ limit: filter.limit,
313
+ cursor: filter.cursor,
314
+ },
315
+ ...sig(filter),
316
+ }),
317
+ get: (team, options) => hub.call("GET", base(team), sig(options)),
318
+ update: (team, patch, expectedRevision, options = {}) => hub.call("PATCH", base(team), { body: { ...patch, expectedRevision, ...(options.publish ? { publish: true } : {}) }, ...sig(options) }),
319
+ draft: (team, options) => hub.call("GET", `${base(team)}/draft`, sig(options)),
320
+ discardDraft: (team, expectedRevision, options) => hub.call("DELETE", `${base(team)}/draft`, { query: { expectedRevision }, ...sig(options) }),
321
+ setRole: (team, role, expectedRevision, options) => {
322
+ const { name, ...fields } = role
323
+ return hub.call("PUT", `${base(team)}/draft/roles/${seg(name)}`, { body: { ...fields, expectedRevision }, ...sig(options) })
324
+ },
325
+ removeRole: (team, role, expectedRevision, options) => hub.call("DELETE", `${base(team)}/draft/roles/${seg(role)}`, { query: { expectedRevision }, ...sig(options) }),
326
+ setEdge: (team, edge, expectedRevision, options) => hub.call("PUT", `${base(team)}/draft/edges`, { body: { ...edge, expectedRevision }, ...sig(options) }),
327
+ removeEdge: (team, from, to, expectedRevision, options) => hub.call("DELETE", `${base(team)}/draft/edges`, { query: { from, to, expectedRevision }, ...sig(options) }),
328
+ publish: (team, expectedRevision, options) => hub.call("POST", `${base(team)}/publish`, { body: { expectedRevision }, ...sig(options) }),
329
+ pause: revised("pause"),
330
+ resume: revised("resume"),
331
+ archive: revised("archive"),
332
+ restore: revised("restore"),
333
+ versions: (team, options = {}) => hub.call("GET", `${base(team)}/versions`, { query: { limit: options.limit, cursor: options.cursor }, ...sig(options) }),
334
+ version: (team, version, options) => hub.call("GET", `${base(team)}/versions/${version}`, sig(options)),
335
+ diff: (team, options = {}) => hub.call("GET", `${base(team)}/diff`, { query: { from: options.from, to: options.to }, ...sig(options) }),
336
+ staffing: (team, options = {}) => hub.call("GET", `${base(team)}/staffing`, { query: { ended: options.ended ? 1 : undefined }, ...sig(options) }),
337
+ staff: (team, input, options) => created("PUT", `${base(team)}/staffing`, input, options),
338
+ launchMember: (team, input, options) => created("POST", `${base(team)}/staffing/launch`, input, options),
339
+ replace: (team, staffingID, input, options) => hub.call("POST", `${base(team)}/staffing/${seg(staffingID)}/replace`, { body: input, ...sig(options) }),
340
+ unstaff: (team, staffingID, expectedRevision, options) => hub.call("DELETE", `${base(team)}/staffing/${seg(staffingID)}`, { query: { expectedRevision }, ...sig(options) }),
341
+ roster: (team, options) => hub.call("GET", `${base(team)}/roster`, sig(options)),
342
+ status: (team, options) => hub.call("GET", `${base(team)}/status`, sig(options)),
343
+ settings: (options) => hub.call("GET", "/api/agent/team-settings", sig(options)),
344
+ setSettings: (input, expectedRevision, options) => hub.call("PUT", "/api/agent/team-settings", { body: { teamIsolation: input.teamIsolation, expectedRevision }, ...sig(options) }),
345
+ }
346
+ }