@plotday/twister 0.61.0 → 0.63.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.
Files changed (68) hide show
  1. package/dist/connector.d.ts +14 -0
  2. package/dist/connector.d.ts.map +1 -1
  3. package/dist/connector.js +14 -0
  4. package/dist/connector.js.map +1 -1
  5. package/dist/docs/assets/hierarchy.js +1 -1
  6. package/dist/docs/assets/search.js +1 -1
  7. package/dist/docs/classes/index.Connector.html +47 -26
  8. package/dist/docs/classes/index.FileNotFoundError.html +1 -1
  9. package/dist/docs/classes/index.Files.html +1 -1
  10. package/dist/docs/classes/index.Imap.html +1 -1
  11. package/dist/docs/classes/index.Options.html +1 -1
  12. package/dist/docs/classes/index.Smtp.html +1 -1
  13. package/dist/docs/classes/tool.ITool.html +1 -1
  14. package/dist/docs/classes/tool.Tool.html +17 -7
  15. package/dist/docs/classes/tools_ai.AI.html +1 -1
  16. package/dist/docs/classes/tools_callbacks.Callbacks.html +1 -1
  17. package/dist/docs/classes/tools_integrations.Integrations.html +1 -1
  18. package/dist/docs/classes/tools_network.Network.html +1 -1
  19. package/dist/docs/classes/tools_plot.Plot.html +1 -1
  20. package/dist/docs/classes/tools_store.Store.html +1 -1
  21. package/dist/docs/classes/tools_tasks.Tasks.html +40 -14
  22. package/dist/docs/classes/tools_twists.Twists.html +1 -1
  23. package/dist/docs/classes/twist.Twist.html +21 -11
  24. package/dist/docs/documents/Built-in_Tools.html +15 -13
  25. package/dist/docs/documents/Runtime_Environment.html +7 -5
  26. package/dist/docs/hierarchy.html +1 -1
  27. package/dist/docs/media/AGENTS.md +23 -19
  28. package/dist/llm-docs/connector.d.ts +1 -1
  29. package/dist/llm-docs/connector.d.ts.map +1 -1
  30. package/dist/llm-docs/connector.js +1 -1
  31. package/dist/llm-docs/connector.js.map +1 -1
  32. package/dist/llm-docs/tool.d.ts +1 -1
  33. package/dist/llm-docs/tool.d.ts.map +1 -1
  34. package/dist/llm-docs/tool.js +1 -1
  35. package/dist/llm-docs/tool.js.map +1 -1
  36. package/dist/llm-docs/tools/tasks.d.ts +1 -1
  37. package/dist/llm-docs/tools/tasks.d.ts.map +1 -1
  38. package/dist/llm-docs/tools/tasks.js +1 -1
  39. package/dist/llm-docs/tools/tasks.js.map +1 -1
  40. package/dist/llm-docs/twist.d.ts +1 -1
  41. package/dist/llm-docs/twist.d.ts.map +1 -1
  42. package/dist/llm-docs/twist.js +1 -1
  43. package/dist/llm-docs/twist.js.map +1 -1
  44. package/dist/tool.d.ts +16 -0
  45. package/dist/tool.d.ts.map +1 -1
  46. package/dist/tool.js +15 -0
  47. package/dist/tool.js.map +1 -1
  48. package/dist/tools/tasks.d.ts +58 -12
  49. package/dist/tools/tasks.d.ts.map +1 -1
  50. package/dist/tools/tasks.js.map +1 -1
  51. package/dist/twist.d.ts +16 -0
  52. package/dist/twist.d.ts.map +1 -1
  53. package/dist/twist.js +15 -0
  54. package/dist/twist.js.map +1 -1
  55. package/dist/utils/markdown-html.d.ts +15 -0
  56. package/dist/utils/markdown-html.d.ts.map +1 -0
  57. package/dist/utils/markdown-html.js +35 -0
  58. package/dist/utils/markdown-html.js.map +1 -0
  59. package/package.json +7 -1
  60. package/src/connector.ts +15 -0
  61. package/src/llm-docs/connector.ts +1 -1
  62. package/src/llm-docs/tool.ts +1 -1
  63. package/src/llm-docs/tools/tasks.ts +1 -1
  64. package/src/llm-docs/twist.ts +1 -1
  65. package/src/tool.ts +20 -0
  66. package/src/tools/tasks.ts +61 -12
  67. package/src/twist.ts +20 -0
  68. package/src/utils/markdown-html.ts +40 -0
