@agent-native/core 0.136.3 → 0.136.4

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.
Files changed (37) hide show
  1. package/dist/client/agent-page/AgentJobsTab.js +19 -4
  2. package/dist/client/agent-page/use-jobs.d.ts +10 -0
  3. package/dist/client/agent-page/use-jobs.js +21 -0
  4. package/dist/collab/awareness.d.ts +2 -2
  5. package/dist/jobs/actions/run-automation-now.d.ts +6 -0
  6. package/dist/jobs/actions/run-automation-now.js +22 -0
  7. package/dist/jobs/background-automation-runner.d.ts +2 -0
  8. package/dist/jobs/background-automation-runner.js +22 -16
  9. package/dist/jobs/run-history.d.ts +17 -3
  10. package/dist/jobs/run-history.js +73 -7
  11. package/dist/jobs/run-now.d.ts +20 -0
  12. package/dist/jobs/run-now.js +99 -0
  13. package/dist/jobs/scheduler.d.ts +15 -0
  14. package/dist/jobs/scheduler.js +76 -12
  15. package/dist/localization/default-messages.d.ts +3 -0
  16. package/dist/localization/default-messages.js +3 -0
  17. package/dist/mcp/screen-memory-stdio.d.ts +7 -7
  18. package/dist/notifications/routes.d.ts +3 -3
  19. package/dist/observability/routes.d.ts +3 -3
  20. package/dist/provider-api/actions/custom-provider-registration.d.ts +12 -12
  21. package/dist/provider-api/actions/provider-api.d.ts +4 -4
  22. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  23. package/dist/resources/handlers.d.ts +1 -1
  24. package/dist/secrets/routes.d.ts +3 -3
  25. package/dist/server/action-discovery.js +4 -0
  26. package/dist/server/agent-chat-plugin.js +135 -68
  27. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  28. package/dist/server/email-actions.d.ts +6 -3
  29. package/dist/server/email-actions.js +17 -83
  30. package/dist/server/email-markdown.d.ts +4 -0
  31. package/dist/server/email-markdown.js +174 -0
  32. package/dist/server/transcribe-voice.d.ts +1 -1
  33. package/dist/templates/workspace-core/.agents/skills/automations/SKILL.md +1 -0
  34. package/dist/triggers/actions.d.ts +1 -1
  35. package/dist/triggers/actions.js +27 -5
  36. package/package.json +1 -1
  37. package/src/templates/workspace-core/.agents/skills/automations/SKILL.md +1 -0
@@ -420,6 +420,7 @@ export function createAgentChatPlugin(options) {
420
420
  ...loopSettingsScripts,
421
421
  };
422
422
  const callAgentScript = await createCallAgentScriptEntry(options?.appId);
423
+ let runNowSchedulerDeps = null;
423
424
  const browserTools = createBuilderBrowserTool({
424
425
  getOrigin: () => getRequestRunContext()?.requestOrigin ?? "http://localhost:3000",
425
426
  getOwner: () => getRequestRunContext()?.owner ?? getRequestUserEmail(),
@@ -741,15 +742,18 @@ export function createAgentChatPlugin(options) {
741
742
  });
742
743
  }
743
744
  catch { }
744
- // Core send-email tool only registered when RESEND_API_KEY or
745
- // SENDGRID_API_KEY is set. Keyed "core-send-email" to avoid colliding
745
+ // Core send-email tool. Keyed "core-send-email" to avoid colliding
746
746
  // with the mail template's richer "send-email" action (template wins
747
747
  // when both surfaces spread into the same object, but distinct keys
748
748
  // keep both visible and avoid silent shadowing).
749
749
  let coreEmailTools = {};
750
+ let backgroundCoreEmailTools = {};
750
751
  try {
751
752
  const { createCoreEmailActionEntries } = await import("./email-actions.js");
752
753
  coreEmailTools = createCoreEmailActionEntries();
754
+ backgroundCoreEmailTools = createCoreEmailActionEntries({
755
+ unattended: true,
756
+ });
753
757
  }
754
758
  catch { }
755
759
  // Core read-attachment tool — always registered so the agent can page
@@ -760,6 +764,29 @@ export function createAgentChatPlugin(options) {
760
764
  coreAttachmentTools = createCoreAttachmentActionEntries();
761
765
  }
762
766
  catch { }
