@huynle/hub 0.11.0 → 0.11.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 (102) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +19 -5
  3. package/package.json +1 -1
  4. package/packages/claude-code/package.json +1 -1
  5. package/packages/claude-code/src/sessions.ts +4 -1
  6. package/packages/cli/dist/hub-cli.js +240 -83
  7. package/packages/cli/package.json +1 -1
  8. package/packages/cli/src/cli.ts +12 -7
  9. package/packages/cli/src/decisions.ts +52 -4
  10. package/packages/cli/src/format.ts +9 -0
  11. package/packages/cli/src/mcp.ts +20 -4
  12. package/packages/cli/src/team-intake-mcp.ts +2 -1
  13. package/packages/cli/src/teams-mcp.ts +7 -3
  14. package/packages/codex/package.json +1 -1
  15. package/packages/codex/src/sessions.ts +14 -3
  16. package/packages/node/package.json +1 -1
  17. package/packages/preset/package.json +1 -1
  18. package/packages/protocol/package.json +1 -1
  19. package/packages/protocol/schema/api/v1/_root.json +119 -0
  20. package/packages/protocol/schema/api/v1/account.json +419 -0
  21. package/packages/protocol/schema/api/v1/decisions.json +635 -0
  22. package/packages/protocol/schema/api/v1/discovery.json +110 -0
  23. package/packages/protocol/schema/api/v1/events.json +679 -0
  24. package/packages/protocol/schema/api/v1/session-actions.json +660 -0
  25. package/packages/protocol/schema/api/v1/sessions.json +2055 -0
  26. package/packages/protocol/schema/api/v1/speech.json +492 -0
  27. package/packages/protocol/schema/api/v1/unspecified.json +282 -0
  28. package/packages/protocol/schema/app-delivery/v1.schema.json +67 -5
  29. package/packages/protocol/schema/app-front/v1.schema.json +7 -3
  30. package/packages/protocol/src/api-v1.ts +8 -0
  31. package/packages/protocol/src/index.ts +2 -0
  32. package/packages/protocol/src/teams.ts +13 -1
  33. package/packages/runtime-openshell/package.json +1 -1
  34. package/packages/runtime-sdk/package.json +1 -1
  35. package/packages/sdk/package.json +1 -1
  36. package/packages/sdk/src/app-front.ts +2 -2
  37. package/packages/sdk/src/ask-fields.ts +76 -0
  38. package/packages/sdk/src/client.ts +11 -2
  39. package/packages/sdk/src/envelope.ts +3 -2
  40. package/packages/sdk/src/index.ts +1 -0
  41. package/packages/sdk/src/team-intake.ts +6 -0
  42. package/packages/sdk/src/teams.ts +2 -0
  43. package/packages/sdk/src/types.ts +36 -4
  44. package/packages/sdk/src/wake.ts +9 -4
  45. package/packages/spoke-sdk/package.json +1 -1
  46. package/src/central/agent/agent-limits.ts +185 -0
  47. package/src/central/agent/app-decisions.ts +6 -5
  48. package/src/central/agent/decisions.ts +130 -9
  49. package/src/central/agent/delivery.ts +134 -22
  50. package/src/central/agent/errors.ts +2 -1
  51. package/src/central/agent/events.ts +20 -2
  52. package/src/central/agent/launch-engine.ts +6 -3
  53. package/src/central/agent/message-limits.ts +5 -2
  54. package/src/central/agent/message-routes.ts +33 -6
  55. package/src/central/agent/messages.ts +7 -0
  56. package/src/central/agent/placement.ts +4 -2
  57. package/src/central/agent/read-routes.ts +5 -0
  58. package/src/central/agent/routes.ts +2 -0
  59. package/src/central/agent/subscriptions.ts +13 -4
  60. package/src/central/api-v1.ts +115 -0
  61. package/src/central/artifacts/store.ts +4 -2
  62. package/src/central/concierge/board-results.ts +2 -1
  63. package/src/central/concierge/notifier.ts +4 -2
  64. package/src/central/config.ts +20 -6
  65. package/src/central/health/classify.ts +209 -0
  66. package/src/central/http.ts +285 -23
  67. package/src/central/recording/index.ts +2 -1
  68. package/src/central/recording/schema.ts +2 -1
  69. package/src/central/registry.ts +34 -3
  70. package/src/central/teams/budgets.ts +144 -0
  71. package/src/central/teams/hop-stalls.ts +14 -6
  72. package/src/central/teams/intake.ts +38 -6
  73. package/src/central/teams/involvement.ts +95 -0
  74. package/src/central/teams/item-decisions.ts +60 -0
  75. package/src/central/teams/item-eligibility.ts +35 -0
  76. package/src/central/teams/item-stalls.ts +87 -0
  77. package/src/central/teams/loops.ts +40 -0
  78. package/src/central/teams/progress.ts +154 -0
  79. package/src/central/teams/routes.ts +2 -2
  80. package/src/central/teams/store.ts +21 -5
  81. package/src/central/teams/validation.ts +17 -6
  82. package/src/central/workspaces/coordinator.ts +3 -1
  83. package/src/spoke/hub-tool.ts +26 -11
  84. package/web/dist/assets/{ArtifactPage-C5MMqXLN.js → ArtifactPage-TbFJi59n.js} +1 -1
  85. package/web/dist/assets/{ArtifactPane-CXSCmhyJ.js → ArtifactPane-7D-M-Ich.js} +1 -1
  86. package/web/dist/assets/{ArtifactWorkbench-S0RxNGWy.js → ArtifactWorkbench-DlCLEjut.js} +1 -1
  87. package/web/dist/assets/{Diagram-BGmyRb-R.js → Diagram-PvOPetOI.js} +1 -1
  88. package/web/dist/assets/{Library-CFmMbXEa.js → Library-Cdi83pOV.js} +2 -2
  89. package/web/dist/assets/{ProfilesDestination-7jpOc2HM.js → ProfilesDestination-cryC7P6j.js} +1 -1
  90. package/web/dist/assets/TeamsPage-B4xrlGhp.js +1 -0
  91. package/web/dist/assets/Watchtower-60B1JX_J.js +2 -0
  92. package/web/dist/assets/index-DA0gKIzv.js +33 -0
  93. package/web/dist/assets/live-B2IfKm37.js +5 -0
  94. package/web/dist/assets/{live-BUofsQpz.css → live-De-kd3eW.css} +1 -1
  95. package/web/dist/assets/{renderer2d-B2Nh39UN.js → renderer2d-CsZnF3_o.js} +1 -1
  96. package/web/dist/assets/{renderer3d-CTYbljHH.js → renderer3d-BIMA0wFn.js} +2 -2
  97. package/web/dist/assets/{safe-area-CkhEclGo.js → safe-area-dtrnw9NF.js} +1 -1
  98. package/web/dist/index.html +1 -1
  99. package/web/dist/assets/TeamsPage-C1NGrUR-.js +0 -1
  100. package/web/dist/assets/Watchtower-DnvlJaU0.js +0 -2
  101. package/web/dist/assets/index-Da8SKXMw.js +0 -33
  102. package/web/dist/assets/live-BmqLASSo.js +0 -5