@@ -134,18 +134,19 @@ export abstract class Tasks extends ITool {
134
134
  abstract cancelAllTasks(): Promise<void>;
135
135
 
136
136
  /**
137
- * Schedules a **singleton** task identified by `key`: scheduling under a key
138
- * that already has a pending task atomically cancels the existing one and
139
- * replaces it. At most one scheduled task per `key` is ever live.
140
- *
141
- * Use this for any recurring/self-renewing jobwebhook/watch renewals,
142
- * periodic polling, deferred cleanup instead of hand-managing tokens with
143
- * `runTask()` + `cancelTask()`. The manual pattern (store the token, cancel
144
- * it before re-scheduling) is easy to get wrong: a renewal callback that
145
- * re-schedules itself, combined with any *extra* scheduling call (a
146
- * re-dispatched `onChannelEnabled`, a re-init), leaks parallel self-
147
- * perpetuating chains that accumulate forever and can trip the runtime's
148
- * execution quota. Keying makes that leak impossible by construction.
137
+ * Schedules a **one-shot singleton** task identified by `key`: scheduling
138
+ * under a key that already has a pending task atomically cancels the existing
139
+ * one and replaces it. At most one scheduled task per `key` is ever live.
140
+ *
141
+ * Use this for **one-shot keyed deferred work** a single future task whose
142
+ * pending occurrence should be atomically replaced if re-scheduled (e.g.
143
+ * a deferred cleanup, a one-time expiry action, a single future send).
144
+ *
145
+ * For **recurring/self-renewing jobs** (watch renewals, polling loops,
146
+ * periodic syncs, self-heal checks), use {@link scheduleRecurring} instead.
147
+ * It owns the cadence on the platform side, so the chain survives dropped
148
+ * runs, suspensions, and deploys without the callback needing to reschedule
149
+ * itself.
149
150
  *
150
151
  * Replacement is atomic on the server, so concurrent executions racing to
151
152
  * schedule the same key converge on a single task rather than leaking.
@@ -184,4 +185,52 @@ export abstract class Tasks extends ITool {
184
185
  */
185
186
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
186
187
  abstract cancelScheduledTask(key: string): Promise<void>;
188
+
189
+ /**
190
+ * Schedules a **durable recurring** task identified by `key`. Unlike
191
+ * {@link scheduleTask} (one-shot, deleted when it fires), a recurring task's
192
+ * next occurrence is owned by the platform: the runtime re-arms it every
193
+ * `intervalMs` automatically, so the chain survives a dropped queue message,
194
+ * a suspension, a deploy/eviction, or a callback that throws before it could
195
+ * reschedule. The callback just does the work, idempotently — it does NOT
196
+ * need to reschedule itself.
197
+ *
198
+ * `intervalMs` is a **safety ceiling** (the maximum gap between fires). For
199
+ * data-dependent cadence (e.g. renew 24h before a provider-returned expiry),
200
+ * pass `firstRunAt` for the precise next fire and re-call `scheduleRecurring`
201
+ * with the same key on each run to keep tightening it; the ceiling guarantees
202
+ * the chain still fires if a run is lost. `firstRunAt` can pull the next fire
203
+ * earlier than the ceiling but never later.
204
+ *
205
+ * Recurring tasks are keyed/singleton: re-scheduling under the same key
206
+ * atomically replaces the pending occurrence (one live task per key). Tear
207
+ * down with {@link cancelScheduledTask}.
208
+ *
209
+ * @param key - Stable identifier, scoped to what it maintains, e.g.
210
+ * `` `watch-renewal:${folderId}` `` or `"mailbox-self-heal"`.
211
+ * @param callback - Callback created with `this.callback()`.
212
+ * @param options.intervalMs - Safety-ceiling cadence in milliseconds.
213
+ * @param options.firstRunAt - Optional precise time for the next fire
214
+ * (clamped to no later than now + intervalMs).
215
+ *
216
+ * @example
217
+ * ```typescript
218
+ * // Fixed cadence (self-heal, polling): register once, never reschedule.
219
+ * const cb = await this.callback(this.selfHealCheck);
220
+ * await this.scheduleRecurring("mailbox-self-heal", cb, { intervalMs: 60 * 60 * 1000 });
221
+ *
222
+ * // Variable cadence (watch renewal): precise firstRunAt + safety ceiling.
223
+ * const renew = await this.callback(this.renewWatch, folderId);
224
+ * await this.scheduleRecurring(`watch-renewal:${folderId}`, renew, {
225
+ * intervalMs: 3.5 * 24 * 60 * 60 * 1000, // ceiling: half the 7-day watch
226
+ * firstRunAt: new Date(expiry.getTime() - 24 * 60 * 60 * 1000),
227
+ * });
228
+ * ```
229
+ */
230
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
231
+ abstract scheduleRecurring(
232
+ key: string,
233
+ callback: Callback,
234
+ options: { intervalMs: number; firstRunAt?: Date }
235
+ ): Promise<void>;
187
236
  }
