@qoniai/qoni 0.9.1 → 0.9.2

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,39 @@
1
+ # Quickstart v5 候选合同
2
+
3
+ 这些接口从 0.9.2 起随 npm 包发布,需要配套后端;发布不代表完整业务验收,个人 Agent 编排在生产环境尚未开通。
4
+
5
+ ## Personal Agent 创建与绑定
6
+
7
+ `genauth.agents.create({displayName, description?, channels: {email}, idempotencyKey?})` 的 `channels.email` 是用户选择的邮箱本地名。返回的 `id`、`email` 仅来自后端已确认的 ready/bound 回执,SDK 不生成地址或补造 ready。旧 `{displayName, idempotencyKey, mailboxLocalPart?}` 方法仍返回 provisioning 回执。
8
+
9
+ 新 overload 只发一次 create。后端明确受理、返回合法 provisioningId 且状态为 creating/mail_pending 时,最多进行 20 次只读观察;120 秒总预算从初次 create 前开始,包含该请求及所有等待和观察。每次观察最多 5 秒,受剩余总预算限制,并通过 AbortSignal 取消实际传输。错误或响应未知后不会继续观察或重发 create。
10
+
11
+ 失败的 `PersonalAgentCreateError` 保留原错误的 code/status/body/requestId/cause、`retryable: false` 和原 `idempotencyKey`;如果可确认,也保留 provisioningId。调用方可显式执行 `genauth.agents.get(error.recovery)`,按原 key 只读追溯。get 的 provisioningId/agentId/idempotencyKey 三种 selector 恰选一个,原 key 回查要求 `readOnly: true`。
12
+
13
+ 自动 key 只保障当前调用能报告其请求身份。App 要跨进程或硬崩溃恢复,须在调用前持久保存显式 key;SDK 不建隐式文件或仅靠内存恢复,也不以 displayName 推测原请求。
14
+
15
+ `genauth.agents.bindUser({agentId, userAccessToken, relation: 'owner'})` 将当前用户 token 发给后端识别本人,验证真实 bound 回执并返回 bindingId/userId。创建、绑定均不授予任务、邮箱或其他业务权限。既有 `agents.bind({provisioningId, accessToken})` 保持兼容。
16
+
17
+ ## Formal Agent 委托
18
+
19
+ 默认 mode 为 silent,也可显式指定 silent 或 interactive;其他 JavaScript mode 在本地拒绝。formal agentId 必须是 UUID,不能同时使用旧 agent/agentKey 标签。silent 保留明确的目标 userId 要求;interactive 在签名前剔除 user/userId,由真实登录授权决定身份,deprecated 类型兼容不代表采用这些字段。
20
+
21
+ `scopes: ['*']` 仅接受 formal Agent 的默认或显式 silent,不能混入具体 scopes 或 products(包括空数组),不能用于旧标签或 interactive。SDK 原样发送单个星号;后端根据当前 App/凭据、capability、绑定与已有效同意等实际权限有界求交,SDK 不本地展开、缓存授权或扩大权限。要核实实际授予的具体 scopes,应调用 `genauth.introspectDelegationToken({token})`。
22
+
23
+ 首次缺少有效授权依据仍可能返回 `needs_authorization`。HTTP 200 内的业务 403 与原 code/body 保持失败;SDK 不自动切换 interactive、续签、重试或替用户同意。显式 interactive 仍需用户完成真实授权,再按正式 complete 接口兑换。
24
+
25
+ ## Track 与附件兼容
26
+
27
+ Track 使用实际 `/track/tracks` 合同,保留终态 `done`、409 `task_in_progress` 及真实回执;不自动重跑。未实现的 Track 流式/交互、历史快照及旧 `doAnything.readArtifacts()` 在本地抛出 `QoniUnsupportedError`,不发 HTTP。附件读取沿实际 blob 接口及既有所有者校验。
28
+
29
+ ## 本地验证边界
30
+
31
+ `pnpm test` 验证既有 mock、包导出和 Task 回归。Quickstart 新增回归可单独执行:
32
+
33
+ ```sh
34
+ pnpm exec vitest run --coverage=false tests/agent-provisioning.test.ts tests/personal-agent-page.test.ts tests/formal-delegation.test.ts tests/formal-agent-silent.test.ts tests/delegation-normalizer-r3.test.ts tests/formal-agent-wildcard.test.ts tests/composed-intersections.test.ts tests/profile.test.ts tests/structured-forms.test.ts tests/structured-forms-r2.test.ts tests/task-mail.test.ts tests/w11-acceptance.test.ts
35
+ pnpm typecheck
36
+ pnpm build
37
+ ```
38
+
39
+ 这些测试使用 mock 与独立 loopback,不读取真实凭据。它们不证明 GA wildcard 真实签发、邮箱供应商、完整网页业务或 Enterprise namespace/citeSources 记忆接线已通过;这些仍须分别验收。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qoniai/qoni",
3
- "version": "0.9.1",
3
+ "version": "0.9.2",
4
4
  "description": "Unified Node.js SDK for Qoni Agent delegation, GenAuth management, GUMem memory, and WebAgent automation.",