767
+ // Keep scheduler and event-trigger runs on one deliberately bounded
768
+ // background surface. Interactive-only tools stay out of it, while
769
+ // shared capabilities such as call-agent and core-send-email cannot
770
+ // drift independently between the two background entry points.
771
+ const getBackgroundActionEntries = (automation) => ({
772
+ ...templateScripts,
773
+ ...resourceScripts,
774
+ ...docsScripts,
775
+ ...(lazyContext ? frameworkContextTool : {}),
776
+ ...urlTools,
777
+ ...chatScripts,
778
+ ...callAgentScript,
779
+ ...jobTools,
780
+ ...automationTools,
781
+ ...notificationTools,
782
+ ...progressTools,
783
+ ...fetchTool,
784
+ ...webSearchTool,
785
+ ...toolActions,
786
+ ...backgroundCoreEmailTools,
787
+ ...coreAttachmentTools,
788
+ ...getJobMcpActionEntries(automation),
789
+ });
763
790
  // -----------------------------------------------------------------------
764
791
  // Production code-execution mode resolution.
765
792
  //
@@ -4432,6 +4459,50 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
4432
4459
  return { error: prepared.error };
4433
4460
  }
4434
4461
  const preparedMarker = prepared.body[AGENT_CHAT_BACKGROUND_RUN_FIELD];
4462
+ const automationRunId = preparedMarker && typeof preparedMarker === "object"
4463
+ ? preparedMarker.automationRunId
4464
+ : undefined;
4465
+ if (typeof automationRunId === "string" && automationRunId) {
4466
+ const runAutomation = async () => {
4467
+ try {
4468
+ if (!runNowSchedulerDeps) {
4469
+ throw new Error("Automation runner is not initialized.");
4470
+ }
4471
+ const { runQueuedAutomation } = await import("../jobs/scheduler.js");
4472
+ const result = await runQueuedAutomation(automationRunId, runNowSchedulerDeps);
4473
+ return { ok: true, automationRunId, ...result };
4474
+ }
4475
+ catch (error) {
4476
+ const message = error instanceof Error ? error.message : String(error);
4477
+ const { finishAutomationRun } = await import("../jobs/run-history.js");
4478
+ await finishAutomationRun(automationRunId, "error", `Automation worker failed: ${message}. No delivery was confirmed.`).catch((finishError) => {
4479
+ console.warn(`[automations] could not record run-now worker failure for ${automationRunId}:`, finishError);
4480
+ });
4481
+ console.error(`[automations] run-now worker failed for ${automationRunId}:`, error);
4482
+ throw error;
4483
+ }
4484
+ };
4485
+ // Netlify background functions must keep the handler open for the
4486
+ // full automation. Other runtimes get an immediate receipt and
4487
+ // use waitUntil when the platform provides it; long-lived Node
4488
+ // processes can safely continue the promise after the response.
4489
+ if (isInBackgroundFunctionRuntime()) {
4490
+ try {
4491
+ return await runAutomation();
4492
+ }
4493
+ catch {
4494
+ setResponseStatus(event, 500);
4495
+ return { error: "Automation worker failed" };
4496
+ }
4497
+ }
4498
+ const waitUntil = event.req?.waitUntil;
4499
+ const runPromise = runAutomation().catch(() => { });
4500
+ if (typeof waitUntil === "function") {
4501
+ waitUntil(runPromise);
4502
+ }
4503
+ setResponseStatus(event, 202);
4504
+ return { ok: true, accepted: true, automationRunId };
4505
+ }
4435
4506
  const expectsBackgroundRuntime = backgroundRunMarkerExpectsBackgroundRuntime(preparedMarker);
4436
4507
  const runtimeGlobals = globalThis;
4437
4508
  const hadExpectedRuntimeMarker = Object.prototype.hasOwnProperty.call(runtimeGlobals, "__AGENT_NATIVE_BACKGROUND_RUNTIME_EXPECTED__");
@@ -4582,55 +4653,42 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
4582
4653
  // Poll every 60 seconds for due recurring jobs and execute them.
4583
4654
  // Uses setInterval so it works in all deployment environments without
4584
4655
  // requiring Nitro experimental tasks configuration.
