@huynle/hub 0.7.4 → 0.8.1

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 (241) hide show
  1. package/CHANGELOG.md +430 -1
  2. package/Dockerfile +7 -2
  3. package/README.md +230 -32
  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/paths.ts +3 -3
  9. package/packages/claude-code/src/profile.ts +173 -0
  10. package/packages/claude-code/src/sessions.ts +22 -4
  11. package/packages/cli/dist/hub-cli.js +6871 -3460
  12. package/packages/cli/package.json +1 -1
  13. package/packages/cli/src/artifact-jobs-mcp.ts +226 -0
  14. package/packages/cli/src/artifacts-mcp.ts +160 -0
  15. package/packages/cli/src/bootstrap/adapters/node-services/render.ts +2 -2
  16. package/packages/cli/src/bootstrap/discovery/host.ts +15 -1
  17. package/packages/cli/src/bootstrap/discovery/probe.ts +1 -1
  18. package/packages/cli/src/bootstrap/enrollment/enroll.ts +21 -9
  19. package/packages/cli/src/bootstrap/enrollment/policy.ts +138 -12
  20. package/packages/cli/src/bootstrap/enrollment/status.ts +3 -2
  21. package/packages/cli/src/cli.ts +96 -14
  22. package/packages/cli/src/format.ts +48 -10
  23. package/packages/cli/src/http.ts +2 -0
  24. package/packages/cli/src/intents.ts +23 -0
  25. package/packages/cli/src/launches.ts +9 -3
  26. package/packages/cli/src/login.ts +20 -6
  27. package/packages/cli/src/mcp.ts +385 -10
  28. package/packages/cli/src/messaging.ts +7 -3
  29. package/packages/cli/src/profiles-mcp.ts +301 -0
  30. package/packages/cli/src/setup/commands.ts +23 -4
  31. package/packages/cli/src/setup/runtime.ts +3 -2
  32. package/packages/cli/src/spoke-services.ts +1 -1
  33. package/packages/cli/src/team-intake-mcp.ts +158 -0
  34. package/packages/cli/src/teams-mcp.ts +460 -0
  35. package/packages/cli/src/watchlist.ts +135 -0
  36. package/packages/codex/package.json +1 -1
  37. package/packages/codex/src/paths.ts +3 -3
  38. package/packages/codex/src/profile.ts +163 -0
  39. package/packages/codex/src/sessions.ts +12 -0
  40. package/packages/codex/src/spoke.ts +19 -1
  41. package/packages/node/package.json +1 -1
  42. package/packages/node/src/config.ts +104 -2
  43. package/packages/node/src/opencode-warm.ts +344 -0
  44. package/packages/node/src/run.ts +28 -1
  45. package/packages/preset/package.json +1 -1
  46. package/packages/preset/src/profile.ts +34 -0
  47. package/packages/preset/src/spoke.ts +4 -0
  48. package/packages/protocol/package.json +1 -1
  49. package/packages/protocol/src/artifacts.ts +142 -0
  50. package/packages/protocol/src/concierge.ts +366 -0
  51. package/packages/protocol/src/contracts.ts +104 -0
  52. package/packages/protocol/src/graph.ts +70 -0
  53. package/packages/protocol/src/hub-workspace.ts +201 -0
  54. package/packages/protocol/src/index.ts +175 -6
  55. package/packages/protocol/src/launch.ts +36 -0
  56. package/packages/protocol/src/profiles.ts +301 -0
  57. package/packages/protocol/src/teams.ts +204 -0
  58. package/packages/runtime-openshell/package.json +1 -1
  59. package/packages/runtime-sdk/package.json +1 -1
  60. package/packages/sdk/package.json +1 -1
  61. package/packages/sdk/src/address.ts +50 -2
  62. package/packages/sdk/src/artifact-jobs.ts +194 -0
  63. package/packages/sdk/src/artifacts.ts +187 -0
  64. package/packages/sdk/src/client.ts +226 -5
  65. package/packages/sdk/src/concierge.ts +88 -0
  66. package/packages/sdk/src/errors.ts +4 -1
  67. package/packages/sdk/src/index.ts +6 -0
  68. package/packages/sdk/src/team-intake.ts +122 -0
  69. package/packages/sdk/src/teams.ts +346 -0
  70. package/packages/sdk/src/types.ts +568 -1
  71. package/packages/sdk/src/wake.ts +20 -0
  72. package/packages/spoke-sdk/package.json +1 -1
  73. package/packages/spoke-sdk/src/index.ts +1 -0
  74. package/packages/spoke-sdk/src/profile-apply.ts +245 -0
  75. package/src/central/actions.ts +7 -0
  76. package/src/central/agent/acting-routes.ts +2 -1
  77. package/src/central/agent/acting-switch.ts +2 -1
  78. package/src/central/agent/acting.ts +218 -10
  79. package/src/central/agent/app-tokens.ts +3 -2
  80. package/src/central/agent/audit.ts +18 -1
  81. package/src/central/agent/body.ts +163 -0
  82. package/src/central/agent/decisions.ts +125 -2
  83. package/src/central/agent/delivery.ts +194 -40
  84. package/src/central/agent/directory-routes.ts +37 -7
  85. package/src/central/agent/directory.ts +70 -14
  86. package/src/central/agent/errors.ts +7 -0
  87. package/src/central/agent/escalation.ts +10 -2
  88. package/src/central/agent/events-routes.ts +21 -4
  89. package/src/central/agent/events.ts +18 -4
  90. package/src/central/agent/form-check.ts +85 -0
  91. package/src/central/agent/grant-requests.ts +2 -1
  92. package/src/central/agent/grant-routes.ts +4 -3
  93. package/src/central/agent/grants.ts +29 -2
  94. package/src/central/agent/idempotency.ts +133 -0
  95. package/src/central/agent/intent-routes.ts +116 -0
  96. package/src/central/agent/intents.ts +505 -0
  97. package/src/central/agent/interaction-graph-routes.ts +212 -0
  98. package/src/central/agent/interaction-graph.ts +865 -0
  99. package/src/central/agent/launch-engine.ts +189 -32
  100. package/src/central/agent/launch-routes.ts +26 -10
  101. package/src/central/agent/launch-spec.ts +40 -3
  102. package/src/central/agent/launch-store.ts +55 -2
  103. package/src/central/agent/message-limits.ts +89 -11
  104. package/src/central/agent/message-routes.ts +10 -4
  105. package/src/central/agent/messages.ts +28 -2
  106. package/src/central/agent/names.ts +12 -2
  107. package/src/central/agent/placement.ts +93 -18
  108. package/src/central/agent/read-routes.ts +89 -19
  109. package/src/central/agent/routes.ts +178 -5
  110. package/src/central/agent/webhook-routes.ts +2 -1
  111. package/src/central/agent/webhooks.ts +17 -2
  112. package/src/central/agent-data.ts +4 -0
  113. package/src/central/artifacts/access.ts +44 -0
  114. package/src/central/artifacts/blob-store.ts +113 -0
  115. package/src/central/artifacts/blob-support.ts +98 -0
  116. package/src/central/artifacts/events.ts +27 -0
  117. package/src/central/artifacts/host-caps.ts +162 -0
  118. package/src/central/artifacts/host.ts +315 -0
  119. package/src/central/artifacts/idempotency.ts +109 -0
  120. package/src/central/artifacts/jobs-routes.ts +776 -0
  121. package/src/central/artifacts/jobs.ts +344 -0
  122. package/src/central/artifacts/maintenance.ts +103 -0
  123. package/src/central/artifacts/routes.ts +360 -0
  124. package/src/central/artifacts/store.ts +776 -0
  125. package/src/central/artifacts/workspaces.ts +31 -0
  126. package/src/central/backend/file-blobs.ts +377 -0
  127. package/src/central/backend/index.ts +12 -3
  128. package/src/central/backend/memory-blobs.ts +116 -0
  129. package/src/central/backend/postgres-blobs.ts +513 -0
  130. package/src/central/backend/postgres-migrate.ts +33 -0
  131. package/src/central/backend/postgres-state.ts +19 -0
  132. package/src/central/backend/types.ts +8 -0
  133. package/src/central/concierge/activity.ts +142 -0
  134. package/src/central/concierge/arm.ts +63 -0
  135. package/src/central/concierge/board-render.ts +190 -0
  136. package/src/central/concierge/board-results.ts +169 -0
  137. package/src/central/concierge/board-routes.ts +350 -0
  138. package/src/central/concierge/board.ts +249 -0
  139. package/src/central/concierge/context.ts +221 -0
  140. package/src/central/concierge/entitlements.ts +13 -0
  141. package/src/central/concierge/guard.ts +120 -0
  142. package/src/central/concierge/inbound.ts +86 -0
  143. package/src/central/concierge/index.ts +84 -0
  144. package/src/central/concierge/notes.ts +41 -0
  145. package/src/central/concierge/notifier.ts +986 -0
  146. package/src/central/concierge/prompt.ts +131 -0
  147. package/src/central/concierge/proposals.ts +268 -0
  148. package/src/central/concierge/push-remap.ts +49 -0
  149. package/src/central/concierge/routes.ts +122 -0
  150. package/src/central/concierge/schedule.ts +113 -0
  151. package/src/central/concierge/service.ts +243 -0
  152. package/src/central/concierge/session-routes.ts +125 -0
  153. package/src/central/concierge/settings.ts +217 -0
  154. package/src/central/concierge/supervisor.ts +767 -0
  155. package/src/central/concierge/tiers.ts +260 -0
  156. package/src/central/concierge/watchlist-routes.ts +279 -0
  157. package/src/central/concierge/watchlist.ts +273 -0
  158. package/src/central/config.ts +28 -6
  159. package/src/central/http.ts +660 -54
  160. package/src/central/main.ts +1 -1
  161. package/src/central/mcp-http.ts +10 -21
  162. package/src/central/profiles/apply.ts +333 -0
  163. package/src/central/profiles/content.ts +174 -0
  164. package/src/central/profiles/events.ts +40 -0
  165. package/src/central/profiles/idempotency.ts +14 -0
  166. package/src/central/profiles/import.ts +139 -0
  167. package/src/central/profiles/routes.ts +356 -0
  168. package/src/central/profiles/store.ts +597 -0
  169. package/src/central/profiles/usage-ports.ts +120 -0
  170. package/src/central/profiles/usage.ts +364 -0
  171. package/src/central/push.ts +7 -0
  172. package/src/central/registry.ts +69 -7
  173. package/src/central/store.ts +4 -0
  174. package/src/central/teams/collections.ts +34 -0
  175. package/src/central/teams/discovery.ts +346 -0
  176. package/src/central/teams/draft-edits.ts +46 -0
  177. package/src/central/teams/events.ts +46 -0
  178. package/src/central/teams/intake-routes.ts +100 -0
  179. package/src/central/teams/intake.ts +786 -0
  180. package/src/central/teams/policy.ts +231 -0
  181. package/src/central/teams/routes.ts +516 -0
  182. package/src/central/teams/seats.ts +272 -0
  183. package/src/central/teams/staffing.ts +910 -0
  184. package/src/central/teams/store.ts +667 -0
  185. package/src/central/teams/validation.ts +256 -0
  186. package/src/central/workspaces/board.ts +408 -0
  187. package/src/central/workspaces/checkpoints.ts +316 -0
  188. package/src/central/workspaces/coordinator.ts +841 -0
  189. package/src/central/workspaces/events.ts +85 -0
  190. package/src/central/workspaces/lifecycle.ts +93 -0
  191. package/src/central/workspaces/master.ts +490 -0
  192. package/src/central/workspaces/members.ts +632 -0
  193. package/src/central/workspaces/routes.ts +354 -0
  194. package/src/central/workspaces/sidechats-routes.ts +410 -0
  195. package/src/central/workspaces/sidechats.ts +482 -0
  196. package/src/central/workspaces/store.ts +571 -0
  197. package/src/central/workspaces/supervision.ts +355 -0
  198. package/src/index.ts +126 -23
  199. package/src/migrate.ts +109 -3
  200. package/src/spoke/concierge-agent.ts +33 -0
  201. package/src/spoke/deliver.ts +22 -4
  202. package/src/spoke/env-inject.ts +8 -1
  203. package/src/spoke/fork-revert.ts +142 -0
  204. package/src/spoke/hub-rpc.ts +41 -5
  205. package/src/spoke/hub-tool.ts +404 -25
  206. package/src/spoke/launchers.ts +6 -4
  207. package/src/spoke/listing.ts +65 -10
  208. package/src/spoke/localhub.ts +14 -4
  209. package/src/spoke/locations.ts +40 -0
  210. package/src/spoke/profile-apply.ts +298 -0
  211. package/src/spoke/session-context.ts +256 -0
  212. package/src/spoke/spoke.ts +330 -23
  213. package/src/spoke/timeline.ts +6 -1
  214. package/src/spoke/tracker.ts +23 -1
  215. package/src/spoke/who-teams.ts +81 -0
  216. package/src/tui-messaging.ts +32 -18
  217. package/web/dist/assets/ArtifactPage-mNzjlP_U.js +1 -0
  218. package/web/dist/assets/ArtifactPane-CPQcuX7c.js +1 -0
  219. package/web/dist/assets/ArtifactWorkbench-9eTj1CeW.css +1 -0
  220. package/web/dist/assets/ArtifactWorkbench-NoFC5Sb2.js +6 -0
  221. package/web/dist/assets/Library-DYsFj_5c.css +1 -0
  222. package/web/dist/assets/Library-LXHsP5Fw.js +2 -0
  223. package/web/dist/assets/ProfilesDestination-DhVVcAep.js +1 -0
  224. package/web/dist/assets/TeamsPage-DBrO5hoL.js +1 -0
  225. package/web/dist/assets/Watchtower-CTgqHk5g.css +1 -0
  226. package/web/dist/assets/Watchtower-DxaJYQp_.js +2 -0
  227. package/web/dist/assets/index-BabrNDB3.css +1 -0
  228. package/web/dist/assets/index-lIOfSYck.js +34 -0
  229. package/web/dist/assets/live-BDq0AABg.js +5 -0
  230. package/web/dist/assets/live-BUofsQpz.css +1 -0
  231. package/web/dist/assets/pane-layout-DWXfDU-1.js +1 -0
  232. package/web/dist/assets/plural-Bspvoqfq.js +1 -0
  233. package/web/dist/assets/renderer2d-DAr4IHM8.js +1 -0
  234. package/web/dist/assets/renderer3d-DWZj595u.js +5393 -0
  235. package/web/dist/assets/safe-area-JUb6OeI3.js +1 -0
  236. package/web/dist/assets/source-CDU04kYw.js +1 -0
  237. package/web/dist/assets/ui-Dh1-78Cl.js +3 -0
  238. package/web/dist/index.html +5 -2
  239. package/web/dist/sw.js +22 -2
  240. package/web/dist/assets/index-Dm8OmkaO.css +0 -1
  241. package/web/dist/assets/index-FUJbj_Du.js +0 -20