@@ -0,0 +1,282 @@
1
+ [
2
+ "DELETE /api/admin/devices/:id",
3
+ "DELETE /api/admin/machines/:id",
4
+ "DELETE /api/agent/app-clients/:id",
5
+ "DELETE /api/agent/app-tokens/:id",
6
+ "DELETE /api/agent/artifact-host/:cap",
7
+ "DELETE /api/agent/artifacts/:id",
8
+ "DELETE /api/agent/connections/:id",
9
+ "DELETE /api/agent/grants/:id",
10
+ "DELETE /api/agent/grants/mine/:id",
11
+ "DELETE /api/agent/launches/:id",
12
+ "DELETE /api/agent/sessions/:addr/profile",
13
+ "DELETE /api/agent/subscriptions/:id",
14
+ "DELETE /api/agent/teams/:id/draft",
15
+ "DELETE /api/agent/teams/:id/draft/edges",
16
+ "DELETE /api/agent/teams/:id/draft/roles/:role",
17
+ "DELETE /api/agent/teams/:id/staffing/:staffing",
18
+ "DELETE /api/agent/webhooks/:id",
19
+ "DELETE /api/agent/workspaces/:id/bindings",
20
+ "DELETE /api/agent/workspaces/:id/members/:member",
21
+ "DELETE /api/machines/:machine",
22
+ "DELETE /api/recordings",
23
+ "DELETE /api/recordings/:id",
24
+ "GET /api/activity/mine",
25
+ "GET /api/admin/agents/acting",
26
+ "GET /api/admin/audit",
27
+ "GET /api/admin/machines",
28
+ "GET /api/admin/users",
29
+ "GET /api/agent/acting",
30
+ "GET /api/agent/agent-limits",
31
+ "GET /api/agent/app-clients",
32
+ "GET /api/agent/app-tokens",
33
+ "GET /api/agent/artifacts",
34
+ "GET /api/agent/artifacts/:id",
35
+ "GET /api/agent/artifacts/:id/jobs",
36
+ "GET /api/agent/artifacts/:id/jobs/:job",
37
+ "GET /api/agent/artifacts/:id/jobs/:job/context",
38
+ "GET /api/agent/artifacts/:id/jobs/:job/proposal",
39
+ "GET /api/agent/artifacts/:id/revisions",
40
+ "GET /api/agent/artifacts/:id/revisions/:rev",
41
+ "GET /api/agent/artifacts/:id/revisions/:rev/blobs/:hash",
42
+ "GET /api/agent/audit",
43
+ "GET /api/agent/concierge",
44
+ "GET /api/agent/concierge/arm",
45
+ "GET /api/agent/concierge/board",
46
+ "GET /api/agent/concierge/decisions",
47
+ "GET /api/agent/concierge/decisions/:id",
48
+ "GET /api/agent/concierge/machines/:id/models",
49
+ "GET /api/agent/concierge/notes",
50
+ "GET /api/agent/concierge/sessions/:addr/forms/:form",
51
+ "GET /api/agent/concierge/watchlist",
52
+ "GET /api/agent/connections",
53
+ "GET /api/agent/decisions",
54
+ "GET /api/agent/decisions/:id",
55
+ "GET /api/agent/events",
56
+ "GET /api/agent/grants",
57
+ "GET /api/agent/grants/:id",
58
+ "GET /api/agent/grants/mine",
59
+ "GET /api/agent/grants/mine/:id",
60
+ "GET /api/agent/graph",
61
+ "GET /api/agent/graph/edges/:id",
62
+ "GET /api/agent/graph/nodes/:id",
63
+ "GET /api/agent/inbox",
64
+ "GET /api/agent/launch-nodes",
65
+ "GET /api/agent/launches",
66
+ "GET /api/agent/launches/:id",
67
+ "GET /api/agent/messages/:id",
68
+ "GET /api/agent/messaging",
69
+ "GET /api/agent/profiles",
70
+ "GET /api/agent/profiles/:id",
71
+ "GET /api/agent/profiles/:id/usage",
72
+ "GET /api/agent/profiles/:id/versions",
73
+ "GET /api/agent/profiles/:id/versions/:version",
74
+ "GET /api/agent/profiles/:id/versions/:version/skills/:name",
75
+ "GET /api/agent/sessions",
76
+ "GET /api/agent/sessions/:addr",
77
+ "GET /api/agent/sessions/:addr/context-cards",
78
+ "GET /api/agent/sessions/:addr/intent",
79
+ "GET /api/agent/sessions/:addr/log",
80
+ "GET /api/agent/sessions/:addr/profile",
81
+ "GET /api/agent/sessions/:addr/profile/skills/:name",
82
+ "GET /api/agent/sessions/:addr/stream",
83
+ "GET /api/agent/side-chats/:chat",
84
+ "GET /api/agent/status",
85
+ "GET /api/agent/subscriptions",
86
+ "GET /api/agent/subscriptions/:id",
87
+ "GET /api/agent/team-settings",
88
+ "GET /api/agent/teams",
89
+ "GET /api/agent/teams/:id",
90
+ "GET /api/agent/teams/:id/diff",
91
+ "GET /api/agent/teams/:id/draft",
92
+ "GET /api/agent/teams/:id/intake",
93
+ "GET /api/agent/teams/:id/intake/:intake",
94
+ "GET /api/agent/teams/:id/roster",
95
+ "GET /api/agent/teams/:id/staffing",
96
+ "GET /api/agent/teams/:id/status",
97
+ "GET /api/agent/teams/:id/versions",
98
+ "GET /api/agent/teams/:id/versions/:version",
99
+ "GET /api/agent/watch",
100
+ "GET /api/agent/webhooks",
101
+ "GET /api/agent/webhooks/:id",
102
+ "GET /api/agent/who",
103
+ "GET /api/agent/whoami",
104
+ "GET /api/agent/workspaces",
105
+ "GET /api/agent/workspaces/:id",
106
+ "GET /api/agent/workspaces/:id/assignments",
107
+ "GET /api/agent/workspaces/:id/assignments/:assignment",
108
+ "GET /api/agent/workspaces/:id/bindings",
109
+ "GET /api/agent/workspaces/:id/board",
110
+ "GET /api/agent/workspaces/:id/checkpoints",
111
+ "GET /api/agent/workspaces/:id/master",
112
+ "GET /api/agent/workspaces/:id/members",
113
+ "GET /api/agent/workspaces/:id/side-chats",
114
+ "GET /api/agent/workspaces/:id/side-chats/:chat",
115
+ "GET /api/agent/workspaces/:id/supervision",
116
+ "GET /api/cli-logins/:code",
117
+ "GET /api/connect/flows/:flow",
118
+ "GET /api/context-cards/primary",
119
+ "GET /api/enrollments",
120
+ "GET /api/intents/mine",
121
+ "GET /api/media/remote",
122
+ "GET /api/media/unfurl",
123
+ "GET /api/push/key",
124
+ "GET /api/push/prefs",
125
+ "GET /api/recordings",
126
+ "GET /api/recordings/:id",
127
+ "GET /api/recordings/:id/clips/:n",
128
+ "GET /api/recordings/:id/export",
129
+ "GET /api/recordings/:id/replies/:n",
130
+ "GET /api/recordings/:id/segments/:n",
131
+ "GET /api/recordings/export",
132
+ "GET /api/sessions/:session/context",
133
+ "GET /api/sessions/:session/context-cards",
134
+ "GET /api/sessions/:session/diff",
135
+ "GET /api/sessions/:session/model-prefs",
136
+ "GET /api/sessions/:session/models",
137
+ "GET /api/sessions/:session/revert/preview",
138
+ "GET /api/sessions/:session/summary",
139
+ "GET /api/settings/agent-limits",
140
+ "GET /api/settings/context-cards",
141
+ "GET /api/settings/media",
142
+ "GET /api/settings/recording",
143
+ "GET /api/settings/speech/default",
144
+ "GET /api/settings/speech/models",
145
+ "GET /api/teams/:id/hops",
146
+ "PATCH /api/agent/app-clients/:id",
147
+ "PATCH /api/agent/artifacts/:id",
148
+ "PATCH /api/agent/profiles/:id",
149
+ "PATCH /api/agent/teams/:id",
150
+ "PATCH /api/agent/workspaces/:id",
151
+ "PATCH /api/agent/workspaces/:id/assignments/:assignment",
152
+ "PATCH /api/agent/workspaces/:id/members/:member",
153
+ "POST /api/agent/app-clients",
154
+ "POST /api/agent/app-clients/:id/secret",
155
+ "POST /api/agent/app-tokens",
156
+ "POST /api/agent/artifacts",
157
+ "POST /api/agent/artifacts/:id/archive",
158
+ "POST /api/agent/artifacts/:id/jobs",
159
+ "POST /api/agent/artifacts/:id/jobs/:job/apply",
160
+ "POST /api/agent/artifacts/:id/jobs/:job/cancel",
161
+ "POST /api/agent/artifacts/:id/jobs/:job/result",
162
+ "POST /api/agent/artifacts/:id/keep",
163
+ "POST /api/agent/artifacts/:id/revisions",
164
+ "POST /api/agent/artifacts/:id/revisions/:rev/host",
165
+ "POST /api/agent/artifacts/:id/unarchive",
166
+ "POST /api/agent/artifacts/blobs",
167
+ "POST /api/agent/ask",
168
+ "POST /api/agent/concierge/arm",
169
+ "POST /api/agent/concierge/disable",
170
+ "POST /api/agent/concierge/enable",
171
+ "POST /api/agent/concierge/grant-requests",
172
+ "POST /api/agent/concierge/proposals",
173
+ "POST /api/agent/concierge/restart",
174
+ "POST /api/agent/concierge/watchlist/mute",
175
+ "POST /api/agent/concierge/watchlist/unmute",
176
+ "POST /api/agent/concierge/watchlist/unwatch",
177
+ "POST /api/agent/concierge/watchlist/watch",
178
+ "POST /api/agent/decisions/:id/answer",
179
+ "POST /api/agent/decline",
180
+ "POST /api/agent/grants",
181
+ "POST /api/agent/grants/requests",
182
+ "POST /api/agent/launches",
183
+ "POST /api/agent/msg",
184
+ "POST /api/agent/mute",
185
+ "POST /api/agent/name",
186
+ "POST /api/agent/profiles",
187
+ "POST /api/agent/profiles/:id/versions",
188
+ "POST /api/agent/profiles/:id/versions/:version/commands/:name/render",
189
+ "POST /api/agent/profiles/import",
190
+ "POST /api/agent/reply",
191
+ "POST /api/agent/roles/claim",
192
+ "POST /api/agent/roles/release",
193
+ "POST /api/agent/sessions/:addr/answer",
194
+ "POST /api/agent/sessions/:addr/approve",
195
+ "POST /api/agent/sessions/:addr/conversation",
196
+ "POST /api/agent/sessions/:addr/deny",
197
+ "POST /api/agent/sessions/:addr/interrupt",
198
+ "POST /api/agent/sessions/:addr/profile/commands/:name/render",
199
+ "POST /api/agent/sessions/:addr/prompt",
200
+ "POST /api/agent/sessions/:addr/stop",
201
+ "POST /api/agent/sessions/:addr/utterance",
202
+ "POST /api/agent/sessions/archive-inactive",
203
+ "POST /api/agent/speech/speak",
204
+ "POST /api/agent/speech/transcribe",
205
+ "POST /api/agent/subscriptions",
206
+ "POST /api/agent/teams",
207
+ "POST /api/agent/teams/:id/archive",
208
+ "POST /api/agent/teams/:id/intake",
209
+ "POST /api/agent/teams/:id/intake/:intake/status",
210
+ "POST /api/agent/teams/:id/pause",
211
+ "POST /api/agent/teams/:id/publish",
212
+ "POST /api/agent/teams/:id/restore",
213
+ "POST /api/agent/teams/:id/resume",
214
+ "POST /api/agent/teams/:id/staffing/:staffing/replace",
215
+ "POST /api/agent/teams/:id/staffing/launch",
216
+ "POST /api/agent/unmute",
217
+ "POST /api/agent/webhooks",
218
+ "POST /api/agent/workspaces",
219
+ "POST /api/agent/workspaces/:id/archive",
220
+ "POST /api/agent/workspaces/:id/assignments",
221
+ "POST /api/agent/workspaces/:id/bindings",
222
+ "POST /api/agent/workspaces/:id/checkpoints",
223
+ "POST /api/agent/workspaces/:id/master",
224
+ "POST /api/agent/workspaces/:id/members",
225
+ "POST /api/agent/workspaces/:id/restore",
226
+ "POST /api/agent/workspaces/:id/side-chats",
227
+ "POST /api/agent/workspaces/:id/side-chats/:chat/refresh",
228
+ "POST /api/agent/workspaces/:id/side-chats/:chat/send",
229
+ "POST /api/cli-logins/:code/approve",
230
+ "POST /api/cli-logins/:code/deny",
231
+ "POST /api/connect/flows/:flow/approve",
232
+ "POST /api/connect/flows/:flow/deny",
233
+ "POST /api/enrollments/:code/approve",
234
+ "POST /api/enrollments/:code/deny",
235
+ "POST /api/machines/:machine/sessions",
236
+ "POST /api/push/subscribe",
237
+ "POST /api/push/test",
238
+ "POST /api/push/unsubscribe",
239
+ "POST /api/recordings",
240
+ "POST /api/recordings/:id/end",
241
+ "POST /api/recordings/:id/events",
242
+ "POST /api/sessions/:session/context-cards/:provider/refresh",
243
+ "POST /api/sessions/:session/error/dismiss",
244
+ "POST /api/sessions/:session/fork",
245
+ "POST /api/sessions/:session/previews",
246
+ "POST /api/sessions/:session/rename",
247
+ "POST /api/sessions/:session/revert",
248
+ "POST /api/sessions/:session/revert/commit",
249
+ "POST /api/sessions/:session/revert/restore",
250
+ "POST /api/settings/speech/test",
251
+ "POST /api/teams/:id/renew",
252
+ "PUT /api/admin/agents/acting",
253
+ "PUT /api/admin/users/:id",
254
+ "PUT /api/agent/acting",
255
+ "PUT /api/agent/artifacts/:id/visibility",
256
+ "PUT /api/agent/concierge/notes",
257
+ "PUT /api/agent/concierge/settings",
258
+ "PUT /api/agent/concierge/watchlist",
259
+ "PUT /api/agent/grants/:id",
260
+ "PUT /api/agent/messaging",
261
+ "PUT /api/agent/sessions/:addr/archive",
262
+ "PUT /api/agent/sessions/:addr/intent",
263
+ "PUT /api/agent/sessions/:addr/pin",
264
+ "PUT /api/agent/sessions/:addr/profile",
265
+ "PUT /api/agent/team-settings",
266
+ "PUT /api/agent/teams/:id/draft/edges",
267
+ "PUT /api/agent/teams/:id/draft/roles/:role",
268
+ "PUT /api/agent/teams/:id/staffing",
269
+ "PUT /api/agent/workspaces/:id/supervision",
270
+ "PUT /api/push/mute",
271
+ "PUT /api/push/prefs",
272
+ "PUT /api/recordings/:id/clips/:n",
273
+ "PUT /api/recordings/:id/segments/:n",
274
+ "PUT /api/sessions/:session/model",
275
+ "PUT /api/sessions/:session/model-prefs",
276
+ "PUT /api/settings/agent-limits",
277
+ "PUT /api/settings/context-cards",
278
+ "PUT /api/settings/media",
279
+ "PUT /api/settings/recording",
280
+ "PUT /api/settings/speech",
281
+ "PUT /api/settings/speech/default"
282
+ ]
@@ -99,7 +99,10 @@
99
99
  "description": "How a message reached its recipient session (Delivery in src/central/agent/messages.ts). `expired`: pending past PENDING_MS or (H2) past its notAfter; never delivered (see expiredReason).",
