@huynle/hub 0.7.4 → 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 +217 -0
  2. package/Dockerfile +7 -2
  3. package/README.md +148 -19
  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 +632 -49
  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
@@ -50,6 +50,33 @@ export interface SessionView {
50
50
  /** In sessions answers. */
51
51
  readonly machineID?: string
52
52
  readonly project?: string
53
+ /** (CON-02) The stored intent, when there is one (untrusted text). */
54
+ readonly intent?: IntentField
55
+ }
56
+
57
+ /** (CON-02) Who set a session's intent, highest precedence first. */
58
+ export type IntentSource = "user" | "launch" | "self" | "prompt" | "title"
59
+
60
+ /**
61
+ * (CON-02) What a session is for (GET/PUT /api/agent/sessions/:addr/intent): untrusted text, sanitised, one line,
62
+ * 1–280 characters. Mirrors packages/protocol SessionIntent.
63
+ */
64
+ export interface SessionIntent {
65
+ readonly sessionID: string
66
+ readonly text: string
67
+ readonly source: IntentSource
68
+ /** 0 before the hub stored anything (a title fallback). */
69
+ readonly revision: number
70
+ readonly updatedAt: number
71
+ /** user, app:<tokenID> or ses_… */
72
+ readonly by?: string
73
+ }
74
+
75
+ /** (CON-02) The intent as list and show entries embed it. */
76
+ export interface IntentField {
77
+ readonly text: string
78
+ readonly source: IntentSource
79
+ readonly revision: number
53
80
  }
54
81
 
55
82
  export interface AttentionItem {
@@ -71,6 +98,8 @@ export interface Status {
71
98
  }[]
72
99
  readonly attention: readonly AttentionItem[]
73
100
  readonly you?: Actor
101
+ /** HWA-40: the listed sessions' teams (a team member: its own teams only). */
102
+ readonly teams?: readonly TeamGroupView[]
74
103
  }
75
104
 
76
105
  /** GET /api/agent/sessions/:addr: one session in detail. */
@@ -86,6 +115,8 @@ export interface SessionDetail extends SessionView {
86
115
  }
87
116
  readonly unsupported?: readonly { readonly feature?: string; readonly harness?: string }[]
88
117
  readonly failed?: readonly { readonly feature?: string }[]
118
+ /** HWA-40: the session's teams as the caller may see them. */
119
+ readonly teams?: readonly TeamMembershipView[]
89
120
  }
90
121
 
91
122
  /** One timeline row (newest first). */
@@ -119,6 +150,46 @@ export interface DirectoryEntry {
119
150
  readonly lastSeen?: number
120
151
  readonly archived?: boolean
121
152
  readonly roles: readonly RoleView[]
153
+ /** (CON-02) The stored intent, when there is one (untrusted text). */
154
+ readonly intent?: IntentField
155
+ /** HWA-40 (additive): the harness when reported, team seats and the caller's reach (team members only). */
156
+ readonly harness?: string
157
+ readonly teams?: readonly TeamMembershipView[]
158
+ readonly reach?: TeamReach
159
+ }
160
+
161
+ /** HWA-40: what the caller may send to / receive from a session under team policy (from the hub's verdict). */
162
+ export interface TeamReach {
163
+ readonly send: readonly ("msg" | "ask")[]
164
+ readonly receive: readonly ("msg" | "ask")[]
165
+ readonly direction: "two-way" | "outbound" | "inbound" | "none"
166
+ readonly unreachable?: true
167
+ }
168
+
169
+ /** HWA-40: one team a session is seated in, with its roles there. */
170
+ export interface TeamMembershipView {
171
+ readonly teamID: string
172
+ readonly team: string
173
+ readonly roles: readonly string[]
174
+ readonly reach?: TeamReach
175
+ }
176
+
177
+ /** HWA-40: a team in who/status answers, with its listed members. */
178
+ export interface TeamGroupView {
179
+ readonly teamID: string
180
+ readonly name: string
181
+ readonly title: string
182
+ readonly mode: "open" | "restricted"
183
+ readonly paused?: true
184
+ readonly yourRoles?: readonly string[]
185
+ readonly members: readonly { readonly sessionID: string; readonly role: string; readonly generation: number }[]
186
+ }
187
+
188
+ /** GET /api/agent/who. `scope: "team"`: the caller sees its teams only (HWA-40). */
189
+ export interface WhoAnswer {
190
+ readonly sessions: DirectoryEntry[]
191
+ readonly teams?: readonly TeamGroupView[]
192
+ readonly scope?: "team"
122
193
  }