@@ -2,6 +2,9 @@
2
2
  // HubError (the envelope) or HubTransportError (errors.ts). Typed verbs are added on top of call()/request().
3
3
  import { envelopeOf, HubError, HubTransportError } from "./errors.ts"
4
4
  import { sseEvents } from "./sse.ts"
5
+ import { type Artifacts, artifactsApi } from "./artifacts.ts"
6
+ import { type Teams, teamsApi } from "./teams.ts"
7
+ import { type Concierge, conciergeApi } from "./concierge.ts"
5
8
  import type {
6
9
  Approved,
7
10
  AppTokenView,
@@ -18,6 +21,7 @@ import type {
18
21
  DecisionUrgency,
19
22
  Denied,
20
23
  DirectoryEntry,
24
+ WhoAnswer,
21
25
  FormAnswer,
22
26
  FormAnswered,
23
27
  Grant,
@@ -34,6 +38,7 @@ import type {
34
38
  PromptDelivery,
35
39
  Prompted,
36
40
  Pinned,
41
+ SessionIntent,
37
42
  Replied,
38
43
  RoleClaim,
39
44
  RoleRelease,
@@ -48,6 +53,41 @@ import type {
48
53
  Whoami,
49
54
  WebhookDelivery,
50
55
  WebhookView,
56
+ ProfileAssignment,
57
+ ProfileContent,
58
+ ProfileCreated,
59
+ ProfileListInclude,
60
+ ProfileListPage,
61
+ ProfilePage,
62
+ ProfileUsage,
63
+ ProfileRendered,
64
+ ProfileSkillLoaded,
65
+ ProfileState,
66
+ ProfileVersion,
67
+ ProfileVersionEntry,
68
+ ProfileVersionSummary,
69
+ ProfileView,
70
+ SessionProfile,
71
+ WorkspaceArchived,
72
+ WorkspaceAssignmentRecord,
73
+ WorkspaceAssignmentStatus,
74
+ WorkspaceAssignmentView,
75
+ WorkspaceBinding,
76
+ WorkspaceBoard,
77
+ WorkspaceDeparture,
78
+ WorkspaceMasterReplaced,
79
+ WorkspaceMasterView,
80
+ WorkspaceMemberRecord,
81
+ WorkspaceMemberView,
82
+ WorkspacePage,
83
+ WorkspaceRole,
84
+ WorkspaceSideChat,
85
+ WorkspaceSideChatDelivery,
86
+ WorkspaceSideChatSummary,
87
+ WorkspaceState,
88
+ WorkspaceSupervised,
89
+ WorkspaceSupervisionGraph,
90
+ WorkspaceView,
51
91
  } from "./types.ts"
52
92
 
53
93
  const AGENT = "/api/agent"
@@ -62,7 +102,11 @@ export interface RequestOptions {
62
102
  readonly query?: Readonly<Record<string, QueryValue>>
63
103
  /** Sent as JSON. */
64
104
  readonly body?: unknown
105
+ /** Sent as is with its own content type, instead of a JSON body (artifact blob uploads). */
106
+ readonly raw?: { readonly body: BodyInit; readonly contentType: string }
65
107
  readonly signal?: AbortSignal
108
+ /** Extra request headers (e.g. Idempotency-Key); authorization and content headers are the client's. */
109
+ readonly headers?: Readonly<Record<string, string>>
66
110
  }
67
111
 
68
112
  /** The part of fetch the client uses. */
@@ -106,6 +150,8 @@ export interface SessionsFilter extends CallOptions {
106
150
  readonly machine?: string
107
151
  /** `key=value` labels, all of which must match. */
108
152
  readonly label?: readonly string[]
153
+ /** (CON-11) Words that must all appear in the title or @name (case and punctuation ignored; ≤ 200 characters). */
154
+ readonly q?: string
109
155
  }
110
156
 
111
157
  export interface LaunchesFilter extends CallOptions {
@@ -126,6 +172,105 @@ export interface Launches {
126
172
  stop(id: string, options?: CallOptions): Promise<{ launch: LaunchView }>
127
173
  }
128
174
 
175
+ export interface IdempotentOptions extends CallOptions {
176
+ /** The same key and body within 24 h answer the first result again (Idempotency-Key). */
177
+ readonly idempotencyKey?: string
178
+ }
179
+
180
+ export interface ProfilesFilter extends CallOptions {
181
+ readonly state?: ProfileState | "all"
182
+ readonly limit?: number
183
+ readonly cursor?: string
184
+ /** (RP-08) "usage": per-profile usage counts (ProfileListPage.usage); "summary": latest version counts (summaries). */
185
+ readonly include?: ProfileListInclude | readonly ProfileListInclude[]
186
+ }
187
+
188
+ /**
189
+ * hub.profiles (HWA-14): versioned agent profiles (instructions, skills, commands). Users; app tokens with
190
+ * profiles:read / profiles:manage / profiles:assign (session routes also need sessions:read); session tokens read
191
+ * only their own pinned profile (ofSession, sessionSkill, sessionRender). Mutations take expectedRevision; a stale
192
+ * one is HubError conflict with envelope.currentRevision.
193
+ */
194
+ export interface Profiles {
195
+ create(input: { readonly name: string; readonly title: string; readonly description?: string; readonly tags?: readonly string[]; readonly metadata?: Readonly<Record<string, string>>; readonly projects?: readonly string[]; readonly content: ProfileContent }, options?: IdempotentOptions): Promise<ProfileCreated>
196
+ /** From AGENTS.md / skills/<name>/SKILL.md / commands/<name>.md text files; anything else is unsupported. */
197
+ import(input: { readonly name: string; readonly title: string; readonly description?: string; readonly tags?: readonly string[]; readonly files: readonly { readonly path: string; readonly text: string }[] }, options?: IdempotentOptions): Promise<ProfileCreated>
198
+ list(filter?: ProfilesFilter): Promise<ProfileListPage>
199
+ get(id: string, options?: CallOptions): Promise<{ profile: ProfileView; latest?: ProfileVersionSummary }>
200
+ /** metadata/projects replace the stored ones; null clears them (RP-08). */
201
+ update(
202
+ id: string,
203
+ patch: { readonly expectedRevision: number; readonly title?: string; readonly description?: string | null; readonly tags?: readonly string[]; readonly state?: ProfileState; readonly metadata?: Readonly<Record<string, string>> | null; readonly projects?: readonly string[] | null },
204
+ options?: CallOptions,
205
+ ): Promise<{ profile: ProfileView }>
206
+ /** (RP-08) Where the profile is used: pinned sessions (paged), team roles, launches, workspaces; per-category scopes. */
207
+ usage(id: string, options?: CallOptions & { readonly limit?: number; readonly cursor?: string }): Promise<ProfileUsage>
208
+ versions(id: string, options?: CallOptions & { readonly limit?: number; readonly cursor?: string }): Promise<ProfilePage<ProfileVersionEntry>>
209
+ /** A new immutable version from content, an older version (explicit rollback) or files. */
210
+ createVersion(id: string, input: { readonly expectedRevision: number } & ({ readonly content: ProfileContent } | { readonly fromVersion: number } | { readonly files: readonly { readonly path: string; readonly text: string }[] }), options?: IdempotentOptions): Promise<ProfileCreated>
211
+ version(id: string, version: number, options?: CallOptions): Promise<{ version: ProfileVersion }>
212
+ skill(id: string, version: number, name: string, options?: CallOptions): Promise<ProfileSkillLoaded>
213
+ render(id: string, version: number, name: string, params: Readonly<Record<string, string>>, options?: CallOptions): Promise<ProfileRendered>
214
+ ofSession(addr: string, options?: CallOptions): Promise<SessionProfile>
215
+ assign(addr: string, input: { readonly profileID: string; readonly version: number; readonly expectedRevision: number }, options?: CallOptions): Promise<{ assignment: ProfileAssignment }>
216
+ unassign(addr: string, expectedRevision: number, options?: CallOptions): Promise<void>
217
+ sessionSkill(addr: string, name: string, options?: CallOptions): Promise<ProfileSkillLoaded>
218
+ sessionRender(addr: string, name: string, params: Readonly<Record<string, string>>, options?: CallOptions): Promise<ProfileRendered>
219
+ }
220
+
221
+ /**
222
+ * hub.workspaces (HWA-06): coordination workspaces with bindings, members, assignments and supervision. Users;
223
+ * app tokens with workspaces:read / workspaces:manage (member and assignment routes also need sessions:read);
224
+ * session tokens are refused. Every change of an existing record takes expectedRevision (supervise: the graph's
225
+ * revision from supervision()); a stale one is HubError conflict with envelope.currentRevision. Nothing here
226
+ * grants authority.
227
+ */
228
+ export interface Workspaces {
229
+ create(input: { readonly title: string; readonly description?: string; readonly tags?: readonly string[]; readonly metadata?: Readonly<Record<string, string>> }, options?: IdempotentOptions): Promise<{ workspace: WorkspaceView }>
230
+ list(filter?: CallOptions & { readonly state?: WorkspaceState; readonly limit?: number; readonly cursor?: string }): Promise<WorkspacePage<WorkspaceView>>
231
+ get(id: string, options?: CallOptions): Promise<{ workspace: WorkspaceView; bindings: readonly WorkspaceBinding[] }>
232
+ update(id: string, patch: { readonly expectedRevision: number; readonly title?: string; readonly description?: string | null; readonly tags?: readonly string[] | null; readonly metadata?: Readonly<Record<string, string>> | null }, options?: CallOptions): Promise<{ workspace: WorkspaceView }>
233
+ /** Archives it: current members depart (`archived`) and their supervision links are detached. */
234
+ archive(id: string, expectedRevision: number, options?: CallOptions): Promise<WorkspaceArchived>
235
+ restore(id: string, expectedRevision: number, options?: CallOptions): Promise<{ workspace: WorkspaceView }>
236
+ bindings(id: string, options?: CallOptions): Promise<{ bindings: readonly WorkspaceBinding[] }>
237
+ bind(id: string, input: { readonly machineID: string; readonly repo: string; readonly label?: string }, options?: IdempotentOptions): Promise<{ binding: WorkspaceBinding; created: boolean }>
238
+ unbind(id: string, bindingID: string, expectedRevision: number, options?: CallOptions): Promise<void>
239
+ members(id: string, options?: CallOptions & { readonly includeLeft?: boolean }): Promise<{ members: readonly WorkspaceMemberView[] }>
240
+ join(id: string, input: { readonly sessionID: string; readonly role: Exclude<WorkspaceRole, "master"> }, options?: IdempotentOptions): Promise<{ member: WorkspaceMemberRecord; created: boolean }>
241
+ setRole(id: string, memberID: string, role: Exclude<WorkspaceRole, "master">, expectedRevision: number, options?: CallOptions): Promise<{ member: WorkspaceMemberRecord }>
242
+ /** Removes a member (`removed`) and detaches its supervision links. */
243
+ leave(id: string, memberID: string, expectedRevision: number, options?: CallOptions): Promise<WorkspaceDeparture>
244
+ assignments(id: string, filter?: CallOptions & { readonly memberID?: string; readonly status?: WorkspaceAssignmentStatus; readonly limit?: number; readonly cursor?: string }): Promise<WorkspacePage<WorkspaceAssignmentView>>
245
+ assignment(id: string, assignmentID: string, options?: CallOptions): Promise<{ assignment: WorkspaceAssignmentRecord }>
246
+ assign(id: string, input: { readonly memberID: string; readonly title: string; readonly status?: "open" | "in_progress" | "blocked"; readonly criteria?: readonly string[]; readonly ref?: string }, options?: IdempotentOptions): Promise<{ assignment: WorkspaceAssignmentRecord }>
247
+ setAssignmentStatus(id: string, assignmentID: string, status: WorkspaceAssignmentStatus, expectedRevision: number, options?: CallOptions): Promise<{ assignment: WorkspaceAssignmentRecord }>
248
+ reassign(id: string, assignmentID: string, memberID: string, expectedRevision: number, options?: CallOptions): Promise<{ assignment: WorkspaceAssignmentRecord }>
249
+ supervision(id: string, options?: CallOptions): Promise<WorkspaceSupervisionGraph>
250
+ /** Attaches, moves, reorders or (supervisorMemberID null) detaches a member's supervisor. */
251
+ supervise(id: string, input: { readonly memberID: string; readonly supervisorMemberID: string | null; readonly order?: number; readonly expectedRevision: number }, options?: CallOptions): Promise<WorkspaceSupervised>
252
+ /** The situation board (HWA-10): bounded, read-only; apps also need sessions:read. */
253
+ board(id: string, options?: CallOptions): Promise<WorkspaceBoard>
254
+ /** The current master, earlier masters and a replacement in flight (HWA-12); apps also need sessions:read. */
255
+ master(id: string, options?: CallOptions): Promise<WorkspaceMasterView>
256
+ /** Replaces (or first sets) the master under the workspace revision; sessionID or a running launch's launchID. No grants move. */
257
+ replaceMaster(id: string, input: ({ readonly sessionID: string } | { readonly launchID: string }) & { readonly expectedRevision: number }, options?: IdempotentOptions): Promise<WorkspaceMasterReplaced>
258
+ /**
259
+ * Side chats (HWA-13): a new session launched (through the launch API, under your launch authority) on a host
260
+ * binding of the workspace, started with a bounded copy of the source's messages (none named: the newest).
261
+ * Apps also need sessions:read, messages:read and the launch scope and grant.
262
+ */
263
+ createSideChat(id: string, input: { readonly sourceSessionID: string; readonly sources?: readonly { readonly kind: "message"; readonly id: string }[]; readonly bindingID?: string; readonly harness?: string; readonly model?: string; readonly agent?: string }, options?: IdempotentOptions): Promise<{ sideChat: WorkspaceSideChat }>
264
+ sideChats(id: string, filter?: CallOptions & { readonly limit?: number; readonly cursor?: string }): Promise<WorkspacePage<WorkspaceSideChatSummary>>
265
+ sideChat(id: string, sideChatID: string, options?: CallOptions): Promise<{ sideChat: WorkspaceSideChat }>
266
+ /** A side chat by its ID alone (panes know only that). */
267
+ sideChatByID(sideChatID: string, options?: CallOptions): Promise<{ sideChat: WorkspaceSideChat }>
268
+ /** Re-reads the source (ACL checked again) and sends the new context to the side-chat session as a queued message. */
269
+ refreshSideChat(id: string, sideChatID: string, expectedRevision: number, options?: CallOptions): Promise<{ sideChat: WorkspaceSideChat; delivery: WorkspaceSideChatDelivery }>
270
+ /** Users only: a confirmed, queued message to the source session (never steers its running turn). */
271
+ sendToMain(id: string, sideChatID: string, text: string, options?: CallOptions): Promise<{ sideChatID: string; sent: { message: unknown; delivery?: string } }>
272
+ }
273
+
129
274
  export interface TargetOptions extends CallOptions {
130
275
  /** Act on this session (address) instead of the caller's own. */
131
276
  readonly session?: string
@@ -189,6 +334,7 @@ export interface PromptOptions extends ActOptions {
189
334
  const whyField = (options: ActOptions) => (options.why !== undefined ? { why: options.why } : {})
190
335
 
191
336
  const signalOf = (options: CallOptions) => (options.signal ? { signal: options.signal } : {})
337
+ const idempotent = (options: IdempotentOptions) => ({ ...signalOf(options), ...(options.idempotencyKey ? { headers: { "idempotency-key": options.idempotencyKey } } : {}) })
192
338
  /** The wake and urgent fields of a send, when given. */
193
339
  const sendFields = (options: MsgOptions) => ({ ...(options.wake !== undefined ? { wake: options.wake } : {}), ...(options.urgent !== undefined ? { urgent: options.urgent } : {}) })
194
340
  /** The session and project fields of a role request, when given. */
@@ -216,6 +362,66 @@ export class HubClient {
216
362
  stop: (id, options = {}) => this.call("DELETE", `${AGENT}/launches/${segment(id)}`, signalOf(options)),
217
363
  }
218
364
 
365
+ /** Profiles (HWA-14). */
366
+ readonly profiles: Profiles = {
367
+ create: (input, options = {}) => this.call("POST", `${AGENT}/profiles`, { body: input, ...idempotent(options) }),
368
+ import: (input, options = {}) => this.call("POST", `${AGENT}/profiles/import`, { body: input, ...idempotent(options) }),
369
+ list: (filter = {}) => this.call("GET", `${AGENT}/profiles`, { query: { state: filter.state, limit: filter.limit, cursor: filter.cursor, include: filter.include }, ...signalOf(filter) }),
370
+ get: (id, options = {}) => this.call("GET", `${AGENT}/profiles/${segment(id)}`, signalOf(options)),
371
+ update: (id, patch, options = {}) => this.call("PATCH", `${AGENT}/profiles/${segment(id)}`, { body: patch, ...signalOf(options) }),
372
+ usage: (id, options = {}) => this.call("GET", `${AGENT}/profiles/${segment(id)}/usage`, { query: { limit: options.limit, cursor: options.cursor }, ...signalOf(options) }),
373
+ versions: (id, options = {}) => this.call("GET", `${AGENT}/profiles/${segment(id)}/versions`, { query: { limit: options.limit, cursor: options.cursor }, ...signalOf(options) }),
374
+ createVersion: (id, input, options = {}) => this.call("POST", `${AGENT}/profiles/${segment(id)}/versions`, { body: input, ...idempotent(options) }),
375
+ version: (id, version, options = {}) => this.call("GET", `${AGENT}/profiles/${segment(id)}/versions/${version}`, signalOf(options)),
376
+ skill: (id, version, name, options = {}) => this.call("GET", `${AGENT}/profiles/${segment(id)}/versions/${version}/skills/${segment(name)}`, signalOf(options)),
377
+ render: (id, version, name, params, options = {}) => this.call("POST", `${AGENT}/profiles/${segment(id)}/versions/${version}/commands/${segment(name)}/render`, { body: { params }, ...signalOf(options) }),
378
+ ofSession: (addr, options = {}) => this.call("GET", `${AGENT}/sessions/${segment(addr)}/profile`, signalOf(options)),
379
+ assign: (addr, input, options = {}) => this.call("PUT", `${AGENT}/sessions/${segment(addr)}/profile`, { body: input, ...signalOf(options) }),
380
+ unassign: (addr, expectedRevision, options = {}) => this.call("DELETE", `${AGENT}/sessions/${segment(addr)}/profile`, { query: { expectedRevision }, ...signalOf(options) }),
381
+ sessionSkill: (addr, name, options = {}) => this.call("GET", `${AGENT}/sessions/${segment(addr)}/profile/skills/${segment(name)}`, signalOf(options)),
382
+ sessionRender: (addr, name, params, options = {}) => this.call("POST", `${AGENT}/sessions/${segment(addr)}/profile/commands/${segment(name)}/render`, { body: { params }, ...signalOf(options) }),
383
+ }
384
+
385
+ /** Workspaces (HWA-06). */
386
+ readonly workspaces: Workspaces = {
387
+ create: (input, options = {}) => this.call("POST", `${AGENT}/workspaces`, { body: input, ...idempotent(options) }),
388
+ list: (filter = {}) => this.call("GET", `${AGENT}/workspaces`, { query: { state: filter.state, limit: filter.limit, cursor: filter.cursor }, ...signalOf(filter) }),
389
+ get: (id, options = {}) => this.call("GET", `${AGENT}/workspaces/${segment(id)}`, signalOf(options)),
390
+ update: (id, patch, options = {}) => this.call("PATCH", `${AGENT}/workspaces/${segment(id)}`, { body: patch, ...signalOf(options) }),
391
+ archive: (id, expectedRevision, options = {}) => this.call("POST", `${AGENT}/workspaces/${segment(id)}/archive`, { body: { expectedRevision }, ...signalOf(options) }),
392
+ restore: (id, expectedRevision, options = {}) => this.call("POST", `${AGENT}/workspaces/${segment(id)}/restore`, { body: { expectedRevision }, ...signalOf(options) }),
393
+ bindings: (id, options = {}) => this.call("GET", `${AGENT}/workspaces/${segment(id)}/bindings`, signalOf(options)),
394
+ bind: (id, input, options = {}) => this.call("POST", `${AGENT}/workspaces/${segment(id)}/bindings`, { body: input, ...idempotent(options) }),
395
+ unbind: (id, bindingID, expectedRevision, options = {}) => this.call("DELETE", `${AGENT}/workspaces/${segment(id)}/bindings`, { query: { bindingID, expectedRevision }, ...signalOf(options) }),
396
+ members: (id, options = {}) => this.call("GET", `${AGENT}/workspaces/${segment(id)}/members`, { query: { includeLeft: options.includeLeft ? 1 : undefined }, ...signalOf(options) }),
397
+ join: (id, input, options = {}) => this.call("POST", `${AGENT}/workspaces/${segment(id)}/members`, { body: input, ...idempotent(options) }),
398
+ setRole: (id, memberID, role, expectedRevision, options = {}) => this.call("PATCH", `${AGENT}/workspaces/${segment(id)}/members/${segment(memberID)}`, { body: { role, expectedRevision }, ...signalOf(options) }),
399
+ leave: (id, memberID, expectedRevision, options = {}) => this.call("DELETE", `${AGENT}/workspaces/${segment(id)}/members/${segment(memberID)}`, { query: { expectedRevision }, ...signalOf(options) }),
400
+ assignments: (id, filter = {}) => this.call("GET", `${AGENT}/workspaces/${segment(id)}/assignments`, { query: { memberID: filter.memberID, status: filter.status, limit: filter.limit, cursor: filter.cursor }, ...signalOf(filter) }),
401
+ assignment: (id, assignmentID, options = {}) => this.call("GET", `${AGENT}/workspaces/${segment(id)}/assignments/${segment(assignmentID)}`, signalOf(options)),
402
+ assign: (id, input, options = {}) => this.call("POST", `${AGENT}/workspaces/${segment(id)}/assignments`, { body: input, ...idempotent(options) }),
403
+ setAssignmentStatus: (id, assignmentID, status, expectedRevision, options = {}) => this.call("PATCH", `${AGENT}/workspaces/${segment(id)}/assignments/${segment(assignmentID)}`, { body: { status, expectedRevision }, ...signalOf(options) }),
404
+ reassign: (id, assignmentID, memberID, expectedRevision, options = {}) => this.call("PATCH", `${AGENT}/workspaces/${segment(id)}/assignments/${segment(assignmentID)}`, { body: { memberID, expectedRevision }, ...signalOf(options) }),
405
+ supervision: (id, options = {}) => this.call("GET", `${AGENT}/workspaces/${segment(id)}/supervision`, signalOf(options)),
406
+ supervise: (id, input, options = {}) => this.call("PUT", `${AGENT}/workspaces/${segment(id)}/supervision`, { body: input, ...signalOf(options) }),
407
+ board: (id, options = {}) => this.call("GET", `${AGENT}/workspaces/${segment(id)}/board`, signalOf(options)),
408
+ master: (id, options = {}) => this.call("GET", `${AGENT}/workspaces/${segment(id)}/master`, signalOf(options)),
409
+ replaceMaster: (id, input, options = {}) => this.call("POST", `${AGENT}/workspaces/${segment(id)}/master`, { body: input, ...idempotent(options) }),
410
+ createSideChat: (id, input, options = {}) => this.call("POST", `${AGENT}/workspaces/${segment(id)}/side-chats`, { body: input, ...idempotent(options) }),
411
+ sideChats: (id, filter = {}) => this.call("GET", `${AGENT}/workspaces/${segment(id)}/side-chats`, { query: { limit: filter.limit, cursor: filter.cursor }, ...signalOf(filter) }),
412
+ sideChat: (id, sideChatID, options = {}) => this.call("GET", `${AGENT}/workspaces/${segment(id)}/side-chats/${segment(sideChatID)}`, signalOf(options)),
413
+ sideChatByID: (sideChatID, options = {}) => this.call("GET", `${AGENT}/side-chats/${segment(sideChatID)}`, signalOf(options)),
414
+ refreshSideChat: (id, sideChatID, expectedRevision, options = {}) => this.call("POST", `${AGENT}/workspaces/${segment(id)}/side-chats/${segment(sideChatID)}/refresh`, { body: { expectedRevision }, ...signalOf(options) }),
415
+ sendToMain: (id, sideChatID, text, options = {}) => this.call("POST", `${AGENT}/workspaces/${segment(id)}/side-chats/${segment(sideChatID)}/send`, { body: { text, confirmed: true }, ...signalOf(options) }),
416
+ }
417
+
418
+ /** Persistent artifacts (HWA-21): metadata as JSON, bytes inline when small or staged and referenced. */
419
+ readonly artifacts: Artifacts = artifactsApi(this)
420
+ /** Teams (HWA-37): definitions, drafts, staffing and status, plus intake (HWA-36): give work, read it, report status. */
421
+ readonly teams: Teams = teamsApi(this)
422
+ /** Concierge (CON-01): its watchlist (get, update, watch/unwatch, mute/unmute). */
423
+ readonly concierge: Concierge = conciergeApi(this)
424
+
219
425
  constructor(options: HubClientOptions) {
220
426
  this.url = options.url
221
427
  this.#base = options.url.replace(/\/+$/, "")
@@ -226,14 +432,15 @@ export class HubClient {
226
432
  /** Sends a request; resolves the response when OK, else throws HubError / HubTransportError. */
227
433
  async request(method: string, pathname: string, options: RequestOptions = {}): Promise<Response> {
228
434
  const target = `${pathname}${queryString(options.query)}`
229
- const headers: Record<string, string> = { authorization: `Bearer ${this.#token}`, accept: "application/json" }
435
+ const headers: Record<string, string> = { ...options.headers, authorization: `Bearer ${this.#token}`, accept: "application/json" }
230
436
  if (options.body !== undefined) headers["content-type"] = "application/json"
437
+ if (options.raw) headers["content-type"] = options.raw.contentType
231
438
  let response: Response
232
439
  try {
233
440
  response = await this.#fetch(`${this.#base}${target}`, {
234
441
  method,
235
442
  headers,
236
- ...(options.body !== undefined ? { body: JSON.stringify(options.body) } : {}),
443
+ ...(options.raw ? { body: options.raw.body } : options.body !== undefined ? { body: JSON.stringify(options.body) } : {}),
237
444
  ...(options.signal ? { signal: options.signal } : {}),
238
445
  redirect: "manual",
239
446
  })
@@ -282,7 +489,7 @@ export class HubClient {
282
489
  sessions(filter: SessionsFilter = {}): Promise<{ sessions: SessionView[] }> {
283
490
  const state = typeof filter.state === "string" ? filter.state : filter.state?.join(",")
284
491
  return this.call("GET", `${AGENT}/sessions`, {
285
- query: { project: filter.project, state, machine: filter.machine, label: filter.label },
492
+ query: { project: filter.project, state, machine: filter.machine, label: filter.label, q: filter.q },
286
493
  ...signalOf(filter),
287
494
  })
288
495
  }
@@ -298,8 +505,8 @@ export class HubClient {
298
505
  }
299
506
 
300
507
  /** Named and role-holding sessions. */
301
- who(options: CallOptions & { readonly project?: string; readonly role?: string } = {}): Promise<{ sessions: DirectoryEntry[] }> {
302
- return this.call("GET", `${AGENT}/who`, { query: { project: options.project, role: options.role === undefined ? undefined : bareRole(options.role) }, ...signalOf(options) })
508
+ who(options: CallOptions & { readonly project?: string; readonly role?: string; readonly team?: string } = {}): Promise<WhoAnswer> {
509
+ return this.call("GET", `${AGENT}/who`, { query: { project: options.project, role: options.role === undefined ? undefined : bareRole(options.role), team: options.team }, ...signalOf(options) })
303
510
  }
304
511
 
305
512
  /**
@@ -355,6 +562,20 @@ export class HubClient {
355
562
  return this.call("PUT", `${AGENT}/sessions/${segment(addr)}/pin`, { body: { pinned }, ...signalOf(options) })
356
563
  }
357
564
 
565
+ /** (CON-02) A session's intent: the stored one, else its non-placeholder title (source title), else null. */
566
+ intent(addr: string, options: CallOptions = {}): Promise<{ intent: SessionIntent | null }> {
567
+ return this.call("GET", `${AGENT}/sessions/${segment(addr)}/intent`, signalOf(options))
568
+ }
569
+
570
+ /**
571
+ * (CON-02) Sets (text) or clears (null) the caller's intent layer under expectedRevision (the revision read;
572
+ * stale: conflict with currentRevision). Users and apps (sessions:manage) write source user, a session token its
573
+ * own session only (source self).
574
+ */
575
+ setIntent(addr: string, text: string | null, expectedRevision: number, options: CallOptions = {}): Promise<{ intent: SessionIntent | null }> {
576
+ return this.call("PUT", `${AGENT}/sessions/${segment(addr)}/intent`, { body: { text, expectedRevision }, ...signalOf(options) })
577
+ }
578
+
358
579
  previewArchiveInactive(inactiveFor: string, limit: number, options: CallOptions = {}): Promise<ArchiveInactivePreview> {
359
580
  return this.call("POST", `${AGENT}/sessions/archive-inactive`, { body: { preview: true, inactiveFor, limit }, ...signalOf(options) })
360
581
  }
@@ -0,0 +1,88 @@
1
+ // hub.concierge (CON-01) over /api/agent/concierge: the Concierge's watchlist — which of the user's sessions it
2
+ // watches (auto-active sessions, the console's pins, explicit sessions, projects and launch-label filters, minus
3
+ // mutes). Who: users; app tokens with sessions:read (get) or sessions:manage (update and the verbs); the user's current
4
+ // Concierge session through its act token (get and the verbs only). Every other session token gets 404 (not_found).
5
+ // `update` sends the revision you read (expectedRevision; 409 conflict with currentRevision when stale); the verbs are
6
+ // idempotent (`changed: false` when nothing changed) and resolve the address for the caller (a subagent watches its
7
+ // root). The wire types mirror the hub's protocol (packages/protocol/src/concierge.ts); the SDK imports nothing
8
+ // outside its package.
9
+
10
+ interface Signal {
11
+ readonly signal?: AbortSignal
12
+ }
13
+
14
+ export interface WatchlistConfig {
15
+ readonly autoActive: boolean
16
+ /** 1–24. */
17
+ readonly activeWithinHours: number
18
+ readonly usePins: boolean
19
+ /** Explicit ses_… IDs (≤ 200). */
20
+ readonly sessions: readonly string[]
21
+ /** Canonical absolute project directories (≤ 50). */
22
+ readonly projects: readonly string[]
23
+ /** ≤ 20 filters; each matches when all its pairs equal a launch's labels. */
24
+ readonly labels: readonly Readonly<Record<string, string>>[]
25
+ /** ses_… IDs (≤ 500); beats every include. */
26
+ readonly muted: readonly string[]
27
+ readonly mutedProjects: readonly string[]
28
+ readonly revision: number
29
+ }
30
+
31
+ export type WatchReason = "active" | "pinned" | "pinned_project" | "explicit" | "project" | "label"
32
+
33
+ export interface WatchlistEntry {
34
+ readonly sessionID: string
35
+ readonly reasons: readonly WatchReason[]
36
+ readonly lastActiveAt?: number
37
+ }
38
+
39
+ export interface WatchlistView {
40
+ readonly config: WatchlistConfig
41
+ readonly entries: readonly WatchlistEntry[]
42
+ readonly truncated: boolean
43
+ }
44
+
45
+ /** What PUT takes: any of the config fields (revision is the server's). */
46
+ export type WatchlistUpdate = Partial<Omit<WatchlistConfig, "revision">>
47
+
48
+ /** The answer of the watch/unwatch/mute/unmute verbs: the stored config, the resolved root session, and whether it changed. */
49
+ export interface WatchlistChanged {
50
+ readonly config: WatchlistConfig
51
+ readonly sessionID: string
52
+ readonly changed: boolean
53
+ }
54
+
55
+ export interface ConciergeWatchlistApi {
56
+ get(options?: Signal): Promise<WatchlistView>
57
+ update(patch: WatchlistUpdate, expectedRevision: number, options?: Signal): Promise<{ config: WatchlistConfig }>
58
+ watch(addr: string, options?: Signal): Promise<WatchlistChanged>
59
+ unwatch(addr: string, options?: Signal): Promise<WatchlistChanged>
60
+ mute(addr: string, options?: Signal): Promise<WatchlistChanged>
61
+ unmute(addr: string, options?: Signal): Promise<WatchlistChanged>
62
+ }
63
+
64
+ export interface Concierge {
65
+ readonly watchlist: ConciergeWatchlistApi
66
+ }
67
+
68
+ /** What hub.concierge needs from the client (HubClient's call). */
69
+ export interface ConciergeTransport {
70
+ call<T>(method: string, pathname: string, options?: { readonly body?: unknown; readonly signal?: AbortSignal }): Promise<T>
71
+ }
72
+
73
+ const WATCHLIST = "/api/agent/concierge/watchlist"
74
+ const sig = (options: Signal = {}) => (options.signal ? { signal: options.signal } : {})
75
+
76
+ export function conciergeApi(hub: ConciergeTransport): Concierge {
77
+ const verb = (name: string) => (addr: string, options?: Signal) => hub.call<WatchlistChanged>("POST", `${WATCHLIST}/${name}`, { body: { session: addr }, ...sig(options) })
78
+ return {
79
+ watchlist: {
80
+ get: (options) => hub.call("GET", WATCHLIST, sig(options)),
81
+ update: (patch, expectedRevision, options) => hub.call("PUT", WATCHLIST, { body: { ...patch, expectedRevision }, ...sig(options) }),
82
+ watch: verb("watch"),
83
+ unwatch: verb("unwatch"),
84
+ mute: verb("mute"),
85
+ unmute: verb("unmute"),
86
+ },
87
+ }
88
+ }
@@ -14,12 +14,14 @@ export type ErrorCode =
14
14
  | "not_found"
15
15
  | "ambiguous"
16
16
  | "name_taken"
17
+ | "conflict"
17
18
  | "gone"
18
19
  | "unsupported"
19
20
  | "paused"
20
21
  | "rate_limited"
21
22
  | "internal"
22
23
  | "offline"
24
+ | "busy"
23
25
  | (string & {})
24
26
 
25
27
  export interface Envelope {
@@ -28,7 +30,8 @@ export interface Envelope {
28
30
  readonly hint?: string
29
31
  /**
30
32
  * More fields, by code: candidates (ambiguous), retryAfter (rate_limited, seconds), feature/harness (unsupported),
31
- * evaluation/decisionID (needs_user; see needsUserOf).
33
+ * evaluation/decisionID (needs_user; see needsUserOf),
34
+ * currentRevision (conflict).
32
35
  */
33
36
  readonly [extra: string]: unknown
34
37
  }
@@ -2,9 +2,15 @@
2
2
  // errors (HubError, HubTransportError), the answer types, the SSE parser of the watch stream, and the pure
3
3
  // envelope renderer/sanitiser and address parser shared with the hub, launches, and webhook verification.
4
4
  export * from "./address.ts"
5
+ export * from "./artifacts.ts"
6
+ export * from "./artifact-jobs.ts"
7
+ export * from "./team-intake.ts"
8
+ export * from "./teams.ts"
5
9
  export * from "./client.ts"
10
+ export * from "./concierge.ts"
6
11
  export * from "./envelope.ts"
7
12
  export * from "./errors.ts"
8
13
  export * from "./sse.ts"
14
+ export * from "./wake.ts"
9
15
  export * from "./webhook.ts"
10
16
  export type * from "./types.ts"
@@ -0,0 +1,122 @@
1
+ // hub.teams intake verbs (HWA-36) over /api/agent/teams/:id/intake: give work to a team (its entry role receives it
2
+ // as a hub message and owns it as a workspace assignment), read it back, and report status explicitly. Users; app
3
+ // tokens with teams:submit (submit, read and cancel their own submissions); session tokens (submit under team policy;
4
+ // sessions in the team's entry role report progress). Definitions, staffing and the rest of hub.teams are HWA-37's.
5
+ // The wire type mirrors the hub's StoredIntake (packages/protocol TeamIntake plus additive fields; the SDK imports
6
+ // nothing outside its package; the hub's tests keep them in step).
7
+
8
+ interface Signal {
9
+ readonly signal?: AbortSignal
10
+ }
11
+
12
+ export type TeamIntakeStatus = "queued" | "routed" | "done" | "cancelled" | "failed"
13
+ export type TeamIntakePriority = "low" | "normal" | "high" | "urgent"
14
+ export type TeamIntakeProgress = "open" | "in_progress" | "blocked"
15
+ /** What setIntakeStatus accepts: progress while routed, or a closing status (submitters: cancelled only). */
16
+ export type TeamIntakeStatusInput = TeamIntakeProgress | "done" | "failed" | "cancelled"
17
+
18
+ export interface TeamIntake {
19
+ readonly v: 1
20
+ readonly id: string
21
+ readonly teamID: string
22
+ readonly title: string
23
+ readonly source: { readonly kind: "user" } | { readonly kind: "app"; readonly tokenID: string } | { readonly kind: "session"; readonly sessionID: string }
24
+ readonly ref?: string
25
+ readonly assignmentID?: string
26
+ readonly status: TeamIntakeStatus
27
+ readonly revision: number
28
+ readonly createdAt: number
29
+ readonly priority: TeamIntakePriority
30
+ readonly labels?: readonly string[]
31
+ /** Epoch ms. */
32
+ readonly due?: number
33
+ readonly criteria?: readonly string[]
34
+ readonly workspaceID?: string
35
+ readonly sessionID?: string
36
+ readonly messageID?: string
37
+ readonly progress?: TeamIntakeProgress
38
+ readonly reason?: string
39
+ readonly lastError?: string
40
+ readonly updatedAt: number
41
+ readonly routedAt?: number
42
+ readonly closedAt?: number
43
+ }
44
+
45
+ export interface TeamIntakeSubmit {
46
+ readonly title: string
47
+ /** Delivered in the message only; never stored after delivery, never in events. */
48
+ readonly brief?: string
49
+ readonly priority?: TeamIntakePriority
50
+ readonly labels?: readonly string[]
51
+ /** Epoch ms. */
52
+ readonly due?: number
53
+ readonly criteria?: readonly string[]
54
+ /** An external reference (e.g. a Brain task ID). */
55
+ readonly ref?: string
56
+ }
57
+
58
+ /** The assignment a status change moved (as hub.workspaces shows it). */
59
+ export interface TeamIntakeAssignment {
60
+ readonly id: string
61
+ readonly workspaceID: string
62
+ readonly memberID: string
63
+ readonly status: "open" | "in_progress" | "blocked" | "done" | "cancelled"
64
+ readonly revision: number
65
+ }
66
+
67
+ export interface TeamIntakeApi {
68
+ /** Submits work; `created` false for an Idempotency-Key replay. Queued while the entry role is vacant. */
69
+ submit(team: string, input: TeamIntakeSubmit, options?: Signal & { readonly idempotencyKey?: string }): Promise<{ intake: TeamIntake; created: boolean }>
70
+ intake(team: string, intakeID: string, options?: Signal): Promise<{ intake: TeamIntake }>
71
+ intakes(team: string, filter?: Signal & { readonly status?: TeamIntakeStatus; readonly limit?: number; readonly cursor?: string }): Promise<{ items: readonly TeamIntake[]; cursor?: string }>
72
+ setIntakeStatus(
73
+ team: string,
74
+ intakeID: string,
75
+ input: { readonly status: TeamIntakeStatusInput; readonly expectedRevision: number; readonly reason?: string },
76
+ options?: Signal,
77
+ ): Promise<{ intake: TeamIntake; assignment?: TeamIntakeAssignment }>
78
+ }
79
+
80
+ type Query = Readonly<Record<string, string | number | undefined>>
81
+
82
+ /** What hub.teams needs from the client (HubClient's request and call). */
83
+ export interface TeamIntakeTransport {
84
+ request(method: string, pathname: string, options?: { readonly query?: Query; readonly body?: unknown; readonly headers?: Readonly<Record<string, string>>; readonly signal?: AbortSignal }): Promise<Response>
85
+ call<T>(method: string, pathname: string, options?: { readonly query?: Query; readonly body?: unknown; readonly signal?: AbortSignal }): Promise<T>
86
+ }
87
+
88
+ const seg = (value: string) => encodeURIComponent(value)
89
+ const sig = (options: Signal = {}) => (options.signal ? { signal: options.signal } : {})
90
+ const path = (team: string) => `/api/agent/teams/${seg(team)}/intake`
91
+
92
+ export function teamIntakeApi(hub: TeamIntakeTransport): TeamIntakeApi {
93
+ return {
94
+ submit: async (team, input, options = {}) => {
95
+ const response = await hub.request("POST", path(team), { body: input, ...(options.idempotencyKey ? { headers: { "idempotency-key": options.idempotencyKey } } : {}), ...sig(options) })
96
+ const answer = (await response.json()) as { intake: TeamIntake }
97
+ return { intake: answer.intake, created: response.status === 201 }
98
+ },
99
+ intake: (team, intakeID, options) => hub.call("GET", `${path(team)}/${seg(intakeID)}`, sig(options)),
100
+ intakes: (team, filter = {}) => hub.call("GET", path(team), { query: { status: filter.status, limit: filter.limit, cursor: filter.cursor }, ...sig(filter) }),
101
+ setIntakeStatus: (team, intakeID, input, options) => hub.call("POST", `${path(team)}/${seg(intakeID)}/status`, { body: input, ...sig(options) }),
102
+ }
103
+ }
104
+
105
+ /** Every status setIntakeStatus accepts, in the order tools list them. */
106
+ export const TEAM_INTAKE_STATUS_INPUTS: readonly TeamIntakeStatusInput[] = ["open", "in_progress", "blocked", "done", "failed", "cancelled"]
107
+
108
+ /**
109
+ * Reports an intake item's status (the entry role's progress or closing, a submitter's cancel). Without
110
+ * `expectedRevision` the item is read first and its current revision sent: a change in between is still a conflict.
111
+ * Authority is the hub's (the route's rules); nothing is decided here.
112
+ */
113
+ export async function reportIntakeStatus(
114
+ api: Pick<TeamIntakeApi, "intake" | "setIntakeStatus">,
115
+ team: string,
116
+ intakeID: string,
117
+ input: { readonly status: TeamIntakeStatusInput; readonly expectedRevision?: number; readonly reason?: string },
118
+ options: Signal = {},
119
+ ): Promise<{ intake: TeamIntake; assignment?: TeamIntakeAssignment }> {
120
+ const expectedRevision = input.expectedRevision ?? (await api.intake(team, intakeID, options)).intake.revision
121
+ return api.setIntakeStatus(team, intakeID, { status: input.status, expectedRevision, ...(input.reason !== undefined ? { reason: input.reason } : {}) }, options)
122
+ }