@huynle/hub 0.9.2 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +296 -0
- package/Dockerfile +5 -1
- package/README.md +152 -77
- package/package.json +7 -4
- package/packages/claude-code/package.json +1 -1
- package/packages/cli/dist/hub-cli.js +532 -261
- package/packages/cli/package.json +1 -1
- package/packages/cli/src/cli.ts +36 -1
- package/packages/cli/src/subscriptions.ts +49 -0
- package/packages/codex/package.json +1 -1
- package/packages/codex/src/sessions.ts +10 -6
- package/packages/codex/src/spoke.ts +2 -1
- package/packages/node/package.json +1 -1
- package/packages/preset/package.json +1 -1
- package/packages/protocol/package.json +1 -1
- package/packages/protocol/schema/app-delivery/v1.schema.json +7 -5
- package/packages/protocol/schema/app-front/v1.schema.json +410 -0
- package/packages/protocol/src/app-front.ts +293 -0
- package/packages/protocol/src/concierge.ts +10 -1
- package/packages/protocol/src/context-cards.ts +59 -0
- package/packages/protocol/src/credentials.ts +39 -2
- package/packages/protocol/src/index.ts +69 -11
- package/packages/runtime-openshell/package.json +1 -1
- package/packages/runtime-sdk/package.json +1 -1
- package/packages/sdk/package.json +1 -1
- package/packages/sdk/src/app-front.ts +524 -0
- package/packages/sdk/src/client.ts +37 -4
- package/packages/sdk/src/envelope.ts +9 -2
- package/packages/sdk/src/errors.ts +1 -0
- package/packages/sdk/src/index.ts +1 -0
- package/packages/sdk/src/types.ts +39 -1
- package/packages/spoke-sdk/package.json +1 -1
- package/packages/spoke-sdk/src/delivery.ts +17 -1
- package/packages/spoke-sdk/src/harness-spoke.ts +4 -3
- package/packages/voice-client/README.md +146 -0
- package/packages/voice-client/src/vad/LICENSE +21 -0
- package/src/central/agent/acting-routes.ts +17 -3
- package/src/central/agent/acting.ts +12 -3
- package/src/central/agent/actor.ts +6 -1
- package/src/central/agent/app-decisions.ts +146 -0
- package/src/central/agent/app-tokens.ts +9 -3
- package/src/central/agent/audit.ts +3 -0
- package/src/central/agent/connect-routes.ts +64 -0
- package/src/central/agent/connect.ts +868 -0
- package/src/central/agent/conversation.ts +625 -0
- package/src/central/agent/decisions.ts +71 -6
- package/src/central/agent/delivery.ts +3 -0
- package/src/central/agent/directory.ts +5 -0
- package/src/central/agent/errors.ts +2 -0
- package/src/central/agent/grant-requests.ts +46 -9
- package/src/central/agent/grants.ts +13 -2
- package/src/central/agent/message-limits.ts +2 -2
- package/src/central/agent/message-routes.ts +13 -4
- package/src/central/agent/read-routes.ts +16 -2
- package/src/central/agent/routes.ts +108 -6
- package/src/central/agent/speech-routes.ts +316 -0
- package/src/central/agent/subscription-routes.ts +174 -0
- package/src/central/agent/subscriptions.ts +980 -0
- package/src/central/agent-data.ts +43 -1
- package/src/central/backend/memory-counters.ts +2 -2
- package/src/central/backend/types.ts +5 -2
- package/src/central/backend/valkey-counters.ts +5 -4
- package/src/central/client-address.ts +16 -0
- package/src/central/concierge/board-render.ts +52 -1
- package/src/central/concierge/board-routes.ts +25 -2
- package/src/central/concierge/board.ts +11 -1
- package/src/central/concierge/context.ts +29 -8
- package/src/central/concierge/grant-request.ts +216 -0
- package/src/central/concierge/prompt.ts +49 -2
- package/src/central/concierge/session-routes.ts +3 -0
- package/src/central/config.ts +64 -3
- package/src/central/context-cards/budget.ts +121 -0
- package/src/central/context-cards/changes.ts +29 -0
- package/src/central/context-cards/config.ts +145 -0
- package/src/central/context-cards/engine.ts +379 -0
- package/src/central/context-cards/inputs.ts +111 -0
- package/src/central/context-cards/pickup.ts +134 -0
- package/src/central/context-cards/registry.ts +89 -0
- package/src/central/context-cards/routes.ts +37 -0
- package/src/central/context-cards/service.ts +367 -0
- package/src/central/context-cards/store.ts +67 -0
- package/src/central/context-cards/types.ts +88 -0
- package/src/central/fetcher.ts +10 -2
- package/src/central/front-relays.ts +423 -0
- package/src/central/http.ts +516 -56
- package/src/central/human-activity.ts +453 -0
- package/src/central/llm/chat.ts +133 -0
- package/src/central/registry.ts +162 -9
- package/src/central/speech/service.ts +9 -0
- package/src/central/spoke-ws.ts +13 -0
- package/src/central/store.ts +9 -12
- package/src/config.ts +30 -13
- package/src/index.ts +45 -43
- package/src/rpc.ts +33 -4
- package/src/spoke/concierge-agent.ts +1 -1
- package/src/spoke/conversation.ts +6 -75
- package/src/spoke/deliver.ts +18 -8
- package/src/spoke/hub-rpc.ts +7 -2
- package/src/spoke/hub-tool-subscriptions.ts +82 -0
- package/src/spoke/hub-tool-title.ts +89 -0
- package/src/spoke/hub-tool.ts +56 -18
- package/src/spoke/session-context.ts +10 -2
- package/src/spoke/spoke.ts +209 -238
- package/src/spoke/summary.ts +36 -1
- package/src/spoke/taskboard.ts +5 -6
- package/src/spoke/voice-front.ts +323 -30
- package/src/spoke/who-teams.ts +51 -2
- package/src/tui-messaging.ts +24 -10
- package/src/tui.ts +6 -6
- package/web/dist/assets/{ArtifactPage-BKISN-Qj.js → ArtifactPage-Bz992ZX6.js} +1 -1
- package/web/dist/assets/{ArtifactPane-CKOiAUNH.js → ArtifactPane-BJZNixJC.js} +1 -1
- package/web/dist/assets/ArtifactWorkbench-Dti3VP07.js +6 -0
- package/web/dist/assets/{Diagram-vdpmp3xu.css → Diagram-7y8kg3og.css} +1 -1
- package/web/dist/assets/Diagram-B4HdUxw0.js +1 -0
- package/web/dist/assets/{Library-CcjZXsfF.js → Library-CpzD2HDW.js} +2 -2
- package/web/dist/assets/Lightbox-BlbivCkd.js +1 -0
- package/web/dist/assets/MediaImage-Ch3ryIdR.js +4 -0
- package/web/dist/assets/ProfilesDestination-DlQeogea.js +1 -0
- package/web/dist/assets/{TeamsPage-CDyjrK-I.js → TeamsPage-4xdWCaI-.js} +1 -1
- package/web/dist/assets/{Watchtower-DYwoUWME.js → Watchtower-CpQdIRns.js} +2 -2
- package/web/dist/assets/{artifacts-DU9uRgs1.js → artifacts-But87Jfo.js} +1 -1
- package/web/dist/assets/index-BC0O1gfR.css +1 -0
- package/web/dist/assets/index-BS6lA6fN.js +33 -0
- package/web/dist/assets/{live-ycZ_aQZO.js → live-CVUFIdUX.js} +2 -2
- package/web/dist/assets/{pane-layout-0RrLHGDr.js → pane-layout-BW0RdX5a.js} +1 -1
- package/web/dist/assets/{renderer2d-BIwbgRoX.js → renderer2d-IaJiloPi.js} +1 -1
- package/web/dist/assets/{renderer3d-C1a7eYGQ.js → renderer3d-ZiD1uKGW.js} +1 -1
- package/web/dist/assets/{safe-area-DHhPy61Q.js → safe-area-C7IAlkgY.js} +1 -1
- package/web/dist/assets/{source-BWlvaODa.js → source-C-De8L3s.js} +1 -1
- package/web/dist/assets/toast-CXTwTvDw.js +9 -0
- package/web/dist/assets/vad-worklet-BfxJhrXy.js +27 -0
- package/web/dist/index.html +6 -6
- package/web/dist/assets/ArtifactWorkbench-d7R1o-rs.js +0 -6
- package/web/dist/assets/Diagram-azw2B_QH.js +0 -1
- package/web/dist/assets/Lightbox-Du56MXGX.js +0 -1
- package/web/dist/assets/MediaImage-Ds_JUx3a.js +0 -4
- package/web/dist/assets/ProfilesDestination-DdzbzmeK.js +0 -1
- package/web/dist/assets/index-C3ci5UtY.css +0 -1
- package/web/dist/assets/index-LNMZCqgE.js +0 -36
- package/web/dist/assets/toast-Cuu2K0hx.js +0 -1
- /package/{web/src/voice → packages/voice-client/src}/turn/LICENSE +0 -0
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://github.com/huynle/hub/packages/protocol/schema/app-front/v1.schema.json",
|
|
4
|
+
"title": "Hub app-front surface",
|
|
5
|
+
"description": "What an external app (a server-side BFF) uses to make Hub its AI layer: connect with consent (AF1), speech (AF2), the conversation stream (AF3), answering its own sessions' confirm-tier Decisions (AF4), frozen by docs/plans/2026-11-08-app-front-contracts.md. Every definition lives in $defs; validate against `<$id>#/$defs/<Name>`. A description starting `(AFn)` is contract-only until that task ships its `/.well-known/hub` feature flag (APP_FRONT_SHIPPED in packages/protocol/src/app-front.ts); `(shipped)` validates today's hub. REST views are open (clients ignore unknown fields); stream frames are closed allowlists: a new frame field needs a schema change in the same commit. OAuth endpoints (`/connect/*`) answer the OAuth error body (TokenErrorResponse), never the /api/agent ErrorBody. `schemaVersion` is an annotation keyword (ajv.addVocabulary(['schemaVersion'])).",
|
|
6
|
+
"schemaVersion": 1,
|
|
7
|
+
"$defs": {
|
|
8
|
+
"SchemaVersion": { "description": "The version of this schema document (APP_FRONT_VERSION); additive changes keep 1.", "const": 1 },
|
|
9
|
+
"EpochMs": { "description": "Milliseconds since the Unix epoch.", "type": "integer", "minimum": 0 },
|
|
10
|
+
|
|
11
|
+
"AppFrontScope": {
|
|
12
|
+
"description": "The scopes the app front adds to APP_SCOPES (APP_FRONT_SCOPES). speech:use (AF2) transcribe/speak; conversation:read (AF3) the stream and the log of the app's sessions; conversation:write (AF3) conversation, utterance, interrupt and typed turns, each also behind a grant allowing prompt; decisions:answer (AF4) confirm-tier Decisions of the app's sessions. A scope never implies another.",
|
|
13
|
+
"enum": ["speech:use", "conversation:read", "conversation:write", "decisions:answer"]
|
|
14
|
+
},
|
|
15
|
+
"AppFrontFeature": {
|
|
16
|
+
"description": "`/.well-known/hub` features flags (APP_FRONT_FEATURES): app-connect (AF1), app-speech (AF2), app-conversation (AF3), app-decisions (AF4). A hub lists a flag only once it ships it; clients ignore flags they do not know.",
|
|
17
|
+
"enum": ["app-connect", "app-speech", "app-conversation", "app-decisions"]
|
|
18
|
+
},
|
|
19
|
+
"WellKnownHub": {
|
|
20
|
+
"description": "(shipped) GET /.well-known/hub (wellKnown in src/central/agent/routes.ts): public discovery. `features` is [] until an AF task ships its flag.",
|
|
21
|
+
"type": "object",
|
|
22
|
+
"required": ["publicURL", "version", "minClientVersion", "auth", "features"],
|
|
23
|
+
"properties": {
|
|
24
|
+
"publicURL": { "type": "string" },
|
|
25
|
+
"version": { "type": "string" },
|
|
26
|
+
"minClientVersion": { "type": "string" },
|
|
27
|
+
"auth": { "type": "object", "required": ["modes"], "properties": { "modes": { "type": "array", "items": { "enum": ["pairing", "oidc"] } } } },
|
|
28
|
+
"features": { "type": "array", "uniqueItems": true, "items": { "type": "string", "pattern": "^[a-z][a-z0-9-]{0,39}$" } }
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
"ErrorCode": {
|
|
33
|
+
"description": "The /api/agent error codes the app-front routes answer: today's AGENT_ERROR_STATUS codes plus (AF2) `upstream_failed` 502 (the speech provider failed). Bodies: {error: {code, message, hint?, retryAfter?, ...}} as in the app-delivery schema.",
|
|
34
|
+
"enum": ["invalid", "unauthorized", "token_expired", "forbidden", "not_found", "conflict", "gone", "unsupported", "paused", "rate_limited", "offline", "busy", "upstream_failed"]
|
|
35
|
+
},
|
|
36
|
+
"NewErrorCodeStatus": { "description": "(AF2) Error codes the app front adds to AGENT_ERROR_STATUS, with their status.", "const": { "upstream_failed": 502 } },
|
|
37
|
+
"ErrorBody": {
|
|
38
|
+
"type": "object",
|
|
39
|
+
"required": ["error"],
|
|
40
|
+
"properties": {
|
|
41
|
+
"error": {
|
|
42
|
+
"type": "object",
|
|
43
|
+
"required": ["code", "message"],
|
|
44
|
+
"properties": { "code": { "type": "string", "pattern": "^[a-z][a-z_]{0,39}$" }, "message": { "type": "string" }, "hint": { "type": "string" }, "retryAfter": { "type": "integer", "minimum": 0 } }
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
"AppActor": {
|
|
50
|
+
"description": "(shipped for hat_ tokens) GET /api/agent/whoami `actor` of an app credential. (AF1) A connected app's access token answers `connection: true` and `clientID`; its `tokenID` is the connection ID (stable across refresh and re-consent), so `app:<tokenID>` (launch `by`, grant holders, message parties) keeps naming the same app.",
|
|
51
|
+
"type": "object",
|
|
52
|
+
"required": ["kind", "userID", "tokenID", "scopes"],
|
|
53
|
+
"properties": {
|
|
54
|
+
"kind": { "const": "app" },
|
|
55
|
+
"userID": { "type": "string" },
|
|
56
|
+
"tokenID": { "$ref": "#/$defs/ConnectionID" },
|
|
57
|
+
"scopes": { "type": "array", "items": { "type": "string" } },
|
|
58
|
+
"label": { "type": "string" },
|
|
59
|
+
"clientID": { "$ref": "#/$defs/ClientID" },
|
|
60
|
+
"connection": { "const": true }
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
"ClientID": { "description": "(AF1) A registered client's public ID.", "type": "string", "pattern": "^hcl_[0-9a-f]{16}$" },
|
|
65
|
+
"ClientSecret": { "description": "(AF1) Shown once at registration and on rotation; only its sha256 is stored.", "type": "string", "pattern": "^hcs_[A-Za-z0-9_-]{43}$" },
|
|
66
|
+
"FlowID": { "description": "(AF1) A pending connect flow (authorize → consent), CONNECT_FLOW_TTL_MS.", "type": "string", "pattern": "^hcf_[0-9a-f]{24}$" },
|
|
67
|
+
"AuthorizationCode": { "description": "(AF1) One-time, AUTHORIZATION_CODE_TTL_MS; a second use revokes the connection it issued.", "type": "string", "pattern": "^hco_[A-Za-z0-9_-]{43}$" },
|
|
68
|
+
"AccessToken": { "description": "(AF1) Bearer for /api/agent, ACCESS_TOKEN_TTL_MS; carries the connection ID.", "type": "string", "pattern": "^hca_[0-9a-f]{16}_[A-Za-z0-9_-]{43}$" },
|
|
69
|
+
"RefreshToken": { "description": "(AF1) Rotating, single use, REFRESH_TOKEN_IDLE_MS sliding; the just-rotated one answers the same new pair for REFRESH_GRACE_MS, any other reuse revokes the connection.", "type": "string", "pattern": "^hcr_[0-9a-f]{16}_[A-Za-z0-9_-]{43}$" },
|
|
70
|
+
"ConnectionID": { "description": "A hand-made token's ID or (AF1) a connection's ID: 16 hex.", "type": "string", "pattern": "^[0-9a-f]{16}$" },
|
|
71
|
+
"ConnectLifetimes": {
|
|
72
|
+
"description": "(AF1) Lifetimes in ms (ACCESS_TOKEN_TTL_MS, REFRESH_TOKEN_IDLE_MS, AUTHORIZATION_CODE_TTL_MS, CONNECT_FLOW_TTL_MS, REFRESH_GRACE_MS: the just-rotated refresh token answers the same new pair this long, decision nixct956 3).",
|
|
73
|
+
"const": { "accessToken": 3600000, "refreshTokenIdle": 2592000000, "authorizationCode": 60000, "flow": 600000, "refreshGrace": 30000 }
|
|
74
|
+
},
|
|
75
|
+
"ConnectLimits": {
|
|
76
|
+
"description": "(AF1) CONNECT_LIMITS: clients = hub-wide clients; personalClientsPerUser; authorizePerMinute per client and client address; tokenPerMinute per client, authenticated requests only; failedClientAuthPerMinute per client and client address (beyond: 429 for that address's failures only).",
|
|
77
|
+
"const": { "clients": 50, "personalClientsPerUser": 10, "redirectURIsPerClient": 10, "clientNameMax": 100, "stateMax": 1024, "connectionsPerUser": 50, "authorizePerMinute": 20, "tokenPerMinute": 60, "failedClientAuthPerMinute": 10 }
|
|
78
|
+
},
|
|
79
|
+
"RedirectURI": {
|
|
80
|
+
"description": "(AF1) Absolute https URL (http only on 127.0.0.1, [::1] or localhost), ≤ 2048 characters, no user info, fragment or wildcard (redirectUriProblem is authoritative; this pattern is a coarse check). Matched by exact string comparison.",
|
|
81
|
+
"type": "string",
|
|
82
|
+
"minLength": 1,
|
|
83
|
+
"maxLength": 2048,
|
|
84
|
+
"pattern": "^https?://[^\\s#*]+$"
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
"AuthorizationServerMetadata": {
|
|
88
|
+
"description": "(AF1) GET /.well-known/oauth-authorization-server (RFC 8414), public. The same document shape Brain v2 G1 publishes.",
|
|
89
|
+
"type": "object",
|
|
90
|
+
"required": ["issuer", "authorization_endpoint", "token_endpoint", "revocation_endpoint", "response_types_supported", "grant_types_supported", "code_challenge_methods_supported", "token_endpoint_auth_methods_supported", "scopes_supported", "authorization_response_iss_parameter_supported"],
|
|
91
|
+
"properties": {
|
|
92
|
+
"issuer": { "description": "The hub's public URL.", "type": "string" },
|
|
93
|
+
"authorization_endpoint": { "type": "string" },
|
|
94
|
+
"token_endpoint": { "type": "string" },
|
|
95
|
+
"revocation_endpoint": { "type": "string" },
|
|
96
|
+
"response_types_supported": { "const": ["code"] },
|
|
97
|
+
"grant_types_supported": { "const": ["authorization_code", "refresh_token"] },
|
|
98
|
+
"code_challenge_methods_supported": { "const": ["S256"] },
|
|
99
|
+
"token_endpoint_auth_methods_supported": { "const": ["client_secret_basic", "client_secret_post"] },
|
|
100
|
+
"scopes_supported": { "type": "array", "items": { "type": "string" } },
|
|
101
|
+
"authorization_response_iss_parameter_supported": { "const": true },
|
|
102
|
+
"service_documentation": { "type": "string" }
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
"AuthorizeRequest": {
|
|
106
|
+
"description": "(AF1) GET /connect/authorize query. An unknown client_id or a redirect_uri not registered for it shows an error page and never redirects; every other problem redirects with AuthorizeErrorRedirect.",
|
|
107
|
+
"type": "object",
|
|
108
|
+
"required": ["response_type", "client_id", "redirect_uri", "scope", "state", "code_challenge", "code_challenge_method"],
|
|
109
|
+
"additionalProperties": false,
|
|
110
|
+
"properties": {
|
|
111
|
+
"response_type": { "const": "code" },
|
|
112
|
+
"client_id": { "$ref": "#/$defs/ClientID" },
|
|
113
|
+
"redirect_uri": { "$ref": "#/$defs/RedirectURI" },
|
|
114
|
+
"scope": { "description": "Space-separated (parseScopeParam), within the client's allowed scopes.", "type": "string", "minLength": 1, "pattern": "^[a-z:]+( [a-z:]+)*$" },
|
|
115
|
+
"state": { "type": "string", "minLength": 1, "maxLength": 1024 },
|
|
116
|
+
"code_challenge": { "type": "string", "pattern": "^[A-Za-z0-9_-]{43}$" },
|
|
117
|
+
"code_challenge_method": { "const": "S256" }
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
"AuthorizeSuccessRedirect": {
|
|
121
|
+
"description": "(AF1) Query added to redirect_uri on approval (RFC 9207 `iss`).",
|
|
122
|
+
"type": "object",
|
|
123
|
+
"required": ["code", "state", "iss"],
|
|
124
|
+
"additionalProperties": false,
|
|
125
|
+
"properties": { "code": { "$ref": "#/$defs/AuthorizationCode" }, "state": { "type": "string" }, "iss": { "type": "string" } }
|
|
126
|
+
},
|
|
127
|
+
"AuthorizeError": { "description": "(AF1) AUTHORIZE_ERRORS (RFC 6749 §4.1.2.1).", "enum": ["invalid_request", "unauthorized_client", "access_denied", "unsupported_response_type", "invalid_scope", "server_error", "temporarily_unavailable"] },
|
|
128
|
+
"AuthorizeErrorRedirect": {
|
|
129
|
+
"description": "(AF1) Query added to redirect_uri on a refusal (only once client_id and redirect_uri are valid).",
|
|
130
|
+
"type": "object",
|
|
131
|
+
"required": ["error", "iss"],
|
|
132
|
+
"additionalProperties": false,
|
|
133
|
+
"properties": { "error": { "$ref": "#/$defs/AuthorizeError" }, "error_description": { "type": "string", "maxLength": 200 }, "state": { "type": "string" }, "iss": { "type": "string" } }
|
|
134
|
+
},
|
|
135
|
+
"ConnectFlowView": {
|
|
136
|
+
"description": "(AF1) GET /api/connect/flows/:flow (console, the signed-in user): what the consent page shows. `scopes` is what the app asked for (within its client's allowed scopes); `connection` the user's current connection to this client, if any.",
|
|
137
|
+
"type": "object",
|
|
138
|
+
"required": ["id", "client", "scopes", "expiresAt"],
|
|
139
|
+
"properties": {
|
|
140
|
+
"id": { "$ref": "#/$defs/FlowID" },
|
|
141
|
+
"client": { "type": "object", "required": ["id", "name", "redirectOrigin"], "properties": { "id": { "$ref": "#/$defs/ClientID" }, "name": { "type": "string" }, "redirectOrigin": { "type": "string" } } },
|
|
142
|
+
"scopes": { "type": "array", "minItems": 1, "items": { "type": "string" } },
|
|
143
|
+
"expiresAt": { "$ref": "#/$defs/EpochMs" },
|
|
144
|
+
"connection": { "type": "object", "required": ["id", "scopes"], "properties": { "id": { "$ref": "#/$defs/ConnectionID" }, "scopes": { "type": "array", "items": { "type": "string" } } } }
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
"ConnectApproveRequest": {
|
|
148
|
+
"description": "(AF1) POST /api/connect/flows/:flow/approve (cookie only, same origin): the scopes the user grants, a non-empty subset of the flow's.",
|
|
149
|
+
"type": "object",
|
|
150
|
+
"required": ["scopes"],
|
|
151
|
+
"additionalProperties": false,
|
|
152
|
+
"properties": { "scopes": { "type": "array", "minItems": 1, "uniqueItems": true, "items": { "type": "string" } } }
|
|
153
|
+
},
|
|
154
|
+
"ConnectDecisionResponse": {
|
|
155
|
+
"description": "(AF1) Approve or deny answers where the page navigates: redirect_uri with AuthorizeSuccessRedirect or (deny) error=access_denied.",
|
|
156
|
+
"type": "object",
|
|
157
|
+
"required": ["redirect"],
|
|
158
|
+
"properties": { "redirect": { "type": "string" } }
|
|
159
|
+
},
|
|
160
|
+
"TokenRequestCode": {
|
|
161
|
+
"description": "(AF1) POST /connect/token, application/x-www-form-urlencoded, client authenticated (client_secret_basic or client_secret_post).",
|
|
162
|
+
"type": "object",
|
|
163
|
+
"required": ["grant_type", "code", "redirect_uri", "code_verifier"],
|
|
164
|
+
"properties": {
|
|
165
|
+
"grant_type": { "const": "authorization_code" },
|
|
166
|
+
"code": { "$ref": "#/$defs/AuthorizationCode" },
|
|
167
|
+
"redirect_uri": { "$ref": "#/$defs/RedirectURI" },
|
|
168
|
+
"code_verifier": { "type": "string", "pattern": "^[A-Za-z0-9._~-]{43,128}$" },
|
|
169
|
+
"client_id": { "$ref": "#/$defs/ClientID" },
|
|
170
|
+
"client_secret": { "$ref": "#/$defs/ClientSecret" }
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
"TokenRequestRefresh": {
|
|
174
|
+
"description": "(AF1) POST /connect/token with a refresh token: a new access token and a new refresh token; the old refresh token is spent. `scope` may only narrow.",
|
|
175
|
+
"type": "object",
|
|
176
|
+
"required": ["grant_type", "refresh_token"],
|
|
177
|
+
"properties": {
|
|
178
|
+
"grant_type": { "const": "refresh_token" },
|
|
179
|
+
"refresh_token": { "$ref": "#/$defs/RefreshToken" },
|
|
180
|
+
"scope": { "type": "string" },
|
|
181
|
+
"client_id": { "$ref": "#/$defs/ClientID" },
|
|
182
|
+
"client_secret": { "$ref": "#/$defs/ClientSecret" }
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
"TokenResponse": {
|
|
186
|
+
"description": "(AF1) 200, cache-control no-store.",
|
|
187
|
+
"type": "object",
|
|
188
|
+
"required": ["access_token", "token_type", "expires_in", "refresh_token", "scope", "connection_id"],
|
|
189
|
+
"properties": {
|
|
190
|
+
"access_token": { "$ref": "#/$defs/AccessToken" },
|
|
191
|
+
"token_type": { "const": "Bearer" },
|
|
192
|
+
"expires_in": { "const": 3600 },
|
|
193
|
+
"refresh_token": { "$ref": "#/$defs/RefreshToken" },
|
|
194
|
+
"scope": { "type": "string" },
|
|
195
|
+
"connection_id": { "$ref": "#/$defs/ConnectionID" }
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
"TokenErrorStatus": { "description": "(AF1) TOKEN_ERROR_STATUS: RFC 6749 §5.2 codes and their status (temporarily_unavailable 429 with Retry-After: rate limit).", "const": { "invalid_request": 400, "invalid_client": 401, "invalid_grant": 400, "unauthorized_client": 400, "unsupported_grant_type": 400, "invalid_scope": 400, "temporarily_unavailable": 429 } },
|
|
199
|
+
"TokenErrorResponse": {
|
|
200
|
+
"description": "(AF1) The body of every /connect/token and /connect/revoke refusal.",
|
|
201
|
+
"type": "object",
|
|
202
|
+
"required": ["error"],
|
|
203
|
+
"additionalProperties": false,
|
|
204
|
+
"properties": {
|
|
205
|
+
"error": { "enum": ["invalid_request", "invalid_client", "invalid_grant", "unauthorized_client", "unsupported_grant_type", "invalid_scope", "temporarily_unavailable"] },
|
|
206
|
+
"error_description": { "type": "string", "maxLength": 200 }
|
|
207
|
+
}
|
|
208
|
+
},
|
|
209
|
+
"RevokeRequest": {
|
|
210
|
+
"description": "(AF1) POST /connect/revoke (RFC 7009), form-encoded, client authenticated. Either token ends the whole connection (access and refresh). Always 200 with an empty body, also for an unknown or already revoked token.",
|
|
211
|
+
"type": "object",
|
|
212
|
+
"required": ["token"],
|
|
213
|
+
"properties": { "token": { "type": "string" }, "token_type_hint": { "enum": ["access_token", "refresh_token"] } }
|
|
214
|
+
},
|
|
215
|
+
"ClientAudience": { "description": "(AF1) CLIENT_AUDIENCES (decision nixct956 5): hub = hub-wide (admins register; every user may connect); user = personal (only its owner sees and uses it).", "enum": ["hub", "user"] },
|
|
216
|
+
"AppClientRegisterRequest": {
|
|
217
|
+
"description": "(AF1) POST /api/agent/app-clients (a user actor, cookie or device token; never an app or session). Admins register hub-wide clients (audience hub, their default) or personal ones; other users only personal ones (audience user, their default). No dynamic registration.",
|
|
218
|
+
"type": "object",
|
|
219
|
+
"required": ["name", "redirectURIs", "scopes"],
|
|
220
|
+
"additionalProperties": false,
|
|
221
|
+
"properties": {
|
|
222
|
+
"name": { "type": "string", "minLength": 1, "maxLength": 100 },
|
|
223
|
+
"redirectURIs": { "type": "array", "minItems": 1, "maxItems": 10, "uniqueItems": true, "items": { "$ref": "#/$defs/RedirectURI" } },
|
|
224
|
+
"scopes": { "description": "The ceiling: what a consent may grant (APP_SCOPES plus AppFrontScope).", "type": "array", "minItems": 1, "uniqueItems": true, "items": { "type": "string" } },
|
|
225
|
+
"audience": { "$ref": "#/$defs/ClientAudience" }
|
|
226
|
+
}
|
|
227
|
+
},
|
|
228
|
+
"AppClientUpdateRequest": {
|
|
229
|
+
"description": "(AF1) PATCH /api/agent/app-clients/:id: hub-wide clients by an admin, personal ones by their owner only (else 404). Narrowing scopes narrows every connection; disabled true revokes every connection. The audience never changes.",
|
|
230
|
+
"type": "object",
|
|
231
|
+
"additionalProperties": false,
|
|
232
|
+
"minProperties": 1,
|
|
233
|
+
"properties": {
|
|
234
|
+
"name": { "type": "string", "minLength": 1, "maxLength": 100 },
|
|
235
|
+
"redirectURIs": { "type": "array", "minItems": 1, "maxItems": 10, "uniqueItems": true, "items": { "$ref": "#/$defs/RedirectURI" } },
|
|
236
|
+
"scopes": { "type": "array", "minItems": 1, "uniqueItems": true, "items": { "type": "string" } },
|
|
237
|
+
"disabled": { "type": "boolean" }
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
"AppClientView": {
|
|
241
|
+
"description": "(AF1) A registered client as GET /api/agent/app-clients {clients} shows it (hub-wide ones and the caller's own personal ones): never the secret or its hash.",
|
|
242
|
+
"type": "object",
|
|
243
|
+
"required": ["id", "name", "redirectURIs", "scopes", "created", "createdBy", "audience"],
|
|
244
|
+
"properties": {
|
|
245
|
+
"id": { "$ref": "#/$defs/ClientID" },
|
|
246
|
+
"secret": false,
|
|
247
|
+
"name": { "type": "string" },
|
|
248
|
+
"redirectURIs": { "type": "array", "items": { "type": "string" } },
|
|
249
|
+
"scopes": { "type": "array", "items": { "type": "string" } },
|
|
250
|
+
"created": { "$ref": "#/$defs/EpochMs" },
|
|
251
|
+
"createdBy": { "type": "string" },
|
|
252
|
+
"audience": { "$ref": "#/$defs/ClientAudience" },
|
|
253
|
+
"disabled": { "const": true }
|
|
254
|
+
}
|
|
255
|
+
},
|
|
256
|
+
"AppClientRegisterResponse": {
|
|
257
|
+
"description": "(AF1) 201; the secret is shown only here (and by POST …/:id/secret, which rotates it).",
|
|
258
|
+
"type": "object",
|
|
259
|
+
"required": ["client", "secret"],
|
|
260
|
+
"properties": { "client": { "$ref": "#/$defs/AppClientView" }, "secret": { "$ref": "#/$defs/ClientSecret" } }
|
|
261
|
+
},
|
|
262
|
+
"ConnectionView": {
|
|
263
|
+
"description": "(AF1) GET /api/agent/connections (the user's own): never a token or hash. DELETE …/:id revokes it.",
|
|
264
|
+
"type": "object",
|
|
265
|
+
"required": ["id", "clientID", "clientName", "scopes", "created", "refreshExpiresAt"],
|
|
266
|
+
"properties": {
|
|
267
|
+
"id": { "$ref": "#/$defs/ConnectionID" },
|
|
268
|
+
"clientID": { "$ref": "#/$defs/ClientID" },
|
|
269
|
+
"clientName": { "type": "string" },
|
|
270
|
+
"scopes": { "type": "array", "items": { "type": "string" } },
|
|
271
|
+
"created": { "$ref": "#/$defs/EpochMs" },
|
|
272
|
+
"lastUsed": { "$ref": "#/$defs/EpochMs" },
|
|
273
|
+
"refreshExpiresAt": { "$ref": "#/$defs/EpochMs" }
|
|
274
|
+
}
|
|
275
|
+
},
|
|
276
|
+
|
|
277
|
+
"SpeechRateLimits": { "description": "(AF2) SPEECH_RATE_LIMITS per app credential; beyond: 429 rate_limited with retryAfter.", "const": { "transcribe": { "perMinute": 20, "perHour": 300 }, "speak": { "perMinute": 60, "perHour": 1000, "charsPerHour": 100000 } } },
|
|
278
|
+
"TranscribeResponse": {
|
|
279
|
+
"description": "(AF2) POST /api/agent/speech/transcribe: raw audio body (Content-Type an accepted audio type, ≤ the hub's maxAudioBytes) → 200 {text}.",
|
|
280
|
+
"type": "object",
|
|
281
|
+
"required": ["text"],
|
|
282
|
+
"properties": { "text": { "type": "string" } }
|
|
283
|
+
},
|
|
284
|
+
"SpeakRequest": {
|
|
285
|
+
"description": "(AF2) POST /api/agent/speech/speak → 200 audio/mpeg (streamed). text: 1–maxSpeechChars after speakable(); counts toward charsPerHour.",
|
|
286
|
+
"type": "object",
|
|
287
|
+
"required": ["text"],
|
|
288
|
+
"additionalProperties": false,
|
|
289
|
+
"properties": { "text": { "type": "string", "minLength": 1 } }
|
|
290
|
+
},
|
|
291
|
+
|
|
292
|
+
"ConversationRequest": {
|
|
293
|
+
"description": "(AF3) POST /api/agent/sessions/:addr/conversation (conversation:write + a prompt grant covering the session).",
|
|
294
|
+
"type": "object",
|
|
295
|
+
"required": ["on"],
|
|
296
|
+
"additionalProperties": false,
|
|
297
|
+
"properties": { "on": { "type": "boolean" }, "front": { "type": "boolean" } }
|
|
298
|
+
},
|
|
299
|
+
"ConversationResponse": {
|
|
300
|
+
"description": "(AF3) As the console route answers.",
|
|
301
|
+
"type": "object",
|
|
302
|
+
"required": ["on"],
|
|
303
|
+
"properties": { "on": { "type": "boolean" }, "agent": { "type": "string" }, "front": { "type": "boolean" } }
|
|
304
|
+
},
|
|
305
|
+
"UtteranceRequest": {
|
|
306
|
+
"description": "(AF3) POST /api/agent/sessions/:addr/utterance: a final transcript, 1–4000 characters (MAX_UTTERANCE_CHARS). One action under the grant's maxActionsPerHour.",
|
|
307
|
+
"type": "object",
|
|
308
|
+
"required": ["text"],
|
|
309
|
+
"additionalProperties": false,
|
|
310
|
+
"properties": { "text": { "type": "string", "minLength": 1, "maxLength": 4000 } }
|
|
311
|
+
},
|
|
312
|
+
"UtteranceResponse": { "description": "(AF3) 202; a `heard` frame with this inboxID follows when it reached the agent.", "type": "object", "required": ["inboxID"], "properties": { "inboxID": { "type": "string" } } },
|
|
313
|
+
|
|
314
|
+
"StreamLimits": { "description": "(AF3) APP_STREAM_LIMITS.", "const": { "heartbeatMs": 20000, "retryMs": 2000, "recheckMs": 30000, "bufferMaxBytes": 1048576, "streamsPerApp": 4, "streamsPerUser": 20 } },
|
|
315
|
+
"StreamFrameType": { "description": "(AF3) APP_STREAM_FRAME_TYPES.", "enum": ["ready", "delta", "say", "heard", "status", "conversation", "reset", "end"] },
|
|
316
|
+
"StreamEndReason": { "description": "(AF3) APP_STREAM_END_REASONS.", "enum": ["token_expired", "revoked", "grant", "gone", "slow"] },
|
|
317
|
+
"StreamRefresh": { "description": "(AF3) APP_STREAM_REFRESH: what a reset asks to re-read.", "enum": ["log", "status", "conversation", "decisions"] },
|
|
318
|
+
"SessionState": { "description": "SessionState in packages/protocol.", "enum": ["idle", "working", "blocked", "error"] },
|
|
319
|
+
"StreamReady": {
|
|
320
|
+
"description": "(AF3) First frame, once subscribed: the session's state now.",
|
|
321
|
+
"type": "object",
|
|
322
|
+
"required": ["type", "at", "sessionID", "status", "conversation"],
|
|
323
|
+
"additionalProperties": false,
|
|
324
|
+
"properties": {
|
|
325
|
+
"type": { "const": "ready" },
|
|
326
|
+
"at": { "$ref": "#/$defs/EpochMs" },
|
|
327
|
+
"sessionID": { "type": "string" },
|
|
328
|
+
"status": { "$ref": "#/$defs/SessionState" },
|
|
329
|
+
"conversation": { "type": "object", "required": ["on"], "additionalProperties": false, "properties": { "on": { "type": "boolean" }, "front": { "type": "boolean" } } }
|
|
330
|
+
}
|
|
331
|
+
},
|
|
332
|
+
"StreamDelta": {
|
|
333
|
+
"description": "(AF3) Assistant text of the session itself (never a subagent's, never reasoning), coalesced per STREAM_INTERVAL_MS. Untrusted text.",
|
|
334
|
+
"type": "object",
|
|
335
|
+
"required": ["type", "sessionID", "text"],
|
|
336
|
+
"additionalProperties": false,
|
|
337
|
+
"properties": { "type": { "const": "delta" }, "sessionID": { "type": "string" }, "messageID": { "type": "string" }, "ordinal": { "type": "integer", "minimum": 0 }, "text": { "type": "string" } }
|
|
338
|
+
},
|
|
339
|
+
"StreamSay": {
|
|
340
|
+
"description": "(AF3) A sentence to speak in conversation mode (also the voice front's, with sessionID = main). Untrusted text.",
|
|
341
|
+
"type": "object",
|
|
342
|
+
"required": ["type", "sessionID", "messageID", "ordinal", "text"],
|
|
343
|
+
"additionalProperties": false,
|
|
344
|
+
"properties": { "type": { "const": "say" }, "sessionID": { "type": "string" }, "messageID": { "type": "string" }, "ordinal": { "type": "integer", "minimum": 0 }, "seq": { "type": "integer", "minimum": 0 }, "text": { "type": "string" } }
|
|
345
|
+
},
|
|
346
|
+
"StreamHeard": {
|
|
347
|
+
"description": "(AF3) An utterance reached the agent.",
|
|
348
|
+
"type": "object",
|
|
349
|
+
"required": ["type", "sessionID", "inboxID"],
|
|
350
|
+
"additionalProperties": false,
|
|
351
|
+
"properties": { "type": { "const": "heard" }, "sessionID": { "type": "string" }, "inboxID": { "type": "string" } }
|
|
352
|
+
},
|
|
353
|
+
"StreamStatus": {
|
|
354
|
+
"description": "(AF3) The session's state changed: an enum and a count, never blocker text.",
|
|
355
|
+
"type": "object",
|
|
356
|
+
"required": ["type", "sessionID", "state", "blockers"],
|
|
357
|
+
"additionalProperties": false,
|
|
358
|
+
"properties": { "type": { "const": "status" }, "sessionID": { "type": "string" }, "state": { "$ref": "#/$defs/SessionState" }, "blockers": { "type": "integer", "minimum": 0 }, "since": { "$ref": "#/$defs/EpochMs" } }
|
|
359
|
+
},
|
|
360
|
+
"StreamConversation": {
|
|
361
|
+
"description": "(AF3) Conversation mode changed (front: voice front P1; reason front_failed: utterances go to the session itself).",
|
|
362
|
+
"type": "object",
|
|
363
|
+
"required": ["type", "sessionID", "on"],
|
|
364
|
+
"additionalProperties": false,
|
|
365
|
+
"properties": { "type": { "const": "conversation" }, "sessionID": { "type": "string" }, "on": { "type": "boolean" }, "front": { "type": "boolean" }, "reason": { "const": "front_failed" } }
|
|
366
|
+
},
|
|
367
|
+
"StreamReset": {
|
|
368
|
+
"description": "(AF3) The resume point is not covered (or, in cluster mode, streamed text may be missing): re-read `refresh`, then continue.",
|
|
369
|
+
"type": "object",
|
|
370
|
+
"required": ["type", "at", "refresh"],
|
|
371
|
+
"additionalProperties": false,
|
|
372
|
+
"properties": { "type": { "const": "reset" }, "at": { "$ref": "#/$defs/EpochMs" }, "refresh": { "type": "array", "minItems": 1, "uniqueItems": true, "items": { "$ref": "#/$defs/StreamRefresh" } } }
|
|
373
|
+
},
|
|
374
|
+
"StreamEnd": {
|
|
375
|
+
"description": "(AF3) The last frame before the hub closes the stream.",
|
|
376
|
+
"type": "object",
|
|
377
|
+
"required": ["type", "at", "reason"],
|
|
378
|
+
"additionalProperties": false,
|
|
379
|
+
"properties": { "type": { "const": "end" }, "at": { "$ref": "#/$defs/EpochMs" }, "reason": { "$ref": "#/$defs/StreamEndReason" } }
|
|
380
|
+
},
|
|
381
|
+
"StreamFrame": {
|
|
382
|
+
"description": "(AF3) One `data:` line of GET /api/agent/sessions/:addr/stream. `id:` lines carry the resume cursor (Last-Event-ID); `event: ping` heartbeats are not data frames.",
|
|
383
|
+
"oneOf": [
|
|
384
|
+
{ "$ref": "#/$defs/StreamReady" },
|
|
385
|
+
{ "$ref": "#/$defs/StreamDelta" },
|
|
386
|
+
{ "$ref": "#/$defs/StreamSay" },
|
|
387
|
+
{ "$ref": "#/$defs/StreamHeard" },
|
|
388
|
+
{ "$ref": "#/$defs/StreamStatus" },
|
|
389
|
+
{ "$ref": "#/$defs/StreamConversation" },
|
|
390
|
+
{ "$ref": "#/$defs/StreamReset" },
|
|
391
|
+
{ "$ref": "#/$defs/StreamEnd" }
|
|
392
|
+
]
|
|
393
|
+
},
|
|
394
|
+
|
|
395
|
+
"AnswerableTier": { "description": "(AF4) APP_ANSWERABLE_TIERS: the only tier an app answers (decision w6lfbfaq 1).", "enum": ["confirm"] },
|
|
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`.",
|
|
398
|
+
"type": "object",
|
|
399
|
+
"additionalProperties": false,
|
|
400
|
+
"minProperties": 1,
|
|
401
|
+
"properties": { "option": { "type": "string", "minLength": 1, "maxLength": 64 }, "text": { "type": "string", "minLength": 1, "maxLength": 4000 } }
|
|
402
|
+
},
|
|
403
|
+
"AppDecisionAnswer": {
|
|
404
|
+
"description": "(AF4) Decision.answer when an app answered: answeredBy app, the app's party and (when it has one) its label.",
|
|
405
|
+
"type": "object",
|
|
406
|
+
"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" } }
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|