@agent-native/core 0.160.0 → 0.161.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/corpus/README.md +1 -1
- package/corpus/templates/analytics/actions/bigquery.ts +57 -0
- package/corpus/templates/analytics/actions/restore-analysis-revision.ts +5 -2
- package/corpus/templates/analytics/actions/restore-dashboard-revision.ts +5 -2
- package/corpus/templates/analytics/app/components/analysis/AnalysisHistoryPanel.tsx +14 -3
- package/corpus/templates/analytics/app/components/dashboard/DashboardHistoryPanel.tsx +14 -3
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +3 -0
- package/corpus/templates/analytics/app/hooks/use-analysis-revisions.ts +1 -0
- package/corpus/templates/analytics/app/hooks/use-dashboard-revisions.ts +16 -1
- package/corpus/templates/analytics/app/lib/use-auto-focus-select.ts +19 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/PanelEditorDialog.tsx +0 -1
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/dashboard-sync.ts +14 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +19 -0
- package/corpus/templates/analytics/changelog/2026-08-17-fixed-dashboard-editing-focus-history-recovery-panel-setup-f.md +6 -0
- package/corpus/templates/calendar/actions/create-event.ts +7 -3
- package/corpus/templates/calendar/actions/manage-event-draft.ts +8 -0
- package/corpus/templates/calendar/app/components/calendar/CreateEventDialog.tsx +64 -0
- package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +5 -1
- package/corpus/templates/calendar/app/hooks/use-events.ts +1 -0
- package/corpus/templates/calendar/changelog/2026-08-17-booking-links-now-respect-each-required-host-s-saved-availab.md +6 -0
- package/corpus/templates/calendar/server/lib/booking-host-availability.ts +76 -0
- package/corpus/templates/calendar/server/lib/google-calendar.ts +2 -0
- package/corpus/templates/calendar/shared/api.ts +1 -1
- package/corpus/templates/clips/app/components/player/signed-out-share-actions.tsx +47 -0
- package/corpus/templates/clips/app/components/player/transcript-panel.tsx +3 -1
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +27 -19
- package/corpus/templates/clips/changelog/2026-08-17-shared-clips-sign-in-comment-no-audio.md +6 -0
- package/corpus/templates/clips/server/routes/api/public-recording.get.ts +6 -5
- package/corpus/templates/content/.agents/skills/document-editing/references/databases.md +13 -0
- package/corpus/templates/content/AGENTS.md +2 -0
- package/corpus/templates/content/actions/_blocks-field-identity.ts +17 -0
- package/corpus/templates/content/actions/_database-block-actions.ts +958 -0
- package/corpus/templates/content/actions/_database-row-mutation.ts +7 -7
- package/corpus/templates/content/actions/list-content-database-blocks.ts +31 -0
- package/corpus/templates/content/actions/mutate-content-database-block.ts +49 -0
- package/corpus/templates/content/changelog/2026-08-10-agents-can-safely-insert-update-upsert-delete-and-reorder-on.md +6 -0
- package/corpus/templates/content/docs/product/capabilities/content.object.block.md +6 -2
- package/corpus/templates/content/docs/product/capabilities/content.object.blocks-field.md +2 -1
- package/corpus/templates/content/parity/matrix.md +30 -30
- package/corpus/templates/content/parity/matrix.ts +4 -1
- package/corpus/templates/content/shared/blocks-field-identity.ts +114 -37
- package/corpus/templates/content/shared/database-block-actions.ts +82 -0
- package/corpus/templates/content/shared/database-block-mutations.ts +366 -0
- package/corpus/templates/content/shared/nfm.ts +3 -2
- package/corpus/templates/mail/.agents/skills/email-drafts/SKILL.md +24 -0
- package/corpus/templates/mail/actions/get-mail-settings.ts +1 -1
- package/corpus/templates/mail/actions/update-mail-settings.ts +7 -3
- package/corpus/templates/mail/app/i18n/en-US.ts +3 -1
- package/corpus/templates/mail/app/pages/SettingsPage.tsx +63 -0
- package/corpus/templates/mail/changelog/2026-08-17-gmail-signatures-now-preserve-images-and-support-paste-or-up.md +6 -0
- package/corpus/templates/mail/changelog/2026-08-17-mail-now-updates-durable-drafting-preferences-without-openin.md +6 -0
- package/corpus/templates/mail/server/plugins/agent-chat.ts +19 -0
- package/corpus/templates/mail/shared/gmail-signature.ts +14 -2
- package/corpus/templates/mail/shared/signature.ts +1 -10
- package/corpus/templates/slides/actions/generate-slides-ai.ts +2 -0
- package/corpus/templates/slides/actions/patch-deck.ts +1 -1
- package/corpus/templates/slides/app/components/ThemeToggle.tsx +1 -0
- package/corpus/templates/slides/app/components/editor/SlideThumbnailContextMenu.tsx +59 -0
- package/corpus/templates/slides/app/global.css +1 -0
- package/corpus/templates/slides/app/lib/normalize-slide-padding.ts +40 -11
- package/corpus/templates/slides/changelog/2026-08-17-stable-layout-and-slide-editing.md +6 -0
- package/corpus/templates/slides/shared/deck-title.ts +12 -4
- package/dist/agent/production-agent.js +27 -15
- package/dist/agent/tool-approval-migrations.d.ts +2 -0
- package/dist/agent/tool-approval-migrations.js +20 -0
- package/dist/agent/tool-approval-store.d.ts +17 -3
- package/dist/agent/tool-approval-store.js +56 -6
- package/dist/agent/types.d.ts +3 -1
- package/dist/cli/code-agent-connector.js +79 -7
- package/dist/cli/portal-transfer.d.ts +43 -0
- package/dist/cli/portal-transfer.js +201 -0
- package/dist/client/AssistantChat.js +3 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/localization/core-messages/ar-SA.js +1 -0
- package/dist/localization/core-messages/de-DE.js +1 -0
- package/dist/localization/core-messages/en-US.d.ts +1 -0
- package/dist/localization/core-messages/en-US.js +1 -0
- package/dist/localization/core-messages/es-ES.js +1 -0
- package/dist/localization/core-messages/fr-FR.js +1 -0
- package/dist/localization/core-messages/hi-IN.js +1 -0
- package/dist/localization/core-messages/ja-JP.js +1 -0
- package/dist/localization/core-messages/ko-KR.js +1 -0
- package/dist/localization/core-messages/pt-BR.js +1 -0
- package/dist/localization/core-messages/zh-CN.js +1 -0
- package/dist/localization/core-messages/zh-TW.js +1 -0
- package/dist/localization/core-messages.d.ts +1 -0
- package/dist/mcp/screen-memory-stdio.d.ts +7 -7
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/scripts/resources/read.js +4 -4
- package/dist/scripts/resources/save-memory.js +15 -2
- package/dist/scripts/resources/write.js +3 -3
- package/dist/server/agent-chat/script-entries.d.ts +1 -0
- package/dist/server/agent-chat/script-entries.js +8 -0
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/docs/content/onboarding.mdx +13 -2
- package/package.json +1 -1
|
@@ -69,6 +69,30 @@ Before creating or rewriting a draft, read the user's drafting settings with `pn
|
|
|
69
69
|
- Follow `writingStyle` when present.
|
|
70
70
|
- Keep generated copy natural and specific. Avoid generic AI email tropes, headings, and over-formal filler unless the user asks for that style.
|
|
71
71
|
|
|
72
|
+
## Updating Durable Drafting Settings
|
|
73
|
+
|
|
74
|
+
Treat requests to permanently change, add to, or remove a writing-style rule as
|
|
75
|
+
settings changes, not drafting requests. Route them to `update-mail-settings`.
|
|
76
|
+
Do not call `manage-draft`, open the compose UI, or create a draft unless the user separately asks
|
|
77
|
+
to create or edit an email draft.
|
|
78
|
+
|
|
79
|
+
For a durable settings request:
|
|
80
|
+
|
|
81
|
+
1. Read the current settings with `get-mail-settings`.
|
|
82
|
+
2. Merge the requested rule into the existing `writingStyle`, preserving
|
|
83
|
+
unrelated instructions. For a removal, remove only the requested rule.
|
|
84
|
+
3. If the requested change is ambiguous, conflicts with existing guidance, or
|
|
85
|
+
would materially rewrite the style, show the proposed merged wording and
|
|
86
|
+
ask the user to confirm before changing it.
|
|
87
|
+
4. Call `update-mail-settings` with the complete merged `writingStyle` value.
|
|
88
|
+
5. Re-read the settings with `get-mail-settings` and confirm the persisted result. Do not
|
|
89
|
+
report success based only on the update call response.
|
|
90
|
+
|
|
91
|
+
If the user asks both to change the durable style and to draft an email, update
|
|
92
|
+
the settings first, then create the separately requested draft using the
|
|
93
|
+
confirmed settings. A style-setting request alone must leave compose state
|
|
94
|
+
unchanged.
|
|
95
|
+
|
|
72
96
|
## How It Works
|
|
73
97
|
|
|
74
98
|
1. **Write** `writeAppState("compose-{id}", draft)` — the shared application state row changes
|
|
@@ -17,7 +17,7 @@ function normalize(settings: Partial<UserSettings> | undefined, email: string) {
|
|
|
17
17
|
|
|
18
18
|
export default defineAction({
|
|
19
19
|
description:
|
|
20
|
-
"Read the user's mail drafting settings, including configured signature and writing style.",
|
|
20
|
+
"Read the user's current mail drafting settings, including configured signature and writing style. Use this before updating a durable preference so unrelated settings can be preserved.",
|
|
21
21
|
schema: z.object({}),
|
|
22
22
|
http: { method: "GET" },
|
|
23
23
|
readOnly: true,
|
|
@@ -11,16 +11,20 @@ const settingsSchema = z.object({
|
|
|
11
11
|
signature: z
|
|
12
12
|
.string()
|
|
13
13
|
.optional()
|
|
14
|
-
.describe(
|
|
14
|
+
.describe(
|
|
15
|
+
"Persistent Markdown/plain-text signature for new drafts. Preserve the existing signature unless the user asks to replace it.",
|
|
16
|
+
),
|
|
15
17
|
writingStyle: z
|
|
16
18
|
.string()
|
|
17
19
|
.optional()
|
|
18
|
-
.describe(
|
|
20
|
+
.describe(
|
|
21
|
+
"Persistent writing rules for generated drafts. Read the current setting first, merge the requested change, and preserve unrelated rules.",
|
|
22
|
+
),
|
|
19
23
|
});
|
|
20
24
|
|
|
21
25
|
export default defineAction({
|
|
22
26
|
description:
|
|
23
|
-
"Update the user's mail drafting settings, including signature and writing style.",
|
|
27
|
+
"Update the user's persistent mail drafting settings, including signature and writing style. Use this for durable preferences, not email draft content. Read the current settings first and preserve fields the user did not ask to change.",
|
|
24
28
|
schema: settingsSchema,
|
|
25
29
|
run: async (args) => {
|
|
26
30
|
const ownerEmail = getRequestUserEmail();
|
|
@@ -501,7 +501,9 @@ const messages = {
|
|
|
501
501
|
signature: "Signature",
|
|
502
502
|
importFromGmail: "Import from Gmail",
|
|
503
503
|
signatureHelp:
|
|
504
|
-
"Added to new drafts before quoted reply history. Markdown links and images are supported.",
|
|
504
|
+
"Added to new drafts before quoted reply history. Paste an image here or use Add image; Markdown links and images are supported.",
|
|
505
|
+
addSignatureImage: "Add image",
|
|
506
|
+
signatureImageUploadFailed: "Failed to upload signature image",
|
|
505
507
|
writingStyle: "Writing style",
|
|
506
508
|
writingStylePlaceholder: "Short, specific, warm. Avoid formal filler.",
|
|
507
509
|
saveDraftingSettings: "Save drafting settings",
|
|
@@ -37,6 +37,7 @@ import {
|
|
|
37
37
|
IconClock,
|
|
38
38
|
IconPlayerPlay,
|
|
39
39
|
IconSignature,
|
|
40
|
+
IconPhoto,
|
|
40
41
|
IconFilter,
|
|
41
42
|
IconInfoCircle,
|
|
42
43
|
IconMessage2,
|
|
@@ -89,6 +90,7 @@ import {
|
|
|
89
90
|
} from "@/hooks/use-automations";
|
|
90
91
|
import { useSettings, useUpdateSettings } from "@/hooks/use-emails";
|
|
91
92
|
import { useNavigationState } from "@/hooks/use-navigation-state";
|
|
93
|
+
import { openFilePicker, uploadFile } from "@/lib/upload";
|
|
92
94
|
import { cn } from "@/lib/utils";
|
|
93
95
|
|
|
94
96
|
import changelog from "../../CHANGELOG.md?raw";
|
|
@@ -1137,6 +1139,7 @@ function DraftingSection() {
|
|
|
1137
1139
|
const queryClient = useQueryClient();
|
|
1138
1140
|
const [signature, setSignature] = useState("");
|
|
1139
1141
|
const [writingStyle, setWritingStyle] = useState("");
|
|
1142
|
+
const signatureSelectionRef = useRef({ start: 0, end: 0 });
|
|
1140
1143
|
const importSignature = useActionMutation("import-gmail-signature", {
|
|
1141
1144
|
onSuccess: (result) => {
|
|
1142
1145
|
setSignature(result.signature);
|
|
@@ -1168,6 +1171,49 @@ function DraftingSection() {
|
|
|
1168
1171
|
const isDirty =
|
|
1169
1172
|
signature !== savedSignature || writingStyle !== savedWritingStyle;
|
|
1170
1173
|
|
|
1174
|
+
const insertSignatureImage = async (
|
|
1175
|
+
file: File,
|
|
1176
|
+
start: number,
|
|
1177
|
+
end: number,
|
|
1178
|
+
) => {
|
|
1179
|
+
try {
|
|
1180
|
+
const result = await uploadFile(file);
|
|
1181
|
+
const markdown = `![${file.name.replace(/\.[^.]+$/, "")}](${result.url})`;
|
|
1182
|
+
setSignature(
|
|
1183
|
+
(current) =>
|
|
1184
|
+
`${current.slice(0, start)}${markdown}${current.slice(end)}`,
|
|
1185
|
+
);
|
|
1186
|
+
} catch {
|
|
1187
|
+
toast.error(t("settings.signatureImageUploadFailed"));
|
|
1188
|
+
}
|
|
1189
|
+
};
|
|
1190
|
+
|
|
1191
|
+
const handleSignaturePaste = (
|
|
1192
|
+
event: React.ClipboardEvent<HTMLTextAreaElement>,
|
|
1193
|
+
) => {
|
|
1194
|
+
const image = Array.from(event.clipboardData.items)
|
|
1195
|
+
.find((item) => item.kind === "file" && item.type.startsWith("image/"))
|
|
1196
|
+
?.getAsFile();
|
|
1197
|
+
if (!image) return;
|
|
1198
|
+
event.preventDefault();
|
|
1199
|
+
const target = event.currentTarget;
|
|
1200
|
+
void insertSignatureImage(
|
|
1201
|
+
image,
|
|
1202
|
+
target.selectionStart,
|
|
1203
|
+
target.selectionEnd,
|
|
1204
|
+
);
|
|
1205
|
+
};
|
|
1206
|
+
|
|
1207
|
+
const handleSignatureImage = async () => {
|
|
1208
|
+
const file = await openFilePicker("image/*");
|
|
1209
|
+
if (!file) return;
|
|
1210
|
+
await insertSignatureImage(
|
|
1211
|
+
file,
|
|
1212
|
+
signatureSelectionRef.current.start,
|
|
1213
|
+
signatureSelectionRef.current.end,
|
|
1214
|
+
);
|
|
1215
|
+
};
|
|
1216
|
+
|
|
1171
1217
|
const handleSave = () => {
|
|
1172
1218
|
updateSettings.mutate(
|
|
1173
1219
|
{
|
|
@@ -1223,10 +1269,27 @@ function DraftingSection() {
|
|
|
1223
1269
|
)}
|
|
1224
1270
|
{t("settings.importFromGmail")}
|
|
1225
1271
|
</Button>
|
|
1272
|
+
<Button
|
|
1273
|
+
type="button"
|
|
1274
|
+
variant="ghost"
|
|
1275
|
+
size="sm"
|
|
1276
|
+
className="h-6 px-2 text-[11px]"
|
|
1277
|
+
onClick={() => void handleSignatureImage()}
|
|
1278
|
+
>
|
|
1279
|
+
<IconPhoto className="h-3 w-3" />
|
|
1280
|
+
{t("settings.addSignatureImage")}
|
|
1281
|
+
</Button>
|
|
1226
1282
|
</div>
|
|
1227
1283
|
<Textarea
|
|
1228
1284
|
value={signature}
|
|
1229
1285
|
onChange={(event) => setSignature(event.target.value)}
|
|
1286
|
+
onPaste={handleSignaturePaste}
|
|
1287
|
+
onSelect={(event) => {
|
|
1288
|
+
signatureSelectionRef.current = {
|
|
1289
|
+
start: event.currentTarget.selectionStart,
|
|
1290
|
+
end: event.currentTarget.selectionEnd,
|
|
1291
|
+
};
|
|
1292
|
+
}}
|
|
1230
1293
|
placeholder={"Best,\nSteve"}
|
|
1231
1294
|
rows={5}
|
|
1232
1295
|
className="resize-none px-3 py-2 text-[13px] placeholder:text-muted-foreground/40"
|
|
@@ -23,6 +23,7 @@ const INITIAL_TOOL_NAMES = [
|
|
|
23
23
|
"refresh-list",
|
|
24
24
|
"navigate",
|
|
25
25
|
"get-mail-settings",
|
|
26
|
+
"update-mail-settings",
|
|
26
27
|
"find-contact",
|
|
27
28
|
"provider-api-catalog",
|
|
28
29
|
"provider-api-docs",
|
|
@@ -172,6 +173,24 @@ Before drafting or rewriting email copy, run \`get-mail-settings\`.
|
|
|
172
173
|
- Follow \`writingStyle\` when present.
|
|
173
174
|
- Draft bodies use Markdown only. Avoid generic AI email tropes, headings, and over-formal filler unless the user explicitly asks for a formal template.
|
|
174
175
|
|
|
176
|
+
## Durable Drafting Preferences — CRITICAL
|
|
177
|
+
|
|
178
|
+
Writing style, signature, and other drafting preferences are persistent mail
|
|
179
|
+
settings, not email drafts. If the user asks to add, change, strengthen,
|
|
180
|
+
remove, or remember a writing rule or preference (for example, "never use em
|
|
181
|
+
dashes"), do this instead of composing an email:
|
|
182
|
+
|
|
183
|
+
1. Run \`get-mail-settings\`.
|
|
184
|
+
2. Merge the requested change into the existing \`writingStyle\` or
|
|
185
|
+
\`signature\`, preserving unrelated instructions.
|
|
186
|
+
3. Run \`update-mail-settings\` with the complete merged value.
|
|
187
|
+
4. Confirm the returned setting was updated.
|
|
188
|
+
|
|
189
|
+
Do not call \`manage-draft\`, \`queue-email-draft\`, or \`send-email\` for a
|
|
190
|
+
preference-only request. Only create or edit an email when the user separately
|
|
191
|
+
asks for an email. If a request asks for both a preference change and an email,
|
|
192
|
+
update the preference first, then read it again and apply it to the email.
|
|
193
|
+
|
|
175
194
|
When the user asks to draft/email a specific person (e.g., "email my wife", "draft an email to Alice"):
|
|
176
195
|
- This is a NEW email \u2014 use manage-draft with --action=create and mode "compose", NOT "reply"
|
|
177
196
|
- Look up the recipient's email from AGENTS.md contacts or ask the user
|
|
@@ -45,7 +45,16 @@ export function htmlSignatureToMarkdown(html: string): string {
|
|
|
45
45
|
.replace(/<style[\s\S]*?<\/style>/gi, "");
|
|
46
46
|
|
|
47
47
|
next = next.replace(/<br\s*\/?>/gi, "\n");
|
|
48
|
-
|
|
48
|
+
const imageTokens: string[] = [];
|
|
49
|
+
next = next.replace(/<img\b[^>]*>/gi, (tag) => {
|
|
50
|
+
const src = safeUrl(attr(tag, "src"));
|
|
51
|
+
if (!src) return "";
|
|
52
|
+
const alt = cleanMarkdown(attr(tag, "alt") || attr(tag, "title") || "");
|
|
53
|
+
const image = `![${alt.replace(/]/g, "\\]")}](${normalizeMarkdownUrl(src)})`;
|
|
54
|
+
const token = `\uE100${imageTokens.length}\uE101`;
|
|
55
|
+
imageTokens.push(image);
|
|
56
|
+
return token;
|
|
57
|
+
});
|
|
49
58
|
next = next.replace(
|
|
50
59
|
/<a\b[^>]*href\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]+)[^>]*>([\s\S]*?)<\/a>/gi,
|
|
51
60
|
(match, label) => {
|
|
@@ -69,5 +78,8 @@ export function htmlSignatureToMarkdown(html: string): string {
|
|
|
69
78
|
)
|
|
70
79
|
.replace(/<[^>]+>/g, "");
|
|
71
80
|
|
|
72
|
-
return cleanMarkdown(next)
|
|
81
|
+
return cleanMarkdown(next).replace(
|
|
82
|
+
/\uE100(\d+)\uE101/g,
|
|
83
|
+
(match, index: string) => imageTokens[Number(index)] ?? match,
|
|
84
|
+
);
|
|
73
85
|
}
|
|
@@ -14,16 +14,7 @@ function splitQuotedContent(body: string): [string, string] {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
export function normalizeSignature(signature?: string | null): string {
|
|
17
|
-
return
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function stripSignatureImages(signature: string): string {
|
|
21
|
-
return signature
|
|
22
|
-
.replace(
|
|
23
|
-
/\[!\[[\s\S]*?\]\((?:https?:\/\/|data:image\/)[^)]*\)\]\([^)]*\)/gi,
|
|
24
|
-
"",
|
|
25
|
-
)
|
|
26
|
-
.replace(/!\[[\s\S]*?\]\((?:https?:\/\/|data:image\/)[^)]*\)/gi, "")
|
|
17
|
+
return (signature ?? "")
|
|
27
18
|
.split("\n")
|
|
28
19
|
.map((line) => line.replace(/[ \t]+$/g, ""))
|
|
29
20
|
.join("\n")
|
|
@@ -62,6 +62,8 @@ Rules:
|
|
|
62
62
|
- Last slide should be a summary or call-to-action
|
|
63
63
|
- Content should be concise and presentation-ready (not paragraphs)
|
|
64
64
|
- Use bullet points for lists, keep each point brief
|
|
65
|
+
- Keep title, section, statement, and call-to-action slides centered with generous, even margins
|
|
66
|
+
- Do not use emoji as decorative icons or bullets; use plain text bullets or HTML/CSS shapes instead
|
|
65
67
|
- Do not invent factual numbers, metrics, URLs, source attributions, dates, success rates, benchmarks, customer names, or case-study results. Only include concrete factual claims if they are present in the topic/context. If a useful metric is unknown, use qualitative wording, [metric TBD], or clearly label it as a draft assumption.
|
|
66
68
|
- ${imageInstruction}
|
|
67
69
|
|
|
@@ -357,7 +357,7 @@ export function applyOperation(deck: any, op: Operation): void {
|
|
|
357
357
|
if (slides.length === 0 && !op.allowEmpty) {
|
|
358
358
|
slides.push({
|
|
359
359
|
id: `slide-${Date.now()}-fallback`,
|
|
360
|
-
content: `<div class="fmd-slide" style="padding: 80px 110px; display: flex; flex-direction: column; justify-content: center;"><div style="font-size: 28px; font-weight: 600; color:
|
|
360
|
+
content: `<div class="fmd-slide" style="box-sizing: border-box; width: 100%; height: 100%; padding: 80px 110px; display: flex; flex-direction: column; justify-content: center; align-items: center; text-align: center;"><div style="font-size: 28px; font-weight: 600; color: hsl(var(--muted-foreground) / 0.4);">Double-click to edit</div></div>`,
|
|
361
361
|
notes: "",
|
|
362
362
|
layout: "blank",
|
|
363
363
|
});
|
|
@@ -26,6 +26,7 @@ export function ThemeToggle({ className }: { className?: string }) {
|
|
|
26
26
|
<Button
|
|
27
27
|
variant="ghost"
|
|
28
28
|
size="icon"
|
|
29
|
+
aria-label={t("root.toggleTheme")}
|
|
29
30
|
onClick={() => setTheme(isDark ? "light" : "dark")}
|
|
30
31
|
className={cn("h-7 w-7 text-muted-foreground", className)}
|
|
31
32
|
>
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { useT } from "@agent-native/core/client/i18n";
|
|
2
|
+
import type { ReactElement, ReactNode } from "react";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
ContextMenu,
|
|
6
|
+
ContextMenuContent,
|
|
7
|
+
ContextMenuItem,
|
|
8
|
+
ContextMenuSeparator,
|
|
9
|
+
ContextMenuTrigger,
|
|
10
|
+
} from "@/components/ui/context-menu";
|
|
11
|
+
|
|
12
|
+
interface SlideThumbnailContextMenuProps {
|
|
13
|
+
children: ReactElement;
|
|
14
|
+
canDelete?: boolean;
|
|
15
|
+
onSelect: () => void;
|
|
16
|
+
onDuplicate: () => void;
|
|
17
|
+
onDelete: () => void;
|
|
18
|
+
/** Optional extra items for the owning editor to add later. */
|
|
19
|
+
childrenAfterActions?: ReactNode;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Keep slide actions discoverable without removing the compact hover buttons.
|
|
24
|
+
* The trigger stays the thumbnail itself, so a right-click works anywhere on
|
|
25
|
+
* the preview and the menu remains usable with a keyboard once the thumbnail
|
|
26
|
+
* has focus.
|
|
27
|
+
*/
|
|
28
|
+
export function SlideThumbnailContextMenu({
|
|
29
|
+
children,
|
|
30
|
+
canDelete = true,
|
|
31
|
+
onSelect,
|
|
32
|
+
onDuplicate,
|
|
33
|
+
onDelete,
|
|
34
|
+
childrenAfterActions,
|
|
35
|
+
}: SlideThumbnailContextMenuProps) {
|
|
36
|
+
const t = useT();
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<ContextMenu>
|
|
40
|
+
<ContextMenuTrigger asChild onContextMenu={onSelect}>
|
|
41
|
+
{children}
|
|
42
|
+
</ContextMenuTrigger>
|
|
43
|
+
<ContextMenuContent>
|
|
44
|
+
<ContextMenuItem onSelect={onDuplicate}>
|
|
45
|
+
{t("editorSidebar.duplicateSlide")}
|
|
46
|
+
</ContextMenuItem>
|
|
47
|
+
<ContextMenuSeparator />
|
|
48
|
+
{childrenAfterActions}
|
|
49
|
+
<ContextMenuItem
|
|
50
|
+
disabled={!canDelete}
|
|
51
|
+
onSelect={onDelete}
|
|
52
|
+
className="text-destructive focus:text-destructive"
|
|
53
|
+
>
|
|
54
|
+
{t("editorSidebar.deleteSlide")}
|
|
55
|
+
</ContextMenuItem>
|
|
56
|
+
</ContextMenuContent>
|
|
57
|
+
</ContextMenu>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
@@ -6,16 +6,45 @@
|
|
|
6
6
|
* value here makes a successful-looking agent edit a no-op in the renderer.
|
|
7
7
|
*/
|
|
8
8
|
export function normalizeSlidePadding(html: string): string {
|
|
9
|
-
|
|
9
|
+
for (const match of html.matchAll(/<div\b[^>]*>/gi)) {
|
|
10
|
+
const openingTag = match[0];
|
|
11
|
+
const classMatch = /\bclass\s*=\s*(["'])(.*?)\1/i.exec(openingTag);
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
13
|
+
if (!classMatch || !/\bfmd-slide\b/i.test(classMatch[2])) continue;
|
|
14
|
+
|
|
15
|
+
const styleMatch = /\bstyle\s*=\s*(["'])(.*?)\1/i.exec(openingTag);
|
|
16
|
+
if (styleMatch) {
|
|
17
|
+
const style = styleMatch[2];
|
|
18
|
+
if (/(?:^|;)\s*padding\s*:/i.test(style)) return html;
|
|
19
|
+
|
|
20
|
+
const nextStyle = `padding: 80px 110px;${
|
|
21
|
+
style.startsWith(" ") ? "" : " "
|
|
22
|
+
}${style}`;
|
|
23
|
+
const nextStyleAttribute = styleMatch[0].replace(style, nextStyle);
|
|
24
|
+
const nextOpeningTag = openingTag.replace(
|
|
25
|
+
styleMatch[0],
|
|
26
|
+
nextStyleAttribute,
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
html.slice(0, match.index) +
|
|
31
|
+
nextOpeningTag +
|
|
32
|
+
html.slice(match.index + openingTag.length)
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const classEnd = classMatch.index + classMatch[0].length;
|
|
37
|
+
const nextOpeningTag =
|
|
38
|
+
openingTag.slice(0, classEnd) +
|
|
39
|
+
' style="padding: 80px 110px;"' +
|
|
40
|
+
openingTag.slice(classEnd);
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
html.slice(0, match.index) +
|
|
44
|
+
nextOpeningTag +
|
|
45
|
+
html.slice(match.index + openingTag.length)
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return html;
|
|
21
50
|
}
|
|
@@ -107,14 +107,22 @@ export function deriveDeckTitleFromSlideContent(
|
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
-
const
|
|
111
|
-
/<([a-z][\w:-]*)\b[^>]*\bstyle\s*=\s*(["'])([\s\S]*?)\2[^>]
|
|
112
|
-
for (const match of content.matchAll(
|
|
110
|
+
const styledOpeningPattern =
|
|
111
|
+
/<([a-z][\w:-]*)\b[^>]*\bstyle\s*=\s*(["'])([\s\S]*?)\2[^>]*>/gi;
|
|
112
|
+
for (const match of content.matchAll(styledOpeningPattern)) {
|
|
113
113
|
const fontSize = Number.parseFloat(
|
|
114
114
|
match[3]?.match(/(?:^|;)\s*font-size\s*:\s*([\d.]+)px/i)?.[1] ?? "0",
|
|
115
115
|
);
|
|
116
116
|
if (fontSize < 28) continue;
|
|
117
|
-
|
|
117
|
+
|
|
118
|
+
const bodyStart = (match.index ?? 0) + match[0].length;
|
|
119
|
+
const closingTag = new RegExp(`</${match[1]}\\s*>`, "i").exec(
|
|
120
|
+
content.slice(bodyStart),
|
|
121
|
+
);
|
|
122
|
+
const body = closingTag
|
|
123
|
+
? content.slice(bodyStart, bodyStart + closingTag.index)
|
|
124
|
+
: "";
|
|
125
|
+
const text = usableCandidate(body);
|
|
118
126
|
if (text) candidates.push({ score: fontSize, text });
|
|
119
127
|
}
|
|
120
128
|
|
|
@@ -42,7 +42,7 @@ import { ProcessorChain, TripWire, toolCallsFromContent, } from "./processors.js
|
|
|
42
42
|
import { startRun, subscribeToRun, getActiveRunForThread, getActiveRunForThreadAsync, getRun, abortRun, abortRunDurably, abortTurnDurably, tryClaimRunSlot, isHostedRuntime, resolveRunSoftTimeoutMs, resolveRunToolTimeoutCeilingMs, endsAfterCompletedToolWithoutAssistantFinal, } from "./run-manager.js";
|
|
43
43
|
import { writeLedgerEntry, readLedgerEntry, clearLedgerForThread, insertRun, insertRunEvent, isTurnAborted, markRunAborted, updateRunHeartbeat, updateRunStatusIfRunning, setRunError, setRunTerminalReason, claimBackgroundRun, readBackgroundRunClaim, recordRunDiagnostic, countRunsForTurn, RUN_DIAG_STAGE, UNCLAIMED_BACKGROUND_RUN_GRACE_MS, } from "./run-store.js";
|
|
44
44
|
import { buildCurrentTimeUserContext } from "./runtime-context.js";
|
|
45
|
-
import { consumeAgentToolApproval, createAgentToolApproval, } from "./tool-approval-store.js";
|
|
45
|
+
import { consumeAgentToolApproval, createAgentToolApproval, resolveAgentToolApprovalTurnId, } from "./tool-approval-store.js";
|
|
46
46
|
import { findCompletedJournalEntry, } from "./tool-call-journal.js";
|
|
47
47
|
import { redactSensitiveFields, sanitizeToolErrorText, sanitizeToolErrorValue, } from "./tool-error-redaction.js";
|
|
48
48
|
import { describeToolResultImages, extractAgentImagesFromActionResult, } from "./tool-result-images.js";
|
|
@@ -6923,6 +6923,11 @@ export function createProductionAgentHandler(options) {
|
|
|
6923
6923
|
.filter((key) => typeof key === "string")
|
|
6924
6924
|
.slice(0, 200)
|
|
6925
6925
|
: undefined;
|
|
6926
|
+
// The durable approval row is the authorization boundary. Do not require
|
|
6927
|
+
// the client to reproduce the original structured history exactly: the UI
|
|
6928
|
+
// may truncate tool arguments and intentionally assigns fresh replay ids.
|
|
6929
|
+
// The loop still consumes only a matching server-created grant for the
|
|
6930
|
+
// current owner/org/thread/turn/tool/input tuple.
|
|
6926
6931
|
const exactApprovedToolCall = findApprovedStructuredToolCall(structuredHistory, requestedApprovedToolCalls);
|
|
6927
6932
|
const firstRequestPayloadDetail = buildFirstRequestPayloadDetail({
|
|
6928
6933
|
isFirstRequest: history.length === 0,
|
|
@@ -6971,12 +6976,25 @@ export function createProductionAgentHandler(options) {
|
|
|
6971
6976
|
const isChainedBackgroundContinuation = isBackgroundWorker && backgroundContinuationCount > 0;
|
|
6972
6977
|
const runId = backgroundRunMarker?.runId ?? generateRunId();
|
|
6973
6978
|
const effectiveThreadId = threadId ?? runId;
|
|
6979
|
+
const resolvedApprovalTurnId = !isBackgroundWorker &&
|
|
6980
|
+
ownerEmail &&
|
|
6981
|
+
threadId &&
|
|
6982
|
+
requestedApprovedToolCalls?.length
|
|
6983
|
+
? await resolveAgentToolApprovalTurnId({
|
|
6984
|
+
ownerEmail,
|
|
6985
|
+
orgId: getRequestOrgId() ?? null,
|
|
6986
|
+
threadId,
|
|
6987
|
+
requestedTurnId: requestTurnId,
|
|
6988
|
+
approvalKeys: requestedApprovedToolCalls,
|
|
6989
|
+
})
|
|
6990
|
+
: null;
|
|
6974
6991
|
const effectiveTurnId = typeof backgroundRunMarker?.turnId === "string" &&
|
|
6975
6992
|
backgroundRunMarker.turnId.trim()
|
|
6976
6993
|
? backgroundRunMarker.turnId.trim()
|
|
6977
|
-
:
|
|
6978
|
-
|
|
6979
|
-
|
|
6994
|
+
: (resolvedApprovalTurnId ??
|
|
6995
|
+
(typeof requestTurnId === "string" && requestTurnId.trim()
|
|
6996
|
+
? requestTurnId.trim()
|
|
6997
|
+
: runId));
|
|
6980
6998
|
const approvalStoreBinding = (binding) => {
|
|
6981
6999
|
if (!ownerEmail) {
|
|
6982
7000
|
throw new Error("Cannot create or consume an approval without an authenticated owner");
|
|
@@ -7001,14 +7019,7 @@ export function createProductionAgentHandler(options) {
|
|
|
7001
7019
|
return consumeAgentToolApproval(approvalStoreBinding(binding));
|
|
7002
7020
|
},
|
|
7003
7021
|
};
|
|
7004
|
-
const
|
|
7005
|
-
? requestActions[exactApprovedToolCall.name]
|
|
7006
|
-
: undefined;
|
|
7007
|
-
const approvedToolCallsForExecution = exactApprovedToolCall && exactApprovedToolEntry?.needsApproval
|
|
7008
|
-
? [
|
|
7009
|
-
toolCallCacheKey(exactApprovedToolCall.name, exactApprovedToolCall.input),
|
|
7010
|
-
]
|
|
7011
|
-
: undefined;
|
|
7022
|
+
const approvedToolCallsForExecution = requestedApprovedToolCalls;
|
|
7012
7023
|
if (isBackgroundWorker &&
|
|
7013
7024
|
(await isTurnAborted(effectiveThreadId, effectiveTurnId))) {
|
|
7014
7025
|
await markRunAborted(runId, "user").catch(() => { });
|
|
@@ -7788,9 +7799,10 @@ export function createProductionAgentHandler(options) {
|
|
|
7788
7799
|
...(threadId
|
|
7789
7800
|
? { threadId: effectiveThreadId, turnId: effectiveTurnId }
|
|
7790
7801
|
: {}),
|
|
7791
|
-
// Human-in-the-loop approval grants for this turn
|
|
7792
|
-
//
|
|
7793
|
-
//
|
|
7802
|
+
// Human-in-the-loop approval grants for this turn. The request is
|
|
7803
|
+
// untrusted; the durable approval consumer below validates every key
|
|
7804
|
+
// against the authenticated owner/org/thread/turn and consumes it
|
|
7805
|
+
// atomically for the exact tool/input tuple.
|
|
7794
7806
|
...(approvedToolCallsForExecution
|
|
7795
7807
|
? { approvedToolCalls: approvedToolCallsForExecution }
|
|
7796
7808
|
: {}),
|
|
@@ -33,6 +33,8 @@ export declare const AGENT_TOOL_APPROVAL_TABLE_SQL: {
|
|
|
33
33
|
)`;
|
|
34
34
|
};
|
|
35
35
|
export declare const AGENT_TOOL_APPROVAL_INDEX_SQL = "CREATE INDEX IF NOT EXISTS idx_agent_tool_approvals_binding\n ON agent_tool_approvals(owner_email, org_id, thread_id, tool_name, call_id, status)";
|
|
36
|
+
export declare const AGENT_TOOL_APPROVAL_LOGICAL_INDEX_SQL = "CREATE INDEX IF NOT EXISTS idx_agent_tool_approvals_logical\n ON agent_tool_approvals(owner_email, org_id, thread_id, turn_id, tool_name, approval_key_hash, status)";
|
|
37
|
+
export declare const AGENT_TOOL_APPROVAL_RECOVERY_INDEX_SQL = "CREATE INDEX IF NOT EXISTS idx_agent_tool_approvals_recovery\n ON agent_tool_approvals(owner_email, org_id, thread_id, approval_key_hash, status, turn_id)";
|
|
36
38
|
/**
|
|
37
39
|
* Durable approval grants are created and consumed on request paths, but their
|
|
38
40
|
* schema belongs to the release migration boundary in production.
|
|
@@ -33,6 +33,10 @@ export const AGENT_TOOL_APPROVAL_TABLE_SQL = {
|
|
|
33
33
|
};
|
|
34
34
|
export const AGENT_TOOL_APPROVAL_INDEX_SQL = `CREATE INDEX IF NOT EXISTS idx_agent_tool_approvals_binding
|
|
35
35
|
ON agent_tool_approvals(owner_email, org_id, thread_id, tool_name, call_id, status)`;
|
|
36
|
+
export const AGENT_TOOL_APPROVAL_LOGICAL_INDEX_SQL = `CREATE INDEX IF NOT EXISTS idx_agent_tool_approvals_logical
|
|
37
|
+
ON agent_tool_approvals(owner_email, org_id, thread_id, turn_id, tool_name, approval_key_hash, status)`;
|
|
38
|
+
export const AGENT_TOOL_APPROVAL_RECOVERY_INDEX_SQL = `CREATE INDEX IF NOT EXISTS idx_agent_tool_approvals_recovery
|
|
39
|
+
ON agent_tool_approvals(owner_email, org_id, thread_id, approval_key_hash, status, turn_id)`;
|
|
36
40
|
/**
|
|
37
41
|
* Durable approval grants are created and consumed on request paths, but their
|
|
38
42
|
* schema belongs to the release migration boundary in production.
|
|
@@ -48,4 +52,20 @@ ${AGENT_TOOL_APPROVAL_INDEX_SQL}`,
|
|
|
48
52
|
${AGENT_TOOL_APPROVAL_INDEX_SQL}`,
|
|
49
53
|
},
|
|
50
54
|
},
|
|
55
|
+
{
|
|
56
|
+
version: 2,
|
|
57
|
+
name: "agent-tool-approvals-logical-binding-index",
|
|
58
|
+
sql: {
|
|
59
|
+
postgres: AGENT_TOOL_APPROVAL_LOGICAL_INDEX_SQL,
|
|
60
|
+
sqlite: AGENT_TOOL_APPROVAL_LOGICAL_INDEX_SQL,
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
version: 3,
|
|
65
|
+
name: "agent-tool-approvals-recovery-index",
|
|
66
|
+
sql: {
|
|
67
|
+
postgres: AGENT_TOOL_APPROVAL_RECOVERY_INDEX_SQL,
|
|
68
|
+
sqlite: AGENT_TOOL_APPROVAL_RECOVERY_INDEX_SQL,
|
|
69
|
+
},
|
|
70
|
+
},
|
|
51
71
|
];
|
|
@@ -9,10 +9,24 @@ export interface AgentToolApprovalBinding {
|
|
|
9
9
|
expiresAt?: number;
|
|
10
10
|
}
|
|
11
11
|
export declare function hashAgentToolApprovalKey(approvalKey: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* Recover the durable scope for an approval continuation when a transport
|
|
14
|
+
* drops the original turn id. A single pending logical turn is safe to
|
|
15
|
+
* recover; multiple turns are deliberately ambiguous and stay unmatched.
|
|
16
|
+
*/
|
|
17
|
+
export declare function resolveAgentToolApprovalTurnId(binding: {
|
|
18
|
+
ownerEmail: string;
|
|
19
|
+
orgId?: string | null;
|
|
20
|
+
threadId?: string | null;
|
|
21
|
+
requestedTurnId?: string | null;
|
|
22
|
+
approvalKeys: readonly string[];
|
|
23
|
+
}): Promise<string | null>;
|
|
12
24
|
export declare function createAgentToolApproval(binding: AgentToolApprovalBinding): Promise<void>;
|
|
13
25
|
/**
|
|
14
|
-
* Atomically consume the server-created approval for one
|
|
15
|
-
*
|
|
16
|
-
*
|
|
26
|
+
* Atomically consume the server-created approval for one logical tool call.
|
|
27
|
+
* The model's call id is transport metadata and can change when Dispatch
|
|
28
|
+
* reconstructs a paused turn, so it is deliberately not part of authorization.
|
|
29
|
+
* The pending row remains the boundary: client history and approval keys alone
|
|
30
|
+
* cannot manufacture a grant.
|
|
17
31
|
*/
|
|
18
32
|
export declare function consumeAgentToolApproval(binding: AgentToolApprovalBinding): Promise<boolean>;
|