@qoniai/qoni 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,50 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.7.0 - 2026-10-02
4
+
5
+ ### Changed
6
+
7
+ - `InteractionHandle.openLogin()` now resolves to an `OpenLoginResult`
8
+ (`{ profileId, siteId, sessionId, liveUrl, raw, confirm() }`) instead of
9
+ `void`. TypeScript code that stores the method as `() => Promise<void>` needs
10
+ to widen that type; runtime callers that ignore the result are unaffected.
11
+
12
+ ### Added
13
+
14
+ - `OpenLoginResult.confirm()` persists the login into the profile
15
+ (`POST /profiles/{profileId}/confirm`) and reports `active`. Call
16
+ `confirmSignedIn()` once it is active (or when `probeResult` is
17
+ `no_session`), otherwise reopen the session with
18
+ `openLogin({ siteId, profileId })`.
19
+ - `QoniScopes.SITE_LOGIN_REQUEST` (`webagent.site_login:request`) and
20
+ `QoniScopes.SITE_LOGIN_CONFIRM` (`webagent.site_login:confirm`). They are not
21
+ part of any product bundle: delegate them explicitly and allow them in the
22
+ AccessKey policy.
23
+
24
+ ### Fixed
25
+
26
+ - `openLogin()` now starts a working login session. It sends the `site_id` the
27
+ backend's `/profiles/login` route requires (the only site listed on the
28
+ `site_login` interaction, or `openLogin({ siteId })`; pass `profileId` to
29
+ re-login an existing profile) and requests the site-login scope instead of the
30
+ product's manage scope. Previously the backend always rejected it. This
31
+ covers `site_login` interactions on Do Anything and Deep Research runs; Track
32
+ monitor re-login (`monitor.needs_login`, backend `/track/monitors/{id}/re_auth`)
33
+ is not wrapped by the SDK yet.
34
+ - `run.wait()` retries a transient failure of the reads it makes after the run
35
+ finished (run detail, artifact list) up to twice. A dropped connection or a
36
+ timed-out token exchange no longer fails a run that already succeeded. Only
37
+ `retryable` errors are retried; aborts and the `timeoutMs` deadline stop it.
38
+ - `track.create()` sends `{ kind: "interval", intervalSeconds }` as
39
+ `interval_seconds`, as `refine()` already did. The backend previously ignored
40
+ the camelCase field and fell back to its default 60-second interval. Other
41
+ schedule fields such as `tz` pass through unchanged, for `create()` and
42
+ `refine()` alike. Malformed interval or cron schedules are rejected locally.
43
+ - Monitor and tick reads keep user-defined keys verbatim: `extractionSchema`,
44
+ `baselineExtracted`, a tick's `extracted`, and the `before` / `after` /
45
+ `added` / `removed` / `sampleUnchanged` values in `diffs` no longer rename
46
+ `source_url` to `sourceUrl`. Protocol fields are still camelCased.
47
+
3
48
  ## 0.6.0 - 2026-09-23
4
49
 
5
50
  ### Added