package/src/twist.ts CHANGED
@@ -327,6 +327,26 @@ export abstract class Twist<TSelf> {
327
327
  return this.tools.tasks.cancelScheduledTask(key);
328
328
  }
329
329
 
330
+ /**
331
+ * Schedules a durable recurring task under a stable key. The platform
332
+ * re-arms the task every `intervalMs` automatically — the callback does NOT
333
+ * need to reschedule itself. Re-scheduling under the same key atomically
334
+ * replaces the pending occurrence (at most one live task per key). Tear down
335
+ * with {@link cancelScheduledTask}. See {@link Tasks.scheduleRecurring}.
336
+ *
337
+ * @param key - Stable identifier, e.g. `"mailbox-self-heal"`
338
+ * @param callback - Callback token created with `this.callback()`
339
+ * @param options.intervalMs - Safety-ceiling cadence in milliseconds
340
+ * @param options.firstRunAt - Optional precise time for the next fire
341
+ */
342
+ protected async scheduleRecurring(
343
+ key: string,
344
+ callback: Callback,
345
+ options: { intervalMs: number; firstRunAt?: Date }
346
+ ): Promise<void> {
347
+ return this.tools.tasks.scheduleRecurring(key, callback, options);
348
+ }
349
+
330
350
  /**
331
351
  * Called when the twist is installed by a user.
332
352
  *
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Render Plot Markdown note content to HTML, for connectors that send the
3
+ * note out to a system whose native format is HTML email (Gmail, Outlook
4
+ * Mail). Kept separate from {@link markdownToPlainText} so connectors that
5
+ * only need the plain-text path don't pull the Markdown parser into their
6
+ * bundle (`sideEffects: false` lets the unused module tree-shake away).
7
+ */
8
+ import { marked } from "marked";
9
+
10
+ /**
11
+ * Convert Markdown to an HTML fragment suitable for an email body.
12
+ *
13
+ * - Plot mentions `[Name](#@UUID)` render as plain `@Name` text rather than a
14
+ * broken link to the internal `#@UUID` href.
15
+ * - GitHub-flavoured Markdown is enabled, and single newlines become `<br>`
16
+ * (`breaks: true`) so a message reads the way the author wrote it — the same
17
+ * convention chat-style products (Slack, GitHub comments) use.
18
+ *
19
+ * Returns an HTML fragment (e.g. `<p>…</p>`), not a full document; the caller
20
+ * wraps it for its transport (a `text/html` MIME part, a Graph `body`, etc.).
21
+ * Empty/blank input yields an empty string.
22
+ */
23
+ export function markdownToHtml(markdown: string | null | undefined): string {
24
+ if (!markdown || markdown.trim() === "") return "";
25
+
26
+ // Render mentions as "@Name" text before parsing so marked doesn't emit an
27
+ // anchor pointing at the internal "#@UUID" href.
28
+ const withMentions = markdown.replace(
29
+ /\[([^\]]+)\]\(#@[^)]+\)/g,
30
+ (_m, name: string) => `@${name}`
31
+ );
32
+
33
+ const html = marked.parse(withMentions, {
34
+ async: false,
35
+ gfm: true,
36
+ breaks: true,
37
+ });
38
+
39
+ return (html as string).trim();
40
+ }