@qoniai/qoni 0.4.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.
@@ -0,0 +1,50 @@
1
+ # Open Source Readiness
2
+
3
+ Use this checklist before publishing or mirroring `@qoniai/qoni` as an open SDK.
4
+
5
+ ## Consumer Surface
6
+
7
+ - `README.md` explains installation, quick start, authorization model, scopes,
8
+ namespaces, response shape, errors, security notes, and development commands.
9
+ - `README.zh-CN.md` stays aligned with the English README for the primary SDK
10
+ flow.
11
+ - `CHANGELOG.md` records public behavior changes.
12
+ - `LICENSE` is included and matches `package.json`.
13
+
14
+ ## Maintainer Surface
15
+
16
+ - `CONTRIBUTING.md` defines setup, verification, API compatibility, and release
17
+ checklist.
18
+ - `SECURITY.md` defines vulnerability reporting and credential handling.
19
+ - CI runs typecheck, tests, build, and package dry-run.
20
+ - `tests/open-source-readiness.test.ts` guards required project files and package
21
+ metadata.
22
+
23
+ ## Package Surface
24
+
25
+ - `package.json` has `main`, `module`, `types`, and conditional `exports`.
26
+ - `package.json.files` includes only package artifacts and useful docs.
27
+ - `pnpm pack --dry-run` is inspected before publish.
28
+ - `prepack` runs `pnpm build`.
29
+ - Published code is generated from `src/index.ts` through `tsup`.
30
+
31
+ ## Security Review
32
+
33
+ - AK/SK must stay server-side.
34
+ - Delegation tokens and product tokens must not be logged or exposed to
35
+ untrusted clients.
36
+ - Documentation snippets should use environment variables and placeholders only.
37
+ - New product namespaces must document scopes, token audience, and audit fields.
38
+
39
+ ## Verification
40
+
41
+ Run the full local gate before publishing:
42
+
43
+ ```bash
44
+ pnpm typecheck
45
+ pnpm check:brand
46
+ pnpm test
47
+ pnpm build
48
+ pnpm pack --dry-run
49
+ git diff --check
50
+ ```
package/package.json ADDED
@@ -0,0 +1,81 @@
1
+ {
2
+ "name": "@qoniai/qoni",
3
+ "version": "0.4.0",
4
+ "description": "Unified Node.js SDK for Qoni Agent delegation, GUMem memory, and WebAgent automation.",
5
+ "homepage": "https://github.com/QONIAI/qoni-sdk-node#readme",
6
+ "bugs": {
7
+ "url": "https://github.com/QONIAI/qoni-sdk-node/issues"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/QONIAI/qoni-sdk-node.git"
12
+ },
13
+ "license": "MIT",
14
+ "type": "module",
15
+ "packageManager": "pnpm@10.33.2",
16
+ "keywords": [
17
+ "qoni",
18
+ "agent",
19
+ "sdk",
20
+ "delegation",
21
+ "gumem",
22
+ "webagent"
23
+ ],
24
+ "main": "./dist/index.cjs",
25
+ "module": "./dist/index.js",
26
+ "types": "./dist/index.d.ts",
27
+ "exports": {
28
+ ".": {
29
+ "types": "./dist/index.d.ts",
30
+ "import": "./dist/index.js",
31
+ "require": "./dist/index.cjs"
32
+ }
33
+ },
34
+ "files": [
35
+ "dist",
36
+ "README.md",
37
+ "README.zh-CN.md",
38
+ "CHANGELOG.md",
39
+ "LICENSE",
40
+ "docs",
41
+ "skills"
42
+ ],
43
+ "scripts": {
44
+ "build": "tsup",
45
+ "check:brand": "node scripts/check-brand.mjs",
46
+ "gen:event-types": "node scripts/gen-event-types.mjs",
47
+ "typecheck": "tsc --noEmit",
48
+ "test": "pnpm build && pnpm check:brand && pnpm test:mock && pnpm test:package",
49
+ "test:mock": "vitest run --coverage=false tests/qoni.test.ts tests/semantic-surface.test.ts tests/open-source-readiness.test.ts tests/complete-coverage.test.ts tests/event-catalog.test.ts tests/interactions.test.ts tests/contract-surface.test.ts tests/sse-abort.test.ts tests/do-anything-evidence.test.ts tests/query-matrix.test.ts tests/live-evidence.test.ts",
50
+ "test:e2e": "QONI_LIVE_E2E=1 vitest run --coverage=false --fileParallelism=false --maxWorkers=1 --testTimeout=120000 tests/live-e2e/*.test.ts",
51
+ "test:e2e:do-anything": "QONI_LIVE_E2E=1 vitest run --coverage=false --fileParallelism=false --maxWorkers=1 --testTimeout=120000 tests/live-e2e/do-anything.test.ts",
52
+ "test:e2e:do-anything:release": "QONI_LIVE_E2E=1 QONI_LIVE_ALLOW_ENV_CONSTRAINTS=0 vitest run --coverage=false --fileParallelism=false --maxWorkers=1 --testTimeout=120000 tests/live-e2e/do-anything.test.ts",
53
+ "test:e2e:matrix": "QONI_LIVE_E2E=1 QONI_QUERY_MATRIX=1 QONI_LIVE_ALLOW_ENV_CONSTRAINTS=0 vitest run --coverage=false --fileParallelism=false --maxWorkers=1 tests/live-e2e/query-matrix.test.ts",
54
+ "test:e2e:release": "QONI_LIVE_E2E=1 QONI_LIVE_REQUIRE_ALL=1 QONI_LIVE_ALLOW_ENV_CONSTRAINTS=0 vitest run --coverage=false --fileParallelism=false --maxWorkers=1 --testTimeout=120000 tests/live-e2e/*.test.ts",
55
+ "test:package": "vitest run --coverage=false tests/package-surface.test.ts",
56
+ "test:ci": "pnpm test:mock && pnpm test:package",
57
+ "smoke:auth-gum": "pnpm build && node scripts/qoni-auth-gum-smoke.mjs --mock",
58
+ "smoke:auth-gum:live": "pnpm build && QONI_SMOKE_MODE=live node scripts/qoni-auth-gum-smoke.mjs --live",
59
+ "pack:dry-run": "pnpm build && pnpm pack --dry-run",
60
+ "prepack": "pnpm build",
61
+ "prepare": "tsup"
62
+ },
63
+ "publishConfig": {
64
+ "access": "public"
65
+ },
66
+ "sideEffects": false,
67
+ "engines": {
68
+ "node": ">=18"
69
+ },
70
+ "devDependencies": {
71
+ "@types/node": "^20.17.10",
72
+ "tsup": "^8.3.5",
73
+ "typescript": "^5.7.2",
74
+ "vitest": "^2.1.8"
75
+ },
76
+ "directories": {
77
+ "doc": "docs",
78
+ "test": "tests"
79
+ },
80
+ "author": ""
81
+ }
@@ -0,0 +1,324 @@
1
+ ---
2
+ name: qoni-sdk
3
+ description: Use when building, testing, debugging, or documenting Node.js integrations with @qoniai/qoni, QoniAgentKit/Qoni, AK/SK credentials, delegateToken, GenAuth currentUser/userInfo/introspection/user management, Qoni runtime-config discovery, GUMem, WebAgent, Web Search, Do Anything, Deep Research, Track, OBO product token exchange, or local .genauth.localhost SDK flows.
4
+ ---
5
+
6
+ # Qoni SDK
7
+
8
+ > **Requires `@qoniai/qoni` ≥ 0.4.0.** This guide documents the semantic-layer
9
+ > surface: `run()`/`create()` return handles (`RunHandle` / `MonitorHandle`),
10
+ > events are normalized (`event.type`, camelCase `data`, decoded screenshots),
11
+ > and the 1:1 wire methods live under `qoni.<product>.api.*`. If the consumer
12
+ > resolves an older version from npm, install this repo's local build instead
13
+ > (`npm install <path-to-qoni-sdk-node>` or a `pnpm pack` tarball) — otherwise
14
+ > the code below will not match the installed SDK.
15
+
16
+ ## Core Contract
17
+
18
+ Use `@qoniai/qoni` from trusted server-side Node.js code. Keep AK/SK on the server and create one reusable `QoniAgentKit`. For silent runtime product calls, resolve a real GenAuth user id from the credential-bound userpool, call `delegateToken`, then pass `delegation.data.token` explicitly to every product capability call. For interactive runtime authorization, call `delegateToken({ mode: "interactive", redirectUri, state, agent, scopes })` from the server without exposing AK/SK; redirect the user to `authorizationUrl`, then handle the business callback server-side with `completeDelegateToken({ grantId, code, state })` to receive the token. For GenAuth user management, call `qoni.genauth.users.*`; the SDK exchanges AK/SK for a standard GenAuth management token internally and does not require `QONI_USER_ID`.
19
+
20
+ ```ts
21
+ import { QoniAgentKit, QoniScopes } from "@qoniai/qoni";
22
+
23
+ const qoni = new QoniAgentKit({
24
+ accessKey: process.env.QONI_ACCESS_KEY!,
25
+ secretKey: process.env.QONI_SECRET_KEY!,
26
+ host: process.env.QONI_HOST, // Qoni Console/SDK gateway; optional in hosted Qoni.
27
+ });
28
+
29
+ const user = await qoni.currentUser<{ id: string; subject?: string; name?: string }>({
30
+ accessToken: genauthAccessToken,
31
+ });
32
+
33
+ // `products` expands to each product's scope set; `agent` defaults to "sdk".
34
+ // Scope strings are pre-validated locally — a missing service prefix throws
35
+ // QoniValidationError with the correct form before any request is sent.
36
+ const delegation = await qoni.delegateToken({
37
+ user: user.data,
38
+ products: ["doAnything"],
39
+ scopes: [QoniScopes.GUMEM_MEMORY_READ, QoniScopes.GUMEM_MEMORY_WRITE],
40
+ mode: "silent",
41
+ });
42
+
43
+ if ("authorizationUrl" in delegation.data) {
44
+ throw new Error("This server flow requires silent delegation");
45
+ }
46
+
47
+ const token = delegation.data.token;
48
+
49
+ await qoni.gumem.createSession({
50
+ token,
51
+ userId: String(user.data.id),
52
+ sessionId: "account-research",
53
+ });
54
+
55
+ const memory = await qoni.gumem.recall({
56
+ token,
57
+ sessionId: "account-research",
58
+ query: "What should the assistant remember?",
59
+ });
60
+
61
+ // run() returns a RunHandle; the token is held by the handle from here on.
62
+ const run = await qoni.doAnything.run({
63
+ token,
64
+ prompt: "Open the target site and summarize product changes.",
65
+ capture: { screenshots: true },
66
+ });
67
+ const result = await run.wait({
68
+ onScreenshot: (img, i) => saveImage(`step-${i}.jpg`, img.bytes),
69
+ onInputRequest: (req) => openForUser(req.liveUrl),
70
+ });
71
+ console.log(result.output);
72
+ ```
73
+
74
+ For explicit authorization, the Qoni Console/BFF owns GenAuth login and grant completion before it redirects back to the business `redirectUri`. The redirect only carries one-time callback fields such as `code`, `state`, and `grant_state`/`grantId`; never design an API that exposes a delegation token to browser code.
75
+
76
+ ```ts
77
+ const grant = await qoni.delegateToken({
78
+ mode: "interactive",
79
+ redirectUri: "https://app.example.com/qoni/callback",
80
+ state: "business-csrf-state",
81
+ agent: "research-assistant",
82
+ scopes: [QoniScopes.GUMEM_MEMORY_READ],
83
+ });
84
+
85
+ redirectUserTo(grant.data.authorizationUrl);
86
+
87
+ const completed = await qoni.completeDelegateToken({
88
+ grantId,
89
+ code,
90
+ state,
91
+ });
92
+ ```
93
+
94
+ `Qoni` is a short alias for `QoniAgentKit`. `delegateAgent` and `completeDelegateAgent` remain deprecated compatibility names. Use `completeDelegateToken` for interactive callbacks; the current callback contract is `{ grantId, code, state }`, not the old `{ code, state }` shape.
95
+
96
+ ## Runtime Discovery
97
+
98
+ - `host` points to the Qoni Console/SDK gateway, not directly to GenAuth, GUMem, or WebAgent.
99
+ - The SDK signs `GET /api/v3/eak/runtime-config` with AK/SK, caches returned service URLs, and routes downstream calls internally.
100
+ - Public package/API identifiers use Qoni, but the deployed server wire contract has not migrated. Keep `/api/v3/eak/*`, `eak_delegation_token`, `eak_tenant_id`, and backend `eak.*` error codes unchanged; do not teach callers to construct these internal values.
101
+ - Do not configure `genauthBaseUrl`, `gumemBaseUrl`, `webAgentBaseUrl`, or `qoniBaseUrl` in normal integrations. Use those only for focused tests or migration debugging.
102
+ - Product namespaces require an explicit `token` field. The SDK does not read a constructor-level token or `delegateAgent` config.
103
+ - The online delegation API currently expects `agent` to be a string id, for example `agent: "memory-agent"`. Do not teach object-shaped Agent metadata unless the backend API has been updated to accept it.
104
+
105
+ ## Token Roles
106
+
107
+ - GenAuth access token: proves the logged-in user to `currentUser` and `genauth.userInfo`.
108
+ - Qoni-derived GenAuth management token: produced internally by SDK-managed `POST /api/v3/eak/genauth/admin-token` with body `{}` for `genauth.users.*`; application code should not supply or persist it in normal flows.
109
+ - Qoni delegation token: returned directly by silent `delegateToken`, or by server-side `completeDelegateToken({ grantId, code, state })` after an interactive grant; pass it as `token` to GUMem/WebAgent/Web Search/Do Anything/Track calls.
110
+ - Product access token: produced by the SDK through `/api/v3/eak/token-exchange` when a raw Qoni delegation token is used for a product call. Do not hand-roll this exchange in application code.
111
+ - Direct product token: acceptable only when a product service already issued it or a test is intentionally bypassing delegation.
112
+
113
+ ## Management Plane Vs Runtime
114
+
115
+ Use AK/SK-backed `genauth.users.*` for GenAuth management-plane user CRUD. This is not a GUMem/WebAgent runtime capability and does not use `delegateToken` or `QONI_USER_ID`. The SDK exchanges AK/SK through Qoni for a standard GenAuth management token, then attaches `Authorization` and `x-authing-userpool-id` automatically:
116
+
117
+ ```ts
118
+ await qoni.genauth.users.list({ page: 1, limit: 20 });
119
+
120
+ await qoni.genauth.users.create({
121
+ username: "sdk-demo-user",
122
+ password: process.env.GENAUTH_DEMO_USER_PASSWORD!,
123
+ });
124
+ ```
125
+
126
+ Use `delegateToken` output for runtime capability namespaces:
127
+
128
+ - `gumem.createSession`, `gumem.addMessages`, `gumem.recall`, `gumem.uploadResource`, `gumem.actions.*`
129
+ - `webSearch.run/attach` → `RunHandle`
130
+ - `doAnything.run/attach` → `RunHandle`
131
+ - `deepResearch.run/attach` → `RunHandle`
132
+ - `track.create/attach` → `MonitorHandle`
133
+ - Wire-level escape hatch: `qoni.<product>.api.*` (1:1 backend HTTP methods; shapes may evolve with the API)
134
+
135
+ ## Run Handles (doAnything / webSearch / deepResearch)
136
+
137
+ `run(input)` returns a `RunHandle` — the same shape for all three products. The token is passed once at the entry call; handle methods never take a token or ids.
138
+
139
+ ```ts
140
+ const run = await qoni.doAnything.run({
141
+ token,
142
+ prompt: "Open https://example.com and summarize the page, then finish.",
143
+ capture: { screenshots: true }, // opt-in media; core events are always on
144
+ limits: { maxDurationMinutes: 10 },
145
+ // session: prior.sessionRef, // reuse a session / DR follow-up run
146
+ });
147
+
148
+ run.id;
149
+ run.sessionRef; // pass back to run({ session })
150
+ await run.status(); // refresh current state
151
+ await run.respond(requestId, response?); // HITL answer; omit response to skip
152
+ await run.cancel(reason?); // idempotent — safe on a finished run
153
+
154
+ const result = await run.wait({
155
+ timeoutMs: 180_000, // throws QoniTimeoutError on elapse; run keeps going server-side
156
+ onEvent: (e) => console.log(e.type),
157
+ onWireEvent: (e) => appendRawEvidence(e), // every SSE frame before semantic filtering
158
+ onReconnect: (info) => logReconnect(info.attempt, info.lastEventId),
159
+ onInputRequest: (req) => openForUser(req.liveUrl),
160
+ onScreenshot: (img, i) => saveImage(`step-${i}.jpg`, img.bytes),
161
+ });
162
+ // result: { runId, status: "succeeded"|"failed"|"canceled", output?, artifacts,
163
+ // terminalReason?, isTaskSuccessful?, raw }
164
+ // result.raw is the full run-detail envelope — total_cost_usd, step_count, tokens.
165
+ // result.artifacts: deepResearch deliverables (id/name/mime, content() fetches bytes lazily);
166
+ // empty array for the other products.
167
+
168
+ const sameRun = await qoni.doAnything.attach(run.id, { token, session: run.sessionRef });
169
+ // webSearch / deepResearch attach without session: qoni.deepResearch.attach(runId, { token })
170
+ ```
171
+
172
+ ### Semantic events
173
+
174
+ `run.events()` is an `AsyncIterable<RunEvent>` that ends automatically at the terminal event and reconnects dropped streams internally (`Last-Event-ID` catch-up; tune with `sseMaxRetries`, default 5).
175
+
176
+ ```ts
177
+ import { QoniEventTypes } from "@qoniai/qoni";
178
+
179
+ for await (const event of run.events()) {
180
+ // event.type ∈ QoniEventTypes (curated): Progress | Message | InputRequired
181
+ // | Screenshot | Done (core, any product) + ResultsReady (web search)
182
+ // + Phase | SectionReady (deep research) + MonitorCreated | Triggered
183
+ // | CheckCompleted (track). Each product handle is typed to its own subset.
184
+ // Internal wire types fold into Progress; the raw wire string is event.raw.event.
185
+ // event.data per type: simple events → the value itself (progress=string,
186
+ // resultsReady=number, checkCompleted=boolean); rich events → a small
187
+ // object (message={text,role}, done={output,succeeded,terminalReason}).
188
+ event.type;
189
+ event.runId;
190
+ event.at; // ISO 8601
191
+ event.isTerminal; // true only for Done
192
+ event.data; // flat camelCase payload, narrowed by type
193
+ event.raw; // original wire event (escape hatch)
194
+ if (event.type === QoniEventTypes.Screenshot) {
195
+ event.image; // { bytes: Uint8Array; mime; pageUrl?; step? } — data URI already decoded
196
+ }
197
+ }
198
+ ```
199
+
200
+ - Unmapped wire event types surface as `progress` (nothing is silently dropped); the wire shape stays on `event.raw`.
201
+ - Terminal events of internal sub-runs are demoted to `progress`, so they never end your iterator; only YOUR run's terminal ends it.
202
+ - `capture: { screenshots, videoFrames }` controls media events. Without `capture.screenshots`, screenshot events are neither subscribed nor delivered.
203
+ - The terminal event payload is lean — `wait()` settles from the run-detail envelope, so prefer `result.raw` for `total_cost_usd` / `step_count` / token counts.
204
+ - For a complete Agent/tool audit trail, pass `onWireEvent` to `events()` or
205
+ `wait()`. It receives every original SSE frame before semantic filtering;
206
+ `onReconnect` exposes resumable-stream retry telemetry. Never serialize the
207
+ delegation token alongside that evidence.
208
+
209
+ ### Wire-level stream (escape hatch)
210
+
211
+ `qoni.doAnything.api.events({ token, sessionId, runId, signal?, lastEventId?, onlyTopLevel? })` yields raw `{ id?, event, data }` wire events: the type is a raw string on `event.event` (e.g. `"run.status_changed"`), the payload is `event.data.data` (snake_case), and `event.data.task_id` identifies the run. `run.status_changed` payloads carry the new status in `.to` (NOT `.status`). Terminal detection is `event.event === "run.completed"`. Wire names are internal and may evolve — prefer the high-level `run.events()` + `event.type` instead.
212
+
213
+ ## Do Anything Login Wall (human handoff)
214
+
215
+ A web task often hits a page only the end user can log in to. The agent parks and emits an **`inputRequest`** event. The live-browser URL arrives on the request payload (`event.data.liveUrl`; live-url changes also stream as `progress` events with `data.liveUrl`). The client's ONLY job is to surface that URL so the human can sign in — login detection is the BACKEND's job; it re-probes and resumes the run automatically.
216
+
217
+ - Open `liveUrl` ONCE in a real browser when you see `inputRequest`. Do NOT poll, do NOT call `respond`, do NOT wait on the window closing.
218
+ - Keep iterating (or stay inside `wait()`); the run progresses past the wall and reaches its terminal event on its own once the user is signed in.
219
+ - The first login is unavoidable; later runs reuse persisted login state and never reach this wall.
220
+
221
+ ```ts
222
+ const result = await run.wait({
223
+ onInputRequest: (req) => {
224
+ if (req.liveUrl) openBrowserForUser(String(req.liveUrl)); // pop once; the backend resumes by itself
225
+ },
226
+ });
227
+ ```
228
+
229
+ ## Run Lifecycle & State Machine
230
+
231
+ A run's `status` (on `status` events and `run.status()`) moves through:
232
+
233
+ ```
234
+ pending → running → (running ⇄ awaiting_input)* → succeeded | failed | canceled
235
+ ```
236
+
237
+ - `pending` — accepted, not started.
238
+ - `running` — the agent is working.
239
+ - `awaiting_input` — parked. The `waiting_reason` (`human_intervention`,
240
+ `confirmation_required`, `user_paused`, `sleep_until`, `external_signal`) is
241
+ informational; do NOT branch your control flow on it.
242
+ - `succeeded` / `failed` / `canceled` — terminal (`run.completed`; outcome in
243
+ `terminal_reason` / `is_task_successful`).
244
+
245
+ **Client action is event-driven, not status-driven.** The only thing that needs
246
+ you is the `inputRequest` event (surface `liveUrl` so a human can log in /
247
+ confirm — see Login Wall). Everything else, including login walls, the backend
248
+ re-probes and resumes on its own. A run flapping `running ⇄ awaiting_input`
249
+ (e.g. `waiting_reason=confirmation_required` during internal pacing) is
250
+ expected — do NOT poll or call `respond`. Only call `respond` to answer an
251
+ actual `inputRequest`.
252
+
253
+ ### Parent & sub-tasks
254
+
255
+ One `run()` can spawn internal sub-runs (planning / grading). The semantic
256
+ stream demotes sub-run terminals to `progress` and reports each event's owner
257
+ on `event.runId`, so your iterator only ends on YOUR run's terminal. On the
258
+ wire-level stream (`api.events`), each event's run id is `event.data.task_id`;
259
+ filter `event.data.task_id === run_id` (or pass `onlyTopLevel: true`) to keep
260
+ only your run's events. A run envelope's `parent_run_id` / `depth` identify
261
+ where it sits in the hierarchy.
262
+
263
+ ## Local GenAuth And Docker
264
+
265
+ For local stacks, first pin the real service boundary:
266
+
267
+ 1. `QONI_HOST` should normally be the local Qoni Console/BFF gateway, for example `http://127.0.0.1:3100`.
268
+ 2. Confirm `GET /api/v3/eak/runtime-config` returns the GenAuth userpool issuer plus GUMem/WebAgent URLs that the product services validate.
269
+ 3. If a request URL ends with `.genauth.localhost`, the SDK rewrites it to `127.0.0.1` and preserves the original `Host` header. Set `QONI_LOCAL_GENAUTH_TARGET_HOST=host.docker.internal` when the target must be reached from a containerized product service. Set `QONI_LOCAL_GENAUTH_REWRITE=false` only when DNS already resolves correctly.
270
+ 4. If runtime-config works but `delegateToken` returns 403, inspect user binding, scope/resource binding, tenant binding, and any `apiCode` such as `eak.delegation.user_not_bound`.
271
+ 5. If GUMem/WebAgent says `direct delegation token deprecated`, the integration is bypassing the SDK-managed product token exchange or calling the product service with a raw delegation token directly.
272
+
273
+ ## Common Mistakes
274
+
275
+ - Do not put `delegateAgent`, user ids, or product tokens in the constructor.
276
+ - Do not call `qoni.gumem.*`, `qoni.webSearch.*`, `qoni.doAnything.*`, or `qoni.track.*` without `token`.
277
+ - Do not pass Agent metadata objects to `delegateToken`. Use a string Agent id such as `agent: "memory-agent"`; scopes and resource bindings carry permission.
278
+ - Do not invent `user.id`. Silent mode needs a real GenAuth user or the value returned by `currentUser`; interactive mode can omit `user`/`userId` because Console/BFF resolves the user through GenAuth login.
279
+ - Do not pass a long-lived GenAuth admin token to `genauth.users.*` in normal server integrations. Use AK/SK and let SDK call Qoni's admin-token endpoint with body `{}`.
280
+ - Do not add `resource`, `actions`, `expiresIn`, or `QONI_USER_ID` to the GenAuth admin-token exchange.
281
+ - Do not expose AK/SK or delegated tokens to untrusted browser/mobile/public CLI code. Interactive callbacks exchange only `grantId`, `code`, and `state` on the business server.
282
+ - Do not commit real AK/SK, GenAuth access tokens, product tokens, cookies, or LLM keys.
283
+
284
+ ## Real Smoke Inputs
285
+
286
+ For a real hosted GUMem smoke, require:
287
+
288
+ - `QONI_ACCESS_KEY` and `QONI_SECRET_KEY` — the only mandatory inputs.
289
+ - Optional `QONI_USER_ID`, a real user from the GenAuth userpool bound to the Qoni credential. A delegation token is always bound to a real user, but you do not have to supply this: with only AK/SK, call `qoni.resolveAnyBoundUser()` (lists the bound userpool and returns the first user id) — that is exactly what a smoke needs. Set `QONI_USER_ID` only to pin a specific user or skip the lookup. A placeholder like `user_1` fails with `eak.delegation.user_not_bound` (the SDK appends a hint pointing at `resolveAnyBoundUser` / `currentUser`). In real app code, resolve the actual logged-in user via `currentUser` instead of picking the first userpool member.
290
+ - Optional `QONI_HOST`; use it only for private or local Qoni deployments.
291
+ - Optional `QONI_AGENT_ID`; default examples can use `memory-agent`, but the credential and product binding must allow the requested scopes.
292
+
293
+ If the first real request fails:
294
+
295
+ | Error | Meaning | Next step |
296
+ | --- | --- | --- |
297
+ | `agent must be a string` | The online delegation API expects a string Agent id. | Use `agent: "memory-agent"`. |
298
+ | `eak.delegation.user_not_bound` | The user id is not in the credential-bound userpool. | Resolve the real user via `currentUser` or the same GenAuth userpool. |
299
+ | `eak.genauth.userpool_binding_missing` | The AK/SK is not bound to a GenAuth userpool. | Bind the Qoni credential to the target GenAuth userpool before `genauth.users.*`. |
300
+ | `eak.genauth.userpool_owner_missing` | The bound userpool owner cannot be resolved. | Fix the GenAuth userpool owner data or binding. |
301
+ | `eak.token_exchange.upstream_failed` with `unauthorized_client` or `grant_type is not enabled` | Delegation succeeded, but the managed delegation app is missing token-exchange grant support or has drifted from the tenant binding. | Repair the managed delegation app binding before calling GUMem or WebAgent product capabilities. |
302
+ | `delegation.required` | A product namespace call omitted `token`. | Pass `delegation.data.token`. |
303
+ | `direct delegation token deprecated` | The app bypassed SDK-managed token exchange. | Call GUMem/WebAgent through the SDK namespace. |
304
+
305
+ ## Testing And Verification
306
+
307
+ For SDK changes, run in the SDK repo:
308
+
309
+ ```bash
310
+ pnpm test
311
+ pnpm typecheck
312
+ pnpm build
313
+ pnpm pack --dry-run
314
+ ```
315
+
316
+ For skill or docs changes that teach integration, also validate from a fresh consumer project:
317
+
318
+ 1. Pack the current SDK into a temporary directory with `pnpm pack --pack-destination <tmp-dir>`.
319
+ 2. Create a clean Node.js 18+ TypeScript project outside the SDK repo.
320
+ 3. Install the packed tarball.
321
+ 4. Add a small mocked integration that imports from `@qoniai/qoni`, constructs `QoniAgentKit`, returns runtime-config from `host`, calls `delegateToken`, passes `delegation.data.token` to GUMem/WebAgent calls, and asserts product token exchange plus WebAgent tenant routing. Also cover `genauth.users.list()` by mocking `POST /api/v3/eak/genauth/admin-token` with body `{}` and a returned `userPoolId`.
322
+ 5. Run the consumer project's typecheck and test command.
323
+
324
+ Prefer mocked fetch-based tests for normal verification. Use real GenAuth/GUMem/WebAgent services only for explicit smoke tests, and never hard-code real secrets in repository files.
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "Qoni SDK"
3
+ short_description: "Integrate Qoni SDK delegation and product APIs"
4
+ default_prompt: "Use $qoni-sdk to build or debug a Node.js @qoniai/qoni integration with QoniAgentKit, delegateToken, runtime-config discovery, and GUMem/WebAgent product calls."