package/README.md CHANGED
@@ -310,7 +310,7 @@ await qoni.gumem.recall({
310
310
 
311
311
  ## Choosing Scopes
312
312
 
313
- Each WebAgent product exposes exactly two scopes: `read` (observe — status, events, artifacts) and `manage` (anything that changes execution state — run, cancel, respond, messages, monitor CRUD). The full set is the 8 scopes `webagent.{do_anything, web_search, deep_research, track}:{read, manage}`. The simplest authorization is the `products` sugar — each product name expands to its `read` + `manage` pair:
313
+ Each WebAgent product exposes exactly two scopes: `read` (observe — status, events, artifacts) and `manage` (anything that changes execution state — run, cancel, respond, messages, monitor CRUD). The 8 product scopes are `webagent.{do_anything, web_search, deep_research, track}:{read, manage}`. Interactive site login (`openLogin()`) additionally needs `QoniScopes.SITE_LOGIN_REQUEST` and `QoniScopes.SITE_LOGIN_CONFIRM`, which are not part of any product and must be requested explicitly in `scopes`. The simplest authorization is the `products` sugar — each product name expands to its `read` + `manage` pair:
314
314
 
315
315
  ```ts
316
316
  await qoni.delegateToken({
@@ -457,7 +457,7 @@ const monitor = await qoni.track.create({
457
457
  // runNow()/refine() require `active`, so wait for it before acting:
458
458
  for await (const event of monitor.events()) {
459
459
  if (event.type === "interaction") {
460
- const i = monitor.interactionHandle(event.data); // e.g. a needs-reauth site_login
460
+ const i = monitor.interactionHandle(event.data); // e.g. an intent clarification
461
461
  if (i.can("confirm")) await i.confirm();
462
462
  continue;
463
463
  }
@@ -470,7 +470,9 @@ const ticks = await monitor.runs({ limit: 10 }); // read-only tick run views
470
470
 
471
471
  // Change the definition with refine() (any subset of: schedule / targetUrls /
472
472
  // extractionSchema / triggerDsl / stopConditionDsl / notifyChannel). `schedule`
473
- // is { kind: "cron", expr } or { kind: "interval", intervalSeconds }. The
473
+ // is { kind: "cron", expr } or { kind: "interval", intervalSeconds } (same for
474
+ // track.create()). Extraction field names and extracted values keep their
475
+ // original keys in get()/run() reads. The
474
476
  // natural-language intent isn't refinable — create a new monitor to change it:
475
477
  await monitor.refine({ schedule: { kind: "cron", expr: "0 9 * * *" } });
476
478
  await monitor.pause(); // stop scheduled ticks without deleting
@@ -623,12 +625,49 @@ for await (const event of run.events()) {
623
625
  }
624
626
  ```
625
627
 
626
- Methods: `answer(text)`, `skip()`, `confirm()`, `reject()`, `openLogin()`,
628
+ Methods: `answer(text)`, `skip()`, `confirm()`, `reject()`, `openLogin({ siteId?, profileId? })`,
627
629
  `confirmSignedIn()`, `connectControl()`, `refreshControl()`, `releaseControl()`,
628
630
  `retry()`, `switchProfile()`. `i.can(kind)` reports whether the interaction
629
631
  currently offers that action; calling a method the backend did NOT declare
630
632
  throws (so a "recovery" button only exists when it actually works).
631
633
 
634
+ `openLogin()` starts an interactive login session for a `site_login`
635
+ interaction of a run. Pass `siteId` (one of
636
+ `i.interaction.payload.sites[].siteId`), or omit it when exactly one site is
637
+ listed. Show the returned `liveUrl` to the user; after they sign in, persist the
638
+ login with `login.confirm()`, then tell the agent with `confirmSignedIn()`:
639
+
640
+ ```ts
641
+ import { QoniError } from "@qoniai/qoni";
642
+
643
+ const login = await i.openLogin(); // { profileId, siteId, sessionId, liveUrl }
644
+ showToUser(login.liveUrl); // the user signs in there
645
+ await waitForUserToFinish();
646
+ let result;
647
+ try {
648
+ result = await login.confirm(); // ends the session; saves the login if found
649
+ } catch (error) {
650
+ // 409: no pending login for this profile (session already ended, or profile gone).
651
+ if (!(error instanceof QoniError && error.code === "no_pending_login")) throw error;
652
+ }
653
+ if (!result || result.active || result.probeResult === "no_session") {
654
+ await i.confirmSignedIn(); // the agent rechecks; it asks again if still signed out
655
+ } else {
656
+ // The user was not signed in, and confirm() closed that session's browser.
657
+ const retry = await i.openLogin({ siteId: login.siteId, profileId: login.profileId });
658
+ showToUser(retry.liveUrl); // a new session for the same profile
659
+ }
660
+ ```
661
+
662
+ `probeResult: "no_session"` means the backend no longer holds the login session —
663
+ it may already have saved the login on its own once it detected the sign-in, or
664
+ it restarted. Some browser providers instead reject `confirm()` with a
665
+ `QoniError` coded `no_pending_login` (HTTP 409), which the backend also uses when
666
+ the profile no longer exists for this user; the example handles it the same way,
667
+ because the agent rechecks and asks again if the site is still signed out. Delegate
668
+ `QoniScopes.SITE_LOGIN_REQUEST` and `QoniScopes.SITE_LOGIN_CONFIRM`, and allow
669
+ both in the AccessKey policy.
670
+
632
671
  The discriminant strings used above are also exported as constants so you can
633
672
  avoid bare literals: `InteractionTypes` (`site_login` / `clarification` /
634
673
  `confirmation` / `take_control` / `wait`), `InteractionStatuses`, and
@@ -651,7 +690,7 @@ The 1:1 low-level methods live under `qoni.<product>.api.*` (e.g. `qoni.doAnythi
651
690
 
652
691
  `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).
653
692
 
654
- Browser Use and Site Login scopes are reserved until their product runtime SDK methods are exported.
693
+ Browser Use scopes are reserved until their product runtime SDK methods are exported. Site Login scopes (`QoniScopes.SITE_LOGIN_REQUEST`, `QoniScopes.SITE_LOGIN_CONFIRM`) back `InteractionHandle.openLogin()` and `confirm()` on run interactions.
655
694
 
656
695
  ### Response Shape
657
696
 
package/README.zh-CN.md CHANGED
@@ -281,7 +281,7 @@ await qoni.gumem.recall({
281
281
 
282
282
  ## Scope 怎么选
283
283
 
284
- 每个 WebAgent 产品只有两个 scope:`read`(只读观察——状态、事件、产物)与 `manage`(一切改变执行状态的操作——发起、取消、应答、消息、监控的增删改)。全集共 8 个:`webagent.{do_anything, web_search, deep_research, track}:{read, manage}`。最简单的授权方式是 `products` 糖——每个产品名展开为该产品的 `read` + `manage` 一对:
284
+ 每个 WebAgent 产品只有两个 scope:`read`(只读观察——状态、事件、产物)与 `manage`(一切改变执行状态的操作——发起、取消、应答、消息、监控的增删改)。产品 scope 共 8 个:`webagent.{do_anything, web_search, deep_research, track}:{read, manage}`。交互式站点登录(`openLogin()`)另外需要 `QoniScopes.SITE_LOGIN_REQUEST` 和 `QoniScopes.SITE_LOGIN_CONFIRM`,它们不属于任何产品,必须在 `scopes` 中显式申请。最简单的授权方式是 `products` 糖——每个产品名展开为该产品的 `read` + `manage` 一对:
285
285
 
286
286
  ```ts
287
287
  await qoni.delegateToken({
@@ -426,7 +426,7 @@ const monitor = await qoni.track.create({
426
426
  // 都要求 `active`,所以先等它就绪再操作:
427
427
  for await (const event of monitor.events()) {
428
428
  if (event.type === "interaction") {
429
- const i = monitor.interactionHandle(event.data); // 如 needs-reauth 的 site_login
429
+ const i = monitor.interactionHandle(event.data); // 如意图澄清
430
430
  if (i.can("confirm")) await i.confirm();
431
431
  continue;
432
432
  }
@@ -439,7 +439,8 @@ const ticks = await monitor.runs({ limit: 10 }); // tick 产生的 run 只读列
439
439
 
440
440
  // 用 refine() 改定义(任意字段子集:schedule / targetUrls / extractionSchema /
441
441
  // triggerDsl / stopConditionDsl / notifyChannel)。`schedule` 是
442
- // { kind: "cron", expr } 或 { kind: "interval", intervalSeconds }。
442
+ // { kind: "cron", expr } 或 { kind: "interval", intervalSeconds }(track.create() 相同)。
443
+ // get()/run() 读回时,抽取字段名与抽取结果保留原始键名。
443
444
  // 自然语言意图不可 refine——要改描述请新建 monitor:
444
445
  await monitor.refine({ schedule: { kind: "cron", expr: "0 9 * * *" } });
445
446
  await monitor.pause(); // 停掉定时 tick 但不删除
@@ -568,7 +569,33 @@ for await (const event of run.events()) {
568
569
  }
569
570
  ```
570
571
 
571
- 方法:`answer(text)`、`skip()`、`confirm()`、`reject()`、`openLogin()`、`confirmSignedIn()`、`connectControl()`、`refreshControl()`、`releaseControl()`、`retry()`、`switchProfile()`。`i.can(kind)` 判断该交互当前是否提供这个动作;调用后端没声明的方法会抛错(所以「恢复」按钮只有在真能用时才出现)。
572
+ 方法:`answer(text)`、`skip()`、`confirm()`、`reject()`、`openLogin({ siteId?, profileId? })`、`confirmSignedIn()`、`connectControl()`、`refreshControl()`、`releaseControl()`、`retry()`、`switchProfile()`。`i.can(kind)` 判断该交互当前是否提供这个动作;调用后端没声明的方法会抛错(所以「恢复」按钮只有在真能用时才出现)。
573
+
574
+ `openLogin()` 为运行中的 `site_login` 交互开启一次交互式登录会话。传入 `siteId`(取自 `i.interaction.payload.sites[].siteId`),交互只列出一个站点时可以省略。把返回的 `liveUrl` 展示给用户;用户登录完成后,先用 `login.confirm()` 把登录态写入 Profile,再调用 `confirmSignedIn()` 通知 Agent:
575
+
576
+ ```ts
577
+ import { QoniError } from "@qoniai/qoni";
578
+
579
+ const login = await i.openLogin(); // { profileId, siteId, sessionId, liveUrl }
580
+ showToUser(login.liveUrl); // 用户在这里登录
581
+ await waitForUserToFinish();
582
+ let result;
583
+ try {
584
+ result = await login.confirm(); // 结束这次会话;检测到登录态就写入 Profile
585
+ } catch (error) {
586
+ // 409:该 Profile 没有待确认的登录(会话已结束,或 Profile 已不存在)。
587
+ if (!(error instanceof QoniError && error.code === "no_pending_login")) throw error;
588
+ }
589
+ if (!result || result.active || result.probeResult === "no_session") {
590
+ await i.confirmSignedIn(); // Agent 重新检查;仍未登录会再次请求
591
+ } else {
592
+ // 用户没有登录成功,而 confirm() 已关闭这次会话的浏览器。
593
+ const retry = await i.openLogin({ siteId: login.siteId, profileId: login.profileId });
594
+ showToUser(retry.liveUrl); // 同一个 Profile 的新登录会话
595
+ }
596
+ ```
597
+
598
+ `probeResult: "no_session"` 表示服务端已不再持有这次登录会话:它可能在检测到用户登录后已经自动保存了登录态,也可能已经重启。部分浏览器提供方会改为让 `confirm()` 抛出 `code` 为 `no_pending_login` 的 `QoniError`(HTTP 409),服务端在 Profile 已不属于当前用户或已删除时也会返回这个错误码;示例按同样方式处理,因为 Agent 会重新检查,如果站点仍未登录会再次请求。委托时需要申请 `QoniScopes.SITE_LOGIN_REQUEST` 和 `QoniScopes.SITE_LOGIN_CONFIRM`,并在 AccessKey 权限策略中允许这两个 scope。
572
599
 
573
600
  上面用到的判别字符串也都导出成了常量,避免裸写字面量:`InteractionTypes`(`site_login` / `clarification` / `confirmation` / `take_control` / `wait`)、`InteractionStatuses`、`ActionKinds`(`i.can(...)` 的动作名)。授权 scope 同样有:`QoniScopes`(每个单独 scope)、`QoniProductScopes`(`products` 语法糖展开成的 per-product `read`+`manage` 对)、`QoniScopeBundles`(精选 bundle)。用它们代替手写字符串:
574
601
 
@@ -586,7 +613,7 @@ if (i.type === InteractionTypes.Clarification && i.can(ActionKinds.answer)) {
586
613
 
587
614
  原始 wire 事件类型(`run.action.started` 这种内部传输名,命名是历史遗留、也不一致)**不作为公开导出**——开发者按上面那套精选 `event.type` 匹配即可;极少数要精确 wire 的场景,`event.raw.event` 是个原始字符串,直接比对。
588
615
 
589
- Browser Use、Site Login 的 scope 已预留,但产品运行时 SDK 方法导出前不作为公开方法说明。
616
+ Browser Use 的 scope 已预留,但产品运行时 SDK 方法导出前不作为公开方法说明。Site Login 的 scope(`QoniScopes.SITE_LOGIN_REQUEST`、`QoniScopes.SITE_LOGIN_CONFIRM`)用于运行交互上的 `InteractionHandle.openLogin()` 和 `confirm()`。
590
617
 
591
618
  ### 返回结构
592
619