123
194
 
124
195
  /** One compact event of GET /api/agent/events (no free text). Unknown types are valid: ignore them. */
@@ -198,7 +269,8 @@ export type MessageKind = "msg" | "ask" | "reply" | "decline"
198
269
  /** `sent` for msg/reply/decline; asks are open until answered, declined or expired. */
199
270
  export type MessageState = "sent" | "open" | "answered" | "declined" | "expired"
200
271
  /** How a message reached its recipient session. */
201
- export type MessageDelivery = "steer" | "turn" | "queued" | "retained" | "pending"
272
+ /** (CON-10) "dropped": it was pending for a session that ended for good (a retired Concierge session); never delivered. */
273
+ export type MessageDelivery = "steer" | "turn" | "queued" | "retained" | "pending" | "dropped"
202
274
 
203
275
  /** A stored agent message. */
204
276
  export interface AgentMessage {
@@ -449,9 +521,13 @@ export interface LaunchSpec {
449
521
  readonly runtime?: { readonly kind: RuntimeKind; readonly profile?: string }
450
522
  readonly placement?: { readonly labels?: Readonly<Record<string, string>>; readonly machine?: string; readonly mode?: PlacementMode }
451
523
  readonly prompt?: string
524
+ /** (CON-02) What the session is for: one line, at most 280 characters; becomes its intent (source launch). */
525
+ readonly intent?: string
452
526
  /** Free-form; never interpreted by the hub (find launches and sessions by them). */
453
527
  readonly labels?: Readonly<Record<string, string>>
454
528
  readonly end?: { readonly when?: LaunchEndWhen; readonly timeout?: string; readonly cleanup?: CleanupPolicy }
529
+ /** Pin and apply this profile version before the prompt (explicit version; apps need profiles:assign). */
530
+ readonly agentProfile?: { readonly profileID: string; readonly version: number }
455
531
  }
456
532
 
457
533
  export interface LaunchStep {
@@ -515,3 +591,474 @@ export interface LaunchCreated {
515
591
  readonly launch: LaunchView
516
592
  readonly created: boolean
517
593
  }
594
+
595
+ // Profiles (HWA-14; packages/protocol/src/profiles.ts is the wire source of truth). A profile is a versioned bundle
596
+ // of instructions, skills and commands; versions are immutable, and a session pins one. Assigning confers no
597
+ // authority.
598
+
599
+ export type ProfileState = "active" | "archived"
600
+
601
+ export interface ProfileSkill {
602
+ readonly name: string
603
+ readonly description: string
604
+ readonly body: string
605
+ }
606
+
607
+ export interface ProfileCommand {
608
+ readonly name: string
609
+ readonly description: string
610
+ /** Text with {{name}} parameters and {{secret:NAME}} references (never resolved by the hub). */
611
+ readonly template: string
612
+ }
613
+
614
+ export interface ProfileContent {
615
+ readonly instructions?: string
616
+ readonly model?: string
617
+ readonly skills: readonly ProfileSkill[]
618
+ readonly commands: readonly ProfileCommand[]
619
+ }
620
+
621
+ export interface ProfileView {
622
+ readonly v: 1
623
+ readonly id: string
624
+ readonly name: string
625
+ readonly title: string
626
+ readonly description?: string
627
+ readonly state: ProfileState
628
+ readonly tags?: readonly string[]
629
+ /** (RP-08) Optional bounded key/value details (≤ 32 slug keys, values 1–200 characters). */
630
+ readonly metadata?: Readonly<Record<string, string>>
631
+ /** (RP-08) Project names the profile is meant for (≤ 16). */
632
+ readonly projects?: readonly string[]
633
+ readonly latestVersion: number
634
+ readonly revision: number
635
+ readonly updatedAt: number
636
+ }
637
+
638
+ export interface ProfileVersion {
639
+ readonly v: 1
640
+ readonly profileID: string
641
+ readonly version: number
642
+ readonly content: ProfileContent
643
+ readonly digest: string
644
+ readonly createdAt: number
645
+ }
646
+
647
+ /** A version without its bodies (load skills on demand). */
648
+ export interface ProfileVersionSummary {
649
+ readonly profileID: string
650
+ readonly version: number
651
+ readonly digest: string
652
+ readonly createdAt: number
653
+ readonly bytes: number
654
+ readonly instructions: boolean
655
+ readonly model?: string
656
+ readonly skills: readonly { readonly name: string; readonly description: string }[]
657
+ readonly commands: readonly { readonly name: string; readonly description: string; readonly params: readonly string[] }[]
658
+ readonly secretRefs: readonly string[]
659
+ }
660
+
661
+ export interface ProfileVersionEntry {
662
+ readonly version: number
663
+ readonly digest: string
664
+ readonly bytes: number
665
+ readonly createdAt: number
666
+ /** (RP-08) Content counts (absent on versions written before). */
667
+ readonly skills?: number
668
+ readonly commands?: number
669
+ readonly instructions?: boolean
670
+ }
671
+
672
+ export interface ProfileCreated {
673
+ readonly profile: ProfileView
674
+ readonly version: ProfileVersionSummary
675
+ /** Import only: what was dropped or defaulted. */
676
+ readonly notes?: readonly string[]
677
+ }
678
+
679
+ export interface ProfileAck {
680
+ /** restart_required: accepted, effective after the session restarts. */
681
+ readonly status: "pending" | "applied" | "partial" | "rejected" | "unsupported" | "restart_required"
682
+ readonly digest: string
683
+ readonly notes?: readonly string[]
684
+ readonly at: number
685
+ /** With pending: no spoke reports the session now; applied when one does. */
686
+ readonly offline?: true
687
+ }
688
+
689
+ /** The version a session last confirmed running (kept across later pins). */
690
+ export interface ProfileEffective {
691
+ readonly profileID: string
692
+ readonly version: number
693
+ readonly digest: string
694
+ readonly status: "applied" | "partial"
695
+ readonly at: number
696
+ }
697
+
698
+ export interface ProfileAssignment {
699
+ readonly v: 1
700
+ readonly sessionID: string
701
+ readonly profileID: string
702
+ readonly version: number
703
+ readonly revision: number
704
+ readonly ack?: ProfileAck
705
+ readonly effective?: ProfileEffective
706
+ }
707
+
708
+ export interface SessionProfile {
709
+ readonly assignment: ProfileAssignment | null
710
+ readonly profile?: ProfileView
711
+ readonly version?: ProfileVersionSummary
712
+ }
713
+
714
+ export interface ProfileSkillLoaded {
715
+ readonly skill: ProfileSkill
716
+ readonly profileID: string
717
+ readonly version: number
718
+ }
719
+
720
+ export interface ProfileRendered {
721
+ /** The rendered text; sending it to a session is a separate prompt (its own grants). */
722
+ readonly prompt: string
723
+ readonly secretRefs: readonly string[]
724
+ readonly profileID: string
725
+ readonly version: number
726
+ readonly command: string
727
+ }
728
+
729
+ export interface ProfilePage<T> {
730
+ readonly items: readonly T[]
731
+ readonly cursor?: string
732
+ }
733
+
734
+ // Profile usage (RP-08): GET /profiles/:id/usage and GET /profiles?include=usage. A category the caller may not read
735
+ // (app scopes: sessions:read, teams:read|teams:manage, sessions:launch, workspaces:read + sessions:read) is left out
736
+ // and named in `hidden`; lists past their cap are named in `truncated` (counts stay exact).
737
+ export type ProfileUsageCategory = "sessions" | "teams" | "launches" | "workspaces"
738
+
739
+ export interface ProfileUsageCounts {
740
+ readonly sessions?: number
741
+ readonly online?: number
742
+ /** Sessions pinned to a version older than the latest. */
743
+ readonly olderVersion?: number
744
+ readonly teamRoles?: number
745
+ /** Seat occupancy: only for callers who may also read sessions (apps: sessions:read). */
746
+ readonly staffedSeats?: number
747
+ readonly launches?: number
748
+ readonly liveLaunches?: number
749
+ readonly workspaces?: number
750
+ }
751
+
752
+ export interface ProfileUsageListCounts extends ProfileUsageCounts {
753
+ /** Team names (slugs) whose roles name the profile. */
754
+ readonly teams?: readonly string[]
755
+ readonly harnesses?: readonly string[]
756
+ }
757
+
758
+ export interface ProfileUsageSession {
759
+ readonly sessionID: string
760
+ readonly name?: string
761
+ readonly title?: string
762
+ readonly machineID?: string
763
+ readonly harness?: string
764
+ readonly project?: string
765
+ readonly online: boolean
766
+ readonly lastSeen?: number
767
+ /** false: no longer in the directory (the pin is still stored). */
768
+ readonly known?: false
769
+ /** The requested (pinned) version and the pin's revision (expectedRevision for an explicit upgrade). */
770
+ readonly version: number
771
+ readonly revision: number
772
+ readonly olderVersion: boolean
773
+ readonly ack?: { readonly status: ProfileAck["status"]; readonly at: number; readonly offline?: true }
774
+ readonly effective?: { readonly version: number; readonly status: "applied" | "partial"; readonly at: number }
775
+ }
776
+
777
+ export interface ProfileUsage {
778
+ readonly profileID: string
779
+ readonly latestVersion: number
780
+ readonly counts: ProfileUsageCounts
781
+ readonly sessions?: ProfilePage<ProfileUsageSession>
782
+ readonly projects?: readonly string[]
783
+ readonly teamRoles?: readonly { readonly teamID: string; readonly teamName: string; readonly teamTitle: string; readonly role: string; readonly label?: string; readonly version?: number; /** Only with sessions:read (users always). */ readonly staffed?: number }[]
784
+ readonly launches?: readonly { readonly id: string; readonly state: string; readonly outcome?: string; readonly version: number; readonly sessionID?: string; readonly created: number; readonly updated: number }[]
785
+ readonly workspaces?: readonly { readonly workspaceID: string; readonly title?: string; readonly sessions: number }[]
786
+ readonly hidden?: readonly ProfileUsageCategory[]
787
+ readonly truncated?: readonly ("sessions" | "teamRoles" | "launches" | "workspaces")[]
788
+ }
789
+
790
+ export type ProfileListInclude = "usage" | "summary"
791
+
792
+ /** GET /profiles?include=usage,summary: the page plus counts per listed profile. */
793
+ export interface ProfileListPage extends ProfilePage<ProfileView> {
794
+ readonly usage?: Readonly<Record<string, ProfileUsageListCounts>>
795
+ /** include=summary: the latest version's counts. */
796
+ readonly summaries?: Readonly<Record<string, { readonly version: number; readonly skills: number; readonly commands: number; readonly instructions: boolean }>>
797
+ readonly hidden?: readonly ProfileUsageCategory[]
798
+ readonly truncated?: readonly "sessions"[]
799
+ }
800
+
801
+ // Workspaces (HWA-06; packages/protocol/src/hub-workspace.ts is the wire source of truth). A workspace is a durable,
802
+ // owner-tenanted coordination record: host repo bindings, members (sessions by ID), assignments and a supervision
803
+ // graph. Membership, roles and supervision confer no authority; native parent/root session ancestry is untouched.
804
+
805
+ export type WorkspaceState = "active" | "archived"
806
+ export type WorkspaceRole = "master" | "worker" | "observer"
807
+ export type WorkspaceAssignmentStatus = "open" | "in_progress" | "blocked" | "done" | "cancelled"
808
+ /** What the hub knows about a member's session now. */
809
+ export type WorkspaceSessionPresence = "online" | "offline" | "archived" | "deleted" | "unknown"
810
+
811
+ export interface WorkspaceView {
812
+ readonly v: 1
813
+ readonly id: string
814
+ readonly title: string
815
+ readonly description?: string
816
+ readonly state: WorkspaceState
817
+ readonly tags?: readonly string[]
818
+ readonly metadata?: Readonly<Record<string, string>>
819
+ readonly masterMemberID?: string
820
+ readonly revision: number
821
+ readonly createdAt: number
822
+ readonly updatedAt: number
823
+ }
824
+
825
+ export interface WorkspaceBinding {
826
+ readonly v: 1
827
+ readonly id: string
828
+ readonly workspaceID: string
829
+ readonly machineID: string
830
+ readonly repo: string
831
+ readonly label?: string
832
+ readonly revision: number
833
+ }
834
+
835
+ export interface WorkspaceMemberRecord {
836
+ readonly v: 1
837
+ readonly id: string
838
+ readonly workspaceID: string
839
+ readonly sessionID: string
840
+ readonly role: WorkspaceRole
841
+ readonly generation: number
842
+ readonly joinedAt: number
843
+ readonly leftAt?: number
844
+ readonly leftReason?: "removed" | "replaced" | "ended" | "archived"
845
+ readonly revision: number
846
+ }
847
+
848
+ export interface WorkspaceMemberView {
849
+ readonly member: WorkspaceMemberRecord
850
+ readonly current: boolean
851
+ readonly presence: WorkspaceSessionPresence
852
+ }
853
+
854
+ export interface WorkspaceAssignmentRecord {
855
+ readonly v: 1
856
+ readonly id: string
857
+ readonly workspaceID: string
858
+ readonly memberID: string
859
+ readonly title: string
860
+ readonly status: WorkspaceAssignmentStatus
861
+ readonly ref?: string
862
+ readonly criteria?: readonly string[]
863
+ readonly revision: number
864
+ readonly createdAt: number
865
+ readonly updatedAt: number
866
+ readonly closedAt?: number
867
+ }
868
+
869
+ export interface WorkspaceAssignmentView {
870
+ readonly assignment: WorkspaceAssignmentRecord
871
+ readonly member: "current" | "left" | "missing"
872
+ readonly presence: WorkspaceSessionPresence
873
+ }
874
+
875
+ export interface WorkspaceSupervisionLink {
876
+ readonly v: 1
877
+ readonly id: string
878
+ readonly workspaceID: string
879
+ readonly supervisorMemberID: string
880
+ readonly memberID: string
881
+ readonly order: number
882
+ readonly revision: number
883
+ }
884
+
885
+ export interface WorkspaceSupervisionGraph {
886
+ readonly workspaceID: string
887
+ /** The graph revision a supervise call names as expectedRevision (0: never written). */
888
+ readonly revision: number
889
+ readonly links: readonly { readonly link: WorkspaceSupervisionLink; readonly member: "current" | "left" | "missing"; readonly supervisor: "current" | "left" | "missing" }[]
890
+ }
891
+
892
+ export interface WorkspaceSupervised {
893
+ readonly workspaceID: string
894
+ readonly revision: number
895
+ readonly change: "attached" | "moved" | "reordered" | "detached" | "unchanged"
896
+ readonly link?: WorkspaceSupervisionLink
897
+ readonly pruned: number
898
+ }
899
+
900
+ export interface WorkspaceDeparture {
901
+ readonly member: WorkspaceMemberRecord
902
+ readonly supervision: { readonly workspaceID: string; readonly revision: number; readonly detached: number; readonly transferred: number; readonly pruned: number }
903
+ }
904
+
905
+ /** GET /workspaces/:id/master (HWA-12): the current master, earlier masters (newest first) and a replacement in flight. */
906
+ export interface WorkspaceMasterView {
907
+ readonly workspaceID: string
908
+ readonly revision: number
909
+ readonly master?: { readonly member: WorkspaceMemberRecord; readonly presence: WorkspaceSessionPresence }
910
+ readonly previous: readonly { readonly member: WorkspaceMemberRecord; readonly presence: WorkspaceSessionPresence }[]
911
+ readonly pending?: { readonly phase: "claimed" | "joined"; readonly at: number; readonly stale: boolean }
912
+ }
913
+
914
+ /** POST /workspaces/:id/master (HWA-12). No grants move; the new master starts from a fresh digest snapshot. */
915
+ export interface WorkspaceMasterReplaced {
916
+ readonly workspace: WorkspaceView
917
+ readonly master: WorkspaceMemberRecord
918
+ readonly presence: WorkspaceSessionPresence
919
+ readonly previous?: WorkspaceMemberRecord
920
+ readonly previousPresence?: WorkspaceSessionPresence
921
+ /** False when the session already was the master. */
922
+ readonly changed: boolean
923
+ readonly supervision?: { readonly workspaceID: string; readonly revision: number; readonly detached: number; readonly transferred: number; readonly pruned: number }
924
+ readonly checkpoints: { readonly inherited: false; readonly previousCheckpoint?: { readonly id: string; readonly supervisorMemberID: string; readonly cursor: string; readonly at: number; readonly generation: number; readonly sessionID: string } }
925
+ readonly grants: { readonly transferred: false; readonly note: string }
926
+ }
927
+
928
+ export interface WorkspaceArchived {
929
+ readonly workspace: WorkspaceView
930
+ readonly departed: number
931
+ readonly supervision: { readonly detached: number }
932
+ }
933
+
934
+ export interface WorkspacePage<T> {
935
+ readonly items: readonly T[]
936
+ readonly cursor?: string
937
+ }
938
+
939
+ /** One section of the board an app may lack the scope for, that this hub doesn't serve, or whose source failed. */
940
+ export type WorkspaceBoardSection<T> =
941
+ | { readonly status: "ok"; readonly items: readonly T[]; readonly truncated: boolean }
942
+ | { readonly status: "omitted"; readonly reason: "scope"; readonly scope: string }
943
+ | { readonly status: "unsupported"; readonly feature: string }
944
+ | { readonly status: "failed" }
945
+
946
+ /**
947
+ * GET /workspaces/:id/board (HWA-10): a read-only, bounded situation snapshot. References and states only; detail
948
+ * text (prompts, Decision questions, blocker summaries, criteria, artifact content) stays behind its own routes.
949
+ */
950
+ export interface WorkspaceBoard {
951
+ readonly v: 1
952
+ readonly at: number
953
+ /** Open assignments without an update for this long carry `no_recent_update`. */
954
+ readonly staleAfterMs: number
955
+ readonly workspace: { readonly id: string; readonly title: string; readonly state: WorkspaceState; readonly revision: number; readonly masterMemberID?: string; readonly createdAt: number; readonly updatedAt: number }
956
+ readonly members: {
957
+ readonly items: readonly {
958
+ readonly memberID: string
959
+ readonly sessionID: string
960
+ readonly role: WorkspaceRole
961
+ readonly generation: number
962
+ readonly revision: number
963
+ readonly joinedAt: number
964
+ readonly master: boolean
965
+ readonly presence: WorkspaceSessionPresence
966
+ /** Absent when the hub doesn't know the session now. */
967
+ readonly session?: { readonly state: string; readonly online: boolean; readonly lastSeen?: number; readonly machineID?: string; readonly name?: string; readonly busySince?: number }
968
+ /** `reported: false`: this hub's catalog doesn't report the session now (not "no blockers"). */
969
+ readonly blockers: { readonly reported: false } | { readonly reported: true; readonly count: number; readonly items: readonly { readonly kind: string; readonly id: string; readonly sessionID?: string }[]; readonly truncated?: true }
970
+ readonly assignments: { readonly open: number; readonly in_progress: number; readonly blocked: number }
971
+ readonly decisions?: number
972
+ readonly stale?: Exclude<WorkspaceSessionPresence, "online">
973
+ }[]
974
+ readonly total: number
975
+ readonly truncated: boolean
976
+ }
977
+ readonly supervision: { readonly revision: number; readonly links: readonly { readonly memberID: string; readonly supervisorMemberID: string; readonly order: number; readonly member: "current" | "left" | "missing"; readonly supervisor: "current" | "left" | "missing" }[]; readonly truncated: boolean }
978
+ readonly assignments: {
979
+ readonly items: readonly {
980
+ readonly id: string
981
+ readonly memberID: string
982
+ readonly title: string
983
+ /** Exactly the stored status: never inferred from idleness or presence. */
984
+ readonly status: WorkspaceAssignmentStatus
985
+ readonly ref?: string
986
+ readonly revision: number
987
+ readonly createdAt: number
988
+ readonly updatedAt: number
989
+ readonly closedAt?: number
990
+ readonly assignee: "current" | "left" | "missing"
991
+ readonly stale?: readonly ("assignee_left" | "assignee_missing" | "assignee_offline" | "no_recent_update")[]
992
+ }[]
993
+ readonly counts: Readonly<Record<WorkspaceAssignmentStatus, number>>
994
+ readonly truncated: boolean
995
+ }
996
+ readonly decisions: WorkspaceBoardSection<{ readonly id: string; readonly state: string; readonly urgency: string; readonly tier: string; readonly at: number; readonly expiresAt: number; readonly deadline?: number; readonly escalatedAt?: number; readonly sessionIDs: readonly string[]; readonly memberIDs: readonly string[]; readonly stale?: "past_deadline" }>
997
+ readonly artifacts: WorkspaceBoardSection<{ readonly id: string; readonly kind: string; readonly visibility: string; readonly state: string; readonly revision: number; readonly latestRevisionID: string; readonly updatedAt: number }>
998
+ }
999
+
1000
+ /** One quoted source message of a side chat's context snapshot (HWA-13). */
1001
+ export interface WorkspaceSideChatItem {
1002
+ readonly kind: "message"
1003
+ readonly id: string
1004
+ readonly role: "user" | "assistant" | "message"
1005
+ readonly at?: number
1006
+ readonly pos?: number
1007
+ readonly text: string
1008
+ readonly bytes: number
1009
+ readonly clipped?: true
1010
+ /** Refresh markers: changed since the previous snapshot, or no longer readable (the previous copy is kept). */
1011
+ readonly changed?: true
1012
+ readonly stale?: "missing"
1013
+ }
1014
+
1015
+ /** A side chat's bounded context snapshot: exactly what the side-chat session was given. */
1016
+ export interface WorkspaceSideChatSnapshot {
1017
+ readonly generation: number
1018
+ readonly takenAt: number
1019
+ readonly mode: "selected" | "recent"
1020
+ readonly items: readonly WorkspaceSideChatItem[]
1021
+ readonly bytes: number
1022
+ readonly omitted: number
1023
+ readonly sourceNewestAt?: number
1024
+ }
1025
+
1026
+ interface WorkspaceSideChatBase {
1027
+ readonly v: 1
1028
+ readonly id: string
1029
+ readonly workspaceID: string
1030
+ /** The side-chat session, once its launch reports it. */
1031
+ readonly sessionID?: string
1032
+ readonly sourceSessionID: string
1033
+ readonly sources: readonly { readonly kind: "message" | "artifact_revision"; readonly id: string }[]
1034
+ readonly launchID: string
1035
+ readonly by: string
1036
+ readonly bindingID: string
1037
+ readonly machineID: string
1038
+ readonly harness: string
1039
+ readonly revision: number
1040
+ readonly createdAt: number
1041
+ readonly updatedAt: number
1042
+ /** ready: the side-chat session exists; launching: its launch is under way (or waiting for the machine). */
1043
+ readonly status: "launching" | "ready" | "failed" | "unknown"
1044
+ readonly launch?: { readonly id: string; readonly state: string; readonly outcome?: string; readonly reason?: string }
1045
+ /** When the hub knows the source's last change: newer than the snapshot = stale. */
1046
+ readonly sourceUpdatedAt?: number
1047
+ readonly stale?: boolean
1048
+ }
1049
+
1050
+ /** A side chat with its snapshot (create, get, refresh). */
1051
+ export interface WorkspaceSideChat extends WorkspaceSideChatBase {
1052
+ readonly snapshot: WorkspaceSideChatSnapshot
1053
+ }
1054
+
1055
+ /** A side chat in a list: the snapshot's summary, without quoted text. */
1056
+ export interface WorkspaceSideChatSummary extends WorkspaceSideChatBase {
1057
+ readonly snapshot: Omit<WorkspaceSideChatSnapshot, "items"> & { readonly count: number }
1058
+ }
1059
+
1060
+ /** How a refreshed context reached the side-chat session (a queued message). */
1061
+ export type WorkspaceSideChatDelivery =
1062
+ | { readonly status: "not_started" }
1063
+ | { readonly status: "sent"; readonly message: unknown; readonly delivery?: string }
1064
+ | { readonly status: "failed"; readonly code: string; readonly message: string }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@huynle/hub-spoke-sdk",
3
- "version": "0.7.4",
3
+ "version": "0.8.0",
4
4
  "private": true,
5
5
  "description": "Harness-neutral spoke building blocks: hub connection, enrollment, tokens, idempotent deliver, prompt references, HarnessSpoke (internal; shipped inside @huynle/hub, not published on its own).",
6
6
  "license": "MIT",
@@ -10,3 +10,4 @@ export * from "./session-tokens.ts"
10
10
  export * from "./session-ids.ts"
11
11
  export * from "./harness-hello.ts"
12
12
  export * from "./harness-spoke.ts"
13
+ export * from "./profile-apply.ts"