@qoniai/qoni 0.9.0 → 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.
- package/CHANGELOG.md +203 -0
- package/README.md +548 -76
- package/README.zh-CN.md +443 -53
- package/dist/index.cjs +15082 -699
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +25826 -243
- package/dist/index.d.ts +25826 -243
- package/dist/index.js +15077 -698
- package/dist/index.js.map +1 -1
- package/docs/authing-sdk-LICENSE.txt +21 -0
- package/docs/delivery-examples/README.md +44 -0
- package/docs/delivery-examples/README.zh-CN.md +44 -0
- package/docs/delivery-examples/agents.ts +26 -0
- package/docs/delivery-examples/failure-handling.ts +21 -0
- package/docs/delivery-examples/hosted-login.ts +40 -0
- package/docs/delivery-examples/interactions.ts +30 -0
- package/docs/delivery-examples/local-consumer.mjs +159 -0
- package/docs/delivery-examples/m2-r2/README.md +20 -0
- package/docs/delivery-examples/m2-r2/README.zh-CN.md +20 -0
- package/docs/delivery-examples/m2-r2/local-consumer.mjs +53 -0
- package/docs/delivery-examples/m2-r2/memory-outcome.ts +22 -0
- package/docs/delivery-examples/m2-r2/package.json +1 -0
- package/docs/delivery-examples/m2-r2/tsconfig.json +1 -0
- package/docs/delivery-examples/package.json +5 -0
- package/docs/delivery-examples/profile-memory.ts +30 -0
- package/docs/delivery-examples/tsconfig.json +4 -0
- package/docs/genauth-communication.md +1410 -0
- package/docs/genauth-management-api.md +597 -0
- package/docs/genauth-management.md +285 -0
- package/docs/ordinary-task-profile.md +18 -0
- package/docs/quickstart-v5-contract.md +39 -0
- package/package.json +17 -7
- package/skills/qoni-sdk/SKILL.md +177 -7
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[](https://pnpm.io/)
|
|
6
6
|
[](./LICENSE)
|
|
7
7
|
|
|
8
|
-
Unified Node.js SDK for Qoni Agent delegation, GenAuth
|
|
8
|
+
Unified Node.js SDK for Qoni Agent delegation, GenAuth management, GUMem memory, WebAgent automation, web search, and monitoring.
|
|
9
9
|
|
|
10
10
|
`@qoniai/qoni` gives a trusted server one compact way to use Qoni AK/SK. For runtime product calls, your service verifies the GenAuth user, requests short-lived delegation with `delegateToken`, and then uses the returned token in GUMem, WebAgent, Do Anything, Web Search, and Track calls. For GenAuth user management, the SDK exchanges AK/SK for a standard GenAuth management token internally and calls GenAuth v3 users APIs with that token.
|
|
11
11
|
|
|
@@ -44,6 +44,17 @@ pnpm add @qoniai/qoni
|
|
|
44
44
|
yarn add @qoniai/qoni
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
+
> **Requires the matching backend:** hosted login, current-task login, ordinary
|
|
48
|
+
> forms/Profile, pre-run memory and Personal Agent provisioning below ship in 0.9.2
|
|
49
|
+
> and need the matching Qoni backend. Personal Agent provisioning is not enabled in
|
|
50
|
+
> production yet. See the [typed examples](./docs/delivery-examples/README.md).
|
|
51
|
+
|
|
52
|
+
> This unified local candidate combines those interfaces, the corrected memory
|
|
53
|
+
> error contract, original-key receipt reads and the A1 r2 memory projection.
|
|
54
|
+
> It remains unreleased; use its fixed archive/DTS and matching backend contract.
|
|
55
|
+
|
|
56
|
+
The current candidate's Personal Agent, formal delegation, wildcard and recovery boundaries are documented in the [Quickstart v5 contract](./docs/quickstart-v5-contract.md).
|
|
57
|
+
|
|
47
58
|
Requirements:
|
|
48
59
|
|
|
49
60
|
- Node.js 18 or later.
|
|
@@ -125,6 +136,21 @@ await qoni.genauth.users.update({
|
|
|
125
136
|
// await qoni.genauth.users.deleteBatch({ userIds: [profile.data.userId] });
|
|
126
137
|
```
|
|
127
138
|
|
|
139
|
+
### GenAuth Management
|
|
140
|
+
|
|
141
|
+
`qoni.genauth` exposes **591 typed management methods across 35 namespaces**, aligned with [Authing's official Node SDK 4.0.2](https://github.com/Authing/authing-node-sdk). This includes all 496 official JSON management methods plus GenAuth extensions and compatibility names, including positions, public accounts, invitations, metadata, approvals and risk lists. Calls share the Qoni AK/SK management-token exchange and support cancellation. Deployment features and permissions still determine endpoint availability.
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
await qoni.genauth.groups.create({ code: "engineering", name: "Engineering" });
|
|
145
|
+
await qoni.genauth.groups.addMembers({ code: "engineering", userIds: ["USER_ID"] });
|
|
146
|
+
const roles = await qoni.genauth.management.listRoles({ namespace: "project", page: 1, limit: 20 });
|
|
147
|
+
for await (const user of qoni.genauth.users.iterate({ limit: 50 })) {
|
|
148
|
+
console.log(user.userId);
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
[Integration guide](./docs/genauth-management.md) · [Complete API catalog](./docs/genauth-management-api.md). Roles belong to an existing permission namespace. GenAuth employment and departure rules still apply when deleting users.
|
|
153
|
+
|
|
128
154
|
### Runtime Product Delegation
|
|
129
155
|
|
|
130
156
|
GUMem, WebAgent, Do Anything, Web Search, and Track act for an end user. Silent delegation calls need a real GenAuth user id from the userpool bound to the Qoni credential, then a `delegateToken` result.
|
|
@@ -159,6 +185,11 @@ const result = await run.wait({
|
|
|
159
185
|
console.log(result.output);
|
|
160
186
|
```
|
|
161
187
|
|
|
188
|
+
This public-page example requests only the product's `read` + `manage` pair. Before using the [browser-control handler below](#interactions-hitl), explicitly add `scopes: [QoniScopes.BROWSER_USE_TAKE_CONTROL]` when requesting its run token, allow that scope in the AccessKey policy, and obtain the user's consent. The site-login handler separately needs `QoniScopes.SITE_LOGIN_REQUEST` and `QoniScopes.SITE_LOGIN_CONFIRM`. Use interactive delegation for these permissions; the `products` shorthand does not request them.
|
|
189
|
+
|
|
190
|
+
When creating a task that may request human control, pass `taskScopes: [QoniScopes.BROWSER_USE_TAKE_CONTROL]` to `doAnything.run()` (also supported by `api.createRun()` and `runWithMemory()`). Add specific `SITE_LOGIN_*` scopes there only when the task needs them. The SDK always includes manage; omitting `taskScopes` requests only manage. These scopes must already be allowed by the delegation and current policy. Only known concrete Web Agent scopes are accepted, not wildcards. Existing product tokens are passed through unchanged and cannot gain authority through this option. Available from 0.9.2.
|
|
191
|
+
|
|
192
|
+
|
|
162
193
|
`run()` returns a `RunHandle` typed to the product: `qoni.deepResearch.run()` is a `RunHandle<DeepResearchEvent>`, so its events are narrowed to what Deep Research actually emits. `capture: { screenshots: true }` opts into per-step screenshots, decoded to bytes for you. `wait()` drives the run to its terminal state and settles the result (`result.raw` carries cost, step counts, token usage).
|
|
163
194
|
|
|
164
195
|
Undecodable or empty screenshot data URIs never interrupt `wait()` or invoke `onScreenshot`, regardless of capture settings. They follow the existing progress fallback when a human-readable message is available; `onWireEvent` always retains the original payload, including frames with no progress text.
|
|
@@ -305,7 +336,7 @@ if (!pending || req.query.state !== pending.businessState) throw new Error("Inva
|
|
|
305
336
|
|
|
306
337
|
const completed = await qoni.completeDelegateToken({
|
|
307
338
|
grantId: pending.grantId,
|
|
308
|
-
code:
|
|
339
|
+
code: req.query.code,
|
|
309
340
|
state: String(req.query.grant_state), // grant_state, not your business state
|
|
310
341
|
});
|
|
311
342
|
|
|
@@ -325,7 +356,7 @@ const grant = await qoni.genauth.delegateAgent({
|
|
|
325
356
|
agentKey: "email-assistant", // Agent audit label; alias for agent
|
|
326
357
|
scopes: [
|
|
327
358
|
"webagent.do_anything:read", // Read status, events, and artifacts
|
|
328
|
-
"webagent.do_anything:manage", // Start/cancel tasks and respond to interactions
|
|
359
|
+
"webagent.do_anything:manage", // Start/cancel tasks and respond to ordinary interactions; excludes browser control
|
|
329
360
|
],
|
|
330
361
|
expiresIn: "15m", // Converted to 900 seconds before signing
|
|
331
362
|
});
|
|
@@ -347,7 +378,7 @@ prohibit email sending or deletion; a prompt is not an enforced deny policy.
|
|
|
347
378
|
|
|
348
379
|
## Choosing Scopes
|
|
349
380
|
|
|
350
|
-
Each WebAgent product exposes
|
|
381
|
+
Each WebAgent product exposes two product scopes: `read` (status, events, artifacts) and `manage` (run, cancel, ordinary interaction responses, messages, monitor CRUD). Product `manage` does not include browser connection, refresh or release. The 8 product scopes are `webagent.{do_anything, web_search, deep_research, track}:{read, manage}`. Browser control additionally requires `QoniScopes.BROWSER_USE_TAKE_CONTROL` (`webagent.browser_use:take_control`); interactive site login requires `QoniScopes.SITE_LOGIN_REQUEST` and `QoniScopes.SITE_LOGIN_CONFIRM`. Request these independent permissions explicitly in `scopes`. Each name in the `products` shorthand expands only to its product's `read` + `manage` pair:
|
|
351
382
|
|
|
352
383
|
```ts
|
|
353
384
|
await qoni.delegateToken({
|
|
@@ -359,7 +390,7 @@ await qoni.delegateToken({
|
|
|
359
390
|
|
|
360
391
|
Scope strings are validated locally before any request: a scope missing its service prefix (e.g. `do_anything:manage` instead of `webagent.do_anything:manage`) throws `QoniValidationError` immediately with the correct form in the message — every candidate when the short form is ambiguous (`profile:read` → `gumem.profile:read` or `user.profile:read`).
|
|
361
392
|
|
|
362
|
-
The site-login, user Profile, payment and Agent mailbox scopes (`QoniScopes.SITE_LOGIN_*`, `USER_PROFILE_READ`, `USER_PROFILE_WRITE`, `USER_PAYMENT_USE`, `AGENT_MAIL_READ`, `AGENT_MAIL_SEND`) belong to no product: request them explicitly in `scopes`, and allow them in the AccessKey policy. The user Profile, payment and Agent mailbox constants only name the scopes; requesting them does not by itself enable Profile, payment or mailbox access in a run.
|
|
393
|
+
The browser-control, site-login, user Profile, payment and Agent mailbox scopes (`QoniScopes.BROWSER_USE_TAKE_CONTROL`, `SITE_LOGIN_*`, `USER_PROFILE_READ`, `USER_PROFILE_WRITE`, `USER_PAYMENT_USE`, `AGENT_MAIL_READ`, `AGENT_MAIL_SEND`) belong to no product bundle: request them explicitly in `scopes`, and allow them in the AccessKey policy. The corresponding Console changes add separate browser-control and site-login policies; confirm that GenAuth and Console support them before requesting a new grant. The user Profile, payment and Agent mailbox constants only name the scopes; requesting them does not by itself enable Profile, payment or mailbox access in a run.
|
|
363
394
|
|
|
364
395
|
Use explicit scope strings when the requested permission boundary should be visible in code.
|
|
365
396
|
|
|
@@ -369,17 +400,57 @@ Use explicit scope strings when the requested permission boundary should be visi
|
|
|
369
400
|
| Create a GUMem session and recall context | `gumem.memory:read`, `gumem.memory:write`, `gumem.message:write` | Agent can create a memory session, write messages, and recall context for the current user. |
|
|
370
401
|
| Write task results | `gumem.message:write`, `gumem.action:write` | Agent can write this task's confirmed result back to GUMem. |
|
|
371
402
|
| Search public web | `webagent.web_search:read`, `webagent.web_search:manage` | Agent can search public pages and read search results. |
|
|
372
|
-
| Run a bounded web task | `webagent.do_anything:read`, `webagent.do_anything:manage` | Agent can run a visible web task, read progress, stop it, and
|
|
403
|
+
| Run a bounded web task | `webagent.do_anything:read`, `webagent.do_anything:manage` | Agent can run a visible web task, read progress, stop it, and respond to ordinary interactions. Browser control requires its separate scope. |
|
|
373
404
|
| Create a monitor | `webagent.track:read`, `webagent.track:manage` | Agent can configure monitors, read events, run checks, and stop monitors. |
|
|
374
405
|
| Sign in to sites during a run | `webagent.site_login:request`, `webagent.site_login:confirm` | The user signs in through a live browser; the login is saved for later tasks. |
|
|
375
|
-
|
|
|
406
|
+
| Human browser control | `webagent.browser_use:take_control` | The user connects, refreshes, and releases the current browser. Request this explicitly; product manage does not include it. |
|
|
407
|
+
| Use the user's Profile | `user.profile:read`, `user.profile:write` | The ordinary Task adapter reads/saves permitted name/address and supported nonencrypted primitive custom fields. Phone/email verification, payment/card data and other sensitive fields are excluded. |
|
|
376
408
|
| Pay with the user's saved card | `user.payment:use` | After the user approves a payment, the saved card is filled in for them. |
|
|
377
409
|
| Use the Agent's own mailbox | `agent.mail:read`, `agent.mail:send` | Agent can read mail sent to its own address (such as verification codes) and send mail from it. |
|
|
378
410
|
|
|
379
411
|
Silent mode is usually appropriate for low-risk actions such as reading current-user memory, writing a confirmed summary, or searching public web pages.
|
|
380
412
|
|
|
413
|
+
Browser control uses `QoniScopes.BROWSER_USE_TAKE_CONTROL`, outside the `products` shorthand. When upgrading to the corresponding backend, both the AccessKey policy and user delegation must explicitly include it. Legacy `webagent.do_anything:control`, `webagent.deep_research:control`, and `webagent.browser:control` scopes are not automatically converted. Refresh and release require this permission too. This scope adaptation does not establish stale-card protection or durable login persistence.
|
|
414
|
+
|
|
415
|
+
### Migrating browser-control permissions
|
|
416
|
+
|
|
417
|
+
This is an unreleased breaking change. It requires a coordinated cutover: the old Web Agent release endpoint requires product `manage`, whereas the new SDK requests only `webagent.browser_use:take_control` for release; the new Web Agent rejects `manage` as a substitute. Upgrading Web Agent and the SDK one at a time does not guarantee uninterrupted control sessions.
|
|
418
|
+
|
|
419
|
+
1. Prepare GenAuth's scope registration and Console's **WebAgent Browser Control** and **WebAgent Site Login** policies first. Enable the permissions actually needed on the AccessKey; the site-login policy contains `webagent.site_login:request` and `webagent.site_login:confirm`.
|
|
420
|
+
2. Request `QoniScopes.BROWSER_USE_TAKE_CONTROL` explicitly and obtain new user consent through interactive delegation. Add the site-login scopes only when needed. Updating the AccessKey policy or renewing an existing delegation must not silently add permissions to that user's grant.
|
|
421
|
+
3. During a maintenance window, stop admitting new browser-control tasks and finish or drain existing control sessions with the old versions. Then coordinate the Web Agent and SDK cutover; do not add a `manage` compatibility bypass.
|
|
422
|
+
4. Verify connect, refresh and release with the newly consented scope, and verify that delegations lacking it or carrying only legacy control scopes are rejected. The new Console lists permissions not covered by the selected policies separately, including legacy, unknown and partial policy entries. It preserves them by default and removes them only when the user selects “Remove these permissions when saving”. Inspect the saved AccessKey allowlist. Preserving an old control scope does not grant `webagent.browser_use:take_control`; migrate callers to the independent scope.
|
|
423
|
+
|
|
424
|
+
Deployment and publication still require a separate Owner instruction. This migration guide does not authorize them.
|
|
425
|
+
|
|
426
|
+
### Raw browser-control calls
|
|
427
|
+
|
|
428
|
+
After the policy and consent migration, `doAnything.api.intervene()` accepts `interactionId` (wire alias `interaction_id`). Refresh and release must send the ID captured for that control round. A first user-initiated `take_control` may omit it only when no control marker is active; reconnecting to an active round must send its exact ID. The new Web Agent take/refresh response includes `interaction_id`; keep the raw response field, rather than deriving an ID from a URL or fetching a newer card.
|
|
429
|
+
|
|
430
|
+
```ts
|
|
431
|
+
import type { DoAnythingControlResponse } from "@qoniai/qoni";
|
|
432
|
+
|
|
433
|
+
// Only for a first user-initiated take when no control round is active.
|
|
434
|
+
const first = await qoni.doAnything.api.intervene<DoAnythingControlResponse>({
|
|
435
|
+
token, runId: run.id, kind: "take_control",
|
|
436
|
+
});
|
|
437
|
+
const interactionId = first.data.interaction_id;
|
|
438
|
+
if (!interactionId) throw new Error("This backend did not return the control-round ID; stop and complete the migration");
|
|
439
|
+
|
|
440
|
+
// When this round's URL needs refreshing:
|
|
441
|
+
await qoni.doAnything.api.intervene({ token, runId: run.id, kind: "refresh_control_url", interactionId });
|
|
442
|
+
// When the user hands this round back:
|
|
443
|
+
await qoni.doAnything.api.intervene({ token, runId: run.id, kind: "release_control", interactionId });
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
Missing, ended, expired or mismatched IDs are rejected by Web Agent with HTTP 409 before side effects. The SDK preserves that rejection and does not fetch a current ID or retry with another one. Invalidate the old UI and let the user act on a newly received interaction separately; keep old `ControlSession` objects and handles bound to their original card. Card methods continue to use the ID in the declared action path. `requestId` identifies an input question, not a browser-control round. Supplying conflicting `interactionId` and `interaction_id`, or an explicit non-string/empty ID, throws `QoniValidationError` before HTTP; omission is forwarded so the server can enforce its state-dependent rule.
|
|
447
|
+
|
|
381
448
|
Interactive mode is recommended for higher-risk actions such as browser takeover, external site login, long-running monitoring, form submission, or access to sensitive artifacts and recordings.
|
|
382
449
|
|
|
450
|
+
Raw `answer_input_request` and `skip_input_request` calls must identify the current pending input with `requestId` (wire `input_request_id`). Web Agent checks the persisted request, waiting state, expiry and declared action before accepting it. A stale or undeclared action returns 409; the SDK does not replace the ID or retry. Use the declared confirmation and sign-in methods for those interactions instead of treating a raw answer or skip as approval or successful login.
|
|
451
|
+
|
|
452
|
+
In this backend change, card `connect_control` returns a pending control round. OBO/API-key callers cannot make it an active Console connection by sending `surface: "console_inline"`. Releasing such a pending round leaves a pending login/credential question unresolved. The unreleased task-login policy below uses declared `open_login` and `confirm_signed_in` for reliable save/release/reply; a connect-only card cannot substitute for that flow. The agent still verifies actual sign-in; deployment requires a separate Owner instruction.
|
|
453
|
+
|
|
383
454
|
The SDK also exports a GUMem-oriented bundle for the common session/write/recall path:
|
|
384
455
|
|
|
385
456
|
```ts
|
|
@@ -388,6 +459,58 @@ QoniScopeBundles.GUMEM_SESSION_RECALL
|
|
|
388
459
|
|
|
389
460
|
## Capability Examples
|
|
390
461
|
|
|
462
|
+
### Internal Pi host memory entry (Unreleased)
|
|
463
|
+
|
|
464
|
+
The local build adds `createTaskClient` for the existing Pi host. Application
|
|
465
|
+
Quickstart code still uses DoAnything and its interactions; it does not call
|
|
466
|
+
memory or Profile directly. Select the new task facade archive explicitly;
|
|
467
|
+
the frozen current-task integration package remains a separate input.
|
|
468
|
+
|
|
469
|
+
```ts
|
|
470
|
+
import { createTaskClient } from "@qoniai/qoni";
|
|
471
|
+
|
|
472
|
+
const task = createTaskClient({
|
|
473
|
+
productHosts: { gumem: trustedHosts.gumem },
|
|
474
|
+
runId: trustedRunId,
|
|
475
|
+
tokenProvider: getCurrentRunPermission,
|
|
476
|
+
});
|
|
477
|
+
await task.memory.createSession();
|
|
478
|
+
await task.memory.addMessages({
|
|
479
|
+
messages: [{ id: `${piSessionId}:${entry.id}`, role: entry.role, content: entry.content }],
|
|
480
|
+
});
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
The host supplies trusted service configuration, a stable run ID and
|
|
484
|
+
`getCurrentRunPermission(product, requiredScopes, runId, signal)`. The provider
|
|
485
|
+
returns `{ accessToken, expiresAt }` with expiry in Unix milliseconds, and must
|
|
486
|
+
check current permission on every request. This entry needs no AK/SK and only
|
|
487
|
+
exposes `memory.createSession` and `memory.addMessages` for `qoni-run-<runId>`.
|
|
488
|
+
Run IDs keep their complete value, allow at most 55 Unicode characters and
|
|
489
|
+
reject path separators; run and message IDs reject C0/C1 controls and DEL.
|
|
490
|
+
Messages require a stable nonempty `id`, a nonempty GUM string `role` and string
|
|
491
|
+
`content`. Role and content must be well-formed Unicode; valid pairs, whitespace,
|
|
492
|
+
newlines and empty content are preserved. No identity/token/header/path overrides
|
|
493
|
+
are accepted, including at runtime. Once a whole batch passes synchronous validation,
|
|
494
|
+
its IDs are submitted and bound on that client, including batches not yet sent.
|
|
495
|
+
Provider rejection or timeout before HTTP does not release the binding: changed
|
|
496
|
+
content or role fails locally; the original content can be explicitly retried.
|
|
497
|
+
An invalid batch binds no IDs.
|
|
498
|
+
|
|
499
|
+
`timeoutMs` (default 30 seconds) covers provider, HTTP and response body;
|
|
500
|
+
optional per-call `signal` cancels all stages. HTTPS is required except for
|
|
501
|
+
loopback HTTP fixtures; service base paths are preserved and redirects are
|
|
502
|
+
refused. Every call obtains current permission. No POST is automatically
|
|
503
|
+
retried. Ordered immutable batches receive a stable internal `X-Request-ID`
|
|
504
|
+
for existing usage-outbox deduplication; changed or overlapping batches do not
|
|
505
|
+
prove exactly-once processing. The facade returns `QoniResponse` with only
|
|
506
|
+
Session fields or `{ request_id, message_count }`, and typed Qoni errors without
|
|
507
|
+
raw private provider/HTTP payloads.
|
|
508
|
+
|
|
509
|
+
This method boundary is not resource-server authorization. Trusted G6/WA token
|
|
510
|
+
issuance and GUM run/session enforcement, durable Pi cursors, recovery and real
|
|
511
|
+
Pi/E2B/offline acceptance remain dependencies; provider/loopback tests do not
|
|
512
|
+
prove reliable background memory.
|
|
513
|
+
|
|
391
514
|
### GUMem
|
|
392
515
|
|
|
393
516
|
```ts
|
|
@@ -428,6 +551,87 @@ const context = await qoni.gumem.recall({
|
|
|
428
551
|
// The SDK example stops here because application orchestration is product-specific.
|
|
429
552
|
```
|
|
430
553
|
|
|
554
|
+
**Unreleased recall mapping:** `metadataFilters` is sent inside `recall_config.metadata_filters`. Top-level and nested camel/snake aliases merge only when duplicate values agree; conflicting values throw `QoniValidationError` before HTTP. Filter values must be strings, finite numbers or booleans; null, object and array values are rejected. System fields (`project_id`, `group_id`, `user_id`, `thread_id`, `session_id`, `scope`, `expired_at`, `validity`, including normalized aliases) are reserved. Other recall configuration keys pass through unchanged. This validation protects SDK calls; it does not fix server-side isolation or verify cross-task recall.
|
|
555
|
+
|
|
556
|
+
### Recall before starting a task (Unreleased)
|
|
557
|
+
|
|
558
|
+
Use `runWithMemory` to opt into bounded GUM context before a Do Anything task.
|
|
559
|
+
Keep the original delegation token with both `gumem.memory:read` and
|
|
560
|
+
`webagent.do_anything:manage` permissions. Each resource performs its existing
|
|
561
|
+
exchange; a WA-only product token cannot grant memory access.
|
|
562
|
+
|
|
563
|
+
```ts
|
|
564
|
+
const { run, memory } = await qoni.doAnything.runWithMemory({
|
|
565
|
+
token: delegationToken,
|
|
566
|
+
prompt: "Plan my next trip using my confirmed preferences.",
|
|
567
|
+
memory: {
|
|
568
|
+
sessionId: "daily-assistant", // existing GUM session, not a WA session ID
|
|
569
|
+
query: "confirmed travel preferences",
|
|
570
|
+
metadataFilters: { namespace: "travel" },
|
|
571
|
+
maxItems: 12,
|
|
572
|
+
maxContextBytes: 8192,
|
|
573
|
+
},
|
|
574
|
+
timeoutMs: 30_000,
|
|
575
|
+
// signal: abortController.signal,
|
|
576
|
+
});
|
|
577
|
+
console.log(run.id, memory.items, memory.truncated);
|
|
578
|
+
|
|
579
|
+
// For an application that composes its own input:
|
|
580
|
+
const { data: context } = await qoni.gumem.recallContext({
|
|
581
|
+
token: delegationToken,
|
|
582
|
+
sessionId: "daily-assistant",
|
|
583
|
+
query: "confirmed travel preferences",
|
|
584
|
+
});
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
Ordinary `run()` stays unchanged. An empty recall keeps the original prompt.
|
|
588
|
+
A failed recall, invalid context, timeout or cancellation before submission
|
|
589
|
+
prevents the Task POST. The combined deadline defaults to 30 seconds and accepts
|
|
590
|
+
1..60000 ms; writes are never automatically retried. A failure after the Task
|
|
591
|
+
POST can have an unknown commit outcome. `runWithMemory` errors expose `phase`
|
|
592
|
+
(`recall`, `task_permission`, or `task_submit`) and `submissionUnknown`. Once the
|
|
593
|
+
Task POST is dispatched, a timeout, disconnect, cancellation or unusable success
|
|
594
|
+
response raises `QoniTaskSubmissionUnknownError` with `code: "task.submit_unknown"`,
|
|
595
|
+
`phase: "task_submit"`, `submissionUnknown: true`, and `retryable: false`. The error
|
|
596
|
+
contains no original cause or request body. Reconcile the original request with
|
|
597
|
+
the server; do not blindly submit again. Before dispatch, `submissionUnknown` is
|
|
598
|
+
`false`; caller cancellation uses the safe `request.aborted` code instead of
|
|
599
|
+
returning the arbitrary signal reason. 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.
|
|
600
|
+
Once fetch is invoked, even connection refusal is conservatively unknown; this
|
|
601
|
+
does not prove that the server received or created a Task and never permits an automatic retry. Cancellation here does not cancel an
|
|
602
|
+
already created Task.
|
|
603
|
+
|
|
604
|
+
`recallContext()` returns `QoniResponse<MemoryContext>`; read `.data` for
|
|
605
|
+
`origin`, typed `items`, bounded
|
|
606
|
+
`referenceText`, and `truncated`. Defaults are 12 items / 8192 UTF8 bytes;
|
|
607
|
+
allowed limits are 1..32 items / 512..16384 bytes. Only facts, summary and user or
|
|
608
|
+
assistant history text become quoted reference data. Working memory,
|
|
609
|
+
`formatted_context`, tool channels and arbitrary metadata are excluded.
|
|
610
|
+
Authorization-shaped values are omitted; the references confer no permission or
|
|
611
|
+
approval and quoting is not a model prompt-injection guarantee.
|
|
612
|
+
|
|
613
|
+
History sources contain the returned message `id` and GUM `sessionId`, with an
|
|
614
|
+
optional exact stored `metadata.source_url` if it is a bounded safe HTTP(S) URL.
|
|
615
|
+
It is caller-stored message evidence, not verified fact provenance. No URL is
|
|
616
|
+
inferred from content, and `resource_urls` remain media-resource references.
|
|
617
|
+
With the matching A1 r2 backend projection, facts/summary items can carry
|
|
618
|
+
`recordId`, `engine` and `raw_input` sources (`id`, `sessionId`, `messageId`,
|
|
619
|
+
`createdAt`, optional stored `url`), with `sourceStatus: "record_id"`.
|
|
620
|
+
`memory_records_coverage` must confirm complete coverage before typed records
|
|
621
|
+
replace a nonempty legacy section. Missing coverage keeps the complete legacy
|
|
622
|
+
section with `sources: []` and `sourceStatus: "unavailable"`; text equality never
|
|
623
|
+
attaches a source. Content has priority within the item/byte budget: omitted
|
|
624
|
+
source sets, identities or content set `truncated`; sources are kept as whole
|
|
625
|
+
sets or omitted. Older backends still return source-less facts/summary.
|
|
626
|
+
User-level semantic recall retains its project/user/scope=user path. Missing
|
|
627
|
+
stored URLs cannot be repaired by a response field. Local GA/HTTP/PG/Qdrant evidence does not imply hosted-vector,
|
|
628
|
+
extraction-worker, Pi/cloud or production acceptance; existing GUM expiration
|
|
629
|
+
filter limitations remain separate.
|
|
630
|
+
|
|
631
|
+
The corrected-candidate markers above apply only to `runWithMemory`; ordinary
|
|
632
|
+
`run()` and older packages may lack them. Absence of these fields never makes a
|
|
633
|
+
write safe to replay. See the [corrected memory-error example](./docs/delivery-examples/m2-r2/README.md).
|
|
634
|
+
|
|
431
635
|
### Do Anything
|
|
432
636
|
|
|
433
637
|
```ts
|
|
@@ -486,46 +690,118 @@ const followUp = await qoni.deepResearch.run({
|
|
|
486
690
|
});
|
|
487
691
|
```
|
|
488
692
|
|
|
693
|
+
`doAnything.api.readArtifacts()` and the exported `DoAnythingSnapshot`,
|
|
694
|
+
`SnapshotImage`, and `SnapshotAction` types are deprecated. There has never been
|
|
695
|
+
a backend endpoint/source for that structured snapshot contract. The method
|
|
696
|
+
raises local `QoniUnsupportedError` (`code: "unsupported.operation"`) with zero
|
|
697
|
+
HTTP. Use `qoni.doAnything.artifacts({ token, runId })` / `api.listArtifacts()` for files, including
|
|
698
|
+
`artifact.content()` or `api.artifactDownloadUrl()` for downloads. Use
|
|
699
|
+
`capture: { screenshots: true }` and screenshot events for images. A text file
|
|
700
|
+
artifact is not a snapshot; the real file APIs are unchanged.
|
|
701
|
+
|
|
489
702
|
### Track
|
|
490
703
|
|
|
704
|
+
Track uses `/track/tracks`. `prompt` maps to `instructions`; include watched URLs
|
|
705
|
+
in that text. An explicit schedule is required: an integer interval of
|
|
706
|
+
600–2592000 seconds, or a daily time with a time zone. A daily check starts
|
|
707
|
+
0–30 minutes before the target time, rather than exactly at that time.
|
|
708
|
+
|
|
491
709
|
```ts
|
|
710
|
+
import type { MonitorSchedule } from "@qoniai/qoni";
|
|
711
|
+
|
|
492
712
|
const monitor = await qoni.track.create({
|
|
493
713
|
token,
|
|
494
|
-
prompt: "
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
//
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
714
|
+
prompt: "Watch https://example.com/policy and report changes.",
|
|
715
|
+
title: "Policy changes",
|
|
716
|
+
schedule: { kind: "interval", intervalSeconds: 3600 },
|
|
717
|
+
}); // create immediately starts a first_look check
|
|
718
|
+
const initial = await monitor.get();
|
|
719
|
+
const firstRunId = initial.lastTaskId; // capture this original ID once
|
|
720
|
+
if (typeof firstRunId !== "string" || !firstRunId) throw new Error("Missing initial lastTaskId");
|
|
721
|
+
const sameMonitor = await qoni.track.attach(monitor.id, { token });
|
|
722
|
+
|
|
723
|
+
async function waitForCheck(runId: string) {
|
|
724
|
+
const deadline = Date.now() + 300_000;
|
|
725
|
+
do {
|
|
726
|
+
const check = await monitor.run(runId); // exact ID in the recent 50 checks
|
|
727
|
+
if (["done", "failed", "expired", "canceled"].includes(String(check.status))) {
|
|
728
|
+
if (check.status !== "done") throw new Error(`Check ${runId} ${String(check.status)}: ${String(check.failure ?? "")}`);
|
|
729
|
+
return check;
|
|
730
|
+
}
|
|
731
|
+
await new Promise(resolve => setTimeout(resolve, Math.min(1000, Math.max(0, deadline - Date.now()))));
|
|
732
|
+
} while (Date.now() < deadline);
|
|
733
|
+
throw new Error(`Check ${runId} timed out`);
|
|
509
734
|
}
|
|
510
735
|
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
await monitor.
|
|
522
|
-
|
|
523
|
-
await monitor.delete();
|
|
524
|
-
|
|
525
|
-
// Reconnect later from a stored id:
|
|
526
|
-
const sameMonitor = await qoni.track.attach(monitor.id, { token });
|
|
736
|
+
try {
|
|
737
|
+
await monitor.pause(); // pause future scheduling; the first look continues
|
|
738
|
+
await waitForCheck(firstRunId);
|
|
739
|
+
await monitor.refine({ schedule: initial.schedule as MonitorSchedule }); // our known, validated input roundtrips
|
|
740
|
+
await monitor.refine({ schedule: { kind: "daily", at: "09:00", tz: "Asia/Singapore" } });
|
|
741
|
+
const task = await monitor.runNow(); // one explicit manual check while scheduling is paused
|
|
742
|
+
const check = await waitForCheck(task.runId); // 202 acceptance is not completion
|
|
743
|
+
const checks = await monitor.runs({ limit: 10, offset: 0 });
|
|
744
|
+
await monitor.resume();
|
|
745
|
+
} finally {
|
|
746
|
+
await monitor.delete(); // soft-delete; any in-flight check continues
|
|
747
|
+
}
|
|
527
748
|
```
|
|
528
749
|
|
|
750
|
+
Create immediately starts a `first_look` check. Capture its initial `lastTaskId`
|
|
751
|
+
and wait for that same check before one explicit `runNow()`. A concurrent run
|
|
752
|
+
can still cause 409; the SDK surfaces it and never retries. Pause stops future
|
|
753
|
+
scheduled checks and keeps any started check running.
|
|
754
|
+
|
|
755
|
+
Track check `status` is `running`, `done`, `failed`, `expired`, or `canceled`;
|
|
756
|
+
`done` is the successful terminal value. It is kept verbatim and differs from
|
|
757
|
+
one-shot `RunResult.status: "succeeded"`. Known wire schedule shapes are read
|
|
758
|
+
without revalidating server values against SDK write bounds or Intl time zones;
|
|
759
|
+
unknown/incomplete shapes remain verbatim. Writing a schedule stays strict, so
|
|
760
|
+
validate/choose supported input rather than casting every server value back.
|
|
761
|
+
|
|
762
|
+
`runs()` retrieves the backend's newest 50 checks and applies `limit`/`offset`
|
|
763
|
+
locally. `run(id)` searches that same bounded window; a missing ID raises
|
|
764
|
+
`QoniError` with local `code: "not_found"` (no HTTP status) and explains the range. A missing Track itself is the backend 404 `track_not_found`. Checks expose the
|
|
765
|
+
actual status, outcome, headline, answer and failure; no extraction, diffs or
|
|
766
|
+
snapshot IDs are synthesized. `runNow()` can return the backend's 409
|
|
767
|
+
`task_in_progress` while an earlier task is working. It returns a task reference
|
|
768
|
+
on 202; read checks to observe completion.
|
|
769
|
+
|
|
770
|
+
| Retained surface | Current behavior |
|
|
771
|
+
| --- | --- |
|
|
772
|
+
| `track.create`, `track.attach`, `MonitorHandle.get` | Create/read the real Track resource; get converts known wire schedules; unknown shapes stay verbatim. |
|
|
773
|
+
| `MonitorHandle.pause`, `resume` | PATCH `status: paused` / `active`; pause keeps an in-flight check running. |
|
|
774
|
+
| `MonitorHandle.refine` | PATCH title/schedule only; changing prompt/instructions/intent requires a new track. |
|
|
775
|
+
| `MonitorHandle.runNow` | POST `run_now`; returns the actual task reference. |
|
|
776
|
+
| `MonitorHandle.runs`, `run` | Read recent checks with Track read scope only; no DoAnything fallback. |
|
|
777
|
+
| `MonitorHandle.delete` | DELETE 204; soft deletion stops scheduling and does not cancel running work. |
|
|
778
|
+
| `track.api.createMonitor`, `getMonitor` | Historical names retained; use real `instructions`, `title`, and wire schedule `{ every_seconds: N }` or `{ daily_at: "HH:MM", tz }`. |
|
|
779
|
+
| `track.api.updateMonitor` | PATCH real title/instructions/schedule/status fields; wire API permits instructions, while high-level refine does not. |
|
|
780
|
+
| `track.api.runNow`, `deleteMonitor` | Real `run_now` / DELETE; raw task reference / empty 204. |
|
|
781
|
+
| `track.api.listRuns`, `getRun` | Raw checks, local limit/offset, exact lookup within recent 50. |
|
|
782
|
+
| `MonitorHandle.events`, `interactionHandle`, `track.api.events`, `intervene` | Deprecated; no backend endpoint. Local `QoniUnsupportedError`, zero HTTP or token exchange. |
|
|
783
|
+
| `targetUrls`, `extractionSchema`, `triggerDsl`, `stopConditionDsl`, `notifyChannel` (including snake_case aliases), legacy `url` | No backend equivalent; rejected locally with `QoniUnsupportedError`. Put URLs in the prompt explicitly. |
|
|
784
|
+
| Cron schedule | Deprecated and rejected locally with a daily alternative. Arbitrary cron is never silently converted. |
|
|
785
|
+
|
|
786
|
+
A missing schedule, malformed input, invalid interval/time/zone, or invalid
|
|
787
|
+
pagination raises `QoniValidationError` before HTTP. `MonitorSchedule` retains
|
|
788
|
+
the deprecated cron branch; optional undefined fields retain omission behavior, while specified unsupported fields fail; historical event and option types remain exported
|
|
789
|
+
for source compatibility. Unsupported methods are not successful capability tests.
|
|
790
|
+
|
|
791
|
+
### The original Task user's ordinary Profile (Unreleased)
|
|
792
|
+
|
|
793
|
+
`doAnything.profile.read({ runId, token, fields })` and
|
|
794
|
+
`doAnything.profile.update({ runId, token, values })` return `QoniResponse<OrdinaryTaskProfile>`.
|
|
795
|
+
Read `.data.values`, `.data.missing`, `.data.writable_fields` and `.data.field_types`.
|
|
796
|
+
Request `user.profile:read` and, only for explicit saves, `user.profile:write` in
|
|
797
|
+
the original delegation together with the required Web Agent scopes. A newer
|
|
798
|
+
token cannot broaden the original Task authorization. This is the original
|
|
799
|
+
Task owner's ordinary Profile, not admin user management or browser login state.
|
|
800
|
+
|
|
801
|
+
Use the [field allowlist and write limits](./docs/ordinary-task-profile.md).
|
|
802
|
+
Unknown writes require readback of the original run; do not replay the update.
|
|
803
|
+
Ordinary form submission alone does not save Profile fields.
|
|
804
|
+
|
|
529
805
|
### GenAuth User Context and Management
|
|
530
806
|
|
|
531
807
|
```ts
|
|
@@ -551,6 +827,41 @@ const created = await client.genauth.users.create({
|
|
|
551
827
|
|
|
552
828
|
`currentUser` and `genauth.userInfo` use a GenAuth access token because they read the logged-in user's identity. `genauth.users.*` is a management-plane capability, but application code still authenticates the SDK with Qoni AK/SK: the SDK signs `POST /api/v3/eak/genauth/admin-token` with an empty JSON body `{}`, receives a standard GenAuth management token plus `userPoolId`, then calls GenAuth v3 user management APIs with `Authorization: Bearer ...` and `x-authing-userpool-id`. It does not send `resource`, `actions`, `expiresIn`, or `QONI_USER_ID`. Runtime capability calls such as GUMem/WebAgent use `delegateToken` output instead.
|
|
553
829
|
|
|
830
|
+
### GenAuth User Sign-Up and Sign-In (Unreleased)
|
|
831
|
+
|
|
832
|
+
Send users to the Qoni-hosted GenAuth sign-up/sign-in page and exchange the callback code on your server. The AccessKey must be bound to a registered business app in Qoni Console; the business app, its client secret, PKCE verifier and nonce stay on the server and never appear in the URL.
|
|
833
|
+
|
|
834
|
+
```ts
|
|
835
|
+
// 1. Start: store `state` in your own session, then redirect the browser.
|
|
836
|
+
const state = crypto.randomUUID();
|
|
837
|
+
session.loginState = state;
|
|
838
|
+
const { url, expiresAt } = await qoni.genauth.auth.createLoginUrl({
|
|
839
|
+
redirectUri: "https://app.example.com/auth/callback", // registered on the business app (exact match)
|
|
840
|
+
state,
|
|
841
|
+
screen: "signup", // optional; default "login"
|
|
842
|
+
});
|
|
843
|
+
res.redirect(url);
|
|
844
|
+
|
|
845
|
+
// 2. Callback: verify `state` yourself first — the SDK does not check it for you.
|
|
846
|
+
if (typeof req.query.state !== "string" || !session.loginState ||
|
|
847
|
+
req.query.state !== session.loginState || req.query.error !== undefined ||
|
|
848
|
+
typeof req.query.code !== "string" || !req.query.code) return res.status(400).end();
|
|
849
|
+
// Use session-scoped atomic match-and-consume storage in a multi-replica app;
|
|
850
|
+
// see the typed hosted-login example. Never exchange this state/code twice.
|
|
851
|
+
delete session.loginState;
|
|
852
|
+
const login = await qoni.genauth.auth.exchangeLoginCode({
|
|
853
|
+
code: req.query.code,
|
|
854
|
+
redirectUri: "https://app.example.com/auth/callback",
|
|
855
|
+
});
|
|
856
|
+
// login.user: { id, email?, phone?, name?, isNewUser }
|
|
857
|
+
// login.accessToken / login.idToken / login.expiresIn
|
|
858
|
+
```
|
|
859
|
+
|
|
860
|
+
- `expiresAt` is the server-side deadline fixed when the URL is created (600 seconds). Reopening or refreshing the page, multi-factor steps and new browser sessions do not extend it; after it passes, the page shows an error or the callback carries `error=access_denied`.
|
|
861
|
+
- A URL completes at most once, and a code is exchanged at most once. If an exchange fails, times out or its response is lost, the code is not reusable: start again with `createLoginUrl`. The SDK never retries these calls.
|
|
862
|
+
- `isNewUser` is decided by GenAuth from the actual registration in this sign-in: it is `true` only when the account was registered in the same hosted-page window that completes the sign-in (same browser session, business app, user pool and final user), including after a page refresh, multi-factor step or signing in again in that window. Signing in to the same account in another window, a reused session or an existing account is `false`. If the server cannot match a registration in this browser session to a window, the callback carries `error=access_denied` instead of guessing.
|
|
863
|
+
- Errors arrive as typed `QoniError`s: GA business failures are HTTP 200 with an error envelope (for example `eak.hosted_login.code_invalid`, `eak.hosted_login.binding_mismatch`, `eak.credential.binding_mismatch`, `eak.hosted_login.business_app_unbound`, or `eak.hosted_login.unavailable` when the server is not configured for hosted login), and the SDK raises them rather than returning data. Use `login.accessToken` with `genauth.userInfo` to read the same user again.
|
|
864
|
+
|
|
554
865
|
## API Surface
|
|
555
866
|
|
|
556
867
|
### Client
|
|
@@ -586,9 +897,9 @@ const qoni = new Qoni({
|
|
|
586
897
|
| Namespace | Methods |
|
|
587
898
|
| --- | --- |
|
|
588
899
|
| Delegation | `delegateToken`, `completeDelegateToken`, deprecated aliases `delegateAgent`, `completeDelegateAgent` |
|
|
589
|
-
| GenAuth | `userInfo`, `jwks`, `discovery`, `introspectDelegationToken`, `users.list`, `users.get`, `users.getBatch`, `users.create`, `users.createBatch`, `users.update`, `users.deleteBatch` |
|
|
590
|
-
| GUMem | `createSession`, `addMessages`, `recall`, `uploadResource`, `actions.record`, `actions.recall`, `actions.stream` |
|
|
591
|
-
| Do Anything | `run`, `attach` → `RunHandle` |
|
|
900
|
+
| GenAuth | `userInfo`, `jwks`, `discovery`, `introspectDelegationToken`, `auth.createLoginUrl`, `auth.exchangeLoginCode`, `agents.create/get/bind` (Unreleased), `users.list`, `users.get`, `users.getBatch`, `users.create`, `users.createBatch`, `users.update`, `users.deleteBatch` |
|
|
901
|
+
| GUMem | `createSession`, `addMessages`, `recall`, `recallContext`, `uploadResource`, `actions.record`, `actions.recall`, `actions.stream` |
|
|
902
|
+
| Do Anything | `run`, `attach` → `RunHandle`; opt-in `runWithMemory`; `profile.read/update` (Unreleased) |
|
|
592
903
|
| Deep Research | `run`, `attach` → `RunHandle` |
|
|
593
904
|
| Web Search | `run`, `attach` → `RunHandle` |
|
|
594
905
|
| Track | `create`, `attach` → `MonitorHandle` |
|
|
@@ -632,7 +943,7 @@ interface RunResult {
|
|
|
632
943
|
runId: string;
|
|
633
944
|
status: "succeeded" | "failed" | "canceled"; // delivery status of the run
|
|
634
945
|
output?: unknown; // final answer / payload (shape is task-specific)
|
|
635
|
-
artifacts: Artifact[]; //
|
|
946
|
+
artifacts: Artifact[]; // persisted DoAnything files / deepResearch reports
|
|
636
947
|
isTaskSuccessful?: boolean; // did the task achieve its goal (distinct from `status`)
|
|
637
948
|
terminalReason?: string;
|
|
638
949
|
raw: JsonObject; // full wire envelope — cost, step counts, token usage
|
|
@@ -645,7 +956,7 @@ is the typed payload for that call (e.g. `genauth.users.list` → a paged user
|
|
|
645
956
|
list on `.data`). The exact `T` shapes live in the bundled `.d.ts` and follow
|
|
646
957
|
the backend contract.
|
|
647
958
|
|
|
648
|
-
`MonitorHandle` (Track)
|
|
959
|
+
`MonitorHandle` (Track) provides `id`, `get()`, `pause()`, `resume()`, `refine(patch)`, `runNow()`, `runs()`, `run(runId)`, and `delete()`. Its deprecated `events()` and `interactionHandle()` methods fail locally; see Track above for the complete mapping.
|
|
649
960
|
|
|
650
961
|
### Interactions (HITL)
|
|
651
962
|
|
|
@@ -657,14 +968,23 @@ has to do, never by business, so one handler covers every task:
|
|
|
657
968
|
| --- | --- | --- |
|
|
658
969
|
| `site_login` | signs in to a site in the live browser | `openLogin()`, `confirmSignedIn()` |
|
|
659
970
|
| `take_control` | operates the browser by hand (captcha, 2FA, …) | `connectControl()`, `refreshControl()`, `releaseControl()` |
|
|
971
|
+
| `fill_form` (Unreleased) | completes ordinary fields for this task | `submit(values)`, `skip()` |
|
|
660
972
|
| `ask_user` | answers a question shaped by `answerType` / `options` | `answer()`, `skip()` |
|
|
661
973
|
| `confirmation` | allows or rejects an action | `confirm()`, `reject()` |
|
|
662
974
|
| `wait` | nothing — the system is waiting (rate limit, external event) | `retry()` |
|
|
663
975
|
|
|
976
|
+
A confirmation records the decision for that card; it does not establish
|
|
977
|
+
forced approval of all final actions through open browser/bash/harness paths.
|
|
978
|
+
Ordinary forms are not a channel for payment secrets or isolated sensitive entry.
|
|
979
|
+
|
|
664
980
|
`Interaction` is a union distributed over `type`, so `switch (request.type)`
|
|
665
981
|
narrows `request.payload` in each branch. Each method posts to the endpoint the
|
|
666
982
|
backend declared in the interaction's `actions[]`, so you never hard-code a route:
|
|
667
983
|
|
|
984
|
+
Create the token for this handler with `products: ["doAnything"]` plus explicit `scopes: [QoniScopes.BROWSER_USE_TAKE_CONTROL, QoniScopes.SITE_LOGIN_REQUEST, QoniScopes.SITE_LOGIN_CONFIRM]` after AccessKey policy preparation and user consent. The product permissions supply DoAnything manage; omit the site-login scopes when the task does not need login. `products: ["doAnything"]` alone cannot authorize `connectControl()`, `refreshControl()` or `releaseControl()`; follow the migration steps above before switching versions.
|
|
985
|
+
|
|
986
|
+
The task must have been created with `taskScopes: [QoniScopes.BROWSER_USE_TAKE_CONTROL]` to initiate human control; delegation scope alone is insufficient. An existing task's original authority cannot be expanded by refreshing a control URL.
|
|
987
|
+
|
|
668
988
|
```ts
|
|
669
989
|
import type { InteractionHandle } from "@qoniai/qoni";
|
|
670
990
|
|
|
@@ -703,8 +1023,14 @@ async function handleInteraction(handle: InteractionHandle) {
|
|
|
703
1023
|
const result = await run.wait({ onInteraction: handleInteraction });
|
|
704
1024
|
```
|
|
705
1025
|
|
|
706
|
-
|
|
707
|
-
|
|
1026
|
+
Follow each card's declared actions and current state. A pending input question
|
|
1027
|
+
may coexist with a control round. Releasing a control round — SDK/OBO pending or
|
|
1028
|
+
Console active — never answers a login/credential ask; in current_task both are
|
|
1029
|
+
`site_login` cards that end only through `confirmSignedIn()` after reliable save,
|
|
1030
|
+
a declared explicit `skip()`, or expiry. Only legacy `secure_input` asks receive
|
|
1031
|
+
a recheck reply on Console active release, which proves neither sign-in nor
|
|
1032
|
+
persistence. Clarification and confirmation are never answered automatically.
|
|
1033
|
+
The same interaction calls back again on every status change and on
|
|
708
1034
|
reconnect replays, always with the same `id` and `type` — key your UI cards by
|
|
709
1035
|
`request.id`. An update with content of its own replaces the card (a take-over
|
|
710
1036
|
that became `active` carries its new `liveUrl`); when the backend settles an
|
|
@@ -714,11 +1040,27 @@ example from Console; show it if your UI offers control there. Methods: `answer(
|
|
|
714
1040
|
`reject()`, `openLogin({ siteId?, profileId? })`, `confirmSignedIn()`,
|
|
715
1041
|
`connectControl()`, `refreshControl()`, `releaseControl()`, `retry()`,
|
|
716
1042
|
`switchProfile()`. `connectControl()` and `refreshControl()` resolve to
|
|
717
|
-
`{ liveUrl, expiresAt?, status?, raw }
|
|
718
|
-
|
|
1043
|
+
`{ liveUrl, expiresAt?, status?, raw }`, where `liveUrl` is the browser-control URL
|
|
1044
|
+
to show the user. `handle.can(kind)` reports whether the interaction currently
|
|
719
1045
|
offers that action; calling a method the backend did NOT declare throws (so a
|
|
720
1046
|
"recovery" button only exists when it actually works).
|
|
721
1047
|
|
|
1048
|
+
### Ordinary forms (Unreleased)
|
|
1049
|
+
|
|
1050
|
+
`fill_form` collects ordinary details for the current task. Render `payload.fields` using the exported `FormField` type, then call `handle.submit(values)` when the card declares `submit`. Keys are the exact `field.name`; select answers use option `value`, numbers stay numbers, and an address is `{ line1, line2?, city?, region?, postalCode?, country? }`. Required text/address values must be nonempty; optional text may be empty and `0` is valid. Unknown fields, wrong types/options, NUL and nonfinite numbers are rejected before transport and again by the backend on the current durable card.
|
|
1051
|
+
|
|
1052
|
+
```ts
|
|
1053
|
+
if (handle.interaction.type === "fill_form" && handle.can("submit")) {
|
|
1054
|
+
await handle.submit({
|
|
1055
|
+
count: 0,
|
|
1056
|
+
method: "ship",
|
|
1057
|
+
address: { line1: "Synthetic road", city: "Singapore" },
|
|
1058
|
+
});
|
|
1059
|
+
}
|
|
1060
|
+
```
|
|
1061
|
+
|
|
1062
|
+
This is task-only collection. Sensitive fields, payment cards and `profileField` writes are unavailable here; passwords use browser sign-in/take-control. It needs the matching W4 backend. Final W6 synthesis and provider acceptance are separate.
|
|
1063
|
+
|
|
722
1064
|
**`ask_user` answers are structured.** `answer()` takes the value matching
|
|
723
1065
|
`payload.answerType` — `text` → string, `number` → number, `boolean` → boolean,
|
|
724
1066
|
`single_choice` → one `options[].value`, `multiple_choice` → an array of option
|
|
@@ -726,21 +1068,39 @@ values — and throws `QoniValidationError` before sending anything that does no
|
|
|
726
1068
|
fit. The wire still calls this type `clarification`; the SDK presents it as
|
|
727
1069
|
`ask_user` (choices arrive as `{ value, label }` options).
|
|
728
1070
|
|
|
729
|
-
**Sign-in takes two calls.**
|
|
730
|
-
`
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
`
|
|
742
|
-
|
|
743
|
-
|
|
1071
|
+
**Sign-in takes two SDK calls.** Pass a `siteId` from `payload.sites[].siteId` to
|
|
1072
|
+
`openLogin()`, or omit it when one site is listed; show its `liveUrl` only to the
|
|
1073
|
+
current user. After the user signs in, call `confirmSignedIn()`.
|
|
1074
|
+
|
|
1075
|
+
For the **unreleased `current_task` policy**, the backend declares open/confirm
|
|
1076
|
+
endpoints on the same DoAnything run and original login question L. Opening uses
|
|
1077
|
+
the existing task browser, without navigating or opening a separate browser;
|
|
1078
|
+
`login.sessionId` is the fixed control round C, also returned in
|
|
1079
|
+
`raw.control_interaction_id`, while `raw.browser_session_id` identifies the browser.
|
|
1080
|
+
Opening requests SITE_LOGIN_REQUEST + BROWSER_USE_TAKE_CONTROL; confirmation
|
|
1081
|
+
requests SITE_LOGIN_CONFIRM + BROWSER_USE_TAKE_CONTROL + DO_ANYTHING_MANAGE.
|
|
1082
|
+
Allow these policies on the AccessKey and obtain explicit user consent before
|
|
1083
|
+
the coordinated migration above; `manage` cannot replace login/control permission.
|
|
1084
|
+
|
|
1085
|
+
One confirm HTTP `{login_session_id:C}` on the original declared endpoint saves
|
|
1086
|
+
reliably, releases C and replies to L. The SDK sends neither Profile confirm nor
|
|
1087
|
+
a second recheck. The deprecated `login.confirm()` shares this same request and
|
|
1088
|
+
returns `state: "saved", active: false, probeResult: "agent_pending"` with the
|
|
1089
|
+
unmodified response in `raw`. Saved does not mean verified: the agent must check
|
|
1090
|
+
the page before continuing. Unknown/missing mode or mismatched L/C fails closed;
|
|
1091
|
+
confirmation without a successful open fails locally. A 409 (including
|
|
1092
|
+
`no_pending_login`) remains an error, with no ID replacement or automatic reopen.
|
|
1093
|
+
503 save errors, timeout/lost responses and `site_login.invalid_response` leave
|
|
1094
|
+
the original L/C available for an explicit retry; a lost success is acknowledged
|
|
1095
|
+
only by the backend's exact saved history.
|
|
1096
|
+
|
|
1097
|
+
The legacy `/profiles/login` policy retains its single request/confirm scopes,
|
|
1098
|
+
Profile save followed by the agent recheck, and existing `active/probeResult`
|
|
1099
|
+
semantics, including the legacy `no_pending_login` recheck behavior. Shared slots,
|
|
1100
|
+
concurrent calls and replacement guards apply to both policies: an old result
|
|
1101
|
+
cannot confirm a newer round. Handles from the same RunHandle/MonitorHandle share
|
|
1102
|
+
the session; a RunHandle from another `attach()` does not. Use
|
|
1103
|
+
`openLogin()` → `confirmSignedIn()`; calling `login.confirm()` first is unnecessary.
|
|
744
1104
|
|
|
745
1105
|
The discriminant strings are also exported as constants so you can avoid bare
|
|
746
1106
|
literals: `InteractionTypes` (`SiteLogin` / `TakeControl` / `AskUser` /
|
|
@@ -761,11 +1121,11 @@ if (handle.type === InteractionTypes.AskUser && handle.can(ActionKinds.answer))
|
|
|
761
1121
|
|
|
762
1122
|
### Wire-Level Escape Hatch
|
|
763
1123
|
|
|
764
|
-
|
|
1124
|
+
Supported low-level methods live under `qoni.<product>.api.*` (e.g. `qoni.doAnything.api.createSession`, `qoni.deepResearch.api.followUp`, `qoni.track.api.createMonitor`). These mirror the backend HTTP contract directly — snake_case fields, double-envelope events, single-ID `/{product}/runs/{run_id}` addressing for run operations — and their shapes may evolve with the API. They are not part of the frozen public contract.
|
|
765
1125
|
|
|
766
1126
|
`event.raw.event` is the **raw wire event type** — an internal transport name (`run.action.started`, `run.section_completed`, …) whose naming is historical and inconsistent, exposed as a plain string. This is an implementation detail, not the product API: developers match on the curated `event.type` above. The full wire-type catalog is intentionally NOT exported — reach for `event.raw.event` (a string) only when you need an exact wire match the curated layer folds into `progress` (e.g. telling a specific browser action apart).
|
|
767
1127
|
|
|
768
|
-
|
|
1128
|
+
`QoniScopes.BROWSER_USE_TAKE_CONTROL` authorizes the browser-control interaction methods and raw take/refresh/release calls described above. Site Login scopes (`QoniScopes.SITE_LOGIN_REQUEST`, `QoniScopes.SITE_LOGIN_CONFIRM`) back `InteractionHandle.openLogin()` and `confirmSignedIn()` on run interactions.
|
|
769
1129
|
|
|
770
1130
|
### Response Shape
|
|
771
1131
|
|
|
@@ -794,9 +1154,9 @@ that type:
|
|
|
794
1154
|
// "phase" string — the Deep Research phase name
|
|
795
1155
|
// "sectionReady" string — the Deep Research section title
|
|
796
1156
|
// "resultsReady" number — Web Search count of unique results
|
|
797
|
-
// "monitorCreated" string —
|
|
798
|
-
// "triggered" string —
|
|
799
|
-
// "checkCompleted" boolean —
|
|
1157
|
+
// "monitorCreated" string — deprecated legacy monitor wire type
|
|
1158
|
+
// "triggered" string — deprecated legacy monitor wire type
|
|
1159
|
+
// "checkCompleted" boolean — deprecated legacy monitor wire type
|
|
800
1160
|
// RICH events — event.data is a small object:
|
|
801
1161
|
// "message" { text, role }
|
|
802
1162
|
// "interaction" the typed Interaction object (HITL) — see Interactions
|
|
@@ -814,7 +1174,7 @@ Each product handle is typed to its own subset:
|
|
|
814
1174
|
| Do Anything | core: `progress` / `message` / `interaction` / `screenshot` / `browserLiveUrlChanged` / `done` |
|
|
815
1175
|
| Web Search | core + `resultsReady` |
|
|
816
1176
|
| Deep Research | core + `phase` / `sectionReady` (the outline gate arrives as an `interaction`) |
|
|
817
|
-
| Track (`MonitorHandle`) |
|
|
1177
|
+
| Track (`MonitorHandle`) | No SSE endpoint; historical MonitorEvent types are deprecated. Read checks with runs()/run(). |
|
|
818
1178
|
|
|
819
1179
|
For a live browser preview, listen for `browserLiveUrlChanged` and show
|
|
820
1180
|
`event.data.liveUrl` in your viewer. A run can switch browser tabs after its
|
|
@@ -846,18 +1206,31 @@ All error classes are exported and can be matched with `instanceof`:
|
|
|
846
1206
|
|
|
847
1207
|
| Class | Fires when | Typical recovery |
|
|
848
1208
|
| --- | --- | --- |
|
|
1209
|
+
| `QoniUnsupportedError` | Retained surface has no backend operation (code: `unsupported.operation`); zero HTTP. | Use the alternative named in the error. |
|
|
849
1210
|
| `QoniValidationError` | Local input check failed before any request (bad scope string, silent delegation missing a user). | Fix the call — the message states the correct form. |
|
|
850
1211
|
| `QoniAuthError` | AK/SK signing rejected by Qoni (bad/rotated credentials). | Check `accessKey` / `secretKey`. |
|
|
851
1212
|
| `QoniPermissionDeniedError` | The AccessKey or delegation token lacks a required scope (403). | Check the AccessKey permission policy and request the missing scope with `delegateToken`. |
|
|
852
|
-
| `QoniTokenExpiredError` | The delegation / product token expired. |
|
|
1213
|
+
| `QoniTokenExpiredError` | The delegation / product token expired. | Re-establish only currently permitted access; do not expand the old grant or replay an uncertain write. |
|
|
853
1214
|
| `QoniDelegationRequiredError` | A product call was made without `token`, or with a raw token the gateway won't accept directly. | Pass `delegation.data.token` to the product call. |
|
|
854
|
-
| `QoniRateLimitError` | Rate limited (429). | Back off
|
|
855
|
-
| `QoniTimeoutError` | A request (or `wait({ timeoutMs })`) timed out. |
|
|
856
|
-
| `
|
|
1215
|
+
| `QoniRateLimitError` | Rate limited (429). | Back off for a safe read; reconcile any uncertain write before an explicit retry. |
|
|
1216
|
+
| `QoniTimeoutError` | A request (or `wait({ timeoutMs })`) timed out. | For an existing run, `attach()`/read status. A timed-out creation may have committed; do not submit a replacement automatically. |
|
|
1217
|
+
| `QoniTaskSubmissionUnknownError` (corrected candidate) | `runWithMemory` dispatched a Task POST without a definitive outcome. | `task.submit_unknown`, `submissionUnknown: true`, `retryable: false`; reconcile the original request, never automatically resubmit. |
|
|
1218
|
+
| `QoniUpstreamError` | A downstream service failed (5xx / upstream exchange failure). | Reconcile writes first; `retryable` does not prove that a side effect was rolled back. |
|
|
857
1219
|
| `QoniError` | Base class / any other code. | Inspect `err.code`. |
|
|
858
1220
|
|
|
859
|
-
|
|
860
|
-
|
|
1221
|
+
JSON requests are **not** automatically retried. `err.retryable` describes an
|
|
1222
|
+
error category, not permission to replay a write; SSE reconnect is separate.
|
|
1223
|
+
A 202/`ready: false` response means accepted or pending, not completed. Keep the
|
|
1224
|
+
original run/provisioning/operation ID. Read it back when supported; a missing
|
|
1225
|
+
receipt, 404 or elapsed time does not prove the first write had no effect.
|
|
1226
|
+
|
|
1227
|
+
When the user narrows or revokes consent in the supported user permission UI,
|
|
1228
|
+
stop scheduling calls that need the removed scopes and respect backend denial.
|
|
1229
|
+
Do not silently recreate a wider grant, switch user/App or refresh into extra
|
|
1230
|
+
permissions. This candidate exposes no `genauth.consents.revoke()` SDK method.
|
|
1231
|
+
Stopping a local wait does not itself stop a server Task or prove an external
|
|
1232
|
+
operation has stopped. [Failure handling examples](./docs/delivery-examples/README.md)
|
|
1233
|
+
keep these outcomes separate.
|
|
861
1234
|
|
|
862
1235
|
```ts
|
|
863
1236
|
import {
|
|
@@ -877,7 +1250,8 @@ try {
|
|
|
877
1250
|
error instanceof QoniTokenExpiredError ||
|
|
878
1251
|
error instanceof QoniDelegationRequiredError
|
|
879
1252
|
) {
|
|
880
|
-
//
|
|
1253
|
+
// Stop this operation. Obtain explicit consent only if the user wants
|
|
1254
|
+
// additional permissions; never broaden the existing Task/grant silently.
|
|
881
1255
|
}
|
|
882
1256
|
|
|
883
1257
|
if (
|
|
@@ -885,7 +1259,8 @@ try {
|
|
|
885
1259
|
error instanceof QoniTimeoutError ||
|
|
886
1260
|
error instanceof QoniUpstreamError
|
|
887
1261
|
) {
|
|
888
|
-
//
|
|
1262
|
+
// Do not repeat run creation or another write here. Read the original
|
|
1263
|
+
// operation when its ID is known; otherwise preserve the unknown outcome.
|
|
889
1264
|
}
|
|
890
1265
|
|
|
891
1266
|
throw error;
|
|
@@ -936,3 +1311,100 @@ const artifacts = await client.doAnything.artifacts({ token, runId: run.id });
|
|
|
936
1311
|
```
|
|
937
1312
|
|
|
938
1313
|
Wire methods: `client.doAnything.api.listArtifacts(...)` and `artifactDownloadUrl(...)`.
|
|
1314
|
+
|
|
1315
|
+
## Agent Mail and Agent Phone
|
|
1316
|
+
|
|
1317
|
+
The local GenAuth communication extension provides `qoni.genauth.agentMail` and `qoni.genauth.agentPhone`, including runtime and administrator APIs. Runtime requires native GenAuth credentials via `genauthCommunicationCredentials`; administration reuses Qoni token exchange. See [the communication guide](docs/genauth-communication.md) and [method index](docs/genauth-communication-api.md).
|
|
1318
|
+
|
|
1319
|
+
## Local Agent integration extension
|
|
1320
|
+
|
|
1321
|
+
The integration branch adds `qoni.genauth.auth.createLoginUrl` / `exchangeLoginCode` and
|
|
1322
|
+
`qoni.genauth.agents.create` / `get` / `bind` / `bindUser`, alongside the existing
|
|
1323
|
+
management, Agent Mail and Agent Phone APIs. These additions are not an npm release.
|
|
1324
|
+
The hosted-login callback must validate the App's stored `state` before exchanging
|
|
1325
|
+
the one-use code. Personal mailbox delegation requires a formal IAM `agentId`; an
|
|
1326
|
+
audit label does not prove mailbox ownership. A Personal creation response may still
|
|
1327
|
+
be pending; an unknown result must be reconciled using its original provisioning
|
|
1328
|
+
reference or idempotency key, without starting another creation.
|
|
1329
|
+
|
|
1330
|
+
The Personal Quickstart email creation flow is separate from the unified Agent
|
|
1331
|
+
management model, where identity, email and phone are optional capabilities and an
|
|
1332
|
+
Agent may have multiple email addresses and phone numbers. Personal binding does
|
|
1333
|
+
not issue credentials or enable IAM-token access to the native Mail/Phone APIs.
|
|
1334
|
+
|
|
1335
|
+
Structured Pi questions and ordinary forms in this unreleased phase use the main agent entry; subagents explicitly reject parameters they cannot forward.
|
|
1336
|
+
|
|
1337
|
+
Configuring a longer run duration does not establish uninterrupted one-hour
|
|
1338
|
+
waiting across credentials, browser and provider lifetimes. Confirmation cards
|
|
1339
|
+
and ordinary forms also do not enforce approval or secret isolation across
|
|
1340
|
+
open browser/bash/harness channels. Those full-business guarantees remain unavailable.
|
|
1341
|
+
|
|
1342
|
+
### Personal Agent provisioning and optional mailbox (Unreleased)
|
|
1343
|
+
|
|
1344
|
+
This candidate interface is available for local integration. Backend business validation is still incomplete; this example does not establish
|
|
1345
|
+
external mailbox-provider support.
|
|
1346
|
+
|
|
1347
|
+
`genauth.agents.create/get/bind` use the existing signed transport. Persist the first `idempotencyKey` and complete request before creation; reuse both for retries. A `ready: false` receipt describes accepted or recoverable work, not completed creation. Never replace an uncertain request with a new key. With a requested mailbox, `ready: true` requires the original Mail operation to succeed, its resource to be active, and IAM to confirm readiness.
|
|
1348
|
+
|
|
1349
|
+
```ts
|
|
1350
|
+
const request = { displayName: "My assistant", mailboxLocalPart: "my-assistant", idempotencyKey: savedRequestKey };
|
|
1351
|
+
let result = await qoni.genauth.agents.create(request);
|
|
1352
|
+
// Persist provisioningId, then resume this same operation later.
|
|
1353
|
+
result = await qoni.genauth.agents.get({ provisioningId: result.provisioningId });
|
|
1354
|
+
if (result.state === "ready" && result.ready) {
|
|
1355
|
+
result = await qoni.genauth.agents.bind({ provisioningId: result.provisioningId, accessToken: currentUserAccessToken });
|
|
1356
|
+
}
|
|
1357
|
+
```
|
|
1358
|
+
|
|
1359
|
+
Binding requires the current person's valid GenAuth user token and matching pool/App authority; a browser-provided owner ID is not accepted. Never-bound Agents are reclaimed seven days after IAM readiness; binding clears that deadline. Definitive failures are compensated promptly; uncertain provider results retain their original operation for reconciliation. Inspect `state/errorCode`; the SDK does not automatically replay uncertain writes. Keep tokens and management credentials on a trusted application server and out of logs. This feature is off by default and requires provisioning, the existing Mail worker, and IAM service authentication.
|
|
1360
|
+
|
|
1361
|
+
### Recover a Do Anything acceptance receipt
|
|
1362
|
+
|
|
1363
|
+
This additive W11 interface requires a Web Agent deployment with the matching
|
|
1364
|
+
request acceptance API. Keep one stable `requestId` and the original payload for
|
|
1365
|
+
each submission. The SDK does not retry writes automatically.
|
|
1366
|
+
|
|
1367
|
+
```ts
|
|
1368
|
+
const requestId = "checkout-task-20261009-001";
|
|
1369
|
+
const run = await qoni.doAnything.run({ token, prompt, requestId });
|
|
1370
|
+
// If the create response was lost, read the original key. Do not create another
|
|
1371
|
+
// task and guess which one is the original.
|
|
1372
|
+
const receipt = await qoni.doAnything.api.getRequest<{ id: string }>({ token, requestId });
|
|
1373
|
+
const trace = await qoni.doAnything.api.getTrace({ token, runId: receipt.data.id });
|
|
1374
|
+
```
|
|
1375
|
+
|
|
1376
|
+
`getRequest` returns the usual run envelope (use a response generic when reading
|
|
1377
|
+
its fields in TypeScript). Include the original `sessionId` for a follow-up.
|
|
1378
|
+
Keys are isolated by the authenticated project, user, App/credential, Agent and
|
|
1379
|
+
initial/follow-up session. A different canonical payload for the same key returns
|
|
1380
|
+
`409 request_payload_conflict`. `409 request_in_progress` and a missing receipt
|
|
1381
|
+
are not proof that submission failed: read the original key first. If the
|
|
1382
|
+
original request never reached the server, that lookup can keep returning 404.
|
|
1383
|
+
You may explicitly resubmit task acceptance with the **same requestId and
|
|
1384
|
+
identical payload**, including the original sessionId for a follow-up, under
|
|
1385
|
+
the same authenticated identity namespace. An accepted request returns its
|
|
1386
|
+
original Task; no new key should be generated to guess the outcome. The SDK
|
|
1387
|
+
does not automatically retry POST requests. This does not authorize repeating
|
|
1388
|
+
an external action whose result is unknown. Canceling or expiring a run does
|
|
1389
|
+
not free its key.
|
|
1390
|
+
|
|
1391
|
+
Calling `runWithMemory` again recalls memory again and may change the actual `instructions`, so after an unknown outcome prefer `api.getRequest`/`api.getTrace` readback using the original request key and returned run ID, and never treat newly recalled content as the original payload to resubmit.
|
|
1392
|
+
|
|
1393
|
+
Runs with acceptance keys retain their Task/Event receipts. Hard-deleting their
|
|
1394
|
+
session returns `409 request_receipt_retained`; cancellation remains available.
|
|
1395
|
+
Supporting erasure while retaining deduplication needs a separate retention
|
|
1396
|
+
model. Operational database erasure is outside this receipt guarantee.
|
|
1397
|
+
|
|
1398
|
+
Trace contains only original identity references and safe states. Missing grant,
|
|
1399
|
+
consent or operation evidence stays missing. A completed run is distinct from an
|
|
1400
|
+
external operation result: `external_result: "unknown"` requires checking the
|
|
1401
|
+
original website/service. Acceptance deduplication does not guarantee that a
|
|
1402
|
+
website order or email never repeats, and trace is never an approval credential.
|
|
1403
|
+
|
|
1404
|
+
### Protected form input (local candidate)
|
|
1405
|
+
|
|
1406
|
+
A trusted application UI can use `run.interactionHandle(card).submitSensitive(values)` for a current `fill_form` whose protected text field is named `qoni_sensitive_<customData key>`. Use the original Web Agent product access token that created the Task; a delegation token or a fresh token exchange cannot replace that source. The SDK sends each protected value directly to the configured GenAuth endpoint and sends only its opaque reference in the original Web Agent reply. Ordinary `submit()` rejects these fields. Do not call this API from a model or an open browser tool.
|
|
1407
|
+
|
|
1408
|
+
For explicit recovery, use `qoni.doAnything.forms.prepare({ token, runId, interactionId, fieldName })`, then `intake({ token, ref, value })`, `status({ token, ref })`, or `cancel({ token, ref })`. Prepare preserves the original reference and deadline; it does not authorize another intake. Persistence additionally requires the server allowlisted non-security-code definition `cardNumber`, explicit `persist: true` (or `persistFields` on `submitSensitive`), current UDF write permission and existing AES encryption. Every other or ambiguous custom key is transient-only; caller labels/types cannot authorize storage. CVC/CVV is always transient, cannot be persisted, and has no digest. Missing or unsupported UDF configuration fails closed.
|
|
1409
|
+
|
|
1410
|
+
`intake`, cancellation and reference-reply uncertainty use `submissionUnknown: true` and `retryable: false`; no POST is automatically retried and no raw response body is retained in these errors. Read the original reference/Task after a missing response. Explicit cancel removes the temporary value only when acknowledged. GA revocation denies access; next authenticated access attempts cleanup, while the fixed Redis TTL independently reclaims the value. This is not a claim of instantaneous erase at revocation or deployed backup isolation. The original card deadline (`card_deadline`) remains unchanged, including the default hour. GenAuth records `value_deadline` once as the earliest of that deadline, the original run/source deadline and issue time plus five minutes; `expires_at` is its compatibility alias. All three fields use epoch milliseconds. The value expires independently while the card may still be waiting. Only an unexpired `awaiting` reference accepts one intake; `writing`, `unknown`, `consumed` and `expired` cannot be resubmitted or replaced on the same card. Prepare/status never slide the deadline or create a replacement reference. A read of `ready` resolves the original intake only after its SQL commit was confirmed and its Redis ready transition completed; it does not authorize another POST. Production sensitive release remains disabled until the target platform's isolation is demonstrated; this candidate does not claim automatic fill, payment or external delivery.
|