100
100
  "enum": ["steer", "turn", "queued", "retained", "pending", "dropped", "expired"]
101
101
  },
102
- "WakeSkip": { "enum": ["wake_false", "subagent", "hop_cap", "budget"] },
102
+ "WakeSkip": {
103
+ "description": "Why delivery did not wake. item_budget and item_loop were added by team-autonomy phase 2; clients must treat an unknown value as delivered, not woken.",
104
+ "enum": ["wake_false", "subagent", "hop_cap", "budget", "item_budget", "item_loop"]
105
+ },
103
106
  "Message": {
104
107
  "type": "object",
105
108
  "required": ["id", "kind", "from", "to", "recipient", "text", "state", "at", "hop"],
@@ -138,7 +141,7 @@
138
141
  }
139
142
  },
140
143
  "AskRequest": {
141
- "description": "POST /api/agent/ask (scope messages:send). Optional header Idempotency-Key (H1, per sender). expiresIn and wait are seconds (wait at most 60, never with all). The Decision fields (options … about) are for asks to @me only.",
144
+ "description": "POST /api/agent/ask (scope messages:send). Optional header Idempotency-Key (H1, per sender). expiresIn and wait are seconds (wait at most 60, never with all). The Decision fields (options … about, choices, kind) are for asks to @me only. (DC-1) choices and options are exclusive.",
142
145
  "type": "object",
143
146
  "required": ["to", "text"],
144
147
  "additionalProperties": false,
@@ -156,6 +159,8 @@
156
159
  "items": { "type": "object", "required": ["id", "label"], "properties": { "id": { "type": "string" }, "label": { "type": "string" }, "safe": { "type": "boolean" } } }
157
160
  },
