@foir/sdk 0.29.0 → 0.30.1
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 +84 -0
- package/dist/ai/index.d.ts +186 -0
- package/dist/ai.js +588 -0
- package/dist/auth/auth.d.ts +15 -1
- package/dist/auth/code-flow.d.ts +1 -1
- package/dist/auth/grant-table.generated.d.ts +1 -1
- package/dist/auth/grants.d.ts +2 -1
- package/dist/auth/hosted.d.ts +3 -3
- package/dist/auth/index.d.ts +1 -1
- package/dist/auth/session-client.d.ts +1 -1
- package/dist/auth.js +13 -1
- package/dist/client/platform-client.d.ts +1 -1
- package/dist/data/agents.d.ts +104 -0
- package/dist/data/builtins.d.ts +182 -0
- package/dist/data/index.d.ts +5 -3
- package/dist/data/subjects.d.ts +45 -0
- package/dist/data/subscriptions.d.ts +38 -0
- package/dist/data.js +169 -2
- package/dist/placement.js +7 -1
- package/dist/react/FoirFeedback.d.ts +18 -1
- package/dist/react/SignIn.d.ts +2 -2
- package/dist/react/feedback-notes.d.ts +7 -0
- package/dist/react.js +261 -9
- package/dist/server/webhook-helpers.d.ts +3 -1
- package/dist/server.js +31 -4
- package/package.json +10 -1
package/dist/data.js
CHANGED
|
@@ -1782,7 +1782,8 @@ function describe(payload) {
|
|
|
1782
1782
|
var MODEL_SUBSCRIPTION_METHODS = /* @__PURE__ */ new Set(["onChange"]);
|
|
1783
1783
|
var NAMESPACE_SUBSCRIPTION_METHODS = {
|
|
1784
1784
|
notifications: /* @__PURE__ */ new Set(["onNotification"]),
|
|
1785
|
-
operations: /* @__PURE__ */ new Set(["onJobProgress", "onExecution"])
|
|
1785
|
+
operations: /* @__PURE__ */ new Set(["onJobProgress", "onExecution"]),
|
|
1786
|
+
agents: /* @__PURE__ */ new Set(["onRun"])
|
|
1786
1787
|
};
|
|
1787
1788
|
function isModelSubscriptionMethod(name) {
|
|
1788
1789
|
return MODEL_SUBSCRIPTION_METHODS.has(name);
|
|
@@ -1866,6 +1867,25 @@ var FoirSubscriptionRouter = class {
|
|
|
1866
1867
|
{ ...handlers, onData: (event) => handlers.onData(executionEventWithEnumStatus(event)) }
|
|
1867
1868
|
);
|
|
1868
1869
|
}
|
|
1870
|
+
if (namespace === "agents" && method === "onRun") {
|
|
1871
|
+
const id = input.id;
|
|
1872
|
+
if (typeof id !== "string" || id === "") {
|
|
1873
|
+
throw new FoirRequestError("[foir] onRun requires a run `id`");
|
|
1874
|
+
}
|
|
1875
|
+
const args = [{ name: "id", type: "String!" }];
|
|
1876
|
+
const variables = { id };
|
|
1877
|
+
for (const name of ["after", "visitorToken"]) {
|
|
1878
|
+
const v = input[name];
|
|
1879
|
+
if (typeof v === "string" && v !== "") {
|
|
1880
|
+
args.push({ name, type: "String" });
|
|
1881
|
+
variables[name] = v;
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1884
|
+
return this.open(
|
|
1885
|
+
{ field: "agentRun", operationName: "FoirAgentRun", args, variables },
|
|
1886
|
+
handlers
|
|
1887
|
+
);
|
|
1888
|
+
}
|
|
1869
1889
|
throw new FoirRequestError(`[foir] ns.${namespace}.${method} is not a subscription`);
|
|
1870
1890
|
}
|
|
1871
1891
|
/**
|
|
@@ -1949,12 +1969,24 @@ function normalise(value) {
|
|
|
1949
1969
|
}
|
|
1950
1970
|
|
|
1951
1971
|
// src/data/subjects.ts
|
|
1972
|
+
function markToken(modelKey, recordId, field) {
|
|
1973
|
+
const text = `${modelKey}\0${recordId}\0${field}`;
|
|
1974
|
+
let fnv = 2166136261;
|
|
1975
|
+
let djb = 5381;
|
|
1976
|
+
for (let i = 0; i < text.length; i++) {
|
|
1977
|
+
const code = text.charCodeAt(i);
|
|
1978
|
+
fnv = Math.imul(fnv ^ code, 16777619) >>> 0;
|
|
1979
|
+
djb = (Math.imul(djb, 33) ^ code) >>> 0;
|
|
1980
|
+
}
|
|
1981
|
+
return `f${fnv.toString(36)}${djb.toString(36)}`;
|
|
1982
|
+
}
|
|
1952
1983
|
var keyOf = (modelKey, recordId) => `${modelKey}\0${recordId}`;
|
|
1953
1984
|
function createFoirSubjectRegistry(options = {}) {
|
|
1954
1985
|
const held = /* @__PURE__ */ new Map();
|
|
1955
1986
|
const listeners = /* @__PURE__ */ new Set();
|
|
1956
1987
|
const pinned = { subject: null };
|
|
1957
1988
|
const latest = { key: null };
|
|
1989
|
+
const marks = /* @__PURE__ */ new Map();
|
|
1958
1990
|
const notify = () => {
|
|
1959
1991
|
const failures = [];
|
|
1960
1992
|
for (const listener of [...listeners]) {
|
|
@@ -2025,6 +2057,25 @@ function createFoirSubjectRegistry(options = {}) {
|
|
|
2025
2057
|
return () => {
|
|
2026
2058
|
listeners.delete(listener);
|
|
2027
2059
|
};
|
|
2060
|
+
},
|
|
2061
|
+
field(subject, field, fieldOptions = {}) {
|
|
2062
|
+
if (!subject.modelKey || !subject.recordId || !field) {
|
|
2063
|
+
throw new FoirRequestError(
|
|
2064
|
+
"[foir] a field mark needs a modelKey, a recordId and a field \u2014 a mark missing one points at nothing a note can be filed against"
|
|
2065
|
+
);
|
|
2066
|
+
}
|
|
2067
|
+
const token = markToken(subject.modelKey, subject.recordId, field);
|
|
2068
|
+
const mark = {
|
|
2069
|
+
subject: { modelKey: subject.modelKey, recordId: subject.recordId },
|
|
2070
|
+
field
|
|
2071
|
+
};
|
|
2072
|
+
if (subject.label) mark.subject.label = subject.label;
|
|
2073
|
+
if (fieldOptions.label) mark.label = fieldOptions.label;
|
|
2074
|
+
marks.set(token, mark);
|
|
2075
|
+
return { "data-foir-field": token };
|
|
2076
|
+
},
|
|
2077
|
+
pointedAt(token) {
|
|
2078
|
+
return marks.get(token);
|
|
2028
2079
|
}
|
|
2029
2080
|
};
|
|
2030
2081
|
}
|
|
@@ -2716,6 +2767,119 @@ var FoirDecimal = class _FoirDecimal {
|
|
|
2716
2767
|
function decimal(value) {
|
|
2717
2768
|
return new FoirDecimal(value);
|
|
2718
2769
|
}
|
|
2770
|
+
|
|
2771
|
+
// src/data/agents.ts
|
|
2772
|
+
async function uploadAgentFile(foir, options) {
|
|
2773
|
+
const agents = foir.ns.agents;
|
|
2774
|
+
if (typeof agents?.createAgentAttachment !== "function") {
|
|
2775
|
+
throw new Error("[foir] uploadAgentFile: this client sends no agent files. Its key runs none of the project's agents, or the project has released none.");
|
|
2776
|
+
}
|
|
2777
|
+
const { blob, mediaType, filename } = fileParts(options.file);
|
|
2778
|
+
const up = await agents.createAgentAttachment({
|
|
2779
|
+
agent: options.agent,
|
|
2780
|
+
filename,
|
|
2781
|
+
mediaType,
|
|
2782
|
+
size: blob.size,
|
|
2783
|
+
...options.visitorToken ? { visitorToken: options.visitorToken } : {}
|
|
2784
|
+
});
|
|
2785
|
+
const put = await (options.fetch ?? fetch)(up.uploadUrl, { method: "PUT", headers: { "content-type": mediaType }, body: blob });
|
|
2786
|
+
if (!put.ok) throw new Error(`[foir] uploadAgentFile: ${filename} was not uploaded (${put.status}).`);
|
|
2787
|
+
return up.attachment;
|
|
2788
|
+
}
|
|
2789
|
+
function fileParts(file) {
|
|
2790
|
+
if (typeof Blob !== "undefined" && file instanceof Blob) {
|
|
2791
|
+
const name = file.name;
|
|
2792
|
+
return { blob: file, mediaType: file.type, filename: name || "file" };
|
|
2793
|
+
}
|
|
2794
|
+
const { data, mediaType, filename } = file;
|
|
2795
|
+
const blob = data instanceof Blob ? data : new Blob([data], { type: mediaType });
|
|
2796
|
+
return { blob, mediaType, filename: filename || "file" };
|
|
2797
|
+
}
|
|
2798
|
+
var FINAL = /* @__PURE__ */ new Set(["COMPLETED", "FAILED", "SKIPPED", "CANCELLED", "HELD"]);
|
|
2799
|
+
async function askAgent(foir, options) {
|
|
2800
|
+
const agents = foir.ns.agents;
|
|
2801
|
+
if (typeof agents?.startAgentRun !== "function" || typeof agents.onRun !== "function") {
|
|
2802
|
+
throw new Error(
|
|
2803
|
+
"[foir] askAgent: this client offers no agents. Its key runs none of the project's agents (agents:execute), or the project has released none."
|
|
2804
|
+
);
|
|
2805
|
+
}
|
|
2806
|
+
const { onText, onEvent, signal, files, ...args } = options;
|
|
2807
|
+
if (files?.length) {
|
|
2808
|
+
const sent = await Promise.all(files.map((file) => uploadAgentFile(foir, { agent: args.agent, file, visitorToken: args.visitorToken })));
|
|
2809
|
+
args.attachmentIds = [...args.attachmentIds ?? [], ...sent.map((a) => a.id)];
|
|
2810
|
+
}
|
|
2811
|
+
const run = await agents.startAgentRun(args);
|
|
2812
|
+
const reply = {
|
|
2813
|
+
runId: run.id,
|
|
2814
|
+
threadId: run.threadId ?? null,
|
|
2815
|
+
status: run.status,
|
|
2816
|
+
answer: run.answer ?? "",
|
|
2817
|
+
error: run.error ?? null,
|
|
2818
|
+
...run.output === void 0 || run.output === null ? {} : { output: run.output }
|
|
2819
|
+
};
|
|
2820
|
+
if (FINAL.has(run.status) && run.status !== "HELD") return reply;
|
|
2821
|
+
let written = "";
|
|
2822
|
+
return new Promise((resolve, reject) => {
|
|
2823
|
+
let settled = false;
|
|
2824
|
+
const finish = (sub2, err) => {
|
|
2825
|
+
if (settled) return;
|
|
2826
|
+
settled = true;
|
|
2827
|
+
sub2?.unsubscribe();
|
|
2828
|
+
if (err !== void 0) reject(err);
|
|
2829
|
+
else resolve({ ...reply, answer: reply.answer || written });
|
|
2830
|
+
};
|
|
2831
|
+
let sub = null;
|
|
2832
|
+
sub = agents.onRun({
|
|
2833
|
+
id: run.id,
|
|
2834
|
+
visitorToken: args.visitorToken,
|
|
2835
|
+
onData: (event) => {
|
|
2836
|
+
onEvent?.(event);
|
|
2837
|
+
if (event.kind === "TEXT" && event.text) {
|
|
2838
|
+
written += event.text;
|
|
2839
|
+
onText?.(event.text);
|
|
2840
|
+
}
|
|
2841
|
+
if (event.kind === "STATUS" && event.status) {
|
|
2842
|
+
reply.status = event.status;
|
|
2843
|
+
if (event.answer) reply.answer = event.answer;
|
|
2844
|
+
if (event.error) reply.error = event.error;
|
|
2845
|
+
if (event.output !== void 0 && event.output !== null) reply.output = event.output;
|
|
2846
|
+
if (FINAL.has(event.status)) finish(sub);
|
|
2847
|
+
}
|
|
2848
|
+
},
|
|
2849
|
+
onError: (err) => finish(sub, err),
|
|
2850
|
+
onComplete: () => finish(sub)
|
|
2851
|
+
});
|
|
2852
|
+
signal?.addEventListener("abort", () => finish(sub), { once: true });
|
|
2853
|
+
});
|
|
2854
|
+
}
|
|
2855
|
+
var VISITOR_KEY = "foir:agents:visitor";
|
|
2856
|
+
function visitorToken(storage = browserStorage(), key = VISITOR_KEY) {
|
|
2857
|
+
try {
|
|
2858
|
+
const kept = storage?.getItem(key);
|
|
2859
|
+
if (kept) return kept;
|
|
2860
|
+
} catch {
|
|
2861
|
+
}
|
|
2862
|
+
const made = randomSecret();
|
|
2863
|
+
try {
|
|
2864
|
+
storage?.setItem(key, made);
|
|
2865
|
+
} catch {
|
|
2866
|
+
}
|
|
2867
|
+
return made;
|
|
2868
|
+
}
|
|
2869
|
+
function browserStorage() {
|
|
2870
|
+
try {
|
|
2871
|
+
return typeof localStorage === "undefined" ? null : localStorage;
|
|
2872
|
+
} catch {
|
|
2873
|
+
return null;
|
|
2874
|
+
}
|
|
2875
|
+
}
|
|
2876
|
+
function randomSecret() {
|
|
2877
|
+
const bytes = new Uint8Array(24);
|
|
2878
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
2879
|
+
let out = "";
|
|
2880
|
+
for (const b of bytes) out += b.toString(16).padStart(2, "0");
|
|
2881
|
+
return out;
|
|
2882
|
+
}
|
|
2719
2883
|
export {
|
|
2720
2884
|
FOIR_VISITOR_IP_HEADER,
|
|
2721
2885
|
FoirAuthDowngradedError,
|
|
@@ -2724,6 +2888,7 @@ export {
|
|
|
2724
2888
|
FoirSchemaDriftError,
|
|
2725
2889
|
FoirSessionExpiredError,
|
|
2726
2890
|
FoirSocketTransport,
|
|
2891
|
+
askAgent,
|
|
2727
2892
|
createFoirClient,
|
|
2728
2893
|
createFoirClientFromEnv,
|
|
2729
2894
|
createFoirSubjectRegistry,
|
|
@@ -2737,5 +2902,7 @@ export {
|
|
|
2737
2902
|
planFoirNamespaceCall,
|
|
2738
2903
|
printSubscriptionDocument,
|
|
2739
2904
|
recordFoirSubjects,
|
|
2740
|
-
stepUpRequired
|
|
2905
|
+
stepUpRequired,
|
|
2906
|
+
uploadAgentFile,
|
|
2907
|
+
visitorToken
|
|
2741
2908
|
};
|
package/dist/placement.js
CHANGED
|
@@ -32,6 +32,12 @@ var GRANT_TABLE = [
|
|
|
32
32
|
["operations:write", "none", [], []],
|
|
33
33
|
["operations:complete", "none", [], []],
|
|
34
34
|
["status:write", "none", [], []],
|
|
35
|
+
["agents:read", "none", [], []],
|
|
36
|
+
["agents:execute", "agent", [], []],
|
|
37
|
+
["agents:write", "none", [], []],
|
|
38
|
+
["agenttools:execute", "agent_tool", [], []],
|
|
39
|
+
["brief:read", "none", [], []],
|
|
40
|
+
["brief:write", "none", [], []],
|
|
35
41
|
["appconfig:read", "none", [], []],
|
|
36
42
|
["schedules:read", "none", [], []],
|
|
37
43
|
["schedules:write", "none", [], []],
|
|
@@ -81,7 +87,7 @@ var GRANT_TABLE = [
|
|
|
81
87
|
["projects:read", "none", [], []],
|
|
82
88
|
["projects:write", "none", [], []],
|
|
83
89
|
["project:stand", "none", [], []],
|
|
84
|
-
["project:manage", "none", [], ["project:stand", "records:read.all", "records:create", "records:write.all", "records:delete.all", "records:publish.all", "records:attribute", "search:read", "embeddings:read", "embeddings:write", "schemas:read", "drafts:read", "files:read.all", "files:write.all", "files:delete.all", "users:read.all", "identities:read", "users:create", "identities:delete", "users:write.all", "users:setup", "users:delete.all", "notifications:read.all", "notifications:write.all", "notifications:consent", "pushcredentials:read", "pushcredentials:write", "notes:read.all", "notes:write.all", "notes:create", "approvals:read", "approvals:write", "rooms:read", "rooms:write", "presence:read", "operations:read", "operations:execute", "operations:write", "appconfig:read", "schedules:read", "schedules:write", "hooks:read", "hooks:write", "authclients:read", "authclients:write", "domains:read", "domains:write", "loginpages:read", "loginpages:write", "secrets:read", "secrets:list", "secrets:put", "secrets:delete", "appsecrets:read", "appsecrets:list", "appsecrets:put", "appsecrets:delete", "usersecrets:read", "usersecrets:list", "usersecrets:put", "usersecrets:delete", "opsecrets:read", "opsecrets:write", "settings:read", "settings:write", "designtokens:read", "designtokens:write", "models:read", "models:write", "configs:read", "configs:write", "signinproviders:read", "signinproviders:write", "apps:read", "apps:write", "events:read", "targeting:debug", "visitors:forward", "apikeys:read", "apikeys:write", "organizations:read", "organizations:create", "organizations:delete", "organizations:write", "orgsettings:write", "organizations:reach", "members:read", "members:write", "roles:read", "roles:write", "projects:read", "projects:write", "audit:read"]],
|
|
90
|
+
["project:manage", "none", [], ["project:stand", "records:read.all", "records:create", "records:write.all", "records:delete.all", "records:publish.all", "records:attribute", "search:read", "embeddings:read", "embeddings:write", "schemas:read", "drafts:read", "files:read.all", "files:write.all", "files:delete.all", "users:read.all", "identities:read", "users:create", "identities:delete", "users:write.all", "users:setup", "users:delete.all", "notifications:read.all", "notifications:write.all", "notifications:consent", "pushcredentials:read", "pushcredentials:write", "notes:read.all", "notes:write.all", "notes:create", "approvals:read", "approvals:write", "rooms:read", "rooms:write", "presence:read", "operations:read", "operations:execute", "operations:write", "agents:read", "agents:execute", "agents:write", "agenttools:execute", "brief:read", "brief:write", "appconfig:read", "schedules:read", "schedules:write", "hooks:read", "hooks:write", "authclients:read", "authclients:write", "domains:read", "domains:write", "loginpages:read", "loginpages:write", "secrets:read", "secrets:list", "secrets:put", "secrets:delete", "appsecrets:read", "appsecrets:list", "appsecrets:put", "appsecrets:delete", "usersecrets:read", "usersecrets:list", "usersecrets:put", "usersecrets:delete", "opsecrets:read", "opsecrets:write", "settings:read", "settings:write", "designtokens:read", "designtokens:write", "models:read", "models:write", "configs:read", "configs:write", "signinproviders:read", "signinproviders:write", "apps:read", "apps:write", "events:read", "targeting:debug", "visitors:forward", "apikeys:read", "apikeys:write", "organizations:read", "organizations:create", "organizations:delete", "organizations:write", "orgsettings:write", "organizations:reach", "members:read", "members:write", "roles:read", "roles:write", "projects:read", "projects:write", "audit:read"]],
|
|
85
91
|
["projects:create", "none", [], []],
|
|
86
92
|
["projects:delete", "none", [], []],
|
|
87
93
|
["tenants:read", "none", [], []],
|
|
@@ -59,6 +59,23 @@ export interface FoirFeedbackProps {
|
|
|
59
59
|
* with a backdrop, closed by Escape, the backdrop or its Close button.
|
|
60
60
|
*/
|
|
61
61
|
layout?: 'inline' | 'dialog';
|
|
62
|
+
/**
|
|
63
|
+
* Show the button fixed in a corner of the viewport, rather than where the
|
|
64
|
+
* component is placed, with the inline panel opening above it. Default: where
|
|
65
|
+
* it is placed.
|
|
66
|
+
*/
|
|
67
|
+
launcher?: 'bottom-right' | 'bottom-left';
|
|
68
|
+
/**
|
|
69
|
+
* Let the reader point at the part of the page they mean: a field the host
|
|
70
|
+
* marked with `subjects.field(record, key, { label })`. The note is then filed
|
|
71
|
+
* against that record and names the field, so a page showing two records (a
|
|
72
|
+
* menu and an article) files each comment against the right one.
|
|
73
|
+
*
|
|
74
|
+
* Needs `subjects`, and is offered only while the page shows a marked field.
|
|
75
|
+
* A mouse points by clicking; the keyboard by Tab and Enter; Escape cancels.
|
|
76
|
+
* Default false.
|
|
77
|
+
*/
|
|
78
|
+
pointing?: boolean;
|
|
62
79
|
/**
|
|
63
80
|
* The built-in styles' colours, radius and type. See
|
|
64
81
|
* `FoirFeedbackAppearance`: each is also a CSS custom property, so a
|
|
@@ -121,4 +138,4 @@ export interface FoirFeedbackAppearance {
|
|
|
121
138
|
/** `--foir-feedback-font-size`. Default: inherited. */
|
|
122
139
|
fontSize?: string;
|
|
123
140
|
}
|
|
124
|
-
export declare function FoirFeedback({ client, subjects, subject, label, triggerLabel, title, placeholder, defaultOpen, onSignIn, signIn, layout, appearance, onSubmitted, onVisitorSubmitted, onError, className, }: FoirFeedbackProps): import("react/jsx-runtime").JSX.Element;
|
|
141
|
+
export declare function FoirFeedback({ client, subjects, subject, label, triggerLabel, title, placeholder, defaultOpen, onSignIn, signIn, layout, launcher, pointing, appearance, onSubmitted, onVisitorSubmitted, onError, className, }: FoirFeedbackProps): import("react/jsx-runtime").JSX.Element;
|
package/dist/react/SignIn.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { FoirUser } from '../auth';
|
|
|
2
2
|
export interface SignInProps {
|
|
3
3
|
/** `password` (default) or `otp` (email one-time code). Ignored when `hostedUrl` is set. */
|
|
4
4
|
method?: 'password' | 'otp';
|
|
5
|
-
/** Render a single button that redirects to
|
|
5
|
+
/** Render a single button that redirects to the hosted sign-in page instead of an embedded form. */
|
|
6
6
|
hostedUrl?: string;
|
|
7
7
|
/** Show OAuth provider buttons below the form. Default true. */
|
|
8
8
|
oauth?: boolean;
|
|
@@ -15,7 +15,7 @@ export interface SignInProps {
|
|
|
15
15
|
/**
|
|
16
16
|
* A drop-in user sign-in. Renders an email/password (or OTP) form and,
|
|
17
17
|
* unless disabled, OAuth provider buttons. Set `hostedUrl` to instead render a
|
|
18
|
-
* button that redirects to
|
|
18
|
+
* button that redirects to the hosted sign-in page. All state is handled through
|
|
19
19
|
* {@link FoirProvider}; on success it calls `onSignIn` and optionally navigates
|
|
20
20
|
* to `afterSignInUrl`.
|
|
21
21
|
*/
|
|
@@ -113,6 +113,8 @@ export interface FoirFeedbackSubmission {
|
|
|
113
113
|
*/
|
|
114
114
|
subject: FoirSubject;
|
|
115
115
|
body: string;
|
|
116
|
+
/** The field of the subject the reader pointed at, when they pointed at one. */
|
|
117
|
+
anchorField?: string;
|
|
116
118
|
/** The still the visitor chose to attach, or null. */
|
|
117
119
|
screenshot: FoirScreenCapture | null;
|
|
118
120
|
/** Injectable for the presigned PUT. Defaults to `globalThis.fetch`. */
|
|
@@ -132,6 +134,11 @@ export interface FoirVisitorSubmission {
|
|
|
132
134
|
/** What the note is about, as for a signed-in user's. */
|
|
133
135
|
subject: FoirSubject;
|
|
134
136
|
body: string;
|
|
137
|
+
/**
|
|
138
|
+
* The field of the subject the visitor pointed at. The data API takes a
|
|
139
|
+
* field the visitor can read and refuses any other.
|
|
140
|
+
*/
|
|
141
|
+
anchorField?: string;
|
|
135
142
|
/**
|
|
136
143
|
* Where the project's team may reply, as the visitor typed it. Sent only
|
|
137
144
|
* when it is not blank, and never verified by anyone.
|