@simmalugnt-se/payload-editor-assistant 0.2.0 → 0.3.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 +31 -0
- package/README.md +29 -3
- package/dist/collections/approvals.js +3 -2
- package/dist/collections/audit.js +1 -1
- package/dist/components/ChangedFieldsHighlight.d.ts +8 -0
- package/dist/components/ChangedFieldsHighlight.js +50 -0
- package/dist/components/EditorAssistantAside.js +169 -63
- package/dist/components/EditorAssistantDocumentBridge.js +52 -1
- package/dist/components/EditorAssistantProvider.js +2 -1
- package/dist/components/assistant-context.d.ts +14 -0
- package/dist/components/assistant-context.js +4 -0
- package/dist/components/copy.d.ts +109 -0
- package/dist/components/copy.js +109 -0
- package/dist/components/selection-dom.d.ts +16 -0
- package/dist/components/selection-dom.js +104 -0
- package/dist/domain/propose.d.ts +7 -1
- package/dist/domain/propose.js +8 -3
- package/dist/endpoints/chat.js +2 -0
- package/dist/form/changed-paths.d.ts +6 -0
- package/dist/form/changed-paths.js +26 -0
- package/dist/form/diff.js +1 -22
- package/dist/form/empty.js +1 -19
- package/dist/form/lexical.d.ts +8 -0
- package/dist/form/lexical.js +24 -0
- package/dist/runtime/agent.d.ts +4 -1
- package/dist/runtime/agent.js +21 -8
- package/dist/runtime/context.d.ts +3 -0
- package/dist/runtime/context.js +10 -3
- package/dist/runtime/propose-loop.d.ts +9 -0
- package/dist/runtime/propose-loop.js +13 -1
- package/dist/runtime/selection.d.ts +28 -0
- package/dist/runtime/selection.js +87 -0
- package/dist/runtime/system-prompt.js +17 -1
- package/dist/runtime/tools.d.ts +7 -0
- package/dist/runtime/tools.js +2 -1
- package/dist/runtime/voice.js +2 -2
- package/dist/schema/fields.d.ts +1 -0
- package/dist/schema/fields.js +1 -0
- package/dist/styles/assistant.css +210 -181
- package/package.json +1 -1
package/dist/runtime/agent.js
CHANGED
|
@@ -28,7 +28,8 @@ export async function runAgentTurn(req, options, input) {
|
|
|
28
28
|
const signal = req.signal ? AbortSignal.any([req.signal, timeout]) : timeout;
|
|
29
29
|
const history = truncateMessages(input.messages ?? [{ role: "user", text: input.message }]);
|
|
30
30
|
const captured = [];
|
|
31
|
-
const
|
|
31
|
+
const previews = new Map();
|
|
32
|
+
const tools = buildSdkTools(ai, ctx, captured, previews);
|
|
32
33
|
const turnContext = await loadTurnContext(req, options, input.bridge, input.formData);
|
|
33
34
|
const system = buildSystemPrompt(options.instructions, input.bridge, turnContext);
|
|
34
35
|
const sdkMessages = toSdkMessages(history);
|
|
@@ -45,7 +46,7 @@ export async function runAgentTurn(req, options, input) {
|
|
|
45
46
|
logProviderError(req, "the model returned no result");
|
|
46
47
|
return { ok: false, error: "provider_error" };
|
|
47
48
|
}
|
|
48
|
-
const collected = collectResult(String(first.text ?? ""), first, captured, input.bridge.language ?? input.bridge.locale);
|
|
49
|
+
const collected = collectResult(String(first.text ?? ""), first, captured, input.bridge.language ?? input.bridge.locale, false, previews);
|
|
49
50
|
if (!collected.ok) {
|
|
50
51
|
return collected;
|
|
51
52
|
}
|
|
@@ -54,11 +55,12 @@ export async function runAgentTurn(req, options, input) {
|
|
|
54
55
|
pendingApproval: collected.pendingApproval,
|
|
55
56
|
navigate: collected.navigate,
|
|
56
57
|
text: String(first.text ?? ""),
|
|
58
|
+
request: input.message,
|
|
57
59
|
})) {
|
|
58
60
|
return collected;
|
|
59
61
|
}
|
|
60
62
|
const retryCaptured = [];
|
|
61
|
-
const retryTools = buildSdkTools(ai, ctx, retryCaptured);
|
|
63
|
+
const retryTools = buildSdkTools(ai, ctx, retryCaptured, previews);
|
|
62
64
|
const retry = await generate(ai, {
|
|
63
65
|
model,
|
|
64
66
|
system,
|
|
@@ -75,9 +77,9 @@ export async function runAgentTurn(req, options, input) {
|
|
|
75
77
|
signal,
|
|
76
78
|
});
|
|
77
79
|
if (!retry) {
|
|
78
|
-
return collectResult(String(first.text ?? ""), first, captured, input.bridge.language ?? input.bridge.locale, true);
|
|
80
|
+
return collectResult(String(first.text ?? ""), first, captured, input.bridge.language ?? input.bridge.locale, true, previews);
|
|
79
81
|
}
|
|
80
|
-
return collectResult(String(retry.text ?? ""), retry, retryCaptured, input.bridge.language ?? input.bridge.locale, true);
|
|
82
|
+
return collectResult(String(retry.text ?? ""), retry, retryCaptured, input.bridge.language ?? input.bridge.locale, true, previews);
|
|
81
83
|
}
|
|
82
84
|
catch (error) {
|
|
83
85
|
if (signal.aborted) {
|
|
@@ -91,7 +93,7 @@ export async function runAgentTurn(req, options, input) {
|
|
|
91
93
|
};
|
|
92
94
|
}
|
|
93
95
|
}
|
|
94
|
-
export function collectResult(text, result, captured, locale, expectedProposal = false) {
|
|
96
|
+
export function collectResult(text, result, captured, locale, expectedProposal = false, previews) {
|
|
95
97
|
const outcomes = [...captured];
|
|
96
98
|
harvestOutcomes(outcomes, result.toolResults);
|
|
97
99
|
if (Array.isArray(result.steps)) {
|
|
@@ -116,6 +118,10 @@ export function collectResult(text, result, captured, locale, expectedProposal =
|
|
|
116
118
|
proposeFailed = true;
|
|
117
119
|
}
|
|
118
120
|
}
|
|
121
|
+
const preview = pendingApproval && previews?.get(pendingApproval.approvalId);
|
|
122
|
+
if (pendingApproval && preview) {
|
|
123
|
+
pendingApproval = { ...pendingApproval, preview };
|
|
124
|
+
}
|
|
119
125
|
return {
|
|
120
126
|
ok: true,
|
|
121
127
|
text: editorFacingText({
|
|
@@ -162,7 +168,7 @@ function isToolOutcome(value) {
|
|
|
162
168
|
const record = value;
|
|
163
169
|
return typeof record.ok === "boolean" && typeof record.capability === "string";
|
|
164
170
|
}
|
|
165
|
-
function buildSdkTools(ai, ctx, captured) {
|
|
171
|
+
function buildSdkTools(ai, ctx, captured, previews) {
|
|
166
172
|
const tools = {};
|
|
167
173
|
for (const capability of Object.keys(TOOL_JSON_SCHEMAS)) {
|
|
168
174
|
const schema = TOOL_JSON_SCHEMAS[capability];
|
|
@@ -174,7 +180,14 @@ function buildSdkTools(ai, ctx, captured) {
|
|
|
174
180
|
execute: async (raw) => {
|
|
175
181
|
try {
|
|
176
182
|
const input = raw && typeof raw === "object" ? raw : {};
|
|
177
|
-
const
|
|
183
|
+
const executed = await executeTool(fromProviderToolName(name), input, ctx);
|
|
184
|
+
// The whole form with the change applied is for Admin, not another round through the model.
|
|
185
|
+
const { preview, ...outcome } = executed.ok
|
|
186
|
+
? executed
|
|
187
|
+
: { ...executed, preview: undefined };
|
|
188
|
+
if (preview && executed.ok && executed.pendingApproval) {
|
|
189
|
+
previews.set(executed.pendingApproval.approvalId, preview);
|
|
190
|
+
}
|
|
178
191
|
captured.push(outcome);
|
|
179
192
|
return outcome;
|
|
180
193
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { PayloadRequest } from "payload";
|
|
2
2
|
import type { ValidatedEditorAssistantOptions } from "../config/validate.ts";
|
|
3
3
|
import type { AdminView } from "../view.ts";
|
|
4
|
+
import { type Selection } from "./selection.ts";
|
|
4
5
|
import { type ToolBridge } from "./tools.ts";
|
|
5
6
|
export type TurnContext = {
|
|
6
7
|
allowlist: Array<{
|
|
@@ -31,6 +32,8 @@ export type TurnContext = {
|
|
|
31
32
|
} | null;
|
|
32
33
|
view?: AdminView;
|
|
33
34
|
form?: Record<string, unknown>;
|
|
35
|
+
/** What the editor clicked or focused; "this" in their message refers to it. */
|
|
36
|
+
selection?: Selection;
|
|
34
37
|
otherLanguages?: Record<string, Record<string, unknown>>;
|
|
35
38
|
documents: Record<string, unknown>[];
|
|
36
39
|
};
|
package/dist/runtime/context.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { findContent, findContentById } from "../domain/content.js";
|
|
2
|
-
import { configuredLocales, discoverSchema } from "../schema/discover.js";
|
|
2
|
+
import { configuredLocales, discoverSchema, entityFields, } from "../schema/discover.js";
|
|
3
|
+
import { resolveSelection } from "./selection.js";
|
|
3
4
|
import { executeTool } from "./tools.js";
|
|
4
5
|
export async function loadTurnContext(req, options, bridge, formData) {
|
|
5
6
|
const ctx = { req, options, bridge, formData };
|
|
@@ -13,7 +14,12 @@ export async function loadTurnContext(req, options, bridge, formData) {
|
|
|
13
14
|
locale: bridge.locale,
|
|
14
15
|
}
|
|
15
16
|
: null;
|
|
16
|
-
const form = open
|
|
17
|
+
const form = open
|
|
18
|
+
? ((await readOpenForm(ctx)) ?? (await readSavedDocument(req, options, bridge)))
|
|
19
|
+
: undefined;
|
|
20
|
+
const selection = open
|
|
21
|
+
? resolveSelection(bridge.selection, form, entityFields(req, options, { collection: bridge.collection, global: bridge.global }))
|
|
22
|
+
: undefined;
|
|
17
23
|
const listed = await findContent(req, options, {
|
|
18
24
|
collection: bridge.collection ?? (bridge.view?.kind === "list" ? bridge.view.collection : undefined),
|
|
19
25
|
query: "",
|
|
@@ -24,7 +30,8 @@ export async function loadTurnContext(req, options, bridge, formData) {
|
|
|
24
30
|
allowlist: schema.map(compactEntity),
|
|
25
31
|
open,
|
|
26
32
|
view: bridge.view,
|
|
27
|
-
form
|
|
33
|
+
form,
|
|
34
|
+
selection,
|
|
28
35
|
otherLanguages,
|
|
29
36
|
documents: listed.ok ? listed.results : [],
|
|
30
37
|
};
|
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
export declare function claimedUnstagedChange(text: string): boolean;
|
|
2
|
+
/**
|
|
3
|
+
* The editor asked for a change and the reply writes a value out in chat ("a tighter version could
|
|
4
|
+
* be "Visual editing, live"") instead of staging it. Phrasings vary too much to list, so this only
|
|
5
|
+
* needs the request and a quoted value. A question ("what is this for?") is not a request, so a
|
|
6
|
+
* value the model volunteers while answering it stays advice.
|
|
7
|
+
*/
|
|
8
|
+
export declare function offeredValue(text: string, request: string): boolean;
|
|
2
9
|
export declare function needsProposeRetry(input: {
|
|
3
10
|
hasOpenDocument: boolean;
|
|
4
11
|
pendingApproval?: unknown;
|
|
5
12
|
navigate?: unknown;
|
|
6
13
|
text: string;
|
|
14
|
+
/** The editor's latest message. */
|
|
15
|
+
request?: string;
|
|
7
16
|
}): boolean;
|
|
@@ -1,11 +1,23 @@
|
|
|
1
1
|
export function claimedUnstagedChange(text) {
|
|
2
2
|
return CLAIMED_CHANGE.test(text) || ASKED_TO_MUTATE.test(text);
|
|
3
3
|
}
|
|
4
|
+
/**
|
|
5
|
+
* The editor asked for a change and the reply writes a value out in chat ("a tighter version could
|
|
6
|
+
* be "Visual editing, live"") instead of staging it. Phrasings vary too much to list, so this only
|
|
7
|
+
* needs the request and a quoted value. A question ("what is this for?") is not a request, so a
|
|
8
|
+
* value the model volunteers while answering it stays advice.
|
|
9
|
+
*/
|
|
10
|
+
export function offeredValue(text, request) {
|
|
11
|
+
return QUOTED.test(text) && ASKS_FOR_CHANGE.test(request);
|
|
12
|
+
}
|
|
13
|
+
// Letter lookarounds rather than \b, which treats å, ä and ö as word boundaries.
|
|
14
|
+
const ASKS_FOR_CHANGE = /(?<!\p{L})(make|change|rewrite|write|add|replace|shorten|tighten|fill|translate|update|improve|set|rename|remove|delete|gör|göra|ändra|skriv|skriva|lägg|lägga|byt|byta|korta|fyll|fylla|översätt|uppdatera|förbättra|sätt|ta bort)(?!\p{L})/iu;
|
|
15
|
+
const QUOTED = /["“”«»][^"“”«»]{2,}["“”«»]|'[^']{2,}'/;
|
|
4
16
|
const CLAIMED_CHANGE = /godkänn|godkann|approve|rubriken är nu|the headline is now|sparas den på sidan|fälten i på sidan|pending form change|i've applied|i have applied/i;
|
|
5
17
|
const ASKED_TO_MUTATE = /ska jag (lägga|fylla|ändra|sätta|uppdatera)|should i (put|fill|change|set|update|apply)|shall i (put|fill|change|set|update|apply)|want me to (put|fill|change|set|update|apply)/i;
|
|
6
18
|
export function needsProposeRetry(input) {
|
|
7
19
|
if (!input.hasOpenDocument || input.pendingApproval || input.navigate) {
|
|
8
20
|
return false;
|
|
9
21
|
}
|
|
10
|
-
return claimedUnstagedChange(input.text);
|
|
22
|
+
return claimedUnstagedChange(input.text) || offeredValue(input.text, input.request ?? "");
|
|
11
23
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { FieldNode } from "../schema/fields.ts";
|
|
2
|
+
/** What the editor last clicked or focused in the open form, resolved against the visible fields. */
|
|
3
|
+
export type Selection = {
|
|
4
|
+
/** Form path, e.g. `layout.2.headline` or `layout.2` for a whole block. */
|
|
5
|
+
path: string;
|
|
6
|
+
/** Visible labels from the top down, e.g. `["Hero 3", "Headline"]`. */
|
|
7
|
+
trail: string[];
|
|
8
|
+
/** Innermost block or array row the selection sits in. */
|
|
9
|
+
scope?: {
|
|
10
|
+
path: string;
|
|
11
|
+
kind: "block" | "row";
|
|
12
|
+
label: string;
|
|
13
|
+
blockType?: string;
|
|
14
|
+
};
|
|
15
|
+
/** The selected field when the selection is a single value rather than a whole row. */
|
|
16
|
+
field?: {
|
|
17
|
+
path: string;
|
|
18
|
+
label: string;
|
|
19
|
+
type: string;
|
|
20
|
+
value?: unknown;
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Resolves a path the browser sent against the projected form and the allowlisted fields. Anything
|
|
25
|
+
* the assistant may not see (denied, excluded, missing) resolves to nothing, so a crafted path
|
|
26
|
+
* cannot pull hidden values into the prompt.
|
|
27
|
+
*/
|
|
28
|
+
export declare function resolveSelection(raw: unknown, form: Record<string, unknown> | undefined, fields: FieldNode[]): Selection | undefined;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { isLexical, lexicalPlaintext } from "../form/lexical.js";
|
|
2
|
+
import { getAtPath, parseFieldPath } from "../form/path.js";
|
|
3
|
+
const PATH = /^[A-Za-z0-9_]+(\.[A-Za-z0-9_]+)*$/;
|
|
4
|
+
const MAX_VALUE_CHARS = 600;
|
|
5
|
+
const CONTAINER_TYPES = new Set(["group", "array", "blocks"]);
|
|
6
|
+
/**
|
|
7
|
+
* Resolves a path the browser sent against the projected form and the allowlisted fields. Anything
|
|
8
|
+
* the assistant may not see (denied, excluded, missing) resolves to nothing, so a crafted path
|
|
9
|
+
* cannot pull hidden values into the prompt.
|
|
10
|
+
*/
|
|
11
|
+
export function resolveSelection(raw, form, fields) {
|
|
12
|
+
if (typeof raw !== "string" || !PATH.test(raw) || !form) {
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
const segments = parseFieldPath(raw);
|
|
16
|
+
const trail = [];
|
|
17
|
+
let nodes = fields;
|
|
18
|
+
let node;
|
|
19
|
+
let scope;
|
|
20
|
+
let data = form;
|
|
21
|
+
const walked = [];
|
|
22
|
+
for (const segment of segments) {
|
|
23
|
+
walked.push(segment);
|
|
24
|
+
if (typeof segment === "number") {
|
|
25
|
+
if (!node || (node.type !== "array" && node.type !== "blocks")) {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
const item = getAtPath(data, [segment]);
|
|
29
|
+
if (!item || typeof item !== "object") {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
const path = walked.join(".");
|
|
33
|
+
if (node.type === "blocks") {
|
|
34
|
+
const blockType = item.blockType;
|
|
35
|
+
const block = node.blocks?.find((entry) => entry.slug === blockType);
|
|
36
|
+
if (!block) {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
const label = `${block.label ?? block.slug} ${segment + 1}`;
|
|
40
|
+
scope = { path, kind: "block", label, blockType: block.slug };
|
|
41
|
+
nodes = block.fields;
|
|
42
|
+
trail.push(label);
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
const label = `${node.label ?? node.name} ${segment + 1}`;
|
|
46
|
+
scope = { path, kind: "row", label };
|
|
47
|
+
nodes = node.fields ?? [];
|
|
48
|
+
trail.push(label);
|
|
49
|
+
}
|
|
50
|
+
data = item;
|
|
51
|
+
node = undefined;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
node = nodes.find((entry) => entry.name === segment);
|
|
55
|
+
if (!node) {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
// A list's own label repeats in its rows' labels ("Cards 2"), so only rows name it.
|
|
59
|
+
if (node.type !== "array" && node.type !== "blocks") {
|
|
60
|
+
trail.push(node.label ?? node.name);
|
|
61
|
+
}
|
|
62
|
+
data = getAtPath(data, [segment]);
|
|
63
|
+
if (node.type === "group") {
|
|
64
|
+
nodes = node.fields ?? [];
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const field = node && !CONTAINER_TYPES.has(node.type)
|
|
68
|
+
? { path: raw, label: node.label ?? node.name, type: node.type, value: preview(data) }
|
|
69
|
+
: undefined;
|
|
70
|
+
return { path: raw, trail, scope, field };
|
|
71
|
+
}
|
|
72
|
+
function preview(value) {
|
|
73
|
+
if (value === undefined || value === null) {
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
if (isLexical(value)) {
|
|
77
|
+
return preview(lexicalPlaintext(value));
|
|
78
|
+
}
|
|
79
|
+
if (typeof value === "string") {
|
|
80
|
+
return value.length <= MAX_VALUE_CHARS ? value : `${value.slice(0, MAX_VALUE_CHARS)}…`;
|
|
81
|
+
}
|
|
82
|
+
const json = JSON.stringify(value);
|
|
83
|
+
if (json === undefined || json.length <= MAX_VALUE_CHARS) {
|
|
84
|
+
return value;
|
|
85
|
+
}
|
|
86
|
+
return `${json.slice(0, MAX_VALUE_CHARS)}…`;
|
|
87
|
+
}
|
|
@@ -30,10 +30,20 @@ function openDocumentLine(bridge, turnContext) {
|
|
|
30
30
|
const sources = turnContext?.otherLanguages
|
|
31
31
|
? " Other language versions of this same page are in the quoted context. If the editor asks to fill or translate empty fields, call form.propose with text written in the open language. Translate from the source language; do not copy the source wording unless they asked to copy it unchanged. Do not ask them to paste. Do not say the source is missing if it is in the context. If a layout block already exists, set its empty text fields; do not insert a new block."
|
|
32
32
|
: "";
|
|
33
|
-
return `The editor is looking at ${title} in ${language}. Do not ask them which document it is.${sources}`;
|
|
33
|
+
return `The editor is looking at ${title} in ${language}. Do not ask them which document it is.${sources}${selectionLine(turnContext)}`;
|
|
34
34
|
}
|
|
35
35
|
return `${viewLine(turnContext)}No document is open. You can search, navigate, or create a draft. Form edits need an open document.`;
|
|
36
36
|
}
|
|
37
|
+
function selectionLine(turnContext) {
|
|
38
|
+
const selection = turnContext?.selection;
|
|
39
|
+
if (!selection) {
|
|
40
|
+
return "";
|
|
41
|
+
}
|
|
42
|
+
const scope = selection.field
|
|
43
|
+
? "change only that field unless they ask for more"
|
|
44
|
+
: "change only fields inside it unless they ask for more";
|
|
45
|
+
return ` The editor has selected ${selection.trail.join(" › ")} in the form or preview. When they say "this", "here", "den här", "det här" or name no place, they mean that selection: ${scope}. Refer to it by its labels.`;
|
|
46
|
+
}
|
|
37
47
|
function viewLine(turnContext) {
|
|
38
48
|
const view = turnContext?.view;
|
|
39
49
|
if (view?.kind === "dashboard") {
|
|
@@ -65,6 +75,12 @@ function thisTurnBlock(bridge, turnContext) {
|
|
|
65
75
|
lines.push(`The editor chats in ${languageName(chat)}, so say in one short sentence that the content is written in ${content}.`);
|
|
66
76
|
}
|
|
67
77
|
}
|
|
78
|
+
const selection = turnContext?.selection;
|
|
79
|
+
if (selection) {
|
|
80
|
+
lines.push(selection.field
|
|
81
|
+
? `Selected field: ${selection.field.path}.`
|
|
82
|
+
: `Selected: ${selection.path}; its fields are under that path in the form.`);
|
|
83
|
+
}
|
|
68
84
|
const form = turnContext?.form;
|
|
69
85
|
if (form) {
|
|
70
86
|
const empty = listEmptyTextPaths(form);
|
package/dist/runtime/tools.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { PayloadRequest } from "payload";
|
|
2
2
|
import type { ValidatedEditorAssistantOptions } from "../config/validate.ts";
|
|
3
|
+
import { type ProposalPreview } from "../domain/propose.ts";
|
|
3
4
|
import type { AdminView } from "../view.ts";
|
|
4
5
|
import type { ChatLanguage } from "./language.ts";
|
|
5
6
|
export type ToolBridge = {
|
|
@@ -11,8 +12,12 @@ export type ToolBridge = {
|
|
|
11
12
|
locale: string;
|
|
12
13
|
/** Language the editor chats in; may differ from `locale`. */
|
|
13
14
|
language?: ChatLanguage;
|
|
15
|
+
/** Admin's language, for what the panel shows around a proposal (its title and change list). */
|
|
16
|
+
uiLanguage?: ChatLanguage;
|
|
14
17
|
/** Where the editor is in Admin. Context only; tools act on the open document fields above. */
|
|
15
18
|
view?: AdminView;
|
|
19
|
+
/** Form path of what the editor last clicked or focused in the open form, unverified. */
|
|
20
|
+
selection?: string;
|
|
16
21
|
};
|
|
17
22
|
export type ToolContext = {
|
|
18
23
|
req: PayloadRequest;
|
|
@@ -40,6 +45,8 @@ export type ToolSuccess = {
|
|
|
40
45
|
label: string;
|
|
41
46
|
}>;
|
|
42
47
|
};
|
|
48
|
+
/** For Admin only; the agent keeps it out of what the model sees. */
|
|
49
|
+
preview?: ProposalPreview;
|
|
43
50
|
};
|
|
44
51
|
export type ToolFailure = {
|
|
45
52
|
ok: false;
|
package/dist/runtime/tools.js
CHANGED
|
@@ -155,7 +155,7 @@ export async function executeTool(name, input, ctx) {
|
|
|
155
155
|
global: name === "form.propose" ? ctx.bridge.global : undefined,
|
|
156
156
|
documentId: name === "form.propose" ? ctx.bridge.documentId : undefined,
|
|
157
157
|
locale: ctx.bridge.locale,
|
|
158
|
-
language: ctx.bridge.language,
|
|
158
|
+
language: ctx.bridge.uiLanguage ?? ctx.bridge.language,
|
|
159
159
|
operations: input.operations ?? input.patches ?? input.changes ?? input.ops,
|
|
160
160
|
formData: name === "form.propose" ? ctx.formData : undefined,
|
|
161
161
|
});
|
|
@@ -172,6 +172,7 @@ export async function executeTool(name, input, ctx) {
|
|
|
172
172
|
nonce: proposed.nonce,
|
|
173
173
|
capability: name,
|
|
174
174
|
},
|
|
175
|
+
preview: proposed.preview,
|
|
175
176
|
};
|
|
176
177
|
}
|
|
177
178
|
default:
|
package/dist/runtime/voice.js
CHANGED
|
@@ -30,11 +30,11 @@ export function approvalCopy(locale, capability) {
|
|
|
30
30
|
if (isSwedish(locale)) {
|
|
31
31
|
return capability === "draft.create"
|
|
32
32
|
? "Utkastet ligger i panelen. Godkänn det så skapas det."
|
|
33
|
-
: "
|
|
33
|
+
: "Jag har lagt in förslaget i formuläret. Behåll det eller ångra.";
|
|
34
34
|
}
|
|
35
35
|
return capability === "draft.create"
|
|
36
36
|
? "The draft is ready in the panel. Approve it to create it."
|
|
37
|
-
: "
|
|
37
|
+
: "I've put the change in the form. Keep it or undo it.";
|
|
38
38
|
}
|
|
39
39
|
export function couldNotStageCopy(locale) {
|
|
40
40
|
return isSwedish(locale)
|
package/dist/schema/fields.d.ts
CHANGED
package/dist/schema/fields.js
CHANGED
|
@@ -68,6 +68,7 @@ function walkField(field, parentPath, allowlist, denyFields) {
|
|
|
68
68
|
.filter((block) => Boolean(block.slug))
|
|
69
69
|
.map((block) => ({
|
|
70
70
|
slug: block.slug,
|
|
71
|
+
label: asLabel(block.labels?.singular),
|
|
71
72
|
fields: walkFields(block.fields, path, allowlist, denyFields),
|
|
72
73
|
}));
|
|
73
74
|
}
|