@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 +45 -0
- package/README.md +44 -5
- package/README.zh-CN.md +32 -5
- package/dist/index.cjs +291 -146
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +87 -7
- package/dist/index.d.ts +87 -7
- package/dist/index.js +291 -146
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
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
|
|
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.
|
|
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 }
|
|
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
|
|
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
|
|
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); //
|
|
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
|
|
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
|
|