@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.cjs
CHANGED
|
@@ -538,6 +538,87 @@ var ActionKinds = {
|
|
|
538
538
|
};
|
|
539
539
|
var ACTION_KIND_VALUES = Object.values(ActionKinds);
|
|
540
540
|
|
|
541
|
+
// src/scopes.ts
|
|
542
|
+
var QoniScopes = {
|
|
543
|
+
GUMEM_SESSION_CREATE: "gumem.session:create",
|
|
544
|
+
GUMEM_MESSAGE_WRITE: "gumem.message:write",
|
|
545
|
+
GUMEM_MEMORY_READ: "gumem.memory:read",
|
|
546
|
+
GUMEM_MEMORY_WRITE: "gumem.memory:write",
|
|
547
|
+
GUMEM_MEMORY_DELETE: "gumem.memory:delete",
|
|
548
|
+
GUMEM_SEARCH_RUN: "gumem.search:run",
|
|
549
|
+
GUMEM_ADMIN_MANAGE: "gumem.admin:manage",
|
|
550
|
+
GUMEM_RESOURCE_WRITE: "gumem.resource:write",
|
|
551
|
+
GUMEM_ACTION_WRITE: "gumem.action:write",
|
|
552
|
+
GUMEM_ACTION_READ: "gumem.action:read",
|
|
553
|
+
GUMEM_PROFILE_READ: "gumem.profile:read",
|
|
554
|
+
// WebAgent products: exactly two verbs per product — `read` (observe) and
|
|
555
|
+
// `manage` (anything that changes execution state). See
|
|
556
|
+
// wa/docs/qoni-sdk-public-surface.md §3.
|
|
557
|
+
DO_ANYTHING_READ: "webagent.do_anything:read",
|
|
558
|
+
DO_ANYTHING_MANAGE: "webagent.do_anything:manage",
|
|
559
|
+
WEB_SEARCH_READ: "webagent.web_search:read",
|
|
560
|
+
WEB_SEARCH_MANAGE: "webagent.web_search:manage",
|
|
561
|
+
DEEP_RESEARCH_READ: "webagent.deep_research:read",
|
|
562
|
+
DEEP_RESEARCH_MANAGE: "webagent.deep_research:manage",
|
|
563
|
+
TRACK_READ: "webagent.track:read",
|
|
564
|
+
TRACK_MANAGE: "webagent.track:manage",
|
|
565
|
+
// Interactive site login (`InteractionHandle.openLogin()` on a `site_login`
|
|
566
|
+
// interaction). Not part of any product bundle: the AccessKey policy must
|
|
567
|
+
// allow it, and the delegation must request it explicitly.
|
|
568
|
+
SITE_LOGIN_REQUEST: "webagent.site_login:request",
|
|
569
|
+
// Persist that login into the browser profile (`OpenLoginResult.confirm()`).
|
|
570
|
+
SITE_LOGIN_CONFIRM: "webagent.site_login:confirm"
|
|
571
|
+
};
|
|
572
|
+
var QoniProductScopes = {
|
|
573
|
+
doAnything: [
|
|
574
|
+
QoniScopes.DO_ANYTHING_READ,
|
|
575
|
+
QoniScopes.DO_ANYTHING_MANAGE
|
|
576
|
+
],
|
|
577
|
+
webSearch: [
|
|
578
|
+
QoniScopes.WEB_SEARCH_READ,
|
|
579
|
+
QoniScopes.WEB_SEARCH_MANAGE
|
|
580
|
+
],
|
|
581
|
+
deepResearch: [
|
|
582
|
+
QoniScopes.DEEP_RESEARCH_READ,
|
|
583
|
+
QoniScopes.DEEP_RESEARCH_MANAGE
|
|
584
|
+
],
|
|
585
|
+
track: [
|
|
586
|
+
QoniScopes.TRACK_READ,
|
|
587
|
+
QoniScopes.TRACK_MANAGE
|
|
588
|
+
]
|
|
589
|
+
};
|
|
590
|
+
var KNOWN_SCOPES = Object.values(QoniScopes);
|
|
591
|
+
var QoniScopeBundles = {
|
|
592
|
+
GUMEM_READONLY: [QoniScopes.GUMEM_MEMORY_READ, QoniScopes.GUMEM_PROFILE_READ],
|
|
593
|
+
GUMEM_SESSION_RECALL: [
|
|
594
|
+
QoniScopes.GUMEM_MEMORY_READ,
|
|
595
|
+
QoniScopes.GUMEM_MEMORY_WRITE,
|
|
596
|
+
QoniScopes.GUMEM_MESSAGE_WRITE
|
|
597
|
+
],
|
|
598
|
+
GUMEM_WRITE: [
|
|
599
|
+
QoniScopes.GUMEM_SESSION_CREATE,
|
|
600
|
+
QoniScopes.GUMEM_MESSAGE_WRITE,
|
|
601
|
+
QoniScopes.GUMEM_ACTION_WRITE
|
|
602
|
+
],
|
|
603
|
+
// One bundle per WebAgent product = [read, manage].
|
|
604
|
+
DO_ANYTHING: [
|
|
605
|
+
QoniScopes.DO_ANYTHING_READ,
|
|
606
|
+
QoniScopes.DO_ANYTHING_MANAGE
|
|
607
|
+
],
|
|
608
|
+
WEB_SEARCH: [
|
|
609
|
+
QoniScopes.WEB_SEARCH_READ,
|
|
610
|
+
QoniScopes.WEB_SEARCH_MANAGE
|
|
611
|
+
],
|
|
612
|
+
DEEP_RESEARCH: [
|
|
613
|
+
QoniScopes.DEEP_RESEARCH_READ,
|
|
614
|
+
QoniScopes.DEEP_RESEARCH_MANAGE
|
|
615
|
+
],
|
|
616
|
+
TRACK: [
|
|
617
|
+
QoniScopes.TRACK_READ,
|
|
618
|
+
QoniScopes.TRACK_MANAGE
|
|
619
|
+
]
|
|
620
|
+
};
|
|
621
|
+
|
|
541
622
|
// src/interactions.ts
|
|
542
623
|
function asString2(value) {
|
|
543
624
|
return typeof value === "string" && value ? value : void 0;
|
|
@@ -636,6 +717,9 @@ function normalizeInteraction(payload) {
|
|
|
636
717
|
if (evidence) interaction.evidence = evidence;
|
|
637
718
|
return interaction;
|
|
638
719
|
}
|
|
720
|
+
function interactionActionScopes(action, productManageScope) {
|
|
721
|
+
return action.kind === ActionKinds.openLogin ? [QoniScopes.SITE_LOGIN_REQUEST] : [productManageScope];
|
|
722
|
+
}
|
|
639
723
|
var InteractionHandle = class {
|
|
640
724
|
constructor(interaction, post) {
|
|
641
725
|
this.post = post;
|
|
@@ -660,48 +744,122 @@ var InteractionHandle = class {
|
|
|
660
744
|
return this.interaction.actions.some((a) => a.kind === kind);
|
|
661
745
|
}
|
|
662
746
|
/** Answer a clarification ask. */
|
|
663
|
-
answer(text) {
|
|
664
|
-
|
|
747
|
+
async answer(text) {
|
|
748
|
+
await this.invoke(ActionKinds.answer, { response: text });
|
|
665
749
|
}
|
|
666
750
|
/** Skip / dismiss the interaction (proceed without acting on it). */
|
|
667
|
-
skip() {
|
|
668
|
-
|
|
751
|
+
async skip() {
|
|
752
|
+
await this.invoke(ActionKinds.skip);
|
|
669
753
|
}
|
|
670
754
|
/** Confirm a pending confirmation (approve the plan / destructive op). */
|
|
671
|
-
confirm() {
|
|
672
|
-
|
|
755
|
+
async confirm() {
|
|
756
|
+
await this.invoke(ActionKinds.confirm);
|
|
673
757
|
}
|
|
674
758
|
/** Reject a pending confirmation. */
|
|
675
|
-
reject() {
|
|
676
|
-
|
|
759
|
+
async reject() {
|
|
760
|
+
await this.invoke(ActionKinds.reject);
|
|
677
761
|
}
|
|
678
|
-
/**
|
|
679
|
-
|
|
680
|
-
|
|
762
|
+
/**
|
|
763
|
+
* Start an interactive login session for a `site_login` interaction. The
|
|
764
|
+
* backend needs the site to sign in to: pass `siteId`, or omit it when the
|
|
765
|
+
* interaction lists exactly one site. Show `liveUrl` to the user; when they
|
|
766
|
+
* finish, call `confirm()` on the result to save the login, and act on its
|
|
767
|
+
* outcome (see {@link OpenLoginResult.confirm}) before {@link confirmSignedIn}.
|
|
768
|
+
* The delegation must include `QoniScopes.SITE_LOGIN_REQUEST` and
|
|
769
|
+
* `QoniScopes.SITE_LOGIN_CONFIRM`; the AccessKey policy must allow both.
|
|
770
|
+
*/
|
|
771
|
+
async openLogin(options = {}) {
|
|
772
|
+
this.#declaredAction(ActionKinds.openLogin);
|
|
773
|
+
const body = { site_id: this.#loginSiteId(options.siteId) };
|
|
774
|
+
if (options.profileId !== void 0) {
|
|
775
|
+
if (typeof options.profileId !== "string" || !options.profileId) {
|
|
776
|
+
throw new QoniValidationError("openLogin profileId must be a non-empty string");
|
|
777
|
+
}
|
|
778
|
+
body.profile_id = options.profileId;
|
|
779
|
+
}
|
|
780
|
+
const response = await this.invoke(ActionKinds.openLogin, body);
|
|
781
|
+
const raw = isJsonObject(response) ? response : {};
|
|
782
|
+
const fields = camelizeRecord(raw);
|
|
783
|
+
const profileId = asString2(fields.profileId) ?? "";
|
|
784
|
+
const siteId = asString2(fields.siteId) ?? String(body.site_id);
|
|
785
|
+
return {
|
|
786
|
+
profileId,
|
|
787
|
+
siteId,
|
|
788
|
+
sessionId: asString2(fields.sessionId) ?? "",
|
|
789
|
+
liveUrl: asString2(fields.liveUrl) ?? "",
|
|
790
|
+
raw,
|
|
791
|
+
confirm: async (confirmOptions = {}) => {
|
|
792
|
+
if (!profileId) {
|
|
793
|
+
throw new QoniValidationError("openLogin response did not include a profileId to confirm");
|
|
794
|
+
}
|
|
795
|
+
const confirmBody = { site_id: siteId };
|
|
796
|
+
if (confirmOptions.displayLabel !== void 0) confirmBody.display_label = confirmOptions.displayLabel;
|
|
797
|
+
const response2 = await this.post(
|
|
798
|
+
{
|
|
799
|
+
kind: ActionKinds.openLogin,
|
|
800
|
+
label: "Confirm login",
|
|
801
|
+
method: "POST",
|
|
802
|
+
endpoint: `/profiles/${encodeURIComponent(profileId)}/confirm`
|
|
803
|
+
},
|
|
804
|
+
confirmBody,
|
|
805
|
+
{ requiredScopes: [QoniScopes.SITE_LOGIN_CONFIRM] }
|
|
806
|
+
);
|
|
807
|
+
const raw2 = isJsonObject(response2) ? response2 : {};
|
|
808
|
+
const fields2 = camelizeRecord(raw2);
|
|
809
|
+
const state = asString2(fields2.state) ?? "";
|
|
810
|
+
return {
|
|
811
|
+
profileId: asString2(fields2.profileId) ?? profileId,
|
|
812
|
+
active: state === "active",
|
|
813
|
+
state,
|
|
814
|
+
probeResult: asString2(fields2.probeResult) ?? "",
|
|
815
|
+
raw: raw2
|
|
816
|
+
};
|
|
817
|
+
}
|
|
818
|
+
};
|
|
681
819
|
}
|
|
682
820
|
/** Tell the agent the user has finished signing in. */
|
|
683
|
-
confirmSignedIn() {
|
|
684
|
-
|
|
821
|
+
async confirmSignedIn() {
|
|
822
|
+
await this.invoke(ActionKinds.confirmSignedIn);
|
|
685
823
|
}
|
|
686
824
|
/** Connect to the live browser for a take_control interaction. */
|
|
687
|
-
connectControl() {
|
|
688
|
-
|
|
825
|
+
async connectControl() {
|
|
826
|
+
await this.invoke(ActionKinds.connectControl);
|
|
689
827
|
}
|
|
690
828
|
/** Refresh the live control surface / live url. */
|
|
691
|
-
refreshControl() {
|
|
692
|
-
|
|
829
|
+
async refreshControl() {
|
|
830
|
+
await this.invoke(ActionKinds.refreshControl);
|
|
693
831
|
}
|
|
694
832
|
/** Hand control back to the agent. */
|
|
695
|
-
releaseControl() {
|
|
696
|
-
|
|
833
|
+
async releaseControl() {
|
|
834
|
+
await this.invoke(ActionKinds.releaseControl);
|
|
697
835
|
}
|
|
698
836
|
/** Retry a wait interaction now. */
|
|
699
|
-
retry() {
|
|
700
|
-
|
|
837
|
+
async retry() {
|
|
838
|
+
await this.invoke(ActionKinds.retry);
|
|
701
839
|
}
|
|
702
840
|
/** Switch the profile a wait/login interaction is bound to. */
|
|
703
|
-
switchProfile() {
|
|
704
|
-
|
|
841
|
+
async switchProfile() {
|
|
842
|
+
await this.invoke(ActionKinds.switchProfile);
|
|
843
|
+
}
|
|
844
|
+
/** Resolve the site for `openLogin`: explicit, or the only listed site. */
|
|
845
|
+
#loginSiteId(requested) {
|
|
846
|
+
const payload = this.interaction.payload;
|
|
847
|
+
const listed = Array.isArray(payload?.sites) ? payload.sites.map((site) => site.siteId).filter((id) => typeof id === "string" && id.length > 0) : [];
|
|
848
|
+
if (requested !== void 0) {
|
|
849
|
+
if (typeof requested !== "string" || !requested) {
|
|
850
|
+
throw new QoniValidationError("openLogin siteId must be a non-empty string");
|
|
851
|
+
}
|
|
852
|
+
if (listed.length > 0 && !listed.includes(requested)) {
|
|
853
|
+
throw new QoniValidationError(
|
|
854
|
+
`openLogin siteId "${requested}" is not one of the sites this interaction asks for: ${listed.join(", ")}`
|
|
855
|
+
);
|
|
856
|
+
}
|
|
857
|
+
return requested;
|
|
858
|
+
}
|
|
859
|
+
if (listed.length === 1) return listed[0];
|
|
860
|
+
throw new QoniValidationError(
|
|
861
|
+
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(", ")}`
|
|
862
|
+
);
|
|
705
863
|
}
|
|
706
864
|
/**
|
|
707
865
|
* Invoke an action by kind with an optional body. `async` so a missing-action
|
|
@@ -709,6 +867,10 @@ var InteractionHandle = class {
|
|
|
709
867
|
* method contract uniformly promise-based.
|
|
710
868
|
*/
|
|
711
869
|
async invoke(kind, body) {
|
|
870
|
+
return this.post(this.#declaredAction(kind), body);
|
|
871
|
+
}
|
|
872
|
+
/** The declared action of `kind`; throws when the backend did not offer it. */
|
|
873
|
+
#declaredAction(kind) {
|
|
712
874
|
const action = this.interaction.actions.find((a) => a.kind === kind);
|
|
713
875
|
if (!action) {
|
|
714
876
|
const offered = this.interaction.actions.map((a) => a.kind).join(", ") || "none";
|
|
@@ -716,7 +878,7 @@ var InteractionHandle = class {
|
|
|
716
878
|
`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.`
|
|
717
879
|
);
|
|
718
880
|
}
|
|
719
|
-
|
|
881
|
+
return action;
|
|
720
882
|
}
|
|
721
883
|
};
|
|
722
884
|
|
|
@@ -786,7 +948,7 @@ var RunHandle = class {
|
|
|
786
948
|
break settled;
|
|
787
949
|
}
|
|
788
950
|
}
|
|
789
|
-
const detail2 = await
|
|
951
|
+
const detail2 = await settleRead(() => this.ops.getDetail(signal), signal);
|
|
790
952
|
if (isTerminalRunStatus(normalizeRunStatus(this.id, detail2).status)) {
|
|
791
953
|
terminalDetail = detail2;
|
|
792
954
|
break;
|
|
@@ -800,10 +962,10 @@ var RunHandle = class {
|
|
|
800
962
|
sawCleanClose = true;
|
|
801
963
|
eventIdAtLastClose = lastEventId;
|
|
802
964
|
}
|
|
803
|
-
const detail = terminalDetail ?? await
|
|
965
|
+
const detail = terminalDetail ?? await settleRead(() => this.ops.getDetail(signal), signal);
|
|
804
966
|
this.#adoptSessionRef(detail);
|
|
805
967
|
const result = settleRunResult(this.id, detail, [], terminalDoneReason);
|
|
806
|
-
result.artifacts = this.ops.listArtifacts ? await
|
|
968
|
+
result.artifacts = this.ops.listArtifacts ? await settleRead(() => this.ops.listArtifacts(signal), signal) : [];
|
|
807
969
|
return result;
|
|
808
970
|
} catch (err) {
|
|
809
971
|
if (timedOut() && !opts.signal?.aborted) {
|
|
@@ -826,7 +988,7 @@ var RunHandle = class {
|
|
|
826
988
|
interactionHandle(interaction) {
|
|
827
989
|
return new InteractionHandle(
|
|
828
990
|
interaction,
|
|
829
|
-
(action, body) => this.ops.postAction(action, body)
|
|
991
|
+
(action, body, options) => this.ops.postAction(action, body, options)
|
|
830
992
|
);
|
|
831
993
|
}
|
|
832
994
|
/**
|
|
@@ -920,6 +1082,35 @@ function withTimeout(signal, timeoutMs) {
|
|
|
920
1082
|
}
|
|
921
1083
|
return { signal: controller.signal, timedOut: () => fired, dispose };
|
|
922
1084
|
}
|
|
1085
|
+
var SETTLE_READ_BACKOFF_MS = [250, 1e3];
|
|
1086
|
+
async function settleRead(read, signal) {
|
|
1087
|
+
for (let attempt = 0; ; attempt++) {
|
|
1088
|
+
try {
|
|
1089
|
+
return await waitWithSignal(read, signal);
|
|
1090
|
+
} catch (err) {
|
|
1091
|
+
const delay = SETTLE_READ_BACKOFF_MS[attempt];
|
|
1092
|
+
if (delay === void 0 || signal?.aborted || !(err instanceof QoniError) || !err.retryable) throw err;
|
|
1093
|
+
await sleepWithSignal(delay, signal);
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
function sleepWithSignal(ms, signal) {
|
|
1098
|
+
return new Promise((resolve, reject) => {
|
|
1099
|
+
if (signal?.aborted) {
|
|
1100
|
+
reject(signal.reason ?? new DOMException("Aborted", "AbortError"));
|
|
1101
|
+
return;
|
|
1102
|
+
}
|
|
1103
|
+
const onAbort = () => {
|
|
1104
|
+
clearTimeout(timer);
|
|
1105
|
+
reject(signal.reason ?? new DOMException("Aborted", "AbortError"));
|
|
1106
|
+
};
|
|
1107
|
+
const timer = setTimeout(() => {
|
|
1108
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1109
|
+
resolve();
|
|
1110
|
+
}, ms);
|
|
1111
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1112
|
+
});
|
|
1113
|
+
}
|
|
923
1114
|
function waitWithSignal(operation, signal) {
|
|
924
1115
|
if (!signal) return Promise.resolve().then(operation);
|
|
925
1116
|
return new Promise((resolve, reject) => {
|
|
@@ -1017,17 +1208,15 @@ function createDeepResearchNamespace(transport) {
|
|
|
1017
1208
|
signal: opts.signal,
|
|
1018
1209
|
onReconnect: opts.onReconnect
|
|
1019
1210
|
}),
|
|
1020
|
-
postAction: async (action, body) =>
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
{
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
);
|
|
1030
|
-
},
|
|
1211
|
+
postAction: async (action, body, options) => (await transport.webAgentJson(
|
|
1212
|
+
action.method || "POST",
|
|
1213
|
+
action.endpoint,
|
|
1214
|
+
token,
|
|
1215
|
+
{
|
|
1216
|
+
...body !== void 0 ? { body } : {},
|
|
1217
|
+
requiredScopes: options?.requiredScopes ?? interactionActionScopes(action, "webagent.deep_research:manage")
|
|
1218
|
+
}
|
|
1219
|
+
)).data,
|
|
1031
1220
|
cancel: async () => asRecord((await api.cancel({ token, runId })).data),
|
|
1032
1221
|
listArtifacts: async (signal) => {
|
|
1033
1222
|
const res = await api.listArtifacts({ token, runId, signal });
|
|
@@ -1292,17 +1481,15 @@ function createDoAnythingNamespace(transport) {
|
|
|
1292
1481
|
// Skip screenshot rows in backfill when the caller did not opt in.
|
|
1293
1482
|
includeScreenshots: capture?.screenshots ? void 0 : false
|
|
1294
1483
|
}),
|
|
1295
|
-
postAction: async (action, body) =>
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
{
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
);
|
|
1305
|
-
},
|
|
1484
|
+
postAction: async (action, body, options) => (await transport.webAgentJson(
|
|
1485
|
+
action.method || "POST",
|
|
1486
|
+
action.endpoint,
|
|
1487
|
+
token,
|
|
1488
|
+
{
|
|
1489
|
+
...body !== void 0 ? { body } : {},
|
|
1490
|
+
requiredScopes: options?.requiredScopes ?? interactionActionScopes(action, "webagent.do_anything:manage")
|
|
1491
|
+
}
|
|
1492
|
+
)).data,
|
|
1306
1493
|
cancel: async (reason) => asRecord2(
|
|
1307
1494
|
(await api.cancel({
|
|
1308
1495
|
token,
|
|
@@ -1423,84 +1610,16 @@ function createQoniNamespace(_transport, delegateToken, completeDelegateToken) {
|
|
|
1423
1610
|
};
|
|
1424
1611
|
}
|
|
1425
1612
|
|
|
1426
|
-
// src/scopes.ts
|
|
1427
|
-
var QoniScopes = {
|
|
1428
|
-
GUMEM_SESSION_CREATE: "gumem.session:create",
|
|
1429
|
-
GUMEM_MESSAGE_WRITE: "gumem.message:write",
|
|
1430
|
-
GUMEM_MEMORY_READ: "gumem.memory:read",
|
|
1431
|
-
GUMEM_MEMORY_WRITE: "gumem.memory:write",
|
|
1432
|
-
GUMEM_MEMORY_DELETE: "gumem.memory:delete",
|
|
1433
|
-
GUMEM_SEARCH_RUN: "gumem.search:run",
|
|
1434
|
-
GUMEM_ADMIN_MANAGE: "gumem.admin:manage",
|
|
1435
|
-
GUMEM_RESOURCE_WRITE: "gumem.resource:write",
|
|
1436
|
-
GUMEM_ACTION_WRITE: "gumem.action:write",
|
|
1437
|
-
GUMEM_ACTION_READ: "gumem.action:read",
|
|
1438
|
-
GUMEM_PROFILE_READ: "gumem.profile:read",
|
|
1439
|
-
// WebAgent products: exactly two verbs per product — `read` (observe) and
|
|
1440
|
-
// `manage` (anything that changes execution state). See
|
|
1441
|
-
// wa/docs/qoni-sdk-public-surface.md §3.
|
|
1442
|
-
DO_ANYTHING_READ: "webagent.do_anything:read",
|
|
1443
|
-
DO_ANYTHING_MANAGE: "webagent.do_anything:manage",
|
|
1444
|
-
WEB_SEARCH_READ: "webagent.web_search:read",
|
|
1445
|
-
WEB_SEARCH_MANAGE: "webagent.web_search:manage",
|
|
1446
|
-
DEEP_RESEARCH_READ: "webagent.deep_research:read",
|
|
1447
|
-
DEEP_RESEARCH_MANAGE: "webagent.deep_research:manage",
|
|
1448
|
-
TRACK_READ: "webagent.track:read",
|
|
1449
|
-
TRACK_MANAGE: "webagent.track:manage"
|
|
1450
|
-
};
|
|
1451
|
-
var QoniProductScopes = {
|
|
1452
|
-
doAnything: [
|
|
1453
|
-
QoniScopes.DO_ANYTHING_READ,
|
|
1454
|
-
QoniScopes.DO_ANYTHING_MANAGE
|
|
1455
|
-
],
|
|
1456
|
-
webSearch: [
|
|
1457
|
-
QoniScopes.WEB_SEARCH_READ,
|
|
1458
|
-
QoniScopes.WEB_SEARCH_MANAGE
|
|
1459
|
-
],
|
|
1460
|
-
deepResearch: [
|
|
1461
|
-
QoniScopes.DEEP_RESEARCH_READ,
|
|
1462
|
-
QoniScopes.DEEP_RESEARCH_MANAGE
|
|
1463
|
-
],
|
|
1464
|
-
track: [
|
|
1465
|
-
QoniScopes.TRACK_READ,
|
|
1466
|
-
QoniScopes.TRACK_MANAGE
|
|
1467
|
-
]
|
|
1468
|
-
};
|
|
1469
|
-
var KNOWN_SCOPES = Object.values(QoniScopes);
|
|
1470
|
-
var QoniScopeBundles = {
|
|
1471
|
-
GUMEM_READONLY: [QoniScopes.GUMEM_MEMORY_READ, QoniScopes.GUMEM_PROFILE_READ],
|
|
1472
|
-
GUMEM_SESSION_RECALL: [
|
|
1473
|
-
QoniScopes.GUMEM_MEMORY_READ,
|
|
1474
|
-
QoniScopes.GUMEM_MEMORY_WRITE,
|
|
1475
|
-
QoniScopes.GUMEM_MESSAGE_WRITE
|
|
1476
|
-
],
|
|
1477
|
-
GUMEM_WRITE: [
|
|
1478
|
-
QoniScopes.GUMEM_SESSION_CREATE,
|
|
1479
|
-
QoniScopes.GUMEM_MESSAGE_WRITE,
|
|
1480
|
-
QoniScopes.GUMEM_ACTION_WRITE
|
|
1481
|
-
],
|
|
1482
|
-
// One bundle per WebAgent product = [read, manage].
|
|
1483
|
-
DO_ANYTHING: [
|
|
1484
|
-
QoniScopes.DO_ANYTHING_READ,
|
|
1485
|
-
QoniScopes.DO_ANYTHING_MANAGE
|
|
1486
|
-
],
|
|
1487
|
-
WEB_SEARCH: [
|
|
1488
|
-
QoniScopes.WEB_SEARCH_READ,
|
|
1489
|
-
QoniScopes.WEB_SEARCH_MANAGE
|
|
1490
|
-
],
|
|
1491
|
-
DEEP_RESEARCH: [
|
|
1492
|
-
QoniScopes.DEEP_RESEARCH_READ,
|
|
1493
|
-
QoniScopes.DEEP_RESEARCH_MANAGE
|
|
1494
|
-
],
|
|
1495
|
-
TRACK: [
|
|
1496
|
-
QoniScopes.TRACK_READ,
|
|
1497
|
-
QoniScopes.TRACK_MANAGE
|
|
1498
|
-
]
|
|
1499
|
-
};
|
|
1500
|
-
|
|
1501
1613
|
// src/genauth.ts
|
|
1502
1614
|
function createGenAuthNamespace(transport, options = {}) {
|
|
1615
|
+
async function delegateAgent(input) {
|
|
1616
|
+
if (!options.delegateTokenFor) {
|
|
1617
|
+
throw new QoniValidationError("GenAuth delegateAgent requires a Qoni delegation provider");
|
|
1618
|
+
}
|
|
1619
|
+
return (await options.delegateTokenFor(input)).data;
|
|
1620
|
+
}
|
|
1503
1621
|
return {
|
|
1622
|
+
delegateAgent,
|
|
1504
1623
|
userInfo: (input) => transport.genauthJson("GET", "/oidc/me", input.accessToken),
|
|
1505
1624
|
jwks: () => transport.genauthJson("GET", "/oidc/.well-known/jwks.json"),
|
|
1506
1625
|
discovery: () => transport.genauthJson("GET", "/oidc/.well-known/openid-configuration"),
|
|
@@ -1696,7 +1815,7 @@ var MonitorHandle = class {
|
|
|
1696
1815
|
id;
|
|
1697
1816
|
/** Refresh and return the monitor (normalized camelCase fields). */
|
|
1698
1817
|
async get() {
|
|
1699
|
-
return
|
|
1818
|
+
return normalizeMonitorRecord(await this.ops.get());
|
|
1700
1819
|
}
|
|
1701
1820
|
/**
|
|
1702
1821
|
* Pause the monitor — stops scheduled ticks until {@link resume}. The
|
|
@@ -1704,11 +1823,11 @@ var MonitorHandle = class {
|
|
|
1704
1823
|
* deleting.
|
|
1705
1824
|
*/
|
|
1706
1825
|
async pause() {
|
|
1707
|
-
return
|
|
1826
|
+
return normalizeMonitorRecord(await this.ops.update({ action: "pause" }));
|
|
1708
1827
|
}
|
|
1709
1828
|
/** Resume a paused monitor. */
|
|
1710
1829
|
async resume() {
|
|
1711
|
-
return
|
|
1830
|
+
return normalizeMonitorRecord(await this.ops.update({ action: "resume" }));
|
|
1712
1831
|
}
|
|
1713
1832
|
/**
|
|
1714
1833
|
* Change the monitor definition. Pass any subset of fields (at least one);
|
|
@@ -1726,11 +1845,11 @@ var MonitorHandle = class {
|
|
|
1726
1845
|
"track refine requires at least one field to change (schedule / targetUrls / extractionSchema / triggerDsl / stopConditionDsl / notifyChannel)"
|
|
1727
1846
|
);
|
|
1728
1847
|
}
|
|
1729
|
-
return
|
|
1848
|
+
return normalizeMonitorRecord(await this.ops.update({ action: "refine", patch: body }));
|
|
1730
1849
|
}
|
|
1731
1850
|
/** Trigger an immediate tick outside the schedule. */
|
|
1732
1851
|
async runNow() {
|
|
1733
|
-
return
|
|
1852
|
+
return normalizeMonitorRecord(await this.ops.runNow());
|
|
1734
1853
|
}
|
|
1735
1854
|
/**
|
|
1736
1855
|
* Stream semantic monitor events. Monitors are resident, so the stream has
|
|
@@ -1751,7 +1870,7 @@ var MonitorHandle = class {
|
|
|
1751
1870
|
interactionHandle(interaction) {
|
|
1752
1871
|
return new InteractionHandle(
|
|
1753
1872
|
interaction,
|
|
1754
|
-
(action, body) => this.ops.postAction(action, body)
|
|
1873
|
+
(action, body, options) => this.ops.postAction(action, body, options)
|
|
1755
1874
|
);
|
|
1756
1875
|
}
|
|
1757
1876
|
/**
|
|
@@ -1856,17 +1975,15 @@ function createTrackNamespace(transport) {
|
|
|
1856
1975
|
update: async (input) => asRecord3((await api.updateMonitor({ token, monitorId, ...input })).data),
|
|
1857
1976
|
runNow: async () => asRecord3((await api.runNow({ token, monitorId })).data),
|
|
1858
1977
|
streamEvents: (opts) => api.events({ token, monitorId, lastEventId: opts.lastEventId, signal: opts.signal }),
|
|
1859
|
-
postAction: async (action, body) =>
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
{
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
);
|
|
1869
|
-
},
|
|
1978
|
+
postAction: async (action, body, options) => (await transport.webAgentJson(
|
|
1979
|
+
action.method || "POST",
|
|
1980
|
+
action.endpoint,
|
|
1981
|
+
token,
|
|
1982
|
+
{
|
|
1983
|
+
...body !== void 0 ? { body } : {},
|
|
1984
|
+
requiredScopes: options?.requiredScopes ?? interactionActionScopes(action, "webagent.track:manage")
|
|
1985
|
+
}
|
|
1986
|
+
)).data,
|
|
1870
1987
|
listRuns: async (opts) => asRecord3((await api.listRuns({ token, monitorId, ...opts })).data),
|
|
1871
1988
|
getRun: async (runId) => asRecord3((await api.getRun({ token, monitorId, runId })).data),
|
|
1872
1989
|
delete: async () => {
|
|
@@ -1881,10 +1998,12 @@ function createTrackNamespace(transport) {
|
|
|
1881
1998
|
if (!prompt || typeof prompt !== "string") {
|
|
1882
1999
|
throw new QoniValidationError("track.create requires a prompt string");
|
|
1883
2000
|
}
|
|
2001
|
+
const body = snakeifyKeys(rest);
|
|
2002
|
+
if (body.schedule !== void 0) body.schedule = normalizeSchedule(body.schedule);
|
|
1884
2003
|
const created = await api.createMonitor({
|
|
1885
2004
|
token,
|
|
1886
2005
|
intent: prompt,
|
|
1887
|
-
...
|
|
2006
|
+
...body
|
|
1888
2007
|
});
|
|
1889
2008
|
const monitorId = created.data.id || created.data.monitor_id;
|
|
1890
2009
|
if (!monitorId) {
|
|
@@ -1902,7 +2021,7 @@ function createTrackNamespace(transport) {
|
|
|
1902
2021
|
};
|
|
1903
2022
|
}
|
|
1904
2023
|
function normalizeMonitorRun(item) {
|
|
1905
|
-
const normalized =
|
|
2024
|
+
const normalized = normalizeMonitorRecord(item);
|
|
1906
2025
|
if (typeof normalized.id !== "string" || !normalized.id) {
|
|
1907
2026
|
const id = normalized.runId ?? normalized.snapshotId;
|
|
1908
2027
|
if (typeof id === "string" && id) normalized.id = id;
|
|
@@ -1912,11 +2031,44 @@ function normalizeMonitorRun(item) {
|
|
|
1912
2031
|
function normalizeTrackInterveneInput(input) {
|
|
1913
2032
|
return renameKeys4(input, { requestId: "request_id" });
|
|
1914
2033
|
}
|
|
2034
|
+
var USER_KEYED_FIELDS = /* @__PURE__ */ new Set(["extraction_schema", "baseline_extracted", "extracted"]);
|
|
2035
|
+
var USER_KEYED_DIFF_FIELDS = /* @__PURE__ */ new Set(["before", "after", "added", "removed", "sample_unchanged"]);
|
|
2036
|
+
function normalizeMonitorRecord(value) {
|
|
2037
|
+
const out = {};
|
|
2038
|
+
for (const [key, item] of Object.entries(value)) {
|
|
2039
|
+
if (USER_KEYED_FIELDS.has(key)) out[camelKey2(key)] = item;
|
|
2040
|
+
else if (key === "diffs" && Array.isArray(item)) out.diffs = item.map(normalizeDiff);
|
|
2041
|
+
else out[camelKey2(key)] = camelizeKeys(item);
|
|
2042
|
+
}
|
|
2043
|
+
return out;
|
|
2044
|
+
}
|
|
2045
|
+
function normalizeDiff(entry) {
|
|
2046
|
+
if (!isJsonObject(entry)) return camelizeKeys(entry);
|
|
2047
|
+
const out = {};
|
|
2048
|
+
for (const [key, item] of Object.entries(entry)) {
|
|
2049
|
+
out[camelKey2(key)] = USER_KEYED_DIFF_FIELDS.has(key) ? item : camelizeKeys(item);
|
|
2050
|
+
}
|
|
2051
|
+
return out;
|
|
2052
|
+
}
|
|
2053
|
+
function camelKey2(key) {
|
|
2054
|
+
return key.replace(/_([a-z0-9])/g, (_, ch) => ch.toUpperCase());
|
|
2055
|
+
}
|
|
1915
2056
|
function normalizeSchedule(schedule) {
|
|
2057
|
+
if (!isJsonObject(schedule)) return schedule;
|
|
1916
2058
|
if (schedule.kind === "interval") {
|
|
1917
|
-
|
|
2059
|
+
const { intervalSeconds, interval_seconds, ...rest } = schedule;
|
|
2060
|
+
const seconds = intervalSeconds ?? interval_seconds;
|
|
2061
|
+
if (typeof seconds !== "number" || !Number.isFinite(seconds) || seconds <= 0) {
|
|
2062
|
+
throw new QoniValidationError(
|
|
2063
|
+
'track interval schedule needs a positive number: { kind: "interval", intervalSeconds }'
|
|
2064
|
+
);
|
|
2065
|
+
}
|
|
2066
|
+
return { ...rest, interval_seconds: seconds };
|
|
1918
2067
|
}
|
|
1919
|
-
|
|
2068
|
+
if (schedule.kind === "cron" && (typeof schedule.expr !== "string" || !schedule.expr.trim())) {
|
|
2069
|
+
throw new QoniValidationError('track cron schedule needs an expression: { kind: "cron", expr }');
|
|
2070
|
+
}
|
|
2071
|
+
return schedule;
|
|
1920
2072
|
}
|
|
1921
2073
|
function snakeifyKeys(value) {
|
|
1922
2074
|
const out = {};
|
|
@@ -2103,7 +2255,8 @@ var Qoni = class {
|
|
|
2103
2255
|
webAgentSSE: (...args) => this.webAgentSSE(...args)
|
|
2104
2256
|
};
|
|
2105
2257
|
this.genauth = createGenAuthNamespace(transport, {
|
|
2106
|
-
adminTokenFor: () => this.genauthAdminTokenFor()
|
|
2258
|
+
adminTokenFor: () => this.genauthAdminTokenFor(),
|
|
2259
|
+
delegateTokenFor: (input) => this.delegateToken(input)
|
|
2107
2260
|
});
|
|
2108
2261
|
this.qoni = createQoniNamespace(
|
|
2109
2262
|
transport,
|
|
@@ -2816,19 +2969,43 @@ function toRecord(value) {
|
|
|
2816
2969
|
}
|
|
2817
2970
|
function normalizeDelegateTokenInput(input) {
|
|
2818
2971
|
const body = toRecord(input);
|
|
2972
|
+
if (input.deniedScopes !== void 0) {
|
|
2973
|
+
throw new QoniValidationError(
|
|
2974
|
+
"delegateToken: deniedScopes is not supported by the delegation service. Do not rely on this field to prevent mailbox or browser actions."
|
|
2975
|
+
);
|
|
2976
|
+
}
|
|
2977
|
+
if (input.agent !== void 0 && input.agentKey !== void 0 && input.agent !== input.agentKey) {
|
|
2978
|
+
throw new QoniValidationError("delegateToken: agent and agentKey must identify the same Agent");
|
|
2979
|
+
}
|
|
2819
2980
|
if (!body.userId && isJsonObject(input.user) && typeof input.user.id === "string") {
|
|
2820
2981
|
body.userId = input.user.id;
|
|
2821
2982
|
}
|
|
2822
2983
|
if (input.mode !== "interactive" && !body.userId) {
|
|
2823
2984
|
throw new QoniValidationError(
|
|
2824
|
-
'delegateToken: silent mode requires a user \u2014 pass `user: { id: <genauthUserId> }` (or the
|
|
2985
|
+
'delegateToken: silent mode requires a user \u2014 pass `user: { id: <genauthUserId> }` (or the top-level `userId`). Interactive mode (`mode: "interactive"`) does not need one.'
|
|
2825
2986
|
);
|
|
2826
2987
|
}
|
|
2827
|
-
body.agent = input.agent ?? "sdk";
|
|
2988
|
+
body.agent = input.agent ?? input.agentKey ?? "sdk";
|
|
2989
|
+
delete body.agentKey;
|
|
2990
|
+
if (input.expiresIn !== void 0) body.expiresIn = delegationDurationSeconds(input.expiresIn);
|
|
2828
2991
|
body.scopes = resolveDelegationScopes(input.scopes, input.products);
|
|
2829
2992
|
delete body.products;
|
|
2830
2993
|
return body;
|
|
2831
2994
|
}
|
|
2995
|
+
function delegationDurationSeconds(value) {
|
|
2996
|
+
let seconds = value;
|
|
2997
|
+
if (typeof value === "string") {
|
|
2998
|
+
const duration = /^(\d+)(s|m|h|d)?$/.exec(value);
|
|
2999
|
+
const units = { s: 1, m: 60, h: 3600, d: 86400 };
|
|
3000
|
+
seconds = duration ? Number(duration[1]) * units[duration[2] ?? "s"] : NaN;
|
|
3001
|
+
}
|
|
3002
|
+
if (typeof seconds !== "number" || !Number.isInteger(seconds) || seconds < 60 || seconds > 86400) {
|
|
3003
|
+
throw new QoniValidationError(
|
|
3004
|
+
'delegateToken: expiresIn must be 60\u201386400 integer seconds or a whole-number duration such as "15m"'
|
|
3005
|
+
);
|
|
3006
|
+
}
|
|
3007
|
+
return seconds;
|
|
3008
|
+
}
|
|
2832
3009
|
function resolveDelegationScopes(scopes, products) {
|
|
2833
3010
|
const resolved = /* @__PURE__ */ new Set();
|
|
2834
3011
|
for (const product of products ?? []) {
|
|
@@ -2852,6 +3029,11 @@ function resolveDelegationScopes(scopes, products) {
|
|
|
2852
3029
|
return [...resolved];
|
|
2853
3030
|
}
|
|
2854
3031
|
function validateScopeFormat(scope) {
|
|
3032
|
+
if (scope.startsWith("mailbox.") || scope === "web.search" || scope === "web.extract") {
|
|
3033
|
+
throw new QoniValidationError(
|
|
3034
|
+
`delegateToken: legacy action scope "${scope}" has no enforced mapping in this delegation service. It will not be expanded into broader product permissions.`
|
|
3035
|
+
);
|
|
3036
|
+
}
|
|
2855
3037
|
if (KNOWN_SCOPES.includes(scope)) return;
|
|
2856
3038
|
const prefixed = KNOWN_SCOPES.find((known) => known.endsWith(`.${scope}`) || known === `webagent.${scope}`);
|
|
2857
3039
|
if (prefixed) {
|