158
161
  "recommendation": { "type": "object", "required": ["option"], "properties": { "option": { "type": "string" }, "why": { "type": "string" } } },
162
+ "choices": { "type": "array", "minItems": 1, "maxItems": 12, "items": { "$ref": "#/$defs/DecisionChoice" } },
163
+ "kind": { "$ref": "#/$defs/DecisionKind" },
159
164
  "urgency": { "$ref": "#/$defs/DecisionUrgency" },
160
165
  "tier": { "$ref": "#/$defs/DecisionTier" },
161
166
  "deadline": { "type": "number", "minimum": 0 },
@@ -171,7 +176,8 @@
171
176
  "message": { "$ref": "#/$defs/Message" },
172
177
  "delivery": { "$ref": "#/$defs/MessageDelivery" },
173
178
  "woke": { "type": "boolean" },
174
- "wakeSkipped": { "$ref": "#/$defs/WakeSkip" }
179
+ "wakeSkipped": { "$ref": "#/$defs/WakeSkip" },
180
+ "wakeLimit": { "type": "number", "minimum": 1, "description": "Effective hop cap, wake budget or item wake budget when the hub provides one." }
175
181
  }
176
182
  },
177
183
  "SentAll": {
@@ -214,6 +220,8 @@
214
220
  "items": { "type": "object", "required": ["id", "label"], "properties": { "id": { "type": "string" }, "label": { "type": "string" }, "safe": { "const": true } } }
215
221
  },