5
5
  "homepage": "https://github.com/QONIAI/qoni-sdk-node#readme",
6
6
  "bugs": {
@@ -48,15 +48,15 @@
48
48
  "check:brand": "node scripts/check-brand.mjs",
49
49
  "gen:event-types": "node scripts/gen-event-types.mjs",
50
50
  "typecheck": "tsc --noEmit",
51
- "test": "pnpm build && pnpm check:brand && pnpm test:mock && pnpm test:package",
52
- "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 tests/site-login-settle-track.test.ts tests/abstract-interactions.test.ts tests/genauth-management.test.ts tests/genauth-communication.test.ts tests/genauth-hosted-login.test.ts tests/personal-agent-page.test.ts tests/agent-provisioning.test.ts tests/formal-agent-silent.test.ts tests/formal-agent-wildcard.test.ts tests/delegation-normalizer-r3.test.ts tests/gray-track-contract.test.ts",
51
+ "test": "pnpm build && pnpm check:brand && pnpm test:mock && pnpm test:package && pnpm test:task",
52
+ "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 tests/site-login-settle-track.test.ts tests/abstract-interactions.test.ts tests/genauth-management.test.ts tests/genauth-communication.test.ts tests/genauth-hosted-login.test.ts tests/personal-agent-page.test.ts tests/agent-provisioning.test.ts tests/formal-agent-silent.test.ts tests/formal-agent-wildcard.test.ts tests/delegation-normalizer-r3.test.ts tests/gray-track-contract.test.ts tests/current-task-login.test.ts tests/memory-context.test.ts tests/memory-submit-outcome.test.ts tests/task-scopes.test.ts",
53
53
  "test:e2e": "QONI_LIVE_E2E=1 vitest run --coverage=false --fileParallelism=false --maxWorkers=1 --testTimeout=120000 tests/live-e2e/*.test.ts",
54
54
  "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",
55
55
  "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",
56
56
  "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",
57
57
  "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",
58
58
  "test:package": "vitest run --coverage=false tests/package-surface.test.ts",
59
- "test:ci": "pnpm test:mock && pnpm test:package",
59
+ "test:ci": "pnpm test:mock && pnpm test:package && pnpm test:task",
60
60
  "smoke:auth-gum": "pnpm build && node scripts/qoni-auth-gum-smoke.mjs --mock",
61
61
  "smoke:auth-gum:live": "pnpm build && QONI_SMOKE_MODE=live node scripts/qoni-auth-gum-smoke.mjs --live",
62
62
  "pack:dry-run": "pnpm build && pnpm pack --dry-run",
@@ -67,7 +67,8 @@
67
67
  "gen:genauth-communication": "node scripts/gen-genauth-communication.mjs",
68
68
  "check:genauth-communication": "node scripts/gen-genauth-communication.mjs --check",
69
69
  "test:e2e:communication": "QONI_LIVE_E2E=1 vitest run --coverage=false --fileParallelism=false --maxWorkers=1 --testTimeout=30000 tests/live-e2e/genauth-communication.test.ts",
70
- "check:genauth-communication-examples": "node scripts/check-genauth-communication-examples.mjs"
70
+ "check:genauth-communication-examples": "node scripts/check-genauth-communication-examples.mjs",
71
+ "test:task": "vitest run --coverage=false tests/task-client.test.ts tests/task-client-http.test.ts tests/task-client-types.test.ts"
71
72
  },