4585
- if (disableRecurringJobsRuntime) {
4586
- if (process.env.DEBUG) {
4587
- console.log("[recurring-jobs] Scheduler disabled for local development");
4656
+ try {
4657
+ const { processRecurringJobs } = await import("../jobs/scheduler.js");
4658
+ const schedulerDeps = {
4659
+ getActions: getBackgroundActionEntries,
4660
+ getSystemPrompt: async (owner) => {
4661
+ const resources = await loadResourcesForPrompt(owner, lazyContext, options?.appId);
4662
+ const schemaBlock = lazyContext
4663
+ ? ""
4664
+ : await buildSchemaBlock(owner, databaseToolsMode);
4665
+ return basePrompt + resources + schemaBlock;
4666
+ },
4667
+ // `basePrompt` above is the same prompt the interactive chat
4668
+ // handler builds, so it teaches the same template actions plus
4669
+ // `manage-jobs` (Extended Capabilities / recurring jobs) and
4670
+ // `manage-progress` (SHARED_RULE_14) BY NAME — both are present in
4671
+ // getActions() via jobTools/progressTools. Keep the job runner's
4672
+ // first request on the same compact surface as interactive chat
4673
+ // instead of the full jobTools/automationTools/notificationTools/
4674
+ // fetchTool/webSearchTool/toolActions catalog every tick.
4675
+ getInitialToolNames: (job) => [
4676
+ ...effectiveInitialToolNames,
4677
+ "manage-jobs",
4678
+ "manage-progress",
4679
+ ...(job?.meta.mcpTools ?? []),
4680
+ ],
4681
+ apiKey: options?.apiKey,
4682
+ model: options?.model,
4683
+ appId: options?.appId,
4684
+ };
4685
+ runNowSchedulerDeps = schedulerDeps;
4686
+ if (disableRecurringJobsRuntime) {
4687
+ if (process.env.DEBUG) {
4688
+ console.log("[recurring-jobs] Scheduler disabled for local development");
4689
+ }
4588
4690
  }
4589
- }
4590
- else {
4591
- try {
4592
- const { processRecurringJobs } = await import("../jobs/scheduler.js");
4593
- const schedulerDeps = {
4594
- getActions: (job) => ({
4595
- ...templateScripts,
4596
- ...resourceScripts,
4597
- ...docsScripts,
4598
- ...(lazyContext ? frameworkContextTool : {}),
4599
- ...chatScripts,
4600
- ...jobTools,
4601
- ...automationTools,
4602
- ...notificationTools,
4603
- ...progressTools,
4604
- ...fetchTool,
4605
- ...webSearchTool,
4606
- ...toolActions,
4607
- ...getJobMcpActionEntries(job),
4608
- }),
4609
- getSystemPrompt: async (owner) => {
4610
- const resources = await loadResourcesForPrompt(owner, lazyContext, options?.appId);
4611
- const schemaBlock = lazyContext
4612
- ? ""
4613
- : await buildSchemaBlock(owner, databaseToolsMode);
4614
- return basePrompt + resources + schemaBlock;
4615
- },
4616
- // `basePrompt` above is the same prompt the interactive chat
4617
- // handler builds, so it teaches the same template actions plus
4618
- // `manage-jobs` (Extended Capabilities / recurring jobs) and
4619
- // `manage-progress` (SHARED_RULE_14) BY NAME — both are present in
4620
- // getActions() via jobTools/progressTools. Keep the job runner's
4621
- // first request on the same compact surface as interactive chat
4622
- // instead of the full jobTools/automationTools/notificationTools/
4623
- // fetchTool/webSearchTool/toolActions catalog every tick.
4624
- getInitialToolNames: (job) => [
4625
- ...effectiveInitialToolNames,
4626
- "manage-jobs",
4627
- "manage-progress",
4628
- ...(job?.meta.mcpTools ?? []),
4629
- ],
4630
- apiKey: options?.apiKey,
4631
- model: options?.model,
4632
- appId: options?.appId,
4633
- };
4691
+ else {
4634
4692
  // Start after a 10-second delay to let the server fully initialize
4635
4693
  setTimeout(() => {
4636
4694
  setInterval(() => {
@@ -4642,10 +4700,33 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
4642
4700
  console.log("[recurring-jobs] Scheduler started (60s interval)");
4643
4701
  }, 10_000);
4644
4702
  }
4645
- catch (err) {
4646
- // Jobs module not available — skip silently
4647
- }
4648
4703
  }
4704
+ catch (error) {
4705
+ console.warn("[recurring-jobs] Scheduler module unavailable:", error instanceof Error ? error.message : error);
4706
+ }
4707
+ // A non-Netlify self-dispatch only waits for the request to leave the
4708
+ // current invocation. Keep the durable history row as a retryable queue
4709
+ // so a frozen serverless handoff is recovered on the next sweep.
4710
+ (() => {
4711
+ let inFlight = false;
4712
+ const sweep = async () => {
4713
+ if (inFlight)
4714
+ return;
4715
+ inFlight = true;
4716
+ try {
4717
+ const { redispatchUnclaimedAutomationRuns } = await import("../jobs/run-now.js");
4718
+ await redispatchUnclaimedAutomationRuns();
4719
+ }
4720
+ catch (error) {
4721
+ console.warn("[automations] queued-run sweep failed; retrying next tick:", error);
4722
+ }
4723
+ finally {
4724
+ inFlight = false;
4725
+ }
4726
+ };
4727
+ setTimeout(() => void sweep(), 15_000);
4728
+ setInterval(() => void sweep(), 30_000);
4729
+ })();
4649
4730
  mcpInitializationPromise = initializeMcpManager().catch((err) => {
4650
4731
  console.warn(`[mcp-client] deferred initialization failed: ${err?.message ?? err}`);
4651
4732
  });
@@ -4912,21 +4993,7 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
4912
4993
  try {
4913
4994
  const { initTriggerDispatcher } = await import("../triggers/dispatcher.js");
4914
4995
  await initTriggerDispatcher({
4915
- getActions: (automation) => ({
4916
- ...templateScripts,
4917
- ...resourceScripts,
4918
- ...docsScripts,
4919
- ...(lazyContext ? frameworkContextTool : {}),
4920
- ...chatScripts,
4921
- ...jobTools,
4922
- ...automationTools,
4923
- ...notificationTools,
4924
- ...progressTools,
4925
- ...fetchTool,
4926
- ...webSearchTool,
4927
- ...toolActions,
4928
- ...getJobMcpActionEntries(automation),
4929
- }),
4996
+ getActions: getBackgroundActionEntries,
4930
4997
  getSystemPrompt: async (owner) => {
4931
4998
  const resources = await loadResourcesForPrompt(owner, lazyContext, options?.appId);
4932
4999
  const schemaBlock = lazyContext
@@ -27,11 +27,11 @@ export declare function resolveAgentEngineApiKeyWriteTarget(event: H3Event, scop
27
27
  export declare function createAgentEngineApiKeyHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
28
28
  error: any;
29
29
  } | {
30
- error?: undefined;
31
30
  ok: boolean;
32
31
  key: string;
33
32
  baseUrlKey?: string;
34
33
  scope: AgentEngineApiKeyScope;
34
+ error?: undefined;
35
35
  }>>;
36
36
  export {};
37
37
  //# sourceMappingURL=agent-engine-api-key-route.d.ts.map
@@ -5,8 +5,9 @@
5
5
  * Registered as a native tool in every template. sendEmail() checks the
6
6
  * scoped Resend/SendGrid configuration at call time.
7
7
  *
8
- * SAFETY: the action description instructs the agent to draft-first and only
9
- * send when the user explicitly confirms, matching the mail template convention.
8
+ * SAFETY: interactive runs are draft-first. Unattended automation runs are
9
+ * already authorized when the automation is created, so they get a separate
10
+ * description that permits delivery without interactive confirmation.
10
11
  *
11
12
  * COLLISION: the mail template registers its own richer "send-email" action.
12
13
  * The template's registration comes after this one in the spread, so it wins
@@ -14,5 +15,7 @@
14
15
  * action is keyed "core-send-email" which is distinct from the template name.
15
16
  */
16
17
  import type { ActionEntry } from "../agent/production-agent.js";
17
- export declare function createCoreEmailActionEntries(): Record<string, ActionEntry>;
18
+ export declare function createCoreEmailActionEntries(options?: {
19
+ unattended?: boolean;
20
+ }): Record<string, ActionEntry>;
18
21
  //# sourceMappingURL=email-actions.d.ts.map
@@ -5,103 +5,37 @@
5
5
  * Registered as a native tool in every template. sendEmail() checks the
6
6
  * scoped Resend/SendGrid configuration at call time.
7
7
  *
8
- * SAFETY: the action description instructs the agent to draft-first and only
9
- * send when the user explicitly confirms, matching the mail template convention.
8
+ * SAFETY: interactive runs are draft-first. Unattended automation runs are
9
+ * already authorized when the automation is created, so they get a separate
10
+ * description that permits delivery without interactive confirmation.
10
11
  *
11
12
  * COLLISION: the mail template registers its own richer "send-email" action.
12
13
  * The template's registration comes after this one in the spread, so it wins
13
14
  * when both would be present under the same key. To avoid any ambiguity this
14
15
  * action is keyed "core-send-email" which is distinct from the template name.
15
16
  */
17
+ import { markdownToHtml, markdownToText, wrapInEmailTemplate, } from "./email-markdown.js";
16
18
  import { sendEmail } from "./email.js";
17
- function markdownToText(md) {
18
- return md
19
- .replace(/!\[([^\]]*)\]\([^\s)]+\)/g, "$1")
20
- .replace(/\[([^\]]+)\]\([^\s)]+\)/g, "$1 ($2)")
21
- .replace(/`([^`]+)`/g, "$1")
22
- .replace(/\*\*([^*]+)\*\*/g, "$1")
23
- .replace(/\*([^*\n]+)\*/g, "$1")
24
- .replace(/^#{1,6}\s+/gm, "")
25
- .trim();
26
- }
27
- function markdownToHtml(md) {
28
- const normalized = md.replace(/\r\n/g, "\n").trim();
29
- if (!normalized)
30
- return "<p></p>";
31
- // Escape HTML entities in a string, preserving already-encoded references.
32
- const esc = (s) => s
33
- .replace(/&/g, "&amp;")
34
- .replace(/</g, "&lt;")
35
- .replace(/>/g, "&gt;")
36
- .replace(/"/g, "&quot;");
37
- // Inline markdown: links, bold, italic, code, bare URLs.
38
- const inline = (s) => s
39
- .replace(/\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g, (_m, label, url) => `<a href="${esc(url)}" target="_blank" rel="noopener noreferrer">${esc(label)}</a>`)
40
- .replace(/(^|[^\w"'=])(https?:\/\/[^\s<]+)/g, (_m, pre, url) => `${pre}<a href="${esc(url)}" target="_blank" rel="noopener noreferrer">${esc(url)}</a>`)
41
- .replace(/`([^`]+)`/g, (_m, code) => `<code>${esc(code)}</code>`)
42
- .replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>")
43
- .replace(/(^|[\s(])\*([^*\n]+)\*(?=$|[\s).,!?:;])/g, "$1<em>$2</em>");
44
- const blocks = normalized.split(/\n{2,}/);
45
- const html = blocks
46
- .map((block) => {
47
- const trimmed = block.trim();
48
- // Fenced code block
49
- if (trimmed.startsWith("```") && trimmed.endsWith("```")) {
50
- const code = trimmed
51
- .replace(/^```[^\n]*\n?/, "")
52
- .replace(/\n?```$/, "");
53
- return `<pre><code>${esc(code)}</code></pre>`;
54
- }
55
- // Heading
56
- const hm = trimmed.match(/^(#{1,6})\s+(.+)$/);
57
- if (hm) {
58
- const level = hm[1].length;
59
- return `<h${level}>${inline(hm[2])}</h${level}>`;
60
- }
61
- // Unordered list
62
- if (/^[-*+]\s+/m.test(trimmed)) {
63
- const items = trimmed
64
- .split("\n")
65
- .filter(Boolean)
66
- .map((l) => `<li>${inline(l.replace(/^[-*+]\s+/, ""))}</li>`)
67
- .join("");
68
- return `<ul style="margin:0 0 1em;padding-left:1.5em">${items}</ul>`;
69
- }
70
- // Ordered list
71
- if (/^\d+\.\s+/m.test(trimmed)) {
72
- const items = trimmed
73
- .split("\n")
74
- .filter(Boolean)
75
- .map((l) => `<li>${inline(l.replace(/^\d+\.\s+/, ""))}</li>`)
76
- .join("");
77
- return `<ol style="margin:0 0 1em;padding-left:1.5em">${items}</ol>`;
78
- }
79
- // Blockquote
80
- if (trimmed.startsWith("> ")) {
81
- const inner = trimmed
82
- .split("\n")
83
- .map((l) => (l.startsWith("> ") ? l.slice(2) : l))
84
- .join("\n");
85
- return `<blockquote style="margin:0 0 1em 1em;border-left:3px solid #ccc;padding-left:0.75em;color:#555">${inline(inner)}</blockquote>`;
86
- }
87
- return `<p style="margin:0 0 1em">${inline(trimmed).replace(/\n/g, "<br />")}</p>`;
88
- })
89
- .join("");
90
- return html;
91
- }
92
- export function createCoreEmailActionEntries() {
19
+ export function createCoreEmailActionEntries(options) {
20
+ const unattended = options?.unattended === true;
93
21
  return {
94
22
  "core-send-email": {
95
23
  tool: {
96
24
  description: [
97
25
  "Send a transactional email via the app's configured email provider (Resend or SendGrid).",
98
26
  "",
99
- "IMPORTANT — DRAFT-FIRST SAFETY RULE: Never call this tool until the user has explicitly",
100
- "confirmed they want to send. Always compose the full email content first, show it to the",
101
- "user, and wait for an explicit 'yes, send it' before invoking this action.",
27
+ ...(unattended
28
+ ? [
29
+ "This is an explicitly authorized unattended automation run. Send the email without asking for an interactive confirmation.",
30
+ ]
31
+ : [
32
+ "IMPORTANT — DRAFT-FIRST SAFETY RULE: Never call this tool until the user has explicitly",
33
+ "confirmed they want to send. Always compose the full email content first, show it to the",
34
+ "user, and wait for an explicit 'yes, send it' before invoking this action.",
35
+ ]),
102
36
  "",
103
37
  "The body is written in markdown. Tables, lists, bold, italic, links, and code blocks",
104
- "are all supported and will render correctly in email clients.",
38
+ "are all supported and will render correctly in email clients. Write the body in markdown; do not hand-write HTML.",
105
39
  "",
106
40
  "This sends via the framework transport (Resend/SendGrid). It is NOT the Gmail-based",
107
41
  "send-email action in the mail template — use this for system/notification emails from",
@@ -168,7 +102,7 @@ export function createCoreEmailActionEntries() {
168
102
  await sendEmail({
169
103
  to,
170
104
  subject,
171
- html: markdownToHtml(bodyMd),
105
+ html: wrapInEmailTemplate(markdownToHtml(bodyMd)),
172
106
  text: markdownToText(bodyMd),
173
107
  ...(from ? { from } : {}),
174
108
  ...(cc ? { cc } : {}),
@@ -0,0 +1,4 @@
1
+ export declare function markdownToText(md: string): string;
2
+ export declare function markdownToHtml(md: string): string;
3
+ export declare function wrapInEmailTemplate(bodyHtml: string): string;
4
+ //# sourceMappingURL=email-markdown.d.ts.map
@@ -0,0 +1,174 @@
1
+ const EMAIL_BACKGROUND = "#f6f7f9"; // guard:allow-raw-color - email clients need inlined colors
2
+ const EMAIL_TEXT = "#1f2937"; // guard:allow-raw-color - email clients need inlined colors
3
+ const EMAIL_MUTED = "#64748b"; // guard:allow-raw-color - email clients need inlined colors
4
+ const EMAIL_BORDER = "#dbe3ec"; // guard:allow-raw-color - email clients need inlined colors
5
+ const EMAIL_CODE = "#eef2f7"; // guard:allow-raw-color - email clients need inlined colors
6
+ const EMAIL_SURFACE = "#ffffff"; // guard:allow-raw-color - email clients need inlined colors
7
+ function escapeHtml(value) {
8
+ return value
9
+ .replace(/&/g, "&amp;")
10
+ .replace(/</g, "&lt;")
11
+ .replace(/>/g, "&gt;")
12
+ .replace(/\"/g, "&quot;");
13
+ }
14
+ function inlineMarkdown(value) {
15
+ let html = escapeHtml(value);
16
+ const protectedLinks = [];
17
+ html = html.replace(/\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g, (_match, label, url) => {
18
+ const index = protectedLinks.push(`<a href="${url}" style="color:${EMAIL_TEXT};text-decoration:underline;">${label}</a>`);
19
+ return `\u0000${index - 1}\u0000`;
20
+ });
21
+ html = html.replace(/(^|[^\w"'=])(https?:\/\/[^\s<]+)/g, (_match, prefix, rawUrl) => {
22
+ let url = rawUrl;
23
+ let trailing = "";
24
+ while (/[.,!?;:]$/.test(url)) {
25
+ trailing = url.slice(-1) + trailing;
26
+ url = url.slice(0, -1);
27
+ }
28
+ let label = "Open link";
29
+ const normalizedUrl = url.replace(/&amp;/g, "&");
30
+ if (URL.canParse(normalizedUrl)) {
31
+ const host = new URL(normalizedUrl).hostname.replace(/^www\./, "");
32
+ if (host)
33
+ label = `Open ${host}`;
34
+ }
35
+ return `${prefix}<a href="${url}" style="color:${EMAIL_TEXT};text-decoration:underline;">${label}</a>${trailing}`;
36
+ });
37
+ html = html
38
+ .replace(/`([^`]+)`/g, `<code style="background:${EMAIL_CODE};padding:2px 5px;border-radius:4px;font-size:0.92em;">$1</code>`)
39
+ .replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>")
40
+ .replace(/__([^_]+)__/g, "<strong>$1</strong>")
41
+ .replace(/(^|[\s(])\*([^*\n]+)\*(?=$|[\s).,!?:;])/g, "$1<em>$2</em>")
42
+ .replace(/(^|[\s(])_([^_\n]+)_(?=$|[\s).,!?:;])/g, "$1<em>$2</em>");
43
+ html = html.replace(/\u0000(\d+)\u0000/g, (_match, index) => {
44
+ return protectedLinks[Number(index)] ?? "";
45
+ });
46
+ return html;
47
+ }
48
+ function renderTable(lines) {
49
+ if (lines.length < 2 || !lines.every((line) => line.includes("|"))) {
50
+ return null;
51
+ }
52
+ const parseRow = (line) => line
53
+ .trim()
54
+ .replace(/^\|/, "")
55
+ .replace(/\|$/, "")
56
+ .split("|")
57
+ .map((cell) => cell.trim());
58
+ const header = parseRow(lines[0]);
59
+ const divider = parseRow(lines[1]);
60
+ if (!header.length || !divider.every((cell) => /^:?-{3,}:?$/.test(cell))) {
61
+ return null;
62
+ }
63
+ const body = lines.slice(2).map(parseRow);
64
+ const headHtml = header
65
+ .map((cell) => `<th align="left" style="border:1px solid ${EMAIL_BORDER};padding:8px 10px;background:${EMAIL_CODE};font-weight:600;">${inlineMarkdown(cell)}</th>`)
66
+ .join("");
67
+ const bodyHtml = body
68
+ .map((row) => `<tr>${header
69
+ .map((_cell, index) => `<td style="border:1px solid ${EMAIL_BORDER};padding:8px 10px;vertical-align:top;">${inlineMarkdown(row[index] ?? "")}</td>`)
70
+ .join("")}</tr>`)
71
+ .join("");
72
+ return `<div style="overflow-x:auto;margin:0 0 18px;"><table role="presentation" style="border-collapse:collapse;width:100%;font-size:14px;"><thead><tr>${headHtml}</tr></thead><tbody>${bodyHtml}</tbody></table></div>`;
73
+ }
74
+ export function markdownToText(md) {
75
+ return md
76
+ .replace(/^(?:```[^\n]*\n|```$)/gm, "")
77
+ .replace(/!\[([^\]]*)\]\([^\s)]+\)/g, "$1")
78
+ .replace(/\[([^\]]+)\]\(([^\s)]+)\)/g, "$1 ($2)")
79
+ .replace(/`([^`]+)`/g, "$1")
80
+ .replace(/\*\*([^*]+)\*\*/g, "$1")
81
+ .replace(/__([^_]+)__/g, "$1")
82
+ .replace(/(^|[\s(])\*([^*\n]+)\*(?=$|[\s).,!?:;])/gm, "$1$2")
83
+ .replace(/(^|[\s(])_([^_\n]+)_(?=$|[\s).,!?:;])/gm, "$1$2")
84
+ .replace(/^#{1,6}\s+/gm, "")
85
+ .replace(/^\s*[-*+]\s+/gm, "")
86
+ .replace(/^\s*\d+\.\s+/gm, "")
87
+ .replace(/^>\s?/gm, "")
88
+ .trim();
89
+ }
90
+ export function markdownToHtml(md) {
91
+ const lines = md.replace(/\r\n/g, "\n").trim().split("\n");
92
+ if (!md.trim())
93
+ return '<p style="margin:0;">&nbsp;</p>';
94
+ const blocks = [];
95
+ let index = 0;
96
+ while (index < lines.length) {
97
+ if (!lines[index].trim()) {
98
+ index += 1;
99
+ continue;
100
+ }
101
+ const line = lines[index].trim();
102
+ if (line.startsWith("```") &&
103
+ lines.slice(index + 1).some((item) => item.trim() === "```")) {
104
+ const end = lines.findIndex((item, offset) => offset > index && item.trim() === "```");
105
+ const code = lines.slice(index + 1, end).join("\n");
106
+ blocks.push(`<pre style="margin:0 0 18px;padding:14px 16px;background:${EMAIL_CODE};border-radius:6px;overflow-x:auto;white-space:pre-wrap;"><code>${escapeHtml(code)}</code></pre>`);
107
+ index = end + 1;
108
+ continue;
109
+ }
110
+ const heading = line.match(/^(#{1,6})\s+(.+)$/);
111
+ if (heading) {
112
+ const level = Math.min(heading[1].length, 3);
113
+ const sizes = ["24px", "19px", "16px"];
114
+ blocks.push(`<h${level} style="margin:0 0 10px;color:${EMAIL_TEXT};font-size:${sizes[level - 1]};line-height:1.3;">${inlineMarkdown(heading[2])}</h${level}>`);
115
+ index += 1;
116
+ continue;
117
+ }
118
+ if (/^(?:-{3,}|\*{3,})$/.test(line)) {
119
+ blocks.push(`<hr style="border:0;border-top:1px solid ${EMAIL_BORDER};margin:4px 0 18px;" />`);
120
+ index += 1;
121
+ continue;
122
+ }
123
+ const listMatch = line.match(/^([-*+] |\d+\. )/);
124
+ if (listMatch) {
125
+ const ordered = /^\d/.test(listMatch[1]);
126
+ const items = [];
127
+ while (index < lines.length) {
128
+ const item = lines[index]
129
+ .trim()
130
+ .match(ordered ? /^\d+\.\s+(.+)$/ : /^[-*+]\s+(.+)$/);
131
+ if (!item)
132
+ break;
133
+ items.push(`<li style="margin:0 0 6px;">${inlineMarkdown(item[1])}</li>`);
134
+ index += 1;
135
+ }
136
+ const tag = ordered ? "ol" : "ul";
137
+ blocks.push(`<${tag} style="margin:0 0 18px;padding-left:24px;">${items.join("")}</${tag}>`);
138
+ continue;
139
+ }
140
+ const tableLines = [line];
141
+ let tableEnd = index + 1;
142
+ while (tableEnd < lines.length &&
143
+ lines[tableEnd].trim() &&
144
+ lines[tableEnd].includes("|")) {
145
+ tableLines.push(lines[tableEnd].trim());
146
+ tableEnd += 1;
147
+ }
148
+ const table = renderTable(tableLines);
149
+ if (table) {
150
+ blocks.push(table);
151
+ index = tableEnd;
152
+ continue;
153
+ }
154
+ const paragraph = [line];
155
+ index += 1;
156
+ while (index < lines.length && lines[index].trim()) {
157
+ if (/^(?:#{1,6}\s|[-*+]\s|\d+\.\s|>\s|```)/.test(lines[index].trim()))
158
+ break;
159
+ paragraph.push(lines[index].trim());
160
+ index += 1;
161
+ }
162
+ if (paragraph.every((item) => item.startsWith("> "))) {
163
+ blocks.push(`<blockquote style="margin:0 0 18px;padding:4px 0 4px 14px;border-left:3px solid ${EMAIL_BORDER};color:${EMAIL_MUTED};">${inlineMarkdown(paragraph.map((item) => item.slice(2)).join("\n")).replace(/\n/g, "<br />")}</blockquote>`);
164
+ }
165
+ else {
166
+ blocks.push(`<p style="margin:0 0 18px;line-height:1.65;">${inlineMarkdown(paragraph.join("\n")).replace(/\n/g, "<br />")}</p>`);
167
+ }
168
+ }
169
+ return blocks.join("\n");
170
+ }
171
+ export function wrapInEmailTemplate(bodyHtml) {
172
+ return `<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"></head><body style="margin:0;padding:0;background:${EMAIL_BACKGROUND};font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;color:${EMAIL_TEXT};"><div style="padding:24px 12px;"><div style="box-sizing:border-box;max-width:640px;margin:0 auto;padding:28px 30px;background:${EMAIL_SURFACE};border:1px solid ${EMAIL_BORDER};border-radius:8px;font-size:15px;line-height:1.65;">${bodyHtml}</div></div></body></html>`;
173
+ }
174
+ //# sourceMappingURL=email-markdown.js.map
@@ -20,7 +20,7 @@ export declare function createTranscribeVoiceHandler(): import("h3").EventHandle
20
20
  error: string;
21
21
  text?: undefined;
22
22
  } | {
23
- error?: undefined;
24
23
  text: string;
24
+ error?: undefined;
25
25
  }>>;
26
26
  //# sourceMappingURL=transcribe-voice.d.ts.map
@@ -103,6 +103,7 @@ All automation operations are accessed through a single `manage-automations` too
103
103
  | `update` | Update an existing automation (enabled, condition, body) |
104
104
  | `delete` | Delete an automation (always confirm with user first) |
105
105
  | `fire-test` | Emit a `test.event.fired` event to validate automations |
106
+ | `run-now` | Run one automation immediately with its real actions and side effects |
106
107
 
107
108
  Additional tool: `web-request` — outbound HTTP with `${keys.NAME}` substitution.
108
109
 
@@ -5,7 +5,7 @@
5
5
  * available in every template. The agent uses them to create, list, and
6
6
  * manage automations from chat.
7
7
  *
8
- * All six operations are consolidated into a single `manage-automations` tool
8
+ * All seven operations are consolidated into a single `manage-automations` tool
9
9
  * with an `action` discriminator to keep the tool registry compact.
10
10
  */
11
11
  import type { ActionEntry } from "../agent/production-agent.js";