216
222
  "recommendation": { "type": "object", "required": ["option"], "properties": { "option": { "type": "string" }, "why": { "type": "string" } } },
223
+ "choices": { "type": "array", "items": { "$ref": "#/$defs/DecisionChoice" } },
224
+ "kind": { "$ref": "#/$defs/DecisionKind" },
217
225
  "urgency": { "$ref": "#/$defs/DecisionUrgency" },
218
226
  "tier": { "$ref": "#/$defs/DecisionTier" },
219
227
  "deadline": { "$ref": "#/$defs/EpochMs" },
@@ -226,9 +234,37 @@
226
234
  "type": "object",
227
235
  "required": ["answeredBy", "at"],
228
236
  "description": "answeredBy: user (the console), timeout (the safe onTimeout option), or (AF4, app-front contract §6) app: an app holding decisions:answer answered a confirm-tier Decision of its own session; then `app` is its party (app:<tokenID>) and `appLabel` its label.",
229
- "properties": { "option": { "type": "string" }, "text": { "type": "string" }, "answeredBy": { "enum": ["user", "timeout", "app"] }, "at": { "$ref": "#/$defs/EpochMs" }, "app": { "type": "string", "pattern": "^app:[0-9a-f]{16}$" }, "appLabel": { "type": "string" } }
237
+ "properties": {
238
+ "option": { "type": "string" },
239
+ "picks": { "description": "(DC-1) A choices Decision: the option picked in each group.", "type": "object", "additionalProperties": { "type": "string" } },
240
+ "text": { "type": "string" },
241
+ "answeredBy": { "enum": ["user", "timeout", "app"] },
242
+ "at": { "$ref": "#/$defs/EpochMs" },
243
+ "deviceID": { "type": "string" },
244
+ "via": { "enum": ["voice", "text"] },
245
+ "app": { "type": "string", "pattern": "^app:[0-9a-f]{16}$" },
246
+ "appLabel": { "type": "string" }
247
+ }
230
248
  },