72
73
  "publishConfig": {
73
74
  "access": "public"
@@ -1,11 +1,13 @@
1
1
  ---
2
2
  name: qoni-sdk
3
- description: Use when building, testing, debugging, or documenting Node.js integrations with @qoniai/qoni, the Qoni constructor, AK/SK credentials, delegateToken, GenAuth currentUser/userInfo/introspection/Authing v3 management, Qoni runtime-config discovery, GUMem, WebAgent, Web Search, Do Anything, Deep Research, Track, OBO product token exchange, or local .genauth.localhost SDK flows.
3
+ description: Use when building, testing, debugging, or documenting Node.js integrations with @qoniai/qoni, the Qoni constructor, AK/SK credentials, delegateToken, GenAuth hosted sign-up/sign-in (genauth.auth.createLoginUrl/exchangeLoginCode), currentUser/userInfo/introspection/Authing v3 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
4
  ---
5
5
 
6
6
  # Qoni SDK
7
7
 
8
- > **Requires `@qoniai/qoni` ≥ 0.4.0.** This guide documents the semantic-layer
8
+ > **Released semantic baseline: `@qoniai/qoni` ≥ 0.4.0.** Unreleased additions
9
+ > require the exact candidate archive described below, not merely a version check.
10
+ > This guide documents the semantic-layer
9
11
  > surface: `run()`/`create()` return handles (`RunHandle` / `MonitorHandle`),
10
12
  > events are normalized (`event.type`, camelCase `data`, decoded screenshots),
11
13
  > and supported 1:1 wire methods live under `qoni.<product>.api.*`. If the consumer
@@ -13,6 +15,43 @@ description: Use when building, testing, debugging, or documenting Node.js integ
13
15
  > (`npm install <path-to-qoni-sdk-node>` or a `pnpm pack` tarball) — otherwise
14
16
  > the code below will not match the installed SDK.
15
17
 
18
+ ## 内部 Pi 宿主 task facade(Unreleased、本地独立构建)
19
+
20
+ 现有 Pi 宿主可显式使用 `createTaskClient({ productHosts: { gumem }, runId, tokenProvider, fetch?, timeoutMs? })`。
21
+ 只允许 `task.memory.createSession({ signal? })` 和 `task.memory.addMessages({ messages, signal? })`;
22
+ 调用方不能指定 user/session/token/header/path/scope 或使用 recall/admin/delegation/raw。
23
+ Session 固定为 `qoni-run-<runId>`;run ID 原样保留、最多 55 个 Unicode 字符,拒绝 slash/backslash;
24
+ run 与消息 ID 拒绝 C0/C1 控制字符和 DEL。消息只含稳定非空 id、GUM 字符串 role(非空)、
25
+ 字符串 content;role/content 必须是完整 Unicode,合法 pair、空白、换行和空 content 原样保留。
26
+ 全批同步校验通过即绑定,同一客户端已提交过的 ID(包括尚未发出的批次)内容/role 不可变。
27
+ provider 拒绝或 HTTP 发出前超时仍保留绑定,原内容可显式重试;坏批次不绑定任何 ID。
28
+ 应用层 Quickstart 不直调记忆/Profile;不要把这个内部入口接成模型的任意 HTTP 工具。
29
+
30
+ `tokenProvider(product, requiredScopes, runId, signal)` 每次请求查询当前许可,返回
31
+ `{ accessToken, expiresAt }`(Unix 毫秒,未来有限值);没有 AK/SK 初始化或 token 长期缓存。
32
+ provider/HTTP/body 共享总 timeout 与 abort;host 只用可信配置,HTTPS 默认,loopback HTTP 仅隔离测试。
33
+ 不跟随重定向、不自动重试 POST。固定有序批次内部生成稳定 X-Request-ID,复用 usage outbox 去重;
34
+ 不宣称重叠/并发批次完整 exactly-once。成功只返回 Session 字段或 request_id/message_count,
35
+ 错误保留 typed Qoni 类别/code/status,去掉 provider/HTTP 私密原始正文和 cause。
36
+
37
+ 显式选择 task facade 新归档;不得覆盖 current_task 的 r2 冻结联调包。G6/WA 可信许可来源、GUM
38
+ run/session 资源端限制、Pi 私有游标/恢复及真实 Pi/E2B/离线验收仍待后继实施;方法收窄不是沙箱隔离。
39
+
40
+ ## Candidate examples and limits
41
+
42
+ The hosted-login/current_task/forms/Profile/memory/agents additions below require an explicitly selected unreleased candidate package and matching backend; package version 0.9.0 alone is insufficient. Use the exact tarball and public DTS. See `docs/delivery-examples/README.md` and `README.zh-CN.md` for typed examples.
43
+
44
+ - `gumem.recallContext()` returns `QoniResponse<MemoryContext>`: read `.data`. `doAnything.runWithMemory()` returns `{run,memory}` directly and explicitly opts in; ordinary `run()` never recalls automatically. Facts/summary sources may be unavailable; stored history URLs are not verified fact provenance.
45
+ - `doAnything.profile.read/update` returns `QoniResponse<OrdinaryTaskProfile>`. Preserve original runId/token and original user/App authority; read `.data.values/missing/writable_fields/field_types`. Only the documented ordinary field allowlist is supported, excluding phone/email verification, card/payment data and sensitive/encrypted fields. A plain form does not save Profile.
46
+ - `genauth.agents.create/get/bind` uses the original persisted request/idempotencyKey and provisioningId. `ready:false` is not success. Bind only a ready receipt using the current human accessToken; never use a browser-supplied owner ID. Backend business validation remains incomplete and this does not establish external mailbox-provider support. Do not invent an `unknown` provisioning state; inspect the actual state/errorCode and preserve uncertainty on errors.
47
+ - JSON POSTs are not automatically retried. `retryable` is not replay permission; 202/pending/unknown does not mean completed. Read the original resource/operation where supported. Do not replace an unknown write with a new key, card or Task.
48
+ - After consent narrowing/revocation, stop scheduling affected calls and respect denial. Never silently broaden the original grant, swap identity/App, or invent `genauth.consents.revoke()`. Stopping local waiting is not proof of stopping server work.
49
+ - Longer duration settings do not establish one-hour credential/browser/provider continuity. Confirmation cards do not establish forced approval or sensitive-value isolation across open browser/bash/harness paths. Keep these full-business limits explicit.
50
+
51
+ ### Corrected memory-run errors
52
+
53
+ In the corrected `runWithMemory` candidate only, `QoniError.phase` is `recall`, `task_permission`, or `task_submit`; `submissionUnknown` is false before an actual Task fetch or on an explicit rejection. `QoniTaskSubmissionUnknownError` has code `task.submit_unknown`, phase `task_submit`, submissionUnknown=true, retryable=false, fixed message and no body/cause. Never replay automatically. HTTP 4xx responses and explicit business rejections inside a successful HTTP response retain their original typed error, code, status and retryable flag; actual HTTP 5xx responses after dispatch are submission unknown and nonretryable. Once fetch is invoked, even connection refusal is conservatively unknown; this does not prove that the server received or created a Task and never permits an automatic retry. Old candidates and ordinary run APIs may lack these additive markers; a generic retryable timeout is still not write replay permission. The unified local candidate includes this corrected contract; use the fixed package/DTS. Earlier separate archives remain historical inputs.
54
+
16
55
  ## Core Contract
17
56
 
18
57
  Use `@qoniai/qoni` from trusted server-side Node.js code. Keep AK/SK on the server and create one reusable `Qoni`. 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 management, use `qoni.genauth` domain namespaces (such as `users`, `roles`, `groups`, `organizations`, `applications`) or the Authing-name migration entry `qoni.genauth.management`; the SDK exchanges AK/SK for a standard GenAuth management token internally and does not require `QONI_USER_ID`.
@@ -128,6 +167,15 @@ await qoni.genauth.users.create({
128
167
  });
129
168
  ```
130
169
 
170
+ ## Hosted Sign-Up And Sign-In (Unreleased)
171
+
172
+ For end-user sign-up/sign-in, call `qoni.genauth.auth.createLoginUrl({ redirectUri, state, screen? })` on the server, keep `state` in your own session, and redirect the browser to `url`. In the callback, compare `state` yourself before calling `qoni.genauth.auth.exchangeLoginCode({ code, redirectUri })`; the SDK never validates `state`. The AccessKey must be bound to a registered business app whose redirect URIs contain `redirectUri` exactly.
173
+
174
+ - Do not pass `clientId`, client secrets, issuer or token URLs: undeclared fields are rejected before HTTP. Secrets, PKCE verifier and nonce stay in GenAuth.
175
+ - `expiresAt` is fixed at creation (600 seconds) and is not extended by reopening, MFA or new sessions. Each URL completes once and each code exchanges once; on any exchange failure or unknown outcome, start a new `createLoginUrl`. The SDK does not retry.
176
+ - `user.isNewUser` comes from the actual registration in this sign-in (true only when the account was registered in the same hosted-page window that completes it); never derive it from timestamps or `screen`. `error=access_denied` on the callback means start again.
177
+ - GA business errors are HTTP 200 envelopes (for example `eak.hosted_login.code_invalid`, `eak.credential.binding_mismatch`, `eak.hosted_login.unavailable` when the server is not configured) and surface as typed `QoniError`s.
178
+
131
179
  Use `delegateToken` output for runtime capability namespaces:
132
180
 
133
181
  - `gumem.createSession`, `gumem.addMessages`, `gumem.recall`, `gumem.uploadResource`, `gumem.actions.*`
@@ -315,13 +363,24 @@ snapshot. Deprecated unsupported negatives never count as positive capability co
315
363
  When a run needs the user it pauses and emits one `interaction` event per request (`event.data` is a typed `Interaction`; `wait({ onInteraction })` hands you an `InteractionHandle`). Interactions are typed by what the user must do, never by business: `site_login`, `take_control`, `ask_user`, `confirmation`, plus the system `wait`. `switch (handle.interaction.type)` narrows `payload` per branch.
316
364
 
317
365
  - Act only on `status === "pending"` and dedupe by `interaction.id`: creation, status changes and reconnect replays all call back again with the same `id` and `type`. Updates with real content replace the card (an `active` take-over carries a new `liveUrl`); only empty settling shells keep the original `payload`.
318
- - Interactions arrive one at a time; the run resumes once the current one is handled.
319
- - `site_login`: `openLogin()` → show `liveUrl` → after the user signs in, `confirmSignedIn()` (it saves the login once, then the agent rechecks). Do NOT call the deprecated `login.confirm()` first. A `QoniError` coded `site_login.save_failed` or `site_login.session_replaced` is retryable: call `confirmSignedIn()` again. Delegate `webagent.site_login:request` and `webagent.site_login:confirm`.
366
+ - Follow each interaction's declared actions and current state. A pending input question may coexist with a browser-control round. Releasing a control round — SDK/OBO pending or Console active — never answers a login/credential ask; in current_task both are `site_login` cards that end only through `confirmSignedIn()` after reliable save, a declared explicit `skip()`, or expiry. Only legacy `secure_input` asks receive a recheck reply on Console active release, which proves neither sign-in nor persistence. Clarification and confirmation are never answered automatically.
367
+ - `site_login`: `openLogin()` → show `liveUrl` → after the user signs in, `confirmSignedIn()`. Do NOT call the deprecated `login.confirm()` first. For the unreleased `current_task` policy, require exact same-run/original-L declared open and confirm routes plus explicit response mode; bind L and C (`sessionId === raw.control_interaction_id`, not `raw.browser_session_id`). Open uses the existing task browser with SITE_LOGIN_REQUEST + BROWSER_USE_TAKE_CONTROL. Confirm uses SITE_LOGIN_CONFIRM + BROWSER_USE_TAKE_CONTROL + DO_ANYTHING_MANAGE, sending only one `{login_session_id:C}` request for reliable save/release/reply, without Profile confirm or another recheck. `login.confirm()` shares it and returns `state: saved, active: false, probeResult: agent_pending`; never treat persistence as verified sign-in. A successful open is required first. Invalid mode/L/C fails closed; 409, including `no_pending_login`, remains an error without fetching/replacing IDs or automatic reopen. Save failure, timeout/response loss or `site_login.invalid_response` allows an explicit retry of the original L/C; only backend exact saved history acknowledges lost success. Keep existing shared-slot/replacement guards. Legacy `/profiles/login` and Profile confirm retain single scopes, result semantics and recheck behavior. Obtain explicit user consent for all needed scopes after AccessKey policy preparation; manage cannot replace login/control permission.
368
+ - `fill_form` (Unreleased): render `payload.fields` and call `submit(values)` only when declared. Exact field names, option values, finite numbers with safe integers and ordinary address objects `{line1,line2?,city?,region?,postalCode?,country?}` are preserved. Required text/address.line1 is nonempty after trimming (original text is preserved); `0` and optional empty text are valid. Sensitive/payment-card/Profile-write fields are unavailable; credentials use browser sign-in/control. This requires the matching W4 backend and does not claim W6/provider acceptance. In this unreleased phase, structured Pi questions/forms use the main agent; subagents reject structured parameters before browser/network/card calls.
320
369
  - `ask_user`: `answer(value)` with the type `payload.answerType` names (`text` string, `number`, `boolean`, `single_choice` option value, `multiple_choice` array of option values), or `skip()`. The wire's `clarification` arrives as `ask_user`; never switch on `"clarification"`.
321
- - `take_control`: `connectControl()` only when `handle.can("connect_control")`; it resolves to `{ liveUrl, expiresAt }` (show that URL, else `payload.liveUrl`) → `releaseControl()`. `refreshControl()` returns a fresh URL.
370
+ - `take_control`: `connectControl()` only when `handle.can("connect_control")`; it resolves to `{ liveUrl, expiresAt }` (show that URL, else `payload.liveUrl`) → `releaseControl()`. `refreshControl()` returns a fresh URL. Explicitly delegate `QoniScopes.BROWSER_USE_TAKE_CONTROL` (`webagent.browser_use:take_control`) for all three methods; product `manage` and `products` bundles exclude it. Prepare the AccessKey policy and obtain user consent before creating the run whose token this handler uses.
322
371
  - `confirmation`: `confirm()` or `reject()` after showing `payload.summary`.
323
372
  - Only call methods `handle.can(kind)` reports; an undeclared action throws.
324
373
 
374
+ Browser-control migration is an unreleased breaking change. Prepare GenAuth registration and Console's browser-control/site-login policies, update only the needed AccessKey permissions, and obtain new user consent for the explicitly requested scopes. Do not expand an existing user grant through policy edits or token renewal. The old Web Agent release endpoint expects `manage`, the new SDK requests `take_control`, and the new Web Agent rejects `manage` as a substitute: use a maintenance window to finish or drain existing control tasks, then coordinate the Web Agent + SDK cutover and verify connect, refresh, release and missing-scope rejection. Do not restore legacy control aliases or add a `manage` bypass. The new Console lists permissions not covered by the selected policies separately, including legacy, unknown and partial policy entries. They are preserved by default; removing them requires selecting “Remove these permissions when saving”. Inspect the saved allowlist; preserved legacy scopes do not grant independent browser control. Deployment and publication still require a separate Owner instruction; this skill does not authorize them.
375
+
376
+ For a task that may initiate human control, create it with `taskScopes: [QoniScopes.BROWSER_USE_TAKE_CONTROL]` in `doAnything.run()` (or `api.createRun()` / `runWithMemory()`). Add specific site-login scopes only when required and already authorized. Default task creation requests manage only; delegation scope alone does not enlarge the task's original authority. `taskScopes` is SDK-only, requires known concrete Web Agent scopes, and is not supported by npm 0.9.0. Direct product tokens cannot gain authority through it.
377
+
378
+ For raw `doAnything.api.intervene()` control calls, pass `interactionId` (wire alias `interaction_id`) captured for that round. Refresh/release require it; a first user-initiated take may omit it only without an active marker, while reconnecting to an active round requires its exact ID. New take/refresh responses contain `data.interaction_id`; use the exported `DoAnythingControlResponse` type and save that field. Missing or stale IDs reach the server and return 409; do not fetch the current ID and retry, and do not rebind an old `ControlSession` to a new card. Invalidate the stale UI and wait for a separate user action on a new interaction. Card methods already carry their declared path ID. `requestId` is for input questions, not control rounds. Conflicting aliases or explicit non-string/empty IDs fail SDK validation before HTTP; omission is forwarded for server enforcement.
379
+
380
+ Raw input answer/skip calls must target the current persisted pending request and its declared action; stale or undeclared actions return 409. Use the declared confirmation/sign-in methods, not ordinary answers or skips as substitutes. Card connect returns pending; an OBO/API-key caller cannot claim an active Console connection through `surface`. Releasing a pending round leaves the pending login/credential ask unresolved. A connect-only login card cannot substitute for the declared current_task open/confirm flow above; actual page verification still belongs to the agent and deployment remains pending.
381
+
382
+ The unreleased recall mapping sends `metadataFilters` inside `recall_config.metadata_filters`. Matching aliases merge; conflicts, null/non-scalar/non-finite values and normalized system keys (`project_id`, `group_id`, `user_id`, `thread_id`, `session_id`, `scope`, `expired_at`, `validity`) throw `QoniValidationError` before HTTP. Other config keys pass through unchanged. This is SDK-side validation, not a backend isolation fix or cross-task recall acceptance.
383
+
325
384
  ```ts
326
385
  const result = await run.wait({
327
386
  onInteraction: async (handle) => {