@qoniai/qoni 0.6.0 → 0.8.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 +70 -2
- package/README.md +74 -7
- package/README.zh-CN.md +59 -7
- package/dist/index.cjs +331 -149
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +101 -10
- package/dist/index.d.ts +101 -10
- package/dist/index.js +331 -149
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -490,6 +490,87 @@ var ActionKinds = {
|
|
|
490
490
|
};
|
|
491
491
|
var ACTION_KIND_VALUES = Object.values(ActionKinds);
|
|
492
492
|
|
|
493
|
+
// src/scopes.ts
|
|
494
|
+
var QoniScopes = {
|
|
495
|
+
GUMEM_SESSION_CREATE: "gumem.session:create",
|
|
496
|
+
GUMEM_MESSAGE_WRITE: "gumem.message:write",
|
|
497
|
+
GUMEM_MEMORY_READ: "gumem.memory:read",
|
|
498
|
+
GUMEM_MEMORY_WRITE: "gumem.memory:write",
|
|
499
|
+
GUMEM_MEMORY_DELETE: "gumem.memory:delete",
|
|
500
|
+
GUMEM_SEARCH_RUN: "gumem.search:run",
|
|
501
|
+
GUMEM_ADMIN_MANAGE: "gumem.admin:manage",
|
|
502
|
+
GUMEM_RESOURCE_WRITE: "gumem.resource:write",
|
|
503
|
+
GUMEM_ACTION_WRITE: "gumem.action:write",
|
|
504
|
+
GUMEM_ACTION_READ: "gumem.action:read",
|
|
505
|
+
GUMEM_PROFILE_READ: "gumem.profile:read",
|
|
506
|
+
// WebAgent products: exactly two verbs per product — `read` (observe) and
|
|
507
|
+
// `manage` (anything that changes execution state). See
|
|
508
|
+
// wa/docs/qoni-sdk-public-surface.md §3.
|
|
509
|
+
DO_ANYTHING_READ: "webagent.do_anything:read",
|
|
510
|
+
DO_ANYTHING_MANAGE: "webagent.do_anything:manage",
|
|
511
|
+
WEB_SEARCH_READ: "webagent.web_search:read",
|
|
512
|
+
WEB_SEARCH_MANAGE: "webagent.web_search:manage",
|
|
513
|
+
DEEP_RESEARCH_READ: "webagent.deep_research:read",
|
|
514
|
+
DEEP_RESEARCH_MANAGE: "webagent.deep_research:manage",
|
|
515
|
+
TRACK_READ: "webagent.track:read",
|
|
516
|
+
TRACK_MANAGE: "webagent.track:manage",
|
|
517
|
+
// Interactive site login (`InteractionHandle.openLogin()` on a `site_login`
|
|
518
|
+
// interaction). Not part of any product bundle: the AccessKey policy must
|
|
519
|
+
// allow it, and the delegation must request it explicitly.
|
|
520
|
+
SITE_LOGIN_REQUEST: "webagent.site_login:request",
|
|
521
|
+
// Persist that login into the browser profile (`OpenLoginResult.confirm()`).
|
|
522
|
+
SITE_LOGIN_CONFIRM: "webagent.site_login:confirm"
|
|
523
|
+
};
|
|
524
|
+
var QoniProductScopes = {
|
|
525
|
+
doAnything: [
|
|
526
|
+
QoniScopes.DO_ANYTHING_READ,
|
|
527
|
+
QoniScopes.DO_ANYTHING_MANAGE
|
|
528
|
+
],
|
|
529
|
+
webSearch: [
|
|
530
|
+
QoniScopes.WEB_SEARCH_READ,
|
|
531
|
+
QoniScopes.WEB_SEARCH_MANAGE
|
|
532
|
+
],
|
|
533
|
+
deepResearch: [
|
|
534
|
+
QoniScopes.DEEP_RESEARCH_READ,
|
|
535
|
+
QoniScopes.DEEP_RESEARCH_MANAGE
|
|
536
|
+
],
|
|
537
|
+
track: [
|
|
538
|
+
QoniScopes.TRACK_READ,
|
|
539
|
+
QoniScopes.TRACK_MANAGE
|
|
540
|
+
]
|
|
541
|
+
};
|
|
542
|
+
var KNOWN_SCOPES = Object.values(QoniScopes);
|
|
543
|
+
var QoniScopeBundles = {
|
|
544
|
+
GUMEM_READONLY: [QoniScopes.GUMEM_MEMORY_READ, QoniScopes.GUMEM_PROFILE_READ],
|
|
545
|
+
GUMEM_SESSION_RECALL: [
|
|
546
|
+
QoniScopes.GUMEM_MEMORY_READ,
|
|
547
|
+
QoniScopes.GUMEM_MEMORY_WRITE,
|
|
548
|
+
QoniScopes.GUMEM_MESSAGE_WRITE
|
|
549
|
+
],
|
|
550
|
+
GUMEM_WRITE: [
|
|
551
|
+
QoniScopes.GUMEM_SESSION_CREATE,
|
|
552
|
+
QoniScopes.GUMEM_MESSAGE_WRITE,
|
|
553
|
+
QoniScopes.GUMEM_ACTION_WRITE
|
|
554
|
+
],
|
|
555
|
+
// One bundle per WebAgent product = [read, manage].
|
|
556
|
+
DO_ANYTHING: [
|
|
557
|
+
QoniScopes.DO_ANYTHING_READ,
|
|
558
|
+
QoniScopes.DO_ANYTHING_MANAGE
|
|
559
|
+
],
|
|
560
|
+
WEB_SEARCH: [
|
|
561
|
+
QoniScopes.WEB_SEARCH_READ,
|
|
562
|
+
QoniScopes.WEB_SEARCH_MANAGE
|
|
563
|
+
],
|
|
564
|
+
DEEP_RESEARCH: [
|
|
565
|
+
QoniScopes.DEEP_RESEARCH_READ,
|
|
566
|
+
QoniScopes.DEEP_RESEARCH_MANAGE
|
|
567
|
+
],
|
|
568
|
+
TRACK: [
|
|
569
|
+
QoniScopes.TRACK_READ,
|
|
570
|
+
QoniScopes.TRACK_MANAGE
|
|
571
|
+
]
|
|
572
|
+
};
|
|
573
|
+
|
|
493
574
|
// src/interactions.ts
|
|
494
575
|
function asString2(value) {
|
|
495
576
|
return typeof value === "string" && value ? value : void 0;
|
|
@@ -588,6 +669,9 @@ function normalizeInteraction(payload) {
|
|
|
588
669
|
if (evidence) interaction.evidence = evidence;
|
|
589
670
|
return interaction;
|
|
590
671
|
}
|
|
672
|
+
function interactionActionScopes(action, productManageScope) {
|
|
673
|
+
return action.kind === ActionKinds.openLogin ? [QoniScopes.SITE_LOGIN_REQUEST] : [productManageScope];
|
|
674
|
+
}
|
|
591
675
|
var InteractionHandle = class {
|
|
592
676
|
constructor(interaction, post) {
|
|
593
677
|
this.post = post;
|
|
@@ -612,48 +696,122 @@ var InteractionHandle = class {
|
|
|
612
696
|
return this.interaction.actions.some((a) => a.kind === kind);
|
|
613
697
|
}
|
|
614
698
|
/** Answer a clarification ask. */
|
|
615
|
-
answer(text) {
|
|
616
|
-
|
|
699
|
+
async answer(text) {
|
|
700
|
+
await this.invoke(ActionKinds.answer, { response: text });
|
|
617
701
|
}
|
|
618
702
|
/** Skip / dismiss the interaction (proceed without acting on it). */
|
|
619
|
-
skip() {
|
|
620
|
-
|
|
703
|
+
async skip() {
|
|
704
|
+
await this.invoke(ActionKinds.skip);
|
|
621
705
|
}
|
|
622
706
|
/** Confirm a pending confirmation (approve the plan / destructive op). */
|
|
623
|
-
confirm() {
|
|
624
|
-
|
|
707
|
+
async confirm() {
|
|
708
|
+
await this.invoke(ActionKinds.confirm);
|
|
625
709
|
}
|
|
626
710
|
/** Reject a pending confirmation. */
|
|
627
|
-
reject() {
|
|
628
|
-
|
|
711
|
+
async reject() {
|
|
712
|
+
await this.invoke(ActionKinds.reject);
|
|
629
713
|
}
|
|
630
|
-
/**
|
|
631
|
-
|
|
632
|
-
|
|
714
|
+
/**
|
|
715
|
+
* Start an interactive login session for a `site_login` interaction. The
|
|
716
|
+
* backend needs the site to sign in to: pass `siteId`, or omit it when the
|
|
717
|
+
* interaction lists exactly one site. Show `liveUrl` to the user; when they
|
|
718
|
+
* finish, call `confirm()` on the result to save the login, and act on its
|
|
719
|
+
* outcome (see {@link OpenLoginResult.confirm}) before {@link confirmSignedIn}.
|
|
720
|
+
* The delegation must include `QoniScopes.SITE_LOGIN_REQUEST` and
|
|
721
|
+
* `QoniScopes.SITE_LOGIN_CONFIRM`; the AccessKey policy must allow both.
|
|
722
|
+
*/
|
|
723
|
+
async openLogin(options = {}) {
|
|
724
|
+
this.#declaredAction(ActionKinds.openLogin);
|
|
725
|
+
const body = { site_id: this.#loginSiteId(options.siteId) };
|
|
726
|
+
if (options.profileId !== void 0) {
|
|
727
|
+
if (typeof options.profileId !== "string" || !options.profileId) {
|
|
728
|
+
throw new QoniValidationError("openLogin profileId must be a non-empty string");
|
|
729
|
+
}
|
|
730
|
+
body.profile_id = options.profileId;
|
|
731
|
+
}
|
|
732
|
+
const response = await this.invoke(ActionKinds.openLogin, body);
|
|
733
|
+
const raw = isJsonObject(response) ? response : {};
|
|
734
|
+
const fields = camelizeRecord(raw);
|
|
735
|
+
const profileId = asString2(fields.profileId) ?? "";
|
|
736
|
+
const siteId = asString2(fields.siteId) ?? String(body.site_id);
|
|
737
|
+
return {
|
|
738
|
+
profileId,
|
|
739
|
+
siteId,
|
|
740
|
+
sessionId: asString2(fields.sessionId) ?? "",
|
|
741
|
+
liveUrl: asString2(fields.liveUrl) ?? "",
|
|
742
|
+
raw,
|
|
743
|
+
confirm: async (confirmOptions = {}) => {
|
|
744
|
+
if (!profileId) {
|
|
745
|
+
throw new QoniValidationError("openLogin response did not include a profileId to confirm");
|
|
746
|
+
}
|
|
747
|
+
const confirmBody = { site_id: siteId };
|
|
748
|
+
if (confirmOptions.displayLabel !== void 0) confirmBody.display_label = confirmOptions.displayLabel;
|
|
749
|
+
const response2 = await this.post(
|
|
750
|
+
{
|
|
751
|
+
kind: ActionKinds.openLogin,
|
|
752
|
+
label: "Confirm login",
|
|
753
|
+
method: "POST",
|
|
754
|
+
endpoint: `/profiles/${encodeURIComponent(profileId)}/confirm`
|
|
755
|
+
},
|
|
756
|
+
confirmBody,
|
|
757
|
+
{ requiredScopes: [QoniScopes.SITE_LOGIN_CONFIRM] }
|
|
758
|
+
);
|
|
759
|
+
const raw2 = isJsonObject(response2) ? response2 : {};
|
|
760
|
+
const fields2 = camelizeRecord(raw2);
|
|
761
|
+
const state = asString2(fields2.state) ?? "";
|
|
762
|
+
return {
|
|
763
|
+
profileId: asString2(fields2.profileId) ?? profileId,
|
|
764
|
+
active: state === "active",
|
|
765
|
+
state,
|
|
766
|
+
probeResult: asString2(fields2.probeResult) ?? "",
|
|
767
|
+
raw: raw2
|
|
768
|
+
};
|
|
769
|
+
}
|
|
770
|
+
};
|
|
633
771
|
}
|
|
634
772
|
/** Tell the agent the user has finished signing in. */
|
|
635
|
-
confirmSignedIn() {
|
|
636
|
-
|
|
773
|
+
async confirmSignedIn() {
|
|
774
|
+
await this.invoke(ActionKinds.confirmSignedIn);
|
|
637
775
|
}
|
|
638
776
|
/** Connect to the live browser for a take_control interaction. */
|
|
639
|
-
connectControl() {
|
|
640
|
-
|
|
777
|
+
async connectControl() {
|
|
778
|
+
await this.invoke(ActionKinds.connectControl);
|
|
641
779
|
}
|
|
642
780
|
/** Refresh the live control surface / live url. */
|
|
643
|
-
refreshControl() {
|
|
644
|
-
|
|
781
|
+
async refreshControl() {
|
|
782
|
+
await this.invoke(ActionKinds.refreshControl);
|
|
645
783
|
}
|
|
646
784
|
/** Hand control back to the agent. */
|
|
647
|
-
releaseControl() {
|
|
648
|
-
|
|
785
|
+
async releaseControl() {
|
|
786
|
+
await this.invoke(ActionKinds.releaseControl);
|
|
649
787
|
}
|
|
650
788
|
/** Retry a wait interaction now. */
|
|
651
|
-
retry() {
|
|
652
|
-
|
|
789
|
+
async retry() {
|
|
790
|
+
await this.invoke(ActionKinds.retry);
|
|
653
791
|
}
|
|
654
792
|
/** Switch the profile a wait/login interaction is bound to. */
|
|
655
|
-
switchProfile() {
|
|
656
|
-
|
|
793
|
+
async switchProfile() {
|
|
794
|
+
await this.invoke(ActionKinds.switchProfile);
|
|
795
|
+
}
|
|
796
|
+
/** Resolve the site for `openLogin`: explicit, or the only listed site. */
|
|
797
|
+
#loginSiteId(requested) {
|
|
798
|
+
const payload = this.interaction.payload;
|
|
799
|
+
const listed = Array.isArray(payload?.sites) ? payload.sites.map((site) => site.siteId).filter((id) => typeof id === "string" && id.length > 0) : [];
|
|
800
|
+
if (requested !== void 0) {
|
|
801
|
+
if (typeof requested !== "string" || !requested) {
|
|
802
|
+
throw new QoniValidationError("openLogin siteId must be a non-empty string");
|
|
803
|
+
}
|
|
804
|
+
if (listed.length > 0 && !listed.includes(requested)) {
|
|
805
|
+
throw new QoniValidationError(
|
|
806
|
+
`openLogin siteId "${requested}" is not one of the sites this interaction asks for: ${listed.join(", ")}`
|
|
807
|
+
);
|
|
808
|
+
}
|
|
809
|
+
return requested;
|
|
810
|
+
}
|
|
811
|
+
if (listed.length === 1) return listed[0];
|
|
812
|
+
throw new QoniValidationError(
|
|
813
|
+
listed.length === 0 ? "openLogin needs a siteId: this interaction does not list the site to sign in to" : `openLogin needs a siteId: choose one of ${listed.join(", ")}`
|
|
814
|
+
);
|
|
657
815
|
}
|
|
658
816
|
/**
|
|
659
817
|
* Invoke an action by kind with an optional body. `async` so a missing-action
|
|
@@ -661,6 +819,10 @@ var InteractionHandle = class {
|
|
|
661
819
|
* method contract uniformly promise-based.
|
|
662
820
|
*/
|
|
663
821
|
async invoke(kind, body) {
|
|
822
|
+
return this.post(this.#declaredAction(kind), body);
|
|
823
|
+
}
|
|
824
|
+
/** The declared action of `kind`; throws when the backend did not offer it. */
|
|
825
|
+
#declaredAction(kind) {
|
|
664
826
|
const action = this.interaction.actions.find((a) => a.kind === kind);
|
|
665
827
|
if (!action) {
|
|
666
828
|
const offered = this.interaction.actions.map((a) => a.kind).join(", ") || "none";
|
|
@@ -668,7 +830,7 @@ var InteractionHandle = class {
|
|
|
668
830
|
`interaction "${this.interaction.id}" (${this.interaction.type}) does not offer the "${kind}" action \u2014 the backend declared: ${offered}. Only call methods whose action the interaction currently offers.`
|
|
669
831
|
);
|
|
670
832
|
}
|
|
671
|
-
|
|
833
|
+
return action;
|
|
672
834
|
}
|
|
673
835
|
};
|
|
674
836
|
|
|
@@ -738,7 +900,7 @@ var RunHandle = class {
|
|
|
738
900
|
break settled;
|
|
739
901
|
}
|
|
740
902
|
}
|
|
741
|
-
const detail2 = await
|
|
903
|
+
const detail2 = await settleRead(() => this.ops.getDetail(signal), signal);
|
|
742
904
|
if (isTerminalRunStatus(normalizeRunStatus(this.id, detail2).status)) {
|
|
743
905
|
terminalDetail = detail2;
|
|
744
906
|
break;
|
|
@@ -752,10 +914,10 @@ var RunHandle = class {
|
|
|
752
914
|
sawCleanClose = true;
|
|
753
915
|
eventIdAtLastClose = lastEventId;
|
|
754
916
|
}
|
|
755
|
-
const detail = terminalDetail ?? await
|
|
917
|
+
const detail = terminalDetail ?? await settleRead(() => this.ops.getDetail(signal), signal);
|
|
756
918
|
this.#adoptSessionRef(detail);
|
|
757
919
|
const result = settleRunResult(this.id, detail, [], terminalDoneReason);
|
|
758
|
-
result.artifacts = this.ops.listArtifacts ? await
|
|
920
|
+
result.artifacts = this.ops.listArtifacts ? await settleRead(() => this.ops.listArtifacts(signal), signal) : [];
|
|
759
921
|
return result;
|
|
760
922
|
} catch (err) {
|
|
761
923
|
if (timedOut() && !opts.signal?.aborted) {
|
|
@@ -778,7 +940,7 @@ var RunHandle = class {
|
|
|
778
940
|
interactionHandle(interaction) {
|
|
779
941
|
return new InteractionHandle(
|
|
780
942
|
interaction,
|
|
781
|
-
(action, body) => this.ops.postAction(action, body)
|
|
943
|
+
(action, body, options) => this.ops.postAction(action, body, options)
|
|
782
944
|
);
|
|
783
945
|
}
|
|
784
946
|
/**
|
|
@@ -872,6 +1034,35 @@ function withTimeout(signal, timeoutMs) {
|
|
|
872
1034
|
}
|
|
873
1035
|
return { signal: controller.signal, timedOut: () => fired, dispose };
|
|
874
1036
|
}
|
|
1037
|
+
var SETTLE_READ_BACKOFF_MS = [250, 1e3];
|
|
1038
|
+
async function settleRead(read, signal) {
|
|
1039
|
+
for (let attempt = 0; ; attempt++) {
|
|
1040
|
+
try {
|
|
1041
|
+
return await waitWithSignal(read, signal);
|
|
1042
|
+
} catch (err) {
|
|
1043
|
+
const delay = SETTLE_READ_BACKOFF_MS[attempt];
|
|
1044
|
+
if (delay === void 0 || signal?.aborted || !(err instanceof QoniError) || !err.retryable) throw err;
|
|
1045
|
+
await sleepWithSignal(delay, signal);
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
function sleepWithSignal(ms, signal) {
|
|
1050
|
+
return new Promise((resolve, reject) => {
|
|
1051
|
+
if (signal?.aborted) {
|
|
1052
|
+
reject(signal.reason ?? new DOMException("Aborted", "AbortError"));
|
|
1053
|
+
return;
|
|
1054
|
+
}
|
|
1055
|
+
const onAbort = () => {
|
|
1056
|
+
clearTimeout(timer);
|
|
1057
|
+
reject(signal.reason ?? new DOMException("Aborted", "AbortError"));
|
|
1058
|
+
};
|
|
1059
|
+
const timer = setTimeout(() => {
|
|
1060
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1061
|
+
resolve();
|
|
1062
|
+
}, ms);
|
|
1063
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1064
|
+
});
|
|
1065
|
+
}
|
|
875
1066
|
function waitWithSignal(operation, signal) {
|
|
876
1067
|
if (!signal) return Promise.resolve().then(operation);
|
|
877
1068
|
return new Promise((resolve, reject) => {
|
|
@@ -969,17 +1160,15 @@ function createDeepResearchNamespace(transport) {
|
|
|
969
1160
|
signal: opts.signal,
|
|
970
1161
|
onReconnect: opts.onReconnect
|
|
971
1162
|
}),
|
|
972
|
-
postAction: async (action, body) =>
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
{
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
);
|
|
982
|
-
},
|
|
1163
|
+
postAction: async (action, body, options) => (await transport.webAgentJson(
|
|
1164
|
+
action.method || "POST",
|
|
1165
|
+
action.endpoint,
|
|
1166
|
+
token,
|
|
1167
|
+
{
|
|
1168
|
+
...body !== void 0 ? { body } : {},
|
|
1169
|
+
requiredScopes: options?.requiredScopes ?? interactionActionScopes(action, "webagent.deep_research:manage")
|
|
1170
|
+
}
|
|
1171
|
+
)).data,
|
|
983
1172
|
cancel: async () => asRecord((await api.cancel({ token, runId })).data),
|
|
984
1173
|
listArtifacts: async (signal) => {
|
|
985
1174
|
const res = await api.listArtifacts({ token, runId, signal });
|
|
@@ -1244,17 +1433,15 @@ function createDoAnythingNamespace(transport) {
|
|
|
1244
1433
|
// Skip screenshot rows in backfill when the caller did not opt in.
|
|
1245
1434
|
includeScreenshots: capture?.screenshots ? void 0 : false
|
|
1246
1435
|
}),
|
|
1247
|
-
postAction: async (action, body) =>
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
{
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
);
|
|
1257
|
-
},
|
|
1436
|
+
postAction: async (action, body, options) => (await transport.webAgentJson(
|
|
1437
|
+
action.method || "POST",
|
|
1438
|
+
action.endpoint,
|
|
1439
|
+
token,
|
|
1440
|
+
{
|
|
1441
|
+
...body !== void 0 ? { body } : {},
|
|
1442
|
+
requiredScopes: options?.requiredScopes ?? interactionActionScopes(action, "webagent.do_anything:manage")
|
|
1443
|
+
}
|
|
1444
|
+
)).data,
|
|
1258
1445
|
cancel: async (reason) => asRecord2(
|
|
1259
1446
|
(await api.cancel({
|
|
1260
1447
|
token,
|
|
@@ -1375,84 +1562,16 @@ function createQoniNamespace(_transport, delegateToken, completeDelegateToken) {
|
|
|
1375
1562
|
};
|
|
1376
1563
|
}
|
|
1377
1564
|
|
|
1378
|
-
// src/scopes.ts
|
|
1379
|
-
var QoniScopes = {
|
|
1380
|
-
GUMEM_SESSION_CREATE: "gumem.session:create",
|
|
1381
|
-
GUMEM_MESSAGE_WRITE: "gumem.message:write",
|
|
1382
|
-
GUMEM_MEMORY_READ: "gumem.memory:read",
|
|
1383
|
-
GUMEM_MEMORY_WRITE: "gumem.memory:write",
|
|
1384
|
-
GUMEM_MEMORY_DELETE: "gumem.memory:delete",
|
|
1385
|
-
GUMEM_SEARCH_RUN: "gumem.search:run",
|
|
1386
|
-
GUMEM_ADMIN_MANAGE: "gumem.admin:manage",
|
|
1387
|
-
GUMEM_RESOURCE_WRITE: "gumem.resource:write",
|
|
1388
|
-
GUMEM_ACTION_WRITE: "gumem.action:write",
|
|
1389
|
-
GUMEM_ACTION_READ: "gumem.action:read",
|
|
1390
|
-
GUMEM_PROFILE_READ: "gumem.profile:read",
|
|
1391
|
-
// WebAgent products: exactly two verbs per product — `read` (observe) and
|
|
1392
|
-
// `manage` (anything that changes execution state). See
|
|
1393
|
-
// wa/docs/qoni-sdk-public-surface.md §3.
|
|
1394
|
-
DO_ANYTHING_READ: "webagent.do_anything:read",
|
|
1395
|
-
DO_ANYTHING_MANAGE: "webagent.do_anything:manage",
|
|
1396
|
-
WEB_SEARCH_READ: "webagent.web_search:read",
|
|
1397
|
-
WEB_SEARCH_MANAGE: "webagent.web_search:manage",
|
|
1398
|
-
DEEP_RESEARCH_READ: "webagent.deep_research:read",
|
|
1399
|
-
DEEP_RESEARCH_MANAGE: "webagent.deep_research:manage",
|
|
1400
|
-
TRACK_READ: "webagent.track:read",
|
|
1401
|
-
TRACK_MANAGE: "webagent.track:manage"
|
|
1402
|
-
};
|
|
1403
|
-
var QoniProductScopes = {
|
|
1404
|
-
doAnything: [
|
|
1405
|
-
QoniScopes.DO_ANYTHING_READ,
|
|
1406
|
-
QoniScopes.DO_ANYTHING_MANAGE
|
|
1407
|
-
],
|
|
1408
|
-
webSearch: [
|
|
1409
|
-
QoniScopes.WEB_SEARCH_READ,
|
|
1410
|
-
QoniScopes.WEB_SEARCH_MANAGE
|
|
1411
|
-
],
|
|
1412
|
-
deepResearch: [
|
|
1413
|
-
QoniScopes.DEEP_RESEARCH_READ,
|
|
1414
|
-
QoniScopes.DEEP_RESEARCH_MANAGE
|
|
1415
|
-
],
|
|
1416
|
-
track: [
|
|
1417
|
-
QoniScopes.TRACK_READ,
|
|
1418
|
-
QoniScopes.TRACK_MANAGE
|
|
1419
|
-
]
|
|
1420
|
-
};
|
|
1421
|
-
var KNOWN_SCOPES = Object.values(QoniScopes);
|
|
1422
|
-
var QoniScopeBundles = {
|
|
1423
|
-
GUMEM_READONLY: [QoniScopes.GUMEM_MEMORY_READ, QoniScopes.GUMEM_PROFILE_READ],
|
|
1424
|
-
GUMEM_SESSION_RECALL: [
|
|
1425
|
-
QoniScopes.GUMEM_MEMORY_READ,
|
|
1426
|
-
QoniScopes.GUMEM_MEMORY_WRITE,
|
|
1427
|
-
QoniScopes.GUMEM_MESSAGE_WRITE
|
|
1428
|
-
],
|
|
1429
|
-
GUMEM_WRITE: [
|
|
1430
|
-
QoniScopes.GUMEM_SESSION_CREATE,
|
|
1431
|
-
QoniScopes.GUMEM_MESSAGE_WRITE,
|
|
1432
|
-
QoniScopes.GUMEM_ACTION_WRITE
|
|
1433
|
-
],
|
|
1434
|
-
// One bundle per WebAgent product = [read, manage].
|
|
1435
|
-
DO_ANYTHING: [
|
|
1436
|
-
QoniScopes.DO_ANYTHING_READ,
|
|
1437
|
-
QoniScopes.DO_ANYTHING_MANAGE
|
|
1438
|
-
],
|
|
1439
|
-
WEB_SEARCH: [
|
|
1440
|
-
QoniScopes.WEB_SEARCH_READ,
|
|
1441
|
-
QoniScopes.WEB_SEARCH_MANAGE
|
|
1442
|
-
],
|
|
1443
|
-
DEEP_RESEARCH: [
|
|
1444
|
-
QoniScopes.DEEP_RESEARCH_READ,
|
|
1445
|
-
QoniScopes.DEEP_RESEARCH_MANAGE
|
|
1446
|
-
],
|
|
1447
|
-
TRACK: [
|
|
1448
|
-
QoniScopes.TRACK_READ,
|
|
1449
|
-
QoniScopes.TRACK_MANAGE
|
|
1450
|
-
]
|
|
1451
|
-
};
|
|
1452
|
-
|
|
1453
1565
|
// src/genauth.ts
|
|
1454
1566
|
function createGenAuthNamespace(transport, options = {}) {
|
|
1567
|
+
async function delegateAgent(input) {
|
|
1568
|
+
if (!options.delegateTokenFor) {
|
|
1569
|
+
throw new QoniValidationError("GenAuth delegateAgent requires a Qoni delegation provider");
|
|
1570
|
+
}
|
|
1571
|
+
return (await options.delegateTokenFor(input)).data;
|
|
1572
|
+
}
|
|
1455
1573
|
return {
|
|
1574
|
+
delegateAgent,
|
|
1456
1575
|
userInfo: (input) => transport.genauthJson("GET", "/oidc/me", input.accessToken),
|
|
1457
1576
|
jwks: () => transport.genauthJson("GET", "/oidc/.well-known/jwks.json"),
|
|
1458
1577
|
discovery: () => transport.genauthJson("GET", "/oidc/.well-known/openid-configuration"),
|
|
@@ -1648,7 +1767,7 @@ var MonitorHandle = class {
|
|
|
1648
1767
|
id;
|
|
1649
1768
|
/** Refresh and return the monitor (normalized camelCase fields). */
|
|
1650
1769
|
async get() {
|
|
1651
|
-
return
|
|
1770
|
+
return normalizeMonitorRecord(await this.ops.get());
|
|
1652
1771
|
}
|
|
1653
1772
|
/**
|
|
1654
1773
|
* Pause the monitor — stops scheduled ticks until {@link resume}. The
|
|
@@ -1656,11 +1775,11 @@ var MonitorHandle = class {
|
|
|
1656
1775
|
* deleting.
|
|
1657
1776
|
*/
|
|
1658
1777
|
async pause() {
|
|
1659
|
-
return
|
|
1778
|
+
return normalizeMonitorRecord(await this.ops.update({ action: "pause" }));
|
|
1660
1779
|
}
|
|
1661
1780
|
/** Resume a paused monitor. */
|
|
1662
1781
|
async resume() {
|
|
1663
|
-
return
|
|
1782
|
+
return normalizeMonitorRecord(await this.ops.update({ action: "resume" }));
|
|
1664
1783
|
}
|
|
1665
1784
|
/**
|
|
1666
1785
|
* Change the monitor definition. Pass any subset of fields (at least one);
|
|
@@ -1678,11 +1797,11 @@ var MonitorHandle = class {
|
|
|
1678
1797
|
"track refine requires at least one field to change (schedule / targetUrls / extractionSchema / triggerDsl / stopConditionDsl / notifyChannel)"
|
|
1679
1798
|
);
|
|
1680
1799
|
}
|
|
1681
|
-
return
|
|
1800
|
+
return normalizeMonitorRecord(await this.ops.update({ action: "refine", patch: body }));
|
|
1682
1801
|
}
|
|
1683
1802
|
/** Trigger an immediate tick outside the schedule. */
|
|
1684
1803
|
async runNow() {
|
|
1685
|
-
return
|
|
1804
|
+
return normalizeMonitorRecord(await this.ops.runNow());
|
|
1686
1805
|
}
|
|
1687
1806
|
/**
|
|
1688
1807
|
* Stream semantic monitor events. Monitors are resident, so the stream has
|
|
@@ -1703,7 +1822,7 @@ var MonitorHandle = class {
|
|
|
1703
1822
|
interactionHandle(interaction) {
|
|
1704
1823
|
return new InteractionHandle(
|
|
1705
1824
|
interaction,
|
|
1706
|
-
(action, body) => this.ops.postAction(action, body)
|
|
1825
|
+
(action, body, options) => this.ops.postAction(action, body, options)
|
|
1707
1826
|
);
|
|
1708
1827
|
}
|
|
1709
1828
|
/**
|
|
@@ -1808,17 +1927,15 @@ function createTrackNamespace(transport) {
|
|
|
1808
1927
|
update: async (input) => asRecord3((await api.updateMonitor({ token, monitorId, ...input })).data),
|
|
1809
1928
|
runNow: async () => asRecord3((await api.runNow({ token, monitorId })).data),
|
|
1810
1929
|
streamEvents: (opts) => api.events({ token, monitorId, lastEventId: opts.lastEventId, signal: opts.signal }),
|
|
1811
|
-
postAction: async (action, body) =>
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
{
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
);
|
|
1821
|
-
},
|
|
1930
|
+
postAction: async (action, body, options) => (await transport.webAgentJson(
|
|
1931
|
+
action.method || "POST",
|
|
1932
|
+
action.endpoint,
|
|
1933
|
+
token,
|
|
1934
|
+
{
|
|
1935
|
+
...body !== void 0 ? { body } : {},
|
|
1936
|
+
requiredScopes: options?.requiredScopes ?? interactionActionScopes(action, "webagent.track:manage")
|
|
1937
|
+
}
|
|
1938
|
+
)).data,
|
|
1822
1939
|
listRuns: async (opts) => asRecord3((await api.listRuns({ token, monitorId, ...opts })).data),
|
|
1823
1940
|
getRun: async (runId) => asRecord3((await api.getRun({ token, monitorId, runId })).data),
|
|
1824
1941
|
delete: async () => {
|
|
@@ -1833,10 +1950,12 @@ function createTrackNamespace(transport) {
|
|
|
1833
1950
|
if (!prompt || typeof prompt !== "string") {
|
|
1834
1951
|
throw new QoniValidationError("track.create requires a prompt string");
|
|
1835
1952
|
}
|
|
1953
|
+
const body = snakeifyKeys(rest);
|
|
1954
|
+
if (body.schedule !== void 0) body.schedule = normalizeSchedule(body.schedule);
|
|
1836
1955
|
const created = await api.createMonitor({
|
|
1837
1956
|
token,
|
|
1838
1957
|
intent: prompt,
|
|
1839
|
-
...
|
|
1958
|
+
...body
|
|
1840
1959
|
});
|
|
1841
1960
|
const monitorId = created.data.id || created.data.monitor_id;
|
|
1842
1961
|
if (!monitorId) {
|
|
@@ -1854,7 +1973,7 @@ function createTrackNamespace(transport) {
|
|
|
1854
1973
|
};
|
|
1855
1974
|
}
|
|
1856
1975
|
function normalizeMonitorRun(item) {
|
|
1857
|
-
const normalized =
|
|
1976
|
+
const normalized = normalizeMonitorRecord(item);
|
|
1858
1977
|
if (typeof normalized.id !== "string" || !normalized.id) {
|
|
1859
1978
|
const id = normalized.runId ?? normalized.snapshotId;
|
|
1860
1979
|
if (typeof id === "string" && id) normalized.id = id;
|
|
@@ -1864,11 +1983,44 @@ function normalizeMonitorRun(item) {
|
|
|
1864
1983
|
function normalizeTrackInterveneInput(input) {
|
|
1865
1984
|
return renameKeys4(input, { requestId: "request_id" });
|
|
1866
1985
|
}
|
|
1986
|
+
var USER_KEYED_FIELDS = /* @__PURE__ */ new Set(["extraction_schema", "baseline_extracted", "extracted"]);
|
|
1987
|
+
var USER_KEYED_DIFF_FIELDS = /* @__PURE__ */ new Set(["before", "after", "added", "removed", "sample_unchanged"]);
|
|
1988
|
+
function normalizeMonitorRecord(value) {
|
|
1989
|
+
const out = {};
|
|
1990
|
+
for (const [key, item] of Object.entries(value)) {
|
|
1991
|
+
if (USER_KEYED_FIELDS.has(key)) out[camelKey2(key)] = item;
|
|
1992
|
+
else if (key === "diffs" && Array.isArray(item)) out.diffs = item.map(normalizeDiff);
|
|
1993
|
+
else out[camelKey2(key)] = camelizeKeys(item);
|
|
1994
|
+
}
|
|
1995
|
+
return out;
|
|
1996
|
+
}
|
|
1997
|
+
function normalizeDiff(entry) {
|
|
1998
|
+
if (!isJsonObject(entry)) return camelizeKeys(entry);
|
|
1999
|
+
const out = {};
|
|
2000
|
+
for (const [key, item] of Object.entries(entry)) {
|
|
2001
|
+
out[camelKey2(key)] = USER_KEYED_DIFF_FIELDS.has(key) ? item : camelizeKeys(item);
|
|
2002
|
+
}
|
|
2003
|
+
return out;
|
|
2004
|
+
}
|
|
2005
|
+
function camelKey2(key) {
|
|
2006
|
+
return key.replace(/_([a-z0-9])/g, (_, ch) => ch.toUpperCase());
|
|
2007
|
+
}
|
|
1867
2008
|
function normalizeSchedule(schedule) {
|
|
2009
|
+
if (!isJsonObject(schedule)) return schedule;
|
|
1868
2010
|
if (schedule.kind === "interval") {
|
|
1869
|
-
|
|
2011
|
+
const { intervalSeconds, interval_seconds, ...rest } = schedule;
|
|
2012
|
+
const seconds = intervalSeconds ?? interval_seconds;
|
|
2013
|
+
if (typeof seconds !== "number" || !Number.isFinite(seconds) || seconds <= 0) {
|
|
2014
|
+
throw new QoniValidationError(
|
|
2015
|
+
'track interval schedule needs a positive number: { kind: "interval", intervalSeconds }'
|
|
2016
|
+
);
|
|
2017
|
+
}
|
|
2018
|
+
return { ...rest, interval_seconds: seconds };
|
|
1870
2019
|
}
|
|
1871
|
-
|
|
2020
|
+
if (schedule.kind === "cron" && (typeof schedule.expr !== "string" || !schedule.expr.trim())) {
|
|
2021
|
+
throw new QoniValidationError('track cron schedule needs an expression: { kind: "cron", expr }');
|
|
2022
|
+
}
|
|
2023
|
+
return schedule;
|
|
1872
2024
|
}
|
|
1873
2025
|
function snakeifyKeys(value) {
|
|
1874
2026
|
const out = {};
|
|
@@ -2055,7 +2207,8 @@ var Qoni = class {
|
|
|
2055
2207
|
webAgentSSE: (...args) => this.webAgentSSE(...args)
|
|
2056
2208
|
};
|
|
2057
2209
|
this.genauth = createGenAuthNamespace(transport, {
|
|
2058
|
-
adminTokenFor: () => this.genauthAdminTokenFor()
|
|
2210
|
+
adminTokenFor: () => this.genauthAdminTokenFor(),
|
|
2211
|
+
delegateTokenFor: (input) => this.delegateToken(input)
|
|
2059
2212
|
});
|
|
2060
2213
|
this.qoni = createQoniNamespace(
|
|
2061
2214
|
transport,
|
|
@@ -2768,19 +2921,43 @@ function toRecord(value) {
|
|
|
2768
2921
|
}
|
|
2769
2922
|
function normalizeDelegateTokenInput(input) {
|
|
2770
2923
|
const body = toRecord(input);
|
|
2924
|
+
if (input.deniedScopes !== void 0) {
|
|
2925
|
+
throw new QoniValidationError(
|
|
2926
|
+
"delegateToken: deniedScopes is not supported by the delegation service. Do not rely on this field to prevent mailbox or browser actions."
|
|
2927
|
+
);
|
|
2928
|
+
}
|
|
2929
|
+
if (input.agent !== void 0 && input.agentKey !== void 0 && input.agent !== input.agentKey) {
|
|
2930
|
+
throw new QoniValidationError("delegateToken: agent and agentKey must identify the same Agent");
|
|
2931
|
+
}
|
|
2771
2932
|
if (!body.userId && isJsonObject(input.user) && typeof input.user.id === "string") {
|
|
2772
2933
|
body.userId = input.user.id;
|
|
2773
2934
|
}
|
|
2774
2935
|
if (input.mode !== "interactive" && !body.userId) {
|
|
2775
2936
|
throw new QoniValidationError(
|
|
2776
|
-
'delegateToken: silent mode requires a user \u2014 pass `user: { id: <genauthUserId> }` (or the
|
|
2937
|
+
'delegateToken: silent mode requires a user \u2014 pass `user: { id: <genauthUserId> }` (or the top-level `userId`). Interactive mode (`mode: "interactive"`) does not need one.'
|
|
2777
2938
|
);
|
|
2778
2939
|
}
|
|
2779
|
-
body.agent = input.agent ?? "sdk";
|
|
2940
|
+
body.agent = input.agent ?? input.agentKey ?? "sdk";
|
|
2941
|
+
delete body.agentKey;
|
|
2942
|
+
if (input.expiresIn !== void 0) body.expiresIn = delegationDurationSeconds(input.expiresIn);
|
|
2780
2943
|
body.scopes = resolveDelegationScopes(input.scopes, input.products);
|
|
2781
2944
|
delete body.products;
|
|
2782
2945
|
return body;
|
|
2783
2946
|
}
|
|
2947
|
+
function delegationDurationSeconds(value) {
|
|
2948
|
+
let seconds = value;
|
|
2949
|
+
if (typeof value === "string") {
|
|
2950
|
+
const duration = /^(\d+)(s|m|h|d)?$/.exec(value);
|
|
2951
|
+
const units = { s: 1, m: 60, h: 3600, d: 86400 };
|
|
2952
|
+
seconds = duration ? Number(duration[1]) * units[duration[2] ?? "s"] : NaN;
|
|
2953
|
+
}
|
|
2954
|
+
if (typeof seconds !== "number" || !Number.isInteger(seconds) || seconds < 60 || seconds > 86400) {
|
|
2955
|
+
throw new QoniValidationError(
|
|
2956
|
+
'delegateToken: expiresIn must be 60\u201386400 integer seconds or a whole-number duration such as "15m"'
|
|
2957
|
+
);
|
|
2958
|
+
}
|
|
2959
|
+
return seconds;
|
|
2960
|
+
}
|
|
2784
2961
|
function resolveDelegationScopes(scopes, products) {
|
|
2785
2962
|
const resolved = /* @__PURE__ */ new Set();
|
|
2786
2963
|
for (const product of products ?? []) {
|
|
@@ -2804,6 +2981,11 @@ function resolveDelegationScopes(scopes, products) {
|
|
|
2804
2981
|
return [...resolved];
|
|
2805
2982
|
}
|
|
2806
2983
|
function validateScopeFormat(scope) {
|
|
2984
|
+
if (scope.startsWith("mailbox.") || scope === "web.search" || scope === "web.extract") {
|
|
2985
|
+
throw new QoniValidationError(
|
|
2986
|
+
`delegateToken: legacy action scope "${scope}" has no enforced mapping in this delegation service. It will not be expanded into broader product permissions.`
|
|
2987
|
+
);
|
|
2988
|
+
}
|
|
2807
2989
|
if (KNOWN_SCOPES.includes(scope)) return;
|
|
2808
2990
|
const prefixed = KNOWN_SCOPES.find((known) => known.endsWith(`.${scope}`) || known === `webagent.${scope}`);
|
|
2809
2991
|
if (prefixed) {
|