231
- "closed": { "type": "object", "required": ["at", "reason"], "properties": { "at": { "$ref": "#/$defs/EpochMs" }, "reason": { "type": "string" } } }
249
+ "closed": { "type": "object", "required": ["at", "reason"], "properties": { "at": { "$ref": "#/$defs/EpochMs" }, "reason": { "type": "string" } } },
250
+ "action": { "description": "Set by the hub only (an escalated permission, a grant request, a Concierge proposal, a team hop renewal): its kind and subject.", "type": "object", "required": ["kind"], "properties": { "kind": { "type": "string" } } },
251
+ "as": { "description": "concierge: asked by one of the user's Concierge sessions.", "const": "concierge" }
252
+ }
253
+ },
254
+ "DecisionKind": { "description": "(DC-1) What an ask is about (absent: a question).", "enum": ["question", "plan", "merge", "release"] },
255
+ "DecisionChoice": {
256
+ "description": "(DC-1) One point of a structured ask to @me: 2–6 options, at most one recommended. The answer picks one option per group.",
257
+ "type": "object",
258
+ "required": ["id", "label", "options"],
259
+ "properties": {
260
+ "id": { "type": "string", "pattern": "^[A-Za-z0-9_-]{1,8}$" },
261
+ "label": { "type": "string", "minLength": 1 },
262
+ "options": {
263
+ "type": "array",
264
+ "minItems": 2,
265
+ "maxItems": 6,
266
+ "items": { "type": "object", "required": ["id", "label"], "properties": { "id": { "type": "string" }, "label": { "type": "string" }, "recommended": { "type": "boolean" } } }
267
+ }
232
268
  }
233
269
  },
234
270
 
@@ -418,6 +454,18 @@
418
454
  "lastError": { "type": "string" },
419
455
  "updatedAt": { "$ref": "#/$defs/EpochMs" },
420
456
  "routedAt": { "$ref": "#/$defs/EpochMs" },
457
+ "startedAt": { "$ref": "#/$defs/EpochMs", "description": "First active work; pauses and blocked time continue counting against wall-clock budget." },
458
+ "parentItem": { "type": "string", "description": "Optional root item of a session-submitted child; old stored rows omit this field." },
459
+ "usage": {
460
+ "type": "object", "required": ["wakes", "hours", "generation", "budget"], "additionalProperties": false,
461
+ "properties": {
462
+ "wakes": { "type": "integer", "minimum": 0 }, "hours": { "type": "number", "minimum": 0 },
463
+ "generation": { "type": "integer", "minimum": 1 },
464
+ "chargedTo": { "type": "string" },
465
+ "budget": { "type": "object", "required": ["wakes", "hours"], "additionalProperties": false,
466
+ "properties": { "wakes": { "type": "integer", "minimum": 1 }, "hours": { "type": "integer", "minimum": 1 } } }
467
+ }
468
+ },
421
469
  "closedAt": { "$ref": "#/$defs/EpochMs" },
422
470
  "notAfter": { "description": "(H4) As submitted.", "$ref": "#/$defs/EpochMs" }
423
471
  }
@@ -459,6 +507,20 @@
459
507
  }
460
508
  },
461
509
 
510
+ "TeamItemBudgetExhaustedSubject": {
511
+ "description": "Journal-only team.item_budget.exhausted; webhook subscriptions cannot select this type.",
512
+ "type": "object", "required": ["teamID"], "additionalProperties": false,
513
+ "properties": { "teamID": { "type": "string" }, "intakeID": { "type": "string" } }
514
+ },
515
+ "TeamItemBudgetExhaustedData": {
516
+ "description": "One exhaustion per item/reason/generation or once per unattributed pool's rolling 24 h; flat and text-free.",
517
+ "type": "object", "required": ["scope", "reason", "limit", "usageWakes", "usageHours"], "additionalProperties": false,
518
+ "properties": {
519
+ "scope": { "enum": ["item", "pool"] }, "reason": { "enum": ["wakes", "hours"] },
520
+ "limit": { "type": "number", "minimum": 1 }, "usageWakes": { "type": "integer", "minimum": 0 }, "usageHours": { "type": "number", "minimum": 0 }
521
+ }
522
+ },
523
+
462
524
  "LaunchChangedSubject": {
463
525
  "type": "object",
464
526
  "required": ["launchID"],
@@ -394,17 +394,21 @@
394
394
 
395
395
  "AnswerableTier": { "description": "(AF4) APP_ANSWERABLE_TIERS: the only tier an app answers (decision w6lfbfaq 1).", "enum": ["confirm"] },
396
396
  "DecisionAnswerRequest": {
397
- "description": "(AF4) POST /api/agent/decisions/:id/answer (decisions:answer): an option (and an optional note) or text for a Decision without options. No `via`.",
397
+ "description": "(AF4) POST /api/agent/decisions/:id/answer (decisions:answer): an option (and an optional note) or text for a Decision without options. (DC-1) A choices Decision: picks, one option per group (and an optional note), or text alone. No `via`.",
398
398
  "type": "object",
399
399
  "additionalProperties": false,
400
400
  "minProperties": 1,
401
- "properties": { "option": { "type": "string", "minLength": 1, "maxLength": 64 }, "text": { "type": "string", "minLength": 1, "maxLength": 4000 } }
401
+ "properties": {
402
+ "option": { "type": "string", "minLength": 1, "maxLength": 64 },
403
+ "picks": { "type": "object", "minProperties": 1, "maxProperties": 12, "additionalProperties": { "type": "string", "minLength": 1, "maxLength": 64 } },
404
+ "text": { "type": "string", "minLength": 1, "maxLength": 4000 }
405
+ }
402
406
  },
403
407
  "AppDecisionAnswer": {
404
408
  "description": "(AF4) Decision.answer when an app answered: answeredBy app, the app's party and (when it has one) its label.",
405
409
  "type": "object",
406
410
  "required": ["answeredBy", "app", "at"],
407
- "properties": { "answeredBy": { "const": "app" }, "app": { "type": "string", "pattern": "^app:[0-9a-f]{16}$" }, "appLabel": { "type": "string" }, "option": { "type": "string" }, "text": { "type": "string" }, "at": { "$ref": "#/$defs/EpochMs" } }
411
+ "properties": { "answeredBy": { "const": "app" }, "app": { "type": "string", "pattern": "^app:[0-9a-f]{16}$" }, "appLabel": { "type": "string" }, "option": { "type": "string" }, "picks": { "type": "object", "additionalProperties": { "type": "string" } }, "text": { "type": "string" }, "at": { "$ref": "#/$defs/EpochMs" } }
408
412
  }
409
413
  }
410
414
  }
@@ -0,0 +1,8 @@
1
+ // The versioned HTTP API (docs/plans/2026-11-12-api-v1-contract-and-native-app-design.md): what native apps and
2
+ // other clients build against. Pure: names shared by the hub, the spec tooling and the tests.
3
+
4
+ /**
5
+ * Every route of the hub's console and agent tables also answers under this prefix: `/api/v1/<rest>` is
6
+ * `/api/<rest>`, with one error envelope (`{error: {code, message, ...}}`). v1 changes only by addition.
7
+ */
8
+ export const API_V1_PREFIX = "/api/v1"
@@ -14,6 +14,7 @@
14
14
  // preset.ts (the harness preset schema), webhook.ts (webhook signature format), wire.ts (shared frame primitives).
15
15
  // Workspaces/artifacts/teams contracts (HWA-02, docs/plans/2026-10-30-hub-workspaces-artifacts-contracts.md):
16
16
  // contracts.ts (shared IDs, limits, revisions), hub-workspace.ts, profiles.ts, artifacts.ts, teams.ts, graph.ts.
17
+ // The versioned HTTP API (docs/plans/2026-11-12-api-v1-contract-and-native-app-design.md): api-v1.ts.
17
18
  // All are re-exported here.
18
19
 
19
20
  import { featureList, HUB_URL_MAX_CHARS, ID, isByeFrame as isBye, isHubURL, isObj, isResFrame as isRes, isWelcomeFrame, MAX_FRAME, MAX_RETRY_AFTER_MS, nonEmpty, num, parseFrame as parse, str, token, UNSAFE_CHARS } from "./wire.ts"
@@ -43,6 +44,7 @@ export * from "./teams.ts"
43
44
  export * from "./graph.ts"
44
45
  export * from "./voice-recording.ts"
45
46
  export * from "./app-front.ts"
47
+ export * from "./api-v1.ts"
46
48
  export { HUB_URL_MAX_CHARS, isHubURL, MAX_FRAME, MAX_RETRY_AFTER_MS }
47
49
 
48
50
  export const PROTOCOL_VERSION = 1
@@ -19,9 +19,18 @@ export const TEAM_LIMITS = {
19
19
  sessionsPerRole: 16,
20
20
  /** Open intake items per team. */
21
21
  openIntakePerTeam: 500,
22
- hopLimit: 100,
22
+ /** The absolute bound of any hop limit (HOP_ABSOLUTE_MAX); the hub checks team limits against its live ceiling. */
23
+ hopLimit: 10_000,
23
24
  } as const
24
25
 
26
+ /**
27
+ * (HC-1) The largest hop limit or ceiling a hub accepts: about 10 days of continuous agent-only traffic at the rate
28
+ * #hub-dev measured (~40 hops/h). Past that a cap bounds nothing; "no cap" would be its own decision.
29
+ */
30
+ export const HOP_ABSOLUTE_MAX = 10_000
31
+ /** (HC-1) The hub's hop ceiling when neither hub.json nor the console sets one. */
32
+ export const HOP_CEILING_DEFAULT = 100
33
+
25
34
  export const TEAM_SCOPES = ["teams:read", "teams:manage", "teams:submit"] as const
26
35
 
27
36
  /** "open": every member may msg/ask every other member (two-way). "restricted": only the drawn edges. */
@@ -57,6 +66,9 @@ export interface TeamDefinition extends Revisioned {
57
66
  readonly mode: TeamMode
58
67
  /** Optional limit for messages between this team's seated sessions. */
59
68
  readonly hopLimit?: number
69
+ /** Optional per-team overrides of the hub-wide item budget and no-progress loop threshold. */
70
+ readonly itemBudget?: { readonly wakes: number; readonly hours: number }
71
+ readonly loop?: { readonly messages: number; readonly windowMinutes: number }
60
72
  readonly roles: readonly TeamRole[]
61
73
  /** The intake role; receives submitted work and supervises members (via workspace supervision). */
62
74
  readonly entryRole: string
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@huynle/hub-runtime-openshell",
3
- "version": "0.11.0",
3
+ "version": "0.11.1",
4
4
  "private": true,
5
5
  "description": "The `openshell` runtime of `hub node`: launches in NVIDIA OpenShell sandboxes, driven through the openshell CLI (internal; shipped inside @huynle/hub, not published on its own).",
6
6
  "license": "MIT",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@huynle/hub-runtime-sdk",
3
- "version": "0.11.0",
3
+ "version": "0.11.1",
4
4
  "private": true,
5
5
  "description": "The Runtime interface `hub node` runtimes implement (internal; shipped inside @huynle/hub, not published on its own).",
6
6
  "license": "MIT",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@huynle/hub-sdk",
3
- "version": "0.11.0",
3
+ "version": "0.11.1",
4
4
  "private": true,
5
5
  "description": "Typed client for the hub's /api/agent (internal; shipped inside @huynle/hub, not published on its own).",
6
6
  "license": "MIT",
@@ -383,8 +383,8 @@ export interface AppConversation {
383
383
  export interface AppDecisions {
384
384
  /** The Decisions this app may answer (`state: "open"` for the waiting ones), newest first. */
385
385
  list(options?: Signal & { readonly state?: DecisionState }): Promise<{ decisions: Decision[] }>
386
- /** Answers one with a once-off option or text; the first answer wins (410 gone after). */
387
- answer(id: string, answer: Signal & { readonly option?: string; readonly text?: string }): Promise<DecisionAnswered>
386
+ /** Answers one with a once-off option, picks (one option per choice group) or text; the first answer wins (410 gone after). */
387
+ answer(id: string, answer: Signal & { readonly option?: string; readonly text?: string; readonly picks?: Readonly<Record<string, string>> }): Promise<DecisionAnswered>
388
388
  }
389
389
 
390
390
  export function speechApi(hub: AppFrontTransport): AppSpeech {
@@ -0,0 +1,76 @@
1
+ // (DC-4) The Decision fields an agent tool sends with an ask to @me (the OpenCode hub tool, the MCP ask tool): their
2
+ // JSON Schema properties and a shape check of a tool call's arguments. The hub validates the values (ids, sizes,
3
+ // one recommendation per group, choices xor options; non-@me asks are 400); this only refuses wrong types early.
4
+ import type { AskOptions } from "./client.ts"
5
+ import type { DecisionKind, DecisionTier, DecisionUrgency } from "./types.ts"
6
+
7
+ const URGENCIES: readonly DecisionUrgency[] = ["low", "normal", "high"]
8
+ const TIERS: readonly DecisionTier[] = ["confirm", "tap"]
9
+ const KINDS: readonly DecisionKind[] = ["question", "plan", "merge", "release"]
10
+
11
+ /** JSON Schema properties of the ask @me Decision fields. */
12
+ export const DECISION_ASK_PROPERTIES = {
13
+ options: {
14
+ type: "array",
15
+ items: { type: "object", properties: { id: { type: "string" }, label: { type: "string" }, safe: { type: "boolean" } }, required: ["id", "label"] },
16
+ description: "ask @me: buttons the user taps, [{id, label, safe?}] (at most 8; id: lowercase letters, digits, - or _).",
17
+ },
18
+ recommendation: {
19
+ type: "object",
20
+ properties: { option: { type: "string" }, why: { type: "string" } },
21
+ required: ["option"],
22
+ description: "ask @me: the option you recommend ({option: an option id, why}).",
23
+ },
24
+ urgency: { type: "string", enum: [...URGENCIES], description: "ask @me: low, normal (default) or high." },
25
+ tier: { type: "string", enum: [...TIERS], description: "ask @me: confirm (default: any of the user's devices) or tap (only the signed-in console)." },
26
+ kind: { type: "string", enum: [...KINDS], description: "ask @me: what it is about: question (default), plan, merge or release." },
27
+ choices: {
28
+ type: "array",
29
+ items: {
30
+ type: "object",
31
+ properties: {
32
+ id: { type: "string" },
33
+ label: { type: "string" },
34
+ options: { type: "array", items: { type: "object", properties: { id: { type: "string" }, label: { type: "string" }, recommended: { type: "boolean" } }, required: ["id", "label"] } },
35
+ },
36
+ required: ["id", "label", "options"],
37
+ },
38
+ description: 'ask @me: several points in one ask, instead of options: [{id: "D1", label, options: [{id, label, recommended?}]}], 1–12 groups of 2–6 options, at most one recommended each; the user picks one option per group and you get one "D1: label [a]" line per group.',
39
+ },
40
+ } as const
41
+
42
+ export const DECISION_ASK_FIELDS = Object.keys(DECISION_ASK_PROPERTIES) as readonly (keyof typeof DECISION_ASK_PROPERTIES)[]
43
+
44
+ const isObject = (v: unknown): v is Record<string, unknown> => typeof v === "object" && v !== null && !Array.isArray(v)
45
+
46
+ /**
47
+ * The Decision fields of a tool call's arguments (only those given), shape-checked. Throws an Error naming the field
48
+ * for a wrong type; the hub checks the values.
49
+ */
50
+ export function decisionAskFields(args: Readonly<Record<string, unknown>>): Partial<AskOptions> {
51
+ const out: Record<string, unknown> = {}
52
+ const { options, recommendation, urgency, tier, kind, choices } = args
53
+ const objects = (value: unknown, field: string, shape: string) => {
54
+ if (!Array.isArray(value) || !value.every(isObject)) throw new Error(`"${field}" must be a list of ${shape}`)
55
+ return value
56
+ }
57
+ if (options !== undefined) out.options = objects(options, "options", "{id, label, safe?}")
58
+ if (recommendation !== undefined) {
59
+ if (!isObject(recommendation) || typeof recommendation.option !== "string") throw new Error(`"recommendation" must be {option, why?}`)
60
+ out.recommendation = recommendation
61
+ }
62
+ const oneOf = (value: unknown, field: string, allowed: readonly string[]) => {
63
+ if (value === undefined) return
64
+ if (typeof value !== "string" || !allowed.includes(value)) throw new Error(`"${field}" is one of ${allowed.join(", ")}`)
65
+ out[field] = value
66
+ }
67
+ oneOf(urgency, "urgency", URGENCIES)
68
+ oneOf(tier, "tier", TIERS)
69
+ oneOf(kind, "kind", KINDS)
70
+ if (choices !== undefined) {
71
+ const shape = "{id, label, options: [{id, label, recommended?}]}"
72
+ for (const group of objects(choices, "choices", shape)) objects(group.options, "choices", shape)
73
+ out.choices = choices
74
+ }
75
+ return out as Partial<AskOptions>
76
+ }