@plotday/twister 0.57.0 → 0.58.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 (328) hide show
  1. package/README.md +53 -44
  2. package/bin/commands/create.js +9 -14
  3. package/bin/commands/create.js.map +1 -1
  4. package/bin/commands/deploy.js +2 -0
  5. package/bin/commands/deploy.js.map +1 -1
  6. package/bin/commands/generate.js +8 -5
  7. package/bin/commands/generate.js.map +1 -1
  8. package/bin/index.js +2 -2
  9. package/bin/index.js.map +1 -1
  10. package/bin/templates/AGENTS.template.md +110 -94
  11. package/bin/templates/README.template.md +36 -33
  12. package/cli/templates/AGENTS.template.md +110 -94
  13. package/cli/templates/README.template.md +36 -33
  14. package/dist/connector.d.ts +24 -17
  15. package/dist/connector.d.ts.map +1 -1
  16. package/dist/connector.js +19 -12
  17. package/dist/connector.js.map +1 -1
  18. package/dist/docs/assets/hierarchy.js +1 -1
  19. package/dist/docs/assets/navigation.js +1 -1
  20. package/dist/docs/assets/search.js +1 -1
  21. package/dist/docs/classes/index.Connector.html +66 -60
  22. package/dist/docs/classes/index.FileNotFoundError.html +2 -2
  23. package/dist/docs/classes/index.Files.html +4 -4
  24. package/dist/docs/classes/index.Imap.html +10 -10
  25. package/dist/docs/classes/index.Options.html +2 -2
  26. package/dist/docs/classes/index.Smtp.html +6 -6
  27. package/dist/docs/classes/tool.ITool.html +2 -2
  28. package/dist/docs/classes/tool.Tool.html +23 -23
  29. package/dist/docs/classes/tools_ai.AI.html +5 -5
  30. package/dist/docs/classes/tools_callbacks.Callbacks.html +8 -8
  31. package/dist/docs/classes/tools_integrations.Integrations.html +15 -15
  32. package/dist/docs/classes/tools_network.Network.html +9 -9
  33. package/dist/docs/classes/tools_plot.Plot.html +34 -33
  34. package/dist/docs/classes/tools_store.Store.html +8 -8
  35. package/dist/docs/classes/tools_tasks.Tasks.html +6 -6
  36. package/dist/docs/classes/tools_twists.Twists.html +12 -11
  37. package/dist/docs/classes/twist.Twist.html +28 -28
  38. package/dist/docs/documents/Building_Connectors.html +42 -28
  39. package/dist/docs/documents/Built-in_Tools.html +170 -67
  40. package/dist/docs/documents/CLI_Reference.html +68 -47
  41. package/dist/docs/documents/Core_Concepts.html +52 -81
  42. package/dist/docs/documents/Getting_Started.html +28 -31
  43. package/dist/docs/documents/MULTI_USER_AUTH.html +45 -0
  44. package/dist/docs/documents/Runtime_Environment.html +13 -12
  45. package/dist/docs/documents/SYNC_STRATEGIES.html +373 -0
  46. package/dist/docs/enums/plot.ActionType.html +9 -9
  47. package/dist/docs/enums/plot.ActorType.html +4 -4
  48. package/dist/docs/enums/plot.ConferencingProvider.html +6 -6
  49. package/dist/docs/enums/plot.ThemeColor.html +9 -9
  50. package/dist/docs/enums/tag.Tag.html +3 -3
  51. package/dist/docs/enums/tools_ai.AIModel.html +3 -3
  52. package/dist/docs/enums/tools_integrations.AuthProvider.html +13 -13
  53. package/dist/docs/enums/tools_plot.ContactAccess.html +2 -2
  54. package/dist/docs/enums/tools_plot.FocusAccess.html +3 -3
  55. package/dist/docs/enums/tools_plot.LinkAccess.html +3 -3
  56. package/dist/docs/enums/tools_plot.ThreadAccess.html +4 -4
  57. package/dist/docs/functions/index.Uuid.Generate.html +1 -1
  58. package/dist/docs/functions/utils_hash.quickHash.html +1 -1
  59. package/dist/docs/hierarchy.html +1 -1
  60. package/dist/docs/index.html +7 -8
  61. package/dist/docs/interfaces/tools_ai.AIRequest.html +13 -13
  62. package/dist/docs/interfaces/tools_ai.AIResponse.html +9 -9
  63. package/dist/docs/interfaces/tools_ai.FilePart.html +5 -5
  64. package/dist/docs/interfaces/tools_ai.ImagePart.html +4 -4
  65. package/dist/docs/interfaces/tools_ai.ReasoningPart.html +4 -4
  66. package/dist/docs/interfaces/tools_ai.RedactedReasoningPart.html +3 -3
  67. package/dist/docs/interfaces/tools_ai.TextPart.html +3 -3
  68. package/dist/docs/interfaces/tools_ai.ToolCallPart.html +5 -5
  69. package/dist/docs/interfaces/tools_ai.ToolExecutionOptions.html +4 -4
  70. package/dist/docs/interfaces/tools_ai.ToolResultPart.html +5 -5
  71. package/dist/docs/interfaces/tools_twists.TwistSource.html +3 -3
  72. package/dist/docs/interfaces/utils_types.ToolShed.html +5 -5
  73. package/dist/docs/media/AGENTS.md +101 -74
  74. package/dist/docs/modules.html +1 -1
  75. package/dist/docs/types/index.BooleanDef.html +2 -2
  76. package/dist/docs/types/index.CreateLinkDraft.html +9 -9
  77. package/dist/docs/types/index.ImapAddress.html +3 -3
  78. package/dist/docs/types/index.ImapConnectOptions.html +6 -6
  79. package/dist/docs/types/index.ImapFetchOptions.html +4 -4
  80. package/dist/docs/types/index.ImapFlagOperation.html +1 -1
  81. package/dist/docs/types/index.ImapMailbox.html +5 -5
  82. package/dist/docs/types/index.ImapMailboxStatus.html +7 -7
  83. package/dist/docs/types/index.ImapMessage.html +14 -14
  84. package/dist/docs/types/index.ImapSearchCriteria.html +9 -9
  85. package/dist/docs/types/index.ImapSession.html +1 -1
  86. package/dist/docs/types/index.NewSchedule.html +13 -13
  87. package/dist/docs/types/index.NewScheduleContact.html +2 -2
  88. package/dist/docs/types/index.NewScheduleOccurrence.html +1 -1
  89. package/dist/docs/types/index.NoteWriteBackResult.html +3 -3
  90. package/dist/docs/types/index.NumberDef.html +2 -2
  91. package/dist/docs/types/index.OptionDef.html +1 -1
  92. package/dist/docs/types/index.OptionalScopeGroup.html +6 -6
  93. package/dist/docs/types/index.OptionsSchema.html +1 -1
  94. package/dist/docs/types/index.ReactionCapabilities.html +1 -1
  95. package/dist/docs/types/index.ResolvedOptions.html +1 -1
  96. package/dist/docs/types/index.ResolvedRecipient.html +5 -5
  97. package/dist/docs/types/index.Schedule.html +12 -12
  98. package/dist/docs/types/index.ScheduleContact.html +2 -2
  99. package/dist/docs/types/index.ScheduleContactRole.html +1 -1
  100. package/dist/docs/types/index.ScheduleContactStatus.html +1 -1
  101. package/dist/docs/types/index.ScheduleOccurrence.html +6 -6
  102. package/dist/docs/types/index.ScheduleOccurrenceUpdate.html +1 -1
  103. package/dist/docs/types/index.ScopeConfig.html +3 -3
  104. package/dist/docs/types/index.SelectDef.html +2 -2
  105. package/dist/docs/types/index.Serializable.html +1 -1
  106. package/dist/docs/types/index.SmtpAddress.html +3 -3
  107. package/dist/docs/types/index.SmtpConnectOptions.html +7 -7
  108. package/dist/docs/types/index.SmtpMessage.html +12 -12
  109. package/dist/docs/types/index.SmtpSendResult.html +4 -4
  110. package/dist/docs/types/index.SmtpSession.html +1 -1
  111. package/dist/docs/types/index.TextDef.html +2 -2
  112. package/dist/docs/types/index.Uuid.html +1 -1
  113. package/dist/docs/types/plot.Action.html +1 -1
  114. package/dist/docs/types/plot.Actor.html +5 -5
  115. package/dist/docs/types/plot.ActorId.html +4 -4
  116. package/dist/docs/types/plot.Contact.html +4 -4
  117. package/dist/docs/types/plot.ContentType.html +1 -1
  118. package/dist/docs/types/plot.Focus.html +8 -8
  119. package/dist/docs/types/plot.FocusUpdate.html +1 -1
  120. package/dist/docs/types/plot.Link.html +17 -17
  121. package/dist/docs/types/plot.LinkUpdate.html +1 -1
  122. package/dist/docs/types/plot.NewActor.html +1 -1
  123. package/dist/docs/types/plot.NewContact.html +1 -1
  124. package/dist/docs/types/plot.NewFocus.html +1 -1
  125. package/dist/docs/types/plot.NewLink.html +5 -2
  126. package/dist/docs/types/plot.NewLinkWithNotes.html +1 -1
  127. package/dist/docs/types/plot.NewNote.html +1 -1
  128. package/dist/docs/types/plot.NewReactions.html +1 -1
  129. package/dist/docs/types/plot.NewTags.html +1 -1
  130. package/dist/docs/types/plot.NewThread.html +1 -1
  131. package/dist/docs/types/plot.NewThreadWithNotes.html +1 -1
  132. package/dist/docs/types/plot.Note.html +1 -1
  133. package/dist/docs/types/plot.NoteUpdate.html +1 -1
  134. package/dist/docs/types/plot.PlanOperation.html +1 -1
  135. package/dist/docs/types/plot.Reaction.html +3 -3
  136. package/dist/docs/types/plot.Reactions.html +1 -1
  137. package/dist/docs/types/plot.Tags.html +1 -1
  138. package/dist/docs/types/plot.Thread.html +1 -1
  139. package/dist/docs/types/plot.ThreadAccessLevel.html +1 -1
  140. package/dist/docs/types/plot.ThreadCommon.html +6 -6
  141. package/dist/docs/types/plot.ThreadFilter.html +2 -2
  142. package/dist/docs/types/plot.ThreadMeta.html +1 -1
  143. package/dist/docs/types/plot.ThreadType.html +1 -1
  144. package/dist/docs/types/plot.ThreadUpdate.html +1 -1
  145. package/dist/docs/types/plot.ThreadWithNotes.html +1 -1
  146. package/dist/docs/types/tools_ai.AIAssistantMessage.html +2 -2
  147. package/dist/docs/types/tools_ai.AICapabilities.html +4 -4
  148. package/dist/docs/types/tools_ai.AIMessage.html +1 -1
  149. package/dist/docs/types/tools_ai.AIOptions.html +2 -2
  150. package/dist/docs/types/tools_ai.AISource.html +1 -1
  151. package/dist/docs/types/tools_ai.AISystemMessage.html +2 -2
  152. package/dist/docs/types/tools_ai.AITool.html +1 -1
  153. package/dist/docs/types/tools_ai.AIToolMessage.html +2 -2
  154. package/dist/docs/types/tools_ai.AIToolSet.html +1 -1
  155. package/dist/docs/types/tools_ai.AIUsage.html +5 -5
  156. package/dist/docs/types/tools_ai.AIUserMessage.html +2 -2
  157. package/dist/docs/types/tools_ai.DataContent.html +1 -1
  158. package/dist/docs/types/tools_ai.ModelPreferences.html +5 -5
  159. package/dist/docs/types/tools_callbacks.Callback.html +2 -2
  160. package/dist/docs/types/tools_integrations.ArchiveLinkFilter.html +5 -5
  161. package/dist/docs/types/tools_integrations.ArchiveNotesFilter.html +2 -2
  162. package/dist/docs/types/tools_integrations.AuthToken.html +6 -5
  163. package/dist/docs/types/tools_integrations.Authorization.html +4 -4
  164. package/dist/docs/types/tools_integrations.Channel.html +6 -6
  165. package/dist/docs/types/tools_integrations.ComposeConfig.html +4 -4
  166. package/dist/docs/types/tools_integrations.ContactRoleConfig.html +5 -5
  167. package/dist/docs/types/tools_integrations.LinkTypeConfig.html +21 -21
  168. package/dist/docs/types/tools_integrations.NewCustomEmoji.html +8 -8
  169. package/dist/docs/types/tools_integrations.StatusIcon.html +1 -1
  170. package/dist/docs/types/tools_integrations.SyncContext.html +4 -4
  171. package/dist/docs/types/tools_network.WebhookRequest.html +6 -6
  172. package/dist/docs/types/tools_plot.LinkFilter.html +5 -5
  173. package/dist/docs/types/tools_plot.LinkSearchResult.html +1 -1
  174. package/dist/docs/types/tools_plot.NoteIntentHandler.html +4 -4
  175. package/dist/docs/types/tools_plot.NoteSearchResult.html +1 -1
  176. package/dist/docs/types/tools_plot.SearchOptions.html +4 -4
  177. package/dist/docs/types/tools_plot.SearchResult.html +1 -1
  178. package/dist/docs/types/tools_twists.Log.html +2 -2
  179. package/dist/docs/types/tools_twists.TwistPermissions.html +1 -1
  180. package/dist/docs/types/utils_types.BuiltInTools.html +2 -2
  181. package/dist/docs/types/utils_types.ExtractBuildReturn.html +1 -1
  182. package/dist/docs/types/utils_types.InferOptions.html +1 -1
  183. package/dist/docs/types/utils_types.InferTools.html +1 -1
  184. package/dist/docs/types/utils_types.JSONValue.html +1 -1
  185. package/dist/docs/types/utils_types.PromiseValues.html +1 -1
  186. package/dist/docs/types/utils_types.ToolBuilder.html +1 -1
  187. package/dist/docs/variables/tools_plot.SEARCH_DEFAULT_LIMIT.html +1 -1
  188. package/dist/docs/variables/tools_plot.SEARCH_MAX_LIMIT.html +1 -1
  189. package/dist/facets.d.ts +30 -0
  190. package/dist/facets.d.ts.map +1 -0
  191. package/dist/facets.js +16 -0
  192. package/dist/facets.js.map +1 -0
  193. package/dist/llm-docs/connector.d.ts +1 -1
  194. package/dist/llm-docs/connector.d.ts.map +1 -1
  195. package/dist/llm-docs/connector.js +1 -1
  196. package/dist/llm-docs/connector.js.map +1 -1
  197. package/dist/llm-docs/facets.d.ts +9 -0
  198. package/dist/llm-docs/facets.d.ts.map +1 -0
  199. package/dist/llm-docs/facets.js +8 -0
  200. package/dist/llm-docs/facets.js.map +1 -0
  201. package/dist/llm-docs/index.d.ts.map +1 -1
  202. package/dist/llm-docs/index.js +2 -0
  203. package/dist/llm-docs/index.js.map +1 -1
  204. package/dist/llm-docs/plot.d.ts +1 -1
  205. package/dist/llm-docs/plot.d.ts.map +1 -1
  206. package/dist/llm-docs/plot.js +1 -1
  207. package/dist/llm-docs/plot.js.map +1 -1
  208. package/dist/llm-docs/tool.d.ts +1 -1
  209. package/dist/llm-docs/tool.d.ts.map +1 -1
  210. package/dist/llm-docs/tool.js +1 -1
  211. package/dist/llm-docs/tool.js.map +1 -1
  212. package/dist/llm-docs/tools/ai.d.ts +1 -1
  213. package/dist/llm-docs/tools/ai.d.ts.map +1 -1
  214. package/dist/llm-docs/tools/ai.js +1 -1
  215. package/dist/llm-docs/tools/ai.js.map +1 -1
  216. package/dist/llm-docs/tools/callbacks.d.ts +1 -1
  217. package/dist/llm-docs/tools/callbacks.d.ts.map +1 -1
  218. package/dist/llm-docs/tools/callbacks.js +1 -1
  219. package/dist/llm-docs/tools/callbacks.js.map +1 -1
  220. package/dist/llm-docs/tools/files.d.ts +1 -1
  221. package/dist/llm-docs/tools/files.d.ts.map +1 -1
  222. package/dist/llm-docs/tools/files.js +1 -1
  223. package/dist/llm-docs/tools/files.js.map +1 -1
  224. package/dist/llm-docs/tools/imap.d.ts +1 -1
  225. package/dist/llm-docs/tools/imap.d.ts.map +1 -1
  226. package/dist/llm-docs/tools/imap.js +1 -1
  227. package/dist/llm-docs/tools/imap.js.map +1 -1
  228. package/dist/llm-docs/tools/integrations.d.ts +1 -1
  229. package/dist/llm-docs/tools/integrations.d.ts.map +1 -1
  230. package/dist/llm-docs/tools/integrations.js +1 -1
  231. package/dist/llm-docs/tools/integrations.js.map +1 -1
  232. package/dist/llm-docs/tools/network.d.ts +1 -1
  233. package/dist/llm-docs/tools/network.d.ts.map +1 -1
  234. package/dist/llm-docs/tools/network.js +1 -1
  235. package/dist/llm-docs/tools/network.js.map +1 -1
  236. package/dist/llm-docs/tools/plot.d.ts +1 -1
  237. package/dist/llm-docs/tools/plot.d.ts.map +1 -1
  238. package/dist/llm-docs/tools/plot.js +1 -1
  239. package/dist/llm-docs/tools/plot.js.map +1 -1
  240. package/dist/llm-docs/tools/smtp.d.ts +1 -1
  241. package/dist/llm-docs/tools/smtp.d.ts.map +1 -1
  242. package/dist/llm-docs/tools/smtp.js +1 -1
  243. package/dist/llm-docs/tools/smtp.js.map +1 -1
  244. package/dist/llm-docs/tools/tasks.d.ts +1 -1
  245. package/dist/llm-docs/tools/tasks.d.ts.map +1 -1
  246. package/dist/llm-docs/tools/tasks.js +1 -1
  247. package/dist/llm-docs/tools/tasks.js.map +1 -1
  248. package/dist/llm-docs/tools/twists.d.ts +1 -1
  249. package/dist/llm-docs/tools/twists.d.ts.map +1 -1
  250. package/dist/llm-docs/tools/twists.js +1 -1
  251. package/dist/llm-docs/tools/twists.js.map +1 -1
  252. package/dist/llm-docs/twist-guide-template.d.ts +1 -1
  253. package/dist/llm-docs/twist-guide-template.d.ts.map +1 -1
  254. package/dist/llm-docs/twist-guide-template.js +1 -1
  255. package/dist/llm-docs/twist-guide-template.js.map +1 -1
  256. package/dist/llm-docs/twist.d.ts +1 -1
  257. package/dist/llm-docs/twist.d.ts.map +1 -1
  258. package/dist/llm-docs/twist.js +1 -1
  259. package/dist/llm-docs/twist.js.map +1 -1
  260. package/dist/plot.d.ts +15 -8
  261. package/dist/plot.d.ts.map +1 -1
  262. package/dist/plot.js.map +1 -1
  263. package/dist/tool.d.ts +4 -4
  264. package/dist/tool.js +4 -4
  265. package/dist/tools/ai.d.ts +12 -13
  266. package/dist/tools/ai.d.ts.map +1 -1
  267. package/dist/tools/ai.js +8 -9
  268. package/dist/tools/ai.js.map +1 -1
  269. package/dist/tools/callbacks.d.ts +1 -1
  270. package/dist/tools/files.d.ts +2 -2
  271. package/dist/tools/imap.d.ts +1 -1
  272. package/dist/tools/imap.js +1 -1
  273. package/dist/tools/integrations.d.ts +2 -1
  274. package/dist/tools/integrations.d.ts.map +1 -1
  275. package/dist/tools/network.d.ts +5 -5
  276. package/dist/tools/plot.d.ts +42 -37
  277. package/dist/tools/plot.d.ts.map +1 -1
  278. package/dist/tools/plot.js +16 -12
  279. package/dist/tools/plot.js.map +1 -1
  280. package/dist/tools/smtp.d.ts +1 -1
  281. package/dist/tools/smtp.js +1 -1
  282. package/dist/tools/tasks.d.ts +6 -8
  283. package/dist/tools/tasks.d.ts.map +1 -1
  284. package/dist/tools/tasks.js +5 -7
  285. package/dist/tools/tasks.js.map +1 -1
  286. package/dist/tools/twists.d.ts +15 -14
  287. package/dist/tools/twists.d.ts.map +1 -1
  288. package/dist/tools/twists.js +2 -2
  289. package/dist/tools/twists.js.map +1 -1
  290. package/dist/twist-guide.d.ts +1 -1
  291. package/dist/twist-guide.d.ts.map +1 -1
  292. package/dist/twist.d.ts +2 -2
  293. package/dist/twist.js +2 -2
  294. package/package.json +6 -1
  295. package/src/connector.ts +23 -16
  296. package/src/facets.ts +40 -0
  297. package/src/llm-docs/connector.ts +1 -1
  298. package/src/llm-docs/facets.ts +8 -0
  299. package/src/llm-docs/index.ts +2 -0
  300. package/src/llm-docs/plot.ts +1 -1
  301. package/src/llm-docs/tool.ts +1 -1
  302. package/src/llm-docs/tools/ai.ts +1 -1
  303. package/src/llm-docs/tools/callbacks.ts +1 -1
  304. package/src/llm-docs/tools/files.ts +1 -1
  305. package/src/llm-docs/tools/imap.ts +1 -1
  306. package/src/llm-docs/tools/integrations.ts +1 -1
  307. package/src/llm-docs/tools/network.ts +1 -1
  308. package/src/llm-docs/tools/plot.ts +1 -1
  309. package/src/llm-docs/tools/smtp.ts +1 -1
  310. package/src/llm-docs/tools/tasks.ts +1 -1
  311. package/src/llm-docs/tools/twists.ts +1 -1
  312. package/src/llm-docs/twist-guide-template.ts +1 -1
  313. package/src/llm-docs/twist.ts +1 -1
  314. package/src/plot.ts +15 -8
  315. package/src/tool.ts +4 -4
  316. package/src/tools/ai.ts +12 -13
  317. package/src/tools/callbacks.ts +1 -1
  318. package/src/tools/files.ts +2 -2
  319. package/src/tools/imap.ts +1 -1
  320. package/src/tools/integrations.ts +2 -1
  321. package/src/tools/network.ts +5 -5
  322. package/src/tools/plot.ts +42 -37
  323. package/src/tools/smtp.ts +1 -1
  324. package/src/tools/tasks.ts +6 -8
  325. package/src/tools/twists.ts +15 -14
  326. package/src/twist.ts +2 -2
  327. package/dist/docs/media/MULTI_USER_AUTH.md +0 -116
  328. package/dist/docs/media/SYNC_STRATEGIES.md +0 -818
@@ -1,71 +1,111 @@
1
1
  <!DOCTYPE html><html class="default" lang="en" data-base="../"><head><meta charset="utf-8"/><meta http-equiv="x-ua-compatible" content="IE=edge"/><title>Built-in Tools | Creating Plot Twists</title><link rel="icon" href="../assets/favicon.svg" type="image/svg+xml"/><meta name="description" content="Documentation for Creating Plot Twists"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="../assets/style.css"/><link rel="stylesheet" href="../assets/highlight.css"/><script defer src="../assets/main.js"></script><script async src="../assets/icons.js" id="tsd-icons-script"></script><script async src="../assets/search.js" id="tsd-search-script"></script><script async src="../assets/navigation.js" id="tsd-nav-script"></script><script async src="../assets/hierarchy.js" id="tsd-hierarchy-script"></script></head><body><script>document.documentElement.dataset.theme = localStorage.getItem("tsd-theme") || "os";document.body.style.display="none";setTimeout(() => window.app?app.showPage():document.body.style.removeProperty("display"),500)</script><header class="tsd-page-toolbar"><div class="tsd-toolbar-contents container"><a href="/" class="title">Creating Plot Twists</a><div id="tsd-toolbar-links"><a href="https://plot.day">Plot</a><a href="https://github.com/plotday/plot">GitHub</a><a href="https://www.npmjs.com/package/@plotday/twister">NPM</a></div><button id="tsd-search-trigger" class="tsd-widget" aria-label="Search"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-search"></use></svg></button><dialog id="tsd-search" aria-label="Search"><input role="combobox" id="tsd-search-input" aria-controls="tsd-search-results" aria-autocomplete="list" aria-expanded="true" autocapitalize="off" autocomplete="off" placeholder="Search the docs" maxLength="100"/><ul role="listbox" id="tsd-search-results"></ul><div id="tsd-search-status" aria-live="polite" aria-atomic="true"><div>Preparing search index...</div></div></dialog><a href="#" class="tsd-widget menu" id="tsd-toolbar-menu-trigger" data-toggle="menu" aria-label="Menu"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-menu"></use></svg></a></div></header><div class="container container-main"><div class="col-content"><div class="tsd-page-title"><ul class="tsd-breadcrumb" aria-label="Breadcrumb"><li><a href="" aria-current="page">Built-in Tools</a></li></ul></div><div class="tsd-panel tsd-typography"><h1 id="built-in-tools" class="tsd-anchor-link">Built-in Tools<a href="#built-in-tools" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h1><p>Plot provides a comprehensive set of built-in tools that give your twists powerful capabilities. This guide covers all built-in tools with detailed examples and best practices.</p>
2
2
  <h2 id="table-of-contents" class="tsd-anchor-link">Table of Contents<a href="#table-of-contents" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h2><ul>
3
- <li><a href="#plot">Plot</a> - Managing activities and priorities</li>
3
+ <li><a href="#plot">Plot</a> - Managing threads, notes, and focuses</li>
4
4
  <li><a href="#store">Store</a> - Persistent key-value storage</li>
5
- <li><a href="#integrations">Integrations</a> - OAuth authentication</li>
5
+ <li><a href="#integrations">Integrations</a> - OAuth authentication and connector data sync</li>
6
6
  <li><a href="#tasks">Tasks</a> - Background task execution</li>
7
7
  <li><a href="#network">Network</a> - HTTP access and webhooks</li>
8
8
  <li><a href="#callbacks">Callbacks</a> - Persistent function references</li>
9
9
  <li><a href="#ai">AI</a> - Language model integration</li>
10
+ <li><a href="#files">Files</a> - Reading note attachments</li>
11
+ <li><a href="#other-built-in-tools">Other Built-in Tools</a> - Imap, Smtp, Twists</li>
10
12
  </ul>
11
13
  <hr>
12
- <h2 id="plot" class="tsd-anchor-link">Plot<a href="#plot" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h2><p>The Plot tool is the core interface for creating and managing activities and priorities.</p>
13
- <h3 id="workspace-level-twists" class="tsd-anchor-link">Workspace-Level Twists<a href="#workspace-level-twists" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p>A twist is installed by a single user at the workspace level — it is <strong>not</strong> scoped to a particular priority. <code>this.userId</code> exposes the installing user's ID, and <code>plot.getUserId()</code> is available if you need it explicitly.</p>
14
- <p>When a twist creates a thread or link without specifying a priority, the server picks one automatically for the owner user via <code>match_priority_for_user</code>. You only need to provide a <code>priority</code> (or a <code>priorityId</code> filter) when you want to override that automatic routing.</p>
15
- <h3 id="understanding-activities-and-notes" class="tsd-anchor-link">Understanding Activities and Notes<a href="#understanding-activities-and-notes" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p><strong>Activity</strong> represents something done or to be done (a task, event, or conversation), while <strong>Notes</strong> represent the updates and details on that activity.</p>
16
- <p><strong>Think of an Activity as a thread</strong> on a messaging platform, and <strong>Notes as the messages in that thread</strong>. Always create activities with an initial note, and add notes to existing activities for updates rather than creating new activities.</p>
17
- <h3 id="setup" class="tsd-anchor-link">Setup<a href="#setup" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><pre><code class="typescript"><span class="hl-0">import</span><span class="hl-1"> { </span><span class="hl-2">Plot</span><span class="hl-1"> } </span><span class="hl-0">from</span><span class="hl-1"> </span><span class="hl-3">&quot;@plotday/twister/tools/plot&quot;</span><span class="hl-1">;</span><br/><br/><span class="hl-6">build</span><span class="hl-1">(</span><span class="hl-2">build</span><span class="hl-1">: </span><span class="hl-2">ToolBuilder</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-0">return</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">plot:</span><span class="hl-1"> </span><span class="hl-6">build</span><span class="hl-1">(</span><span class="hl-2">Plot</span><span class="hl-1">),</span><br/><span class="hl-1"> };</span><br/><span class="hl-1">}</span>
14
+ <h2 id="plot" class="tsd-anchor-link">Plot<a href="#plot" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h2><p>The Plot tool is the core interface for creating and managing threads, notes, and focuses.</p>
15
+ <h3 id="workspace-level-twists" class="tsd-anchor-link">Workspace-Level Twists<a href="#workspace-level-twists" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p>A twist is installed by a single user at the workspace level — it is <strong>not</strong> scoped to a particular focus. <code>this.userId</code> exposes the installing user's ID, and <code>plot.getUserId()</code> is available if you need it explicitly. <code>plot.getOwner()</code> returns the full <code>Actor</code> (name/email) for the installing user.</p>
16
+ <p>When a twist creates a thread without specifying a focus, the server classifies it automatically using the owner's focus rules. You only need to provide a <code>focus</code> when you want to override that automatic routing.</p>
17
+ <h3 id="understanding-threads-and-notes" class="tsd-anchor-link">Understanding Threads and Notes<a href="#understanding-threads-and-notes" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p><strong>Thread</strong> represents a conversation or item (a task, event, or discussion), while <strong>Notes</strong> represent the updates and details on that thread.</p>
18
+ <p><strong>Think of a Thread as a thread</strong> on a messaging platform, and <strong>Notes as the messages in that thread</strong>. Always create threads with an initial note, and add notes to existing threads for updates rather than creating new threads.</p>
19
+ <h3 id="setup" class="tsd-anchor-link">Setup<a href="#setup" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p>All Plot permissions must be explicitly requested in <code>build()</code> there are no default permissions:</p>
20
+ <pre><code class="typescript"><span class="hl-0">import</span><span class="hl-1"> { </span><span class="hl-2">Plot</span><span class="hl-1">, </span><span class="hl-2">ThreadAccess</span><span class="hl-1">, </span><span class="hl-2">FocusAccess</span><span class="hl-1">, </span><span class="hl-2">ContactAccess</span><span class="hl-1"> } </span><span class="hl-0">from</span><span class="hl-1"> </span><span class="hl-3">&quot;@plotday/twister/tools/plot&quot;</span><span class="hl-1">;</span><br/><br/><span class="hl-6">build</span><span class="hl-1">(</span><span class="hl-2">build</span><span class="hl-1">: </span><span class="hl-2">ToolBuilder</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-0">return</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">plot:</span><span class="hl-1"> </span><span class="hl-6">build</span><span class="hl-1">(</span><span class="hl-2">Plot</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-2">thread:</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">access:</span><span class="hl-1"> </span><span class="hl-2">ThreadAccess</span><span class="hl-1">.</span><span class="hl-2">Create</span><span class="hl-1">,</span><br/><span class="hl-1"> },</span><br/><span class="hl-1"> </span><span class="hl-2">focus:</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">access:</span><span class="hl-1"> </span><span class="hl-2">FocusAccess</span><span class="hl-1">.</span><span class="hl-2">Create</span><span class="hl-1">,</span><br/><span class="hl-1"> },</span><br/><span class="hl-1"> </span><span class="hl-2">contact:</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">access:</span><span class="hl-1"> </span><span class="hl-2">ContactAccess</span><span class="hl-1">.</span><span class="hl-2">Read</span><span class="hl-1">,</span><br/><span class="hl-1"> },</span><br/><span class="hl-1"> }),</span><br/><span class="hl-1"> };</span><br/><span class="hl-1">}</span>
18
21
  </code><button type="button">Copy</button></pre>
19
22
 
20
- <h3 id="creating-activities" class="tsd-anchor-link">Creating Activities<a href="#creating-activities" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><pre><code class="typescript"><span class="hl-0">import</span><span class="hl-1"> { </span><span class="hl-2">ActivityLinkType</span><span class="hl-1">, </span><span class="hl-2">ActivityType</span><span class="hl-1"> } </span><span class="hl-0">from</span><span class="hl-1"> </span><span class="hl-3">&quot;@plotday/twister&quot;</span><span class="hl-1">;</span><br/><br/><span class="hl-7">// Create a note (thread with initial message)</span><br/><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">createActivity</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">type:</span><span class="hl-1"> </span><span class="hl-2">ActivityType</span><span class="hl-1">.</span><span class="hl-2">Note</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">title:</span><span class="hl-1"> </span><span class="hl-3">&quot;Q1 Planning Meeting Notes&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">notes:</span><span class="hl-1"> [</span><br/><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">content:</span><span class="hl-1"> </span><span class="hl-3">&quot;Discussed goals for Q1 and assigned action items.&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> },</span><br/><span class="hl-1"> ],</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-7">// Create a task from external source with automatic deduplication</span><br/><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">createActivity</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">source:</span><span class="hl-1"> </span><span class="hl-3">&quot;https://github.com/org/repo/pull/123&quot;</span><span class="hl-1">, </span><span class="hl-7">// Enables automatic upserts</span><br/><span class="hl-1"> </span><span class="hl-2">type:</span><span class="hl-1"> </span><span class="hl-2">ActivityType</span><span class="hl-1">.</span><span class="hl-2">Action</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">title:</span><span class="hl-1"> </span><span class="hl-3">&quot;Review pull request #123&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">notes:</span><span class="hl-1"> [</span><br/><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">activity:</span><span class="hl-1"> { </span><span class="hl-2">source:</span><span class="hl-1"> </span><span class="hl-3">&quot;https://github.com/org/repo/pull/123&quot;</span><span class="hl-1"> },</span><br/><span class="hl-1"> </span><span class="hl-2">key:</span><span class="hl-1"> </span><span class="hl-3">&quot;description&quot;</span><span class="hl-1">, </span><span class="hl-7">// Using key enables upserts</span><br/><span class="hl-1"> </span><span class="hl-2">content:</span><span class="hl-1"> </span><span class="hl-3">&quot;Please review the changes and provide feedback.&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">links:</span><span class="hl-1"> [</span><br/><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">type:</span><span class="hl-1"> </span><span class="hl-2">ActivityLinkType</span><span class="hl-1">.</span><span class="hl-2">external</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">title:</span><span class="hl-1"> </span><span class="hl-3">&quot;View PR&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">url:</span><span class="hl-1"> </span><span class="hl-3">&quot;https://github.com/org/repo/pull/123&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> },</span><br/><span class="hl-1"> ],</span><br/><span class="hl-1"> },</span><br/><span class="hl-1"> ],</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-7">// Create an event with description in a note</span><br/><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">createActivity</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">type:</span><span class="hl-1"> </span><span class="hl-2">ActivityType</span><span class="hl-1">.</span><span class="hl-2">Event</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">title:</span><span class="hl-1"> </span><span class="hl-3">&quot;Team standup&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">start:</span><span class="hl-1"> </span><span class="hl-4">new</span><span class="hl-1"> </span><span class="hl-6">Date</span><span class="hl-1">(</span><span class="hl-3">&quot;2025-02-01T10:00:00Z&quot;</span><span class="hl-1">),</span><br/><span class="hl-1"> </span><span class="hl-2">end:</span><span class="hl-1"> </span><span class="hl-4">new</span><span class="hl-1"> </span><span class="hl-6">Date</span><span class="hl-1">(</span><span class="hl-3">&quot;2025-02-01T10:30:00Z&quot;</span><span class="hl-1">),</span><br/><span class="hl-1"> </span><span class="hl-2">notes:</span><span class="hl-1"> [</span><br/><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">content:</span><span class="hl-1"> </span><span class="hl-3">&quot;Daily standup meeting to sync on progress.&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> },</span><br/><span class="hl-1"> ],</span><br/><span class="hl-1">});</span>
23
+ <p>Available permission levels:</p>
24
+ <ul>
25
+ <li><strong><code>ThreadAccess</code></strong>: <code>Respond</code> (notes/tags on threads where the twist is mentioned) → <code>Create</code> (create threads, notes in own threads) → <code>Full</code> (list/query and update any of the owner's threads)</li>
26
+ <li><strong><code>FocusAccess</code></strong>: <code>Create</code> (create focuses, update own) → <code>Full</code> (read, create, update, and archive any of the owner's focuses)</li>
27
+ <li><strong><code>ContactAccess.Read</code></strong>: read contact details (name/email). Without it, only actor IDs are provided.</li>
28
+ <li><strong><code>LinkAccess</code></strong>: <code>Read</code> → <code>Full</code> (update links, including moving them between threads). Enabled under the <code>link</code> option.</li>
29
+ <li><strong><code>link: true</code></strong>: receive links from connected source channels (<code>getLinks()</code>, <code>onLinkCreated</code>, <code>onLinkUpdated</code>, <code>onLinkNoteCreated</code>)</li>
30
+ <li><strong><code>search: true</code></strong>: semantic search across the owner's notes and links</li>
31
+ <li><strong><code>requireApproval: true</code></strong>: admin write operations require user approval via plans (see <a href="#plans-user-approved-operations">Plans</a>)</li>
32
+ </ul>
33
+ <h3 id="creating-threads" class="tsd-anchor-link">Creating Threads<a href="#creating-threads" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><pre><code class="typescript"><span class="hl-0">import</span><span class="hl-1"> { </span><span class="hl-2">ActionType</span><span class="hl-1"> } </span><span class="hl-0">from</span><span class="hl-1"> </span><span class="hl-3">&quot;@plotday/twister&quot;</span><span class="hl-1">;</span><br/><br/><span class="hl-7">// Create a thread with an initial note</span><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">threadId</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">createThread</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">title:</span><span class="hl-1"> </span><span class="hl-3">&quot;Q1 Planning Meeting Notes&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">notes:</span><span class="hl-1"> [</span><br/><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">content:</span><span class="hl-1"> </span><span class="hl-3">&quot;Discussed goals for Q1 and assigned action items.&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> },</span><br/><span class="hl-1"> ],</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-7">// Create a task-style thread with an action button</span><br/><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">createThread</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">type:</span><span class="hl-1"> </span><span class="hl-3">&quot;action&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">title:</span><span class="hl-1"> </span><span class="hl-3">&quot;Review pull request #123&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">notes:</span><span class="hl-1"> [</span><br/><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">key:</span><span class="hl-1"> </span><span class="hl-3">&quot;description&quot;</span><span class="hl-1">, </span><span class="hl-7">// Using key enables upserts</span><br/><span class="hl-1"> </span><span class="hl-2">content:</span><span class="hl-1"> </span><span class="hl-3">&quot;Please review the changes and provide feedback.&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">actions:</span><span class="hl-1"> [</span><br/><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">type:</span><span class="hl-1"> </span><span class="hl-2">ActionType</span><span class="hl-1">.</span><span class="hl-2">external</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">title:</span><span class="hl-1"> </span><span class="hl-3">&quot;View PR&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">url:</span><span class="hl-1"> </span><span class="hl-3">&quot;https://github.com/org/repo/pull/123&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> },</span><br/><span class="hl-1"> ],</span><br/><span class="hl-1"> },</span><br/><span class="hl-1"> ],</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-7">// Create an event with a schedule</span><br/><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">createThread</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">title:</span><span class="hl-1"> </span><span class="hl-3">&quot;Team standup&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">notes:</span><span class="hl-1"> [</span><br/><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">content:</span><span class="hl-1"> </span><span class="hl-3">&quot;Daily standup meeting to sync on progress.&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> },</span><br/><span class="hl-1"> ],</span><br/><span class="hl-1"> </span><span class="hl-2">schedules:</span><span class="hl-1"> [</span><br/><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">start:</span><span class="hl-1"> </span><span class="hl-4">new</span><span class="hl-1"> </span><span class="hl-6">Date</span><span class="hl-1">(</span><span class="hl-3">&quot;2025-02-01T10:00:00Z&quot;</span><span class="hl-1">),</span><br/><span class="hl-1"> </span><span class="hl-2">end:</span><span class="hl-1"> </span><span class="hl-4">new</span><span class="hl-1"> </span><span class="hl-6">Date</span><span class="hl-1">(</span><span class="hl-3">&quot;2025-02-01T10:30:00Z&quot;</span><span class="hl-1">),</span><br/><span class="hl-1"> },</span><br/><span class="hl-1"> ],</span><br/><span class="hl-1">});</span>
34
+ </code><button type="button">Copy</button></pre>
35
+
36
+ <p><code>createThread()</code> returns the new thread's <code>Uuid</code>. Use <code>createThreads()</code> to create many threads in one batch — it's significantly more efficient than calling <code>createThread()</code> in a loop.</p>
37
+ <p>The optional <code>type</code> field sets the thread's sub-type and icon: <code>&quot;action&quot;</code>, <code>&quot;notes&quot;</code>, <code>&quot;idea&quot;</code>, <code>&quot;goal&quot;</code>, <code>&quot;decision&quot;</code>, and (in shared focuses) <code>&quot;discussion&quot;</code>, <code>&quot;announcement&quot;</code>, <code>&quot;ask&quot;</code>.</p>
38
+ <p><strong>Marking items read for historical imports:</strong> Set <code>unread: false</code> on threads/notes created during bulk imports so historical items don't flood the user with unread indicators. Omit <code>unread</code> for normal, fresh content.</p>
39
+ <h3 id="updating-threads" class="tsd-anchor-link">Updating Threads<a href="#updating-threads" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><pre><code class="typescript"><span class="hl-7">// Rename a thread</span><br/><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">updateThread</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">id:</span><span class="hl-1"> </span><span class="hl-2">threadId</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">title:</span><span class="hl-1"> </span><span class="hl-3">&quot;Updated title&quot;</span><span class="hl-1">,</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-7">// Archive a thread (there is no delete — archive instead)</span><br/><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">updateThread</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">id:</span><span class="hl-1"> </span><span class="hl-2">threadId</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">archived:</span><span class="hl-1"> </span><span class="hl-4">true</span><span class="hl-1">,</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-7">// Add or remove the twist&#39;s own tags without touching other actors&#39; tags</span><br/><span class="hl-0">import</span><span class="hl-1"> { </span><span class="hl-2">Tag</span><span class="hl-1"> } </span><span class="hl-0">from</span><span class="hl-1"> </span><span class="hl-3">&quot;@plotday/twister&quot;</span><span class="hl-1">;</span><br/><br/><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">updateThread</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">id:</span><span class="hl-1"> </span><span class="hl-2">threadId</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">twistTags:</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">[Tag.Todo]:</span><span class="hl-1"> </span><span class="hl-4">true</span><span class="hl-1">, </span><span class="hl-7">// Add the to-do tag</span><br/><span class="hl-1"> </span><span class="hl-2">[Tag.Done]:</span><span class="hl-1"> </span><span class="hl-4">false</span><span class="hl-1">, </span><span class="hl-7">// Remove the done tag</span><br/><span class="hl-1"> },</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-7">// Move a thread to a different focus (requires ThreadAccess.Full)</span><br/><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">updateThread</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">id:</span><span class="hl-1"> </span><span class="hl-2">threadId</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">focus:</span><span class="hl-1"> { </span><span class="hl-2">id:</span><span class="hl-1"> </span><span class="hl-2">focusId</span><span class="hl-1"> },</span><br/><span class="hl-1">});</span>
40
+ </code><button type="button">Copy</button></pre>
41
+
42
+ <p><code>updateThread()</code> only updates existing threads and throws if the thread doesn't exist. Only the fields you provide are changed. The thread can be identified by <code>id</code>, by <code>source</code> (the canonical external ID of a link on the thread), or by <code>match</code> for bulk updates of threads the twist created:</p>
43
+ <pre><code class="typescript"><span class="hl-7">// Bulk-archive all threads this twist created with matching metadata</span><br/><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">updateThread</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">match:</span><span class="hl-1"> { </span><span class="hl-2">meta:</span><span class="hl-1"> { </span><span class="hl-2">projectId:</span><span class="hl-1"> </span><span class="hl-3">&quot;TEAM&quot;</span><span class="hl-1"> } },</span><br/><span class="hl-1"> </span><span class="hl-2">archived:</span><span class="hl-1"> </span><span class="hl-4">true</span><span class="hl-1">,</span><br/><span class="hl-1">});</span>
44
+ </code><button type="button">Copy</button></pre>
45
+
46
+ <p>Scheduling is handled separately via <code>createSchedule()</code> / <code>getSchedules()</code> (see below).</p>
47
+ <h3 id="creating-and-managing-notes" class="tsd-anchor-link">Creating and Managing Notes<a href="#creating-and-managing-notes" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><h4 id="creating-notes-on-new-threads" class="tsd-anchor-link">Creating Notes on New Threads<a href="#creating-notes-on-new-threads" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h4><p><strong>Best Practice:</strong> Always create Threads with at least one initial Note containing detailed information. The <code>title</code> is a short summary that may be truncated—detailed content should go in Notes.</p>
48
+ <pre><code class="typescript"><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">createThread</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">type:</span><span class="hl-1"> </span><span class="hl-3">&quot;action&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">title:</span><span class="hl-1"> </span><span class="hl-3">&quot;Customer feedback: Login issues&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">notes:</span><span class="hl-1"> [</span><br/><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">key:</span><span class="hl-1"> </span><span class="hl-3">&quot;description&quot;</span><span class="hl-1">, </span><span class="hl-7">// Using key enables upserts</span><br/><span class="hl-1"> </span><span class="hl-2">content:</span><span class="hl-1"> </span><span class="hl-3">&quot;Customer reported:</span><span class="hl-12">\n\n\&quot;</span><span class="hl-3">I&#39;m unable to log in using Google SSO.</span><span class="hl-12">\&quot;\n\n</span><span class="hl-3">Priority: High</span><span class="hl-12">\n</span><span class="hl-3">Affected users: ~15 reports&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">actions:</span><span class="hl-1"> [</span><br/><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">type:</span><span class="hl-1"> </span><span class="hl-2">ActionType</span><span class="hl-1">.</span><span class="hl-2">external</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">title:</span><span class="hl-1"> </span><span class="hl-3">&quot;View Support Ticket&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">url:</span><span class="hl-1"> </span><span class="hl-3">&quot;https://support.example.com/tickets/12345&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> },</span><br/><span class="hl-1"> ],</span><br/><span class="hl-1"> },</span><br/><span class="hl-1"> ],</span><br/><span class="hl-1">});</span>
21
49
  </code><button type="button">Copy</button></pre>
22
50
 
23
- <h4 id="action-scheduling-states" class="tsd-anchor-link">Action Scheduling States<a href="#action-scheduling-states" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h4><p>When creating Actions (tasks), the <code>start</code> field determines their scheduling state. <strong>Important:</strong> Omitting <code>start</code> defaults to &quot;Do Now&quot; (current time). For most integrations, explicitly set <code>start: null</code> to create backlog items.</p>
24
- <pre><code class="typescript"><span class="hl-7">// &quot;Do Now&quot; - Actionable today (DEFAULT - use sparingly!)</span><br/><span class="hl-7">// Only use for tasks that are urgent or actively in progress</span><br/><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">createActivity</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">type:</span><span class="hl-1"> </span><span class="hl-2">ActivityType</span><span class="hl-1">.</span><span class="hl-2">Action</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">title:</span><span class="hl-1"> </span><span class="hl-3">&quot;Fix production bug&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">notes:</span><span class="hl-1"> [{ </span><span class="hl-2">content:</span><span class="hl-1"> </span><span class="hl-3">&quot;Critical issue affecting users&quot;</span><span class="hl-1"> }],</span><br/><span class="hl-1"> </span><span class="hl-7">// Omitting start defaults to new Date() - becomes &quot;Do Now&quot;</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-7">// &quot;Do Someday&quot; - Backlog item (RECOMMENDED default for most synced tasks)</span><br/><span class="hl-7">// Use for tasks from project management tools, backlog items, future work</span><br/><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">createActivity</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">type:</span><span class="hl-1"> </span><span class="hl-2">ActivityType</span><span class="hl-1">.</span><span class="hl-2">Action</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">title:</span><span class="hl-1"> </span><span class="hl-3">&quot;Refactor user service&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">start:</span><span class="hl-1"> </span><span class="hl-4">null</span><span class="hl-1">, </span><span class="hl-7">// Explicitly null for backlog</span><br/><span class="hl-1"> </span><span class="hl-2">source:</span><span class="hl-1"> </span><span class="hl-3">&quot;linear:issue:ABC-123&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">notes:</span><span class="hl-1"> [{ </span><span class="hl-2">content:</span><span class="hl-1"> </span><span class="hl-3">&quot;Technical debt to address&quot;</span><span class="hl-1"> }],</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-7">// &quot;Do Later&quot; - Scheduled for specific date</span><br/><span class="hl-7">// Use when task has a concrete due date</span><br/><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">createActivity</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">type:</span><span class="hl-1"> </span><span class="hl-2">ActivityType</span><span class="hl-1">.</span><span class="hl-2">Action</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">title:</span><span class="hl-1"> </span><span class="hl-3">&quot;Submit quarterly report&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">start:</span><span class="hl-1"> </span><span class="hl-4">new</span><span class="hl-1"> </span><span class="hl-6">Date</span><span class="hl-1">(</span><span class="hl-3">&quot;2025-03-31&quot;</span><span class="hl-1">), </span><span class="hl-7">// Due date</span><br/><span class="hl-1"> </span><span class="hl-2">notes:</span><span class="hl-1"> [{ </span><span class="hl-2">content:</span><span class="hl-1"> </span><span class="hl-3">&quot;Q1 report due end of March&quot;</span><span class="hl-1"> }],</span><br/><span class="hl-1">});</span>
51
+ <h4 id="adding-notes-to-existing-threads" class="tsd-anchor-link">Adding Notes to Existing Threads<a href="#adding-notes-to-existing-threads" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h4><p><strong>Best Practice:</strong> For related content (email threads, chat conversations, workflows), add Notes to the existing Thread rather than creating new Threads. Think of it like adding a message to an existing thread.</p>
52
+ <pre><code class="typescript"><span class="hl-7">// Add a new Note to an existing Thread (add message to thread)</span><br/><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">createNote</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">thread:</span><span class="hl-1"> { </span><span class="hl-2">id:</span><span class="hl-1"> </span><span class="hl-2">threadId</span><span class="hl-1"> },</span><br/><span class="hl-1"> </span><span class="hl-2">content:</span><span class="hl-1"> </span><span class="hl-3">&quot;Update: Engineering team has identified the root cause. Fix will be deployed in the next release.&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">actions:</span><span class="hl-1"> [</span><br/><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">type:</span><span class="hl-1"> </span><span class="hl-2">ActionType</span><span class="hl-1">.</span><span class="hl-2">external</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">title:</span><span class="hl-1"> </span><span class="hl-3">&quot;View PR Fix&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">url:</span><span class="hl-1"> </span><span class="hl-3">&quot;https://github.com/org/repo/pull/789&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> },</span><br/><span class="hl-1"> ],</span><br/><span class="hl-1">});</span>
25
53
  </code><button type="button">Copy</button></pre>
26
54
 
27
- <p><strong>Best practice for integrations:</strong> Default to <code>start: null</code> for synced tasks, and only set <code>start</code> to current time if the task is explicitly marked as current/in-progress in the source system.</p>
28
- <h3 id="updating-activities" class="tsd-anchor-link">Updating Activities<a href="#updating-activities" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><pre><code class="typescript"><span class="hl-7">// Mark task as done</span><br/><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">updateActivity</span><span class="hl-1">(</span><span class="hl-2">activity</span><span class="hl-1">.</span><span class="hl-2">id</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-2">done:</span><span class="hl-1"> </span><span class="hl-4">new</span><span class="hl-1"> </span><span class="hl-6">Date</span><span class="hl-1">(),</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-7">// Update title and preview</span><br/><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">updateActivity</span><span class="hl-1">(</span><span class="hl-2">activity</span><span class="hl-1">.</span><span class="hl-2">id</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-2">title:</span><span class="hl-1"> </span><span class="hl-3">&quot;Updated title&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">preview:</span><span class="hl-1"> </span><span class="hl-3">&quot;Additional information&quot;</span><span class="hl-1">,</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-7">// Reschedule event</span><br/><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">updateActivity</span><span class="hl-1">(</span><span class="hl-2">activity</span><span class="hl-1">.</span><span class="hl-2">id</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-2">start:</span><span class="hl-1"> </span><span class="hl-4">new</span><span class="hl-1"> </span><span class="hl-6">Date</span><span class="hl-1">(</span><span class="hl-3">&quot;2025-02-02T10:00:00Z&quot;</span><span class="hl-1">),</span><br/><span class="hl-1"> </span><span class="hl-2">end:</span><span class="hl-1"> </span><span class="hl-4">new</span><span class="hl-1"> </span><span class="hl-6">Date</span><span class="hl-1">(</span><span class="hl-3">&quot;2025-02-02T10:30:00Z&quot;</span><span class="hl-1">),</span><br/><span class="hl-1">});</span>
55
+ <p>The <code>thread</code> reference accepts <code>{ id }</code> or <code>{ source }</code> (the canonical external ID of a link on the thread useful for attaching notes to connector-synced threads). Notes support:</p>
56
+ <ul>
57
+ <li><strong><code>content</code></strong> — markdown content (set <code>contentType: &quot;text&quot;</code> or <code>&quot;html&quot;</code> to have the server convert; <code>&quot;markdown&quot;</code> is the default)</li>
58
+ <li><strong><code>key</code></strong> — a stable identifier that enables upserts: creating a note with an existing key updates it instead of duplicating</li>
59
+ <li><strong><code>actions</code></strong> — interactive buttons (<code>ActionType.external</code>, <code>ActionType.callback</code>, etc.)</li>
60
+ <li><strong><code>author</code></strong> — attribute the note to a contact instead of the twist</li>
61
+ </ul>
62
+ <p>Use <code>createNotes()</code> for batches, and <code>updateNote()</code> for partial updates to an existing note (identified by <code>id</code> or <code>key</code>).</p>
63
+ <pre><code class="typescript"><span class="hl-7">// Update note content</span><br/><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">updateNote</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">id:</span><span class="hl-1"> </span><span class="hl-2">noteId</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">content:</span><span class="hl-1"> </span><span class="hl-3">&quot;Updated content with more details&quot;</span><span class="hl-1">,</span><br/><span class="hl-1">});</span>
64
+ </code><button type="button">Copy</button></pre>
65
+
66
+ <h4 id="pattern-conversations-and-message-threads" class="tsd-anchor-link">Pattern: Conversations and Message Threads<a href="#pattern-conversations-and-message-threads" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h4><p>Keep all messages in a conversation within a single Thread. Think of it like a messaging app — one thread, many messages. Use stable note <code>key</code>s so re-syncing the same messages upserts instead of duplicating:</p>
67
+ <pre><code class="typescript"><span class="hl-2">async</span><span class="hl-1"> </span><span class="hl-6">handleConversation</span><span class="hl-1">(</span><span class="hl-2">conversation</span><span class="hl-1">: </span><span class="hl-2">Conversation</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-7">// Look up the thread created for this conversation, if any</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">mappingKey</span><span class="hl-1"> = </span><span class="hl-3">`conversation:</span><span class="hl-4">${</span><span class="hl-2">conversation</span><span class="hl-13">.</span><span class="hl-2">id</span><span class="hl-4">}</span><span class="hl-3">`</span><span class="hl-1">;</span><br/><span class="hl-1"> </span><span class="hl-4">let</span><span class="hl-1"> </span><span class="hl-2">threadId</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">get</span><span class="hl-1">&lt;</span><span class="hl-5">Uuid</span><span class="hl-1">&gt;(</span><span class="hl-2">mappingKey</span><span class="hl-1">);</span><br/><br/><span class="hl-1"> </span><span class="hl-0">if</span><span class="hl-1"> (!</span><span class="hl-2">threadId</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-2">threadId</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">createThread</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">title:</span><span class="hl-1"> </span><span class="hl-2">conversation</span><span class="hl-1">.</span><span class="hl-2">subject</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">notes:</span><span class="hl-1"> </span><span class="hl-2">conversation</span><span class="hl-1">.</span><span class="hl-2">messages</span><span class="hl-1">.</span><span class="hl-6">map</span><span class="hl-1">((</span><span class="hl-2">msg</span><span class="hl-1">) </span><span class="hl-4">=&gt;</span><span class="hl-1"> ({</span><br/><span class="hl-1"> </span><span class="hl-2">key:</span><span class="hl-1"> </span><span class="hl-3">`message-</span><span class="hl-4">${</span><span class="hl-2">msg</span><span class="hl-13">.</span><span class="hl-2">id</span><span class="hl-4">}</span><span class="hl-3">`</span><span class="hl-1">, </span><span class="hl-7">// Unique, immutable key per message</span><br/><span class="hl-1"> </span><span class="hl-2">content:</span><span class="hl-1"> </span><span class="hl-2">msg</span><span class="hl-1">.</span><span class="hl-2">body</span><span class="hl-1">,</span><br/><span class="hl-1"> })),</span><br/><span class="hl-1"> });</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">set</span><span class="hl-1">(</span><span class="hl-2">mappingKey</span><span class="hl-1">, </span><span class="hl-2">threadId</span><span class="hl-1">);</span><br/><span class="hl-1"> } </span><span class="hl-0">else</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-7">// Upsert messages into the existing thread — keys deduplicate</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">createNotes</span><span class="hl-1">(</span><br/><span class="hl-1"> </span><span class="hl-2">conversation</span><span class="hl-1">.</span><span class="hl-2">messages</span><span class="hl-1">.</span><span class="hl-6">map</span><span class="hl-1">((</span><span class="hl-2">msg</span><span class="hl-1">) </span><span class="hl-4">=&gt;</span><span class="hl-1"> ({</span><br/><span class="hl-1"> </span><span class="hl-2">thread:</span><span class="hl-1"> { </span><span class="hl-2">id:</span><span class="hl-1"> </span><span class="hl-2">threadId</span><span class="hl-1">! },</span><br/><span class="hl-1"> </span><span class="hl-2">key:</span><span class="hl-1"> </span><span class="hl-3">`message-</span><span class="hl-4">${</span><span class="hl-2">msg</span><span class="hl-13">.</span><span class="hl-2">id</span><span class="hl-4">}</span><span class="hl-3">`</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">content:</span><span class="hl-1"> </span><span class="hl-2">msg</span><span class="hl-1">.</span><span class="hl-2">body</span><span class="hl-1">,</span><br/><span class="hl-1"> }))</span><br/><span class="hl-1"> );</span><br/><span class="hl-1"> }</span><br/><span class="hl-1">}</span>
29
68
  </code><button type="button">Copy</button></pre>
30
69
 
31
- <h3 id="deleting-activities" class="tsd-anchor-link">Deleting Activities<a href="#deleting-activities" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><pre><code class="typescript"><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">deleteActivity</span><span class="hl-1">(</span><span class="hl-2">activity</span><span class="hl-1">.</span><span class="hl-2">id</span><span class="hl-1">);</span>
70
+ <p><strong>Why this matters:</strong> A conversation with 20 messages should be one Thread with 20 Notes, not 20 separate Threads. This keeps the workspace organized and provides better context.</p>
71
+ <p><strong>Note for connectors:</strong> If you're building a Connector that syncs an external system, don't use the Plot tool for this — use <code>integrations.saveLink()</code> / <code>saveLinks()</code>, which handle thread+link upserts by <code>sources</code> automatically. See <a href="SYNC_STRATEGIES.html">Sync Strategies</a> and <a href="Building_Connectors.html">Building Connectors</a>.</p>
72
+ <h3 id="reading-threads-and-notes" class="tsd-anchor-link">Reading Threads and Notes<a href="#reading-threads-and-notes" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><pre><code class="typescript"><span class="hl-7">// Look up a thread by ID or by a link&#39;s canonical source</span><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">thread</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">getThread</span><span class="hl-1">({ </span><span class="hl-2">id:</span><span class="hl-1"> </span><span class="hl-2">threadId</span><span class="hl-1"> });</span><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">synced</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">getThread</span><span class="hl-1">({ </span><span class="hl-2">source:</span><span class="hl-1"> </span><span class="hl-3">&quot;linear:issue:ABC-123&quot;</span><span class="hl-1"> });</span><br/><br/><span class="hl-7">// Look up a note by ID or key</span><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">note</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">getNote</span><span class="hl-1">({ </span><span class="hl-2">key:</span><span class="hl-1"> </span><span class="hl-3">&quot;description&quot;</span><span class="hl-1"> });</span><br/><br/><span class="hl-7">// All notes in a thread, ordered by creation time</span><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">notes</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">getNotes</span><span class="hl-1">(</span><span class="hl-2">thread</span><span class="hl-1">);</span><br/><br/><span class="hl-7">// List the owner&#39;s threads (requires ThreadAccess.Full).</span><br/><span class="hl-7">// Defaults to the owner&#39;s Inbox; limit defaults to 50 (max 200).</span><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">inboxThreads</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">getThreads</span><span class="hl-1">();</span><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">focusThreads</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">getThreads</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">focusId</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">includeArchived:</span><span class="hl-1"> </span><span class="hl-4">false</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">limit:</span><span class="hl-1"> </span><span class="hl-14">100</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">offset:</span><span class="hl-1"> </span><span class="hl-14">0</span><span class="hl-1">,</span><br/><span class="hl-1">});</span>
32
73
  </code><button type="button">Copy</button></pre>
33
74
 
34
- <h3 id="managing-priorities" class="tsd-anchor-link">Managing Priorities<a href="#managing-priorities" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><pre><code class="typescript"><span class="hl-7">// Create a top-level priority</span><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">work</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">createPriority</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">title:</span><span class="hl-1"> </span><span class="hl-3">&quot;Work&quot;</span><span class="hl-1">,</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-7">// Create a nested priority</span><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">project</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">createPriority</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">title:</span><span class="hl-1"> </span><span class="hl-3">&quot;Project A&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">parentId:</span><span class="hl-1"> </span><span class="hl-2">work</span><span class="hl-1">.</span><span class="hl-2">id</span><span class="hl-1">,</span><br/><span class="hl-1">});</span>
75
+ <h3 id="managing-focuses" class="tsd-anchor-link">Managing Focuses<a href="#managing-focuses" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p>Focuses are flat organizational containers (like projects or areas of life) they have no parents or children. Threads not matched to any focus live in the Inbox.</p>
76
+ <pre><code class="typescript"><span class="hl-7">// Create a focus (upserts by key if one is provided)</span><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">focus</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">createFocus</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">title:</span><span class="hl-1"> </span><span class="hl-3">&quot;Work&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">key:</span><span class="hl-1"> </span><span class="hl-3">&quot;work&quot;</span><span class="hl-1">, </span><span class="hl-7">// Optional: enables lookup/upsert without storing the UUID</span><br/><span class="hl-1">});</span><br/><span class="hl-7">// focus.created tells you whether it was newly created or already existed</span><br/><br/><span class="hl-7">// Look up a focus by ID or key</span><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">existing</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">getFocus</span><span class="hl-1">({ </span><span class="hl-2">key:</span><span class="hl-1"> </span><span class="hl-3">&quot;work&quot;</span><span class="hl-1"> });</span><br/><br/><span class="hl-7">// Update a focus</span><br/><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">updateFocus</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">key:</span><span class="hl-1"> </span><span class="hl-3">&quot;work&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">title:</span><span class="hl-1"> </span><span class="hl-3">&quot;Work Projects&quot;</span><span class="hl-1">,</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-7">// List the owner&#39;s focuses (requires FocusAccess.Full)</span><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">focuses</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">getFocuses</span><span class="hl-1">({ </span><span class="hl-2">includeArchived:</span><span class="hl-1"> </span><span class="hl-4">false</span><span class="hl-1"> });</span>
35
77
  </code><button type="button">Copy</button></pre>
36
78
 
37
- <h3 id="activity-data-synchronization" class="tsd-anchor-link">Activity Data Synchronization<a href="#activity-data-synchronization" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p><strong>Recommended:</strong> Use <code>Activity.source</code> and <code>Note.key</code> for automatic upserts when syncing from external systems:</p>
38
- <pre><code class="typescript"><span class="hl-7">// Simply create - Plot handles deduplication automatically via source</span><br/><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">createActivity</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">source:</span><span class="hl-1"> </span><span class="hl-3">&quot;https://github.com/org/repo/pull/123&quot;</span><span class="hl-1">, </span><span class="hl-7">// Canonical URL for deduplication</span><br/><span class="hl-1"> </span><span class="hl-2">type:</span><span class="hl-1"> </span><span class="hl-2">ActivityType</span><span class="hl-1">.</span><span class="hl-2">Action</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">title:</span><span class="hl-1"> </span><span class="hl-3">&quot;Review PR #123&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">meta:</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">github_repo:</span><span class="hl-1"> </span><span class="hl-3">&quot;org/repo&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">review_status:</span><span class="hl-1"> </span><span class="hl-3">&quot;pending&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> },</span><br/><span class="hl-1"> </span><span class="hl-2">notes:</span><span class="hl-1"> [</span><br/><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">activity:</span><span class="hl-1"> { </span><span class="hl-2">source:</span><span class="hl-1"> </span><span class="hl-3">&quot;https://github.com/org/repo/pull/123&quot;</span><span class="hl-1"> },</span><br/><span class="hl-1"> </span><span class="hl-2">key:</span><span class="hl-1"> </span><span class="hl-3">&quot;description&quot;</span><span class="hl-1">, </span><span class="hl-7">// Using key enables upserts</span><br/><span class="hl-1"> </span><span class="hl-2">content:</span><span class="hl-1"> </span><span class="hl-3">&quot;Please review this pull request.&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> },</span><br/><span class="hl-1"> ],</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-7">// Later, update by referencing the same source (no lookup needed)</span><br/><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">createNote</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">activity:</span><span class="hl-1"> { </span><span class="hl-2">source:</span><span class="hl-1"> </span><span class="hl-3">&quot;https://github.com/org/repo/pull/123&quot;</span><span class="hl-1"> },</span><br/><span class="hl-1"> </span><span class="hl-2">key:</span><span class="hl-1"> </span><span class="hl-3">&quot;update&quot;</span><span class="hl-1">, </span><span class="hl-7">// Different key for different note types</span><br/><span class="hl-1"> </span><span class="hl-2">content:</span><span class="hl-1"> </span><span class="hl-3">&quot;PR has been updated&quot;</span><span class="hl-1">,</span><br/><span class="hl-1">});</span>
79
+ <h3 id="schedules" class="tsd-anchor-link">Schedules<a href="#schedules" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p>Schedules define when a thread occurs in time. A thread can have multiple schedules.</p>
80
+ <pre><code class="typescript"><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">threadId</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">createThread</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">title:</span><span class="hl-1"> </span><span class="hl-3">&quot;Team standup&quot;</span><span class="hl-1">,</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">createSchedule</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">threadId</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">start:</span><span class="hl-1"> </span><span class="hl-4">new</span><span class="hl-1"> </span><span class="hl-6">Date</span><span class="hl-1">(</span><span class="hl-3">&quot;2025-01-15T10:00:00Z&quot;</span><span class="hl-1">),</span><br/><span class="hl-1"> </span><span class="hl-2">end:</span><span class="hl-1"> </span><span class="hl-4">new</span><span class="hl-1"> </span><span class="hl-6">Date</span><span class="hl-1">(</span><span class="hl-3">&quot;2025-01-15T10:30:00Z&quot;</span><span class="hl-1">),</span><br/><span class="hl-1"> </span><span class="hl-2">recurrenceRule:</span><span class="hl-1"> </span><span class="hl-3">&quot;FREQ=DAILY;BYDAY=MO,TU,WE,TH,FR&quot;</span><span class="hl-1">,</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">schedules</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">getSchedules</span><span class="hl-1">(</span><span class="hl-2">threadId</span><span class="hl-1">);</span>
39
81
  </code><button type="button">Copy</button></pre>
40
82
 
41
- <p><strong>Advanced:</strong> For cases where you need multiple Plot activities per external item, use UUID generation and storage:</p>
42
- <pre><code class="typescript"><span class="hl-7">// Generate UUID for the activity</span><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">activityId</span><span class="hl-1"> = </span><span class="hl-2">Uuid</span><span class="hl-1">.</span><span class="hl-6">Generate</span><span class="hl-1">();</span><br/><br/><span class="hl-7">// Store mapping from external ID to Plot UUID</span><br/><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">set</span><span class="hl-1">(</span><span class="hl-3">&quot;pr_mapping:123&quot;</span><span class="hl-1">, </span><span class="hl-2">activityId</span><span class="hl-1">);</span><br/><br/><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">createActivity</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">id:</span><span class="hl-1"> </span><span class="hl-2">activityId</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">type:</span><span class="hl-1"> </span><span class="hl-2">ActivityType</span><span class="hl-1">.</span><span class="hl-2">Action</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">title:</span><span class="hl-1"> </span><span class="hl-3">&quot;Review PR&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">notes:</span><span class="hl-1"> [{</span><br/><span class="hl-1"> </span><span class="hl-2">id:</span><span class="hl-1"> </span><span class="hl-2">Uuid</span><span class="hl-1">.</span><span class="hl-6">Generate</span><span class="hl-1">(),</span><br/><span class="hl-1"> </span><span class="hl-2">content:</span><span class="hl-1"> </span><span class="hl-3">&quot;Please review this pull request.&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> }],</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-7">// Later, find by looking up the mapping</span><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">storedActivityId</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">get</span><span class="hl-1">&lt;</span><span class="hl-5">Uuid</span><span class="hl-1">&gt;(</span><span class="hl-3">&quot;pr_mapping:123&quot;</span><span class="hl-1">);</span><br/><span class="hl-0">if</span><span class="hl-1"> (</span><span class="hl-2">storedActivityId</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">createNote</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">activity:</span><span class="hl-1"> { </span><span class="hl-2">id:</span><span class="hl-1"> </span><span class="hl-2">storedActivityId</span><span class="hl-1"> },</span><br/><span class="hl-1"> </span><span class="hl-2">content:</span><span class="hl-1"> </span><span class="hl-3">&quot;PR has been updated&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> });</span><br/><span class="hl-1">}</span>
83
+ <p>For all-day events, pass <code>&quot;YYYY-MM-DD&quot;</code> date strings for <code>start</code>/<code>end</code> instead of <code>Date</code> objects.</p>
84
+ <h3 id="contacts" class="tsd-anchor-link">Contacts<a href="#contacts" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><pre><code class="typescript"><span class="hl-7">// The user who installed the twist</span><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">owner</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">getOwner</span><span class="hl-1">();</span><br/><br/><span class="hl-7">// Resolve actor IDs to actors (requires ContactAccess.Read for name/email)</span><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">actors</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">getActors</span><span class="hl-1">([</span><span class="hl-2">actorId1</span><span class="hl-1">, </span><span class="hl-2">actorId2</span><span class="hl-1">]);</span>
43
85
  </code><button type="button">Copy</button></pre>
44
86
 
45
- <p>See <a href="../media/SYNC_STRATEGIES.md">Sync Strategies</a> for comprehensive guidance on choosing the right pattern.</p>
46
- <h3 id="creating-and-managing-notes" class="tsd-anchor-link">Creating and Managing Notes<a href="#creating-and-managing-notes" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><h4 id="creating-notes-on-new-activities" class="tsd-anchor-link">Creating Notes on New Activities<a href="#creating-notes-on-new-activities" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h4><p><strong>Best Practice:</strong> Always create Activities with at least one initial Note containing detailed information. The <code>title</code> is a short summary that may be truncated—detailed content should go in Notes.</p>
47
- <pre><code class="typescript"><span class="hl-7">// ✅ Recommended - Activity with source for automatic deduplication</span><br/><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">createActivity</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">source:</span><span class="hl-1"> </span><span class="hl-3">&quot;https://support.example.com/tickets/12345&quot;</span><span class="hl-1">, </span><span class="hl-7">// Enables automatic upserts</span><br/><span class="hl-1"> </span><span class="hl-2">type:</span><span class="hl-1"> </span><span class="hl-2">ActivityType</span><span class="hl-1">.</span><span class="hl-2">Action</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">title:</span><span class="hl-1"> </span><span class="hl-3">&quot;Customer feedback: Login issues&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">notes:</span><span class="hl-1"> [</span><br/><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">activity:</span><span class="hl-1"> { </span><span class="hl-2">source:</span><span class="hl-1"> </span><span class="hl-3">&quot;https://support.example.com/tickets/12345&quot;</span><span class="hl-1"> },</span><br/><span class="hl-1"> </span><span class="hl-2">key:</span><span class="hl-1"> </span><span class="hl-3">&quot;description&quot;</span><span class="hl-1">, </span><span class="hl-7">// Using key enables upserts</span><br/><span class="hl-1"> </span><span class="hl-2">content:</span><span class="hl-1"> </span><span class="hl-3">&quot;Customer reported:</span><span class="hl-12">\n\n\&quot;</span><span class="hl-3">I&#39;m unable to log in using Google SSO. The page redirects but then shows an error &#39;Invalid state parameter&#39;.</span><span class="hl-12">\&quot;\n\n</span><span class="hl-3">Priority: High</span><span class="hl-12">\n</span><span class="hl-3">Affected users: ~15 reports&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">links:</span><span class="hl-1"> [</span><br/><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">type:</span><span class="hl-1"> </span><span class="hl-2">ActivityLinkType</span><span class="hl-1">.</span><span class="hl-2">external</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">title:</span><span class="hl-1"> </span><span class="hl-3">&quot;View Support Ticket&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">url:</span><span class="hl-1"> </span><span class="hl-3">&quot;https://support.example.com/tickets/12345&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> },</span><br/><span class="hl-1"> ],</span><br/><span class="hl-1"> },</span><br/><span class="hl-1"> ],</span><br/><span class="hl-1">});</span>
87
+ <h3 id="links-from-connected-channels" class="tsd-anchor-link">Links from Connected Channels<a href="#links-from-connected-channels" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p>With <code>link: true</code> in the Plot options, a twist can read links synced by the user's connections (calendar events, issues, messages, etc.) and react to them via the <code>onLinkCreated</code> / <code>onLinkUpdated</code> / <code>onLinkNoteCreated</code> lifecycle methods on <code>Twist</code>.</p>
88
+ <pre><code class="typescript"><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">results</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">getLinks</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">since:</span><span class="hl-1"> </span><span class="hl-4">new</span><span class="hl-1"> </span><span class="hl-6">Date</span><span class="hl-1">(</span><span class="hl-2">Date</span><span class="hl-1">.</span><span class="hl-6">now</span><span class="hl-1">() - </span><span class="hl-14">24</span><span class="hl-1"> * </span><span class="hl-14">60</span><span class="hl-1"> * </span><span class="hl-14">60</span><span class="hl-1"> * </span><span class="hl-14">1000</span><span class="hl-1">),</span><br/><span class="hl-1"> </span><span class="hl-2">type:</span><span class="hl-1"> </span><span class="hl-3">&quot;event&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">limit:</span><span class="hl-1"> </span><span class="hl-14">50</span><span class="hl-1">,</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-0">for</span><span class="hl-1"> (</span><span class="hl-4">const</span><span class="hl-1"> { </span><span class="hl-8">link</span><span class="hl-1">, </span><span class="hl-8">notes</span><span class="hl-1"> } </span><span class="hl-4">of</span><span class="hl-1"> </span><span class="hl-2">results</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-2">console</span><span class="hl-1">.</span><span class="hl-6">log</span><span class="hl-1">(</span><span class="hl-2">link</span><span class="hl-1">.</span><span class="hl-2">title</span><span class="hl-1">, </span><span class="hl-2">link</span><span class="hl-1">.</span><span class="hl-2">status</span><span class="hl-1">, </span><span class="hl-2">notes</span><span class="hl-1">.</span><span class="hl-2">length</span><span class="hl-1">);</span><br/><span class="hl-1">}</span><br/><br/><span class="hl-7">// Move a link to a different thread (requires LinkAccess.Full)</span><br/><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">updateLink</span><span class="hl-1">({ </span><span class="hl-2">id:</span><span class="hl-1"> </span><span class="hl-2">linkId</span><span class="hl-1">, </span><span class="hl-2">threadId:</span><span class="hl-1"> </span><span class="hl-2">otherThreadId</span><span class="hl-1"> });</span>
48
89
  </code><button type="button">Copy</button></pre>
49
90
 
50
- <h4 id="adding-notes-to-existing-activities" class="tsd-anchor-link">Adding Notes to Existing Activities<a href="#adding-notes-to-existing-activities" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h4><p><strong>Best Practice:</strong> For related content (email threads, chat conversations, workflows), add Notes to the existing Activity rather than creating new Activities. Think of it like adding a message to an existing thread.</p>
51
- <pre><code class="typescript"><span class="hl-7">// Add a new Note to an existing Activity (add message to thread)</span><br/><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">createNote</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">activity:</span><span class="hl-1"> { </span><span class="hl-2">id:</span><span class="hl-1"> </span><span class="hl-2">activity</span><span class="hl-1">.</span><span class="hl-2">id</span><span class="hl-1"> },</span><br/><span class="hl-1"> </span><span class="hl-2">content:</span><span class="hl-1"> </span><span class="hl-3">&quot;Update: Engineering team has identified the root cause. Fix will be deployed in the next release.&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">links:</span><span class="hl-1"> [</span><br/><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">type:</span><span class="hl-1"> </span><span class="hl-2">ActivityLinkType</span><span class="hl-1">.</span><span class="hl-2">external</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">title:</span><span class="hl-1"> </span><span class="hl-3">&quot;View PR Fix&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">url:</span><span class="hl-1"> </span><span class="hl-3">&quot;https://github.com/org/repo/pull/789&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> },</span><br/><span class="hl-1"> ],</span><br/><span class="hl-1">});</span>
91
+ <h3 id="semantic-search" class="tsd-anchor-link">Semantic Search<a href="#semantic-search" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p>With <code>search: true</code> in the Plot options, search the owner's notes and links by meaning:</p>
92
+ <pre><code class="typescript"><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">results</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">search</span><span class="hl-1">(</span><span class="hl-3">&quot;budget discussion with finance&quot;</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-2">limit:</span><span class="hl-1"> </span><span class="hl-14">10</span><span class="hl-1">, </span><span class="hl-7">// Default 10, max 30</span><br/><span class="hl-1"> </span><span class="hl-2">threshold:</span><span class="hl-1"> </span><span class="hl-14">0.3</span><span class="hl-1">, </span><span class="hl-7">// Minimum similarity 0-1 (default 0.3)</span><br/><span class="hl-1"> </span><span class="hl-2">focusId</span><span class="hl-1">, </span><span class="hl-7">// Optional: scope to one focus</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-0">for</span><span class="hl-1"> (</span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">result</span><span class="hl-1"> </span><span class="hl-4">of</span><span class="hl-1"> </span><span class="hl-2">results</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-7">// result.type is &quot;note&quot; or &quot;link&quot;</span><br/><span class="hl-1"> </span><span class="hl-2">console</span><span class="hl-1">.</span><span class="hl-6">log</span><span class="hl-1">(</span><span class="hl-2">result</span><span class="hl-1">.</span><span class="hl-2">thread</span><span class="hl-1">.</span><span class="hl-2">title</span><span class="hl-1">, </span><span class="hl-2">result</span><span class="hl-1">.</span><span class="hl-2">similarity</span><span class="hl-1">);</span><br/><span class="hl-1">}</span>
52
93
  </code><button type="button">Copy</button></pre>
53
94
 
54
- <h4 id="pattern-email-threads-and-conversations" class="tsd-anchor-link">Pattern: Email Threads and Conversations<a href="#pattern-email-threads-and-conversations" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h4><p>Keep all messages in a thread or conversation within a single Activity. Think of it like a messaging app - one thread, many messages.</p>
55
- <p><strong>Recommended Pattern</strong> - Use source/key for automatic deduplication:</p>
56
- <pre><code class="typescript"><span class="hl-2">async</span><span class="hl-1"> </span><span class="hl-6">handleEmailThread</span><span class="hl-1">(</span><span class="hl-2">thread</span><span class="hl-1">: </span><span class="hl-2">EmailThread</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">threadSource</span><span class="hl-1"> = </span><span class="hl-3">`email:thread:</span><span class="hl-4">${</span><span class="hl-2">thread</span><span class="hl-13">.</span><span class="hl-2">id</span><span class="hl-4">}</span><span class="hl-3">`</span><span class="hl-1">;</span><br/><br/><span class="hl-1"> </span><span class="hl-7">// Simply create notes - Plot handles deduplication via source and key</span><br/><span class="hl-1"> </span><span class="hl-0">for</span><span class="hl-1"> (</span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">message</span><span class="hl-1"> </span><span class="hl-4">of</span><span class="hl-1"> </span><span class="hl-2">thread</span><span class="hl-1">.</span><span class="hl-2">messages</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">createNote</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">activity:</span><span class="hl-1"> { </span><span class="hl-2">source:</span><span class="hl-1"> </span><span class="hl-2">threadSource</span><span class="hl-1"> },</span><br/><span class="hl-1"> </span><span class="hl-2">key:</span><span class="hl-1"> </span><span class="hl-3">`message-</span><span class="hl-4">${</span><span class="hl-2">message</span><span class="hl-13">.</span><span class="hl-2">id</span><span class="hl-4">}</span><span class="hl-3">`</span><span class="hl-1">, </span><span class="hl-7">// Unique key per message</span><br/><span class="hl-1"> </span><span class="hl-2">content:</span><span class="hl-1"> </span><span class="hl-2">message</span><span class="hl-1">.</span><span class="hl-2">body</span><span class="hl-1">,</span><br/><span class="hl-1"> });</span><br/><span class="hl-1"> }</span><br/><br/><span class="hl-1"> </span><span class="hl-7">// Create activity if it doesn&#39;t exist yet (Plot handles deduplication)</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">createActivity</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">source:</span><span class="hl-1"> </span><span class="hl-2">threadSource</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">type:</span><span class="hl-1"> </span><span class="hl-2">ActivityType</span><span class="hl-1">.</span><span class="hl-2">Note</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">title:</span><span class="hl-1"> </span><span class="hl-2">thread</span><span class="hl-1">.</span><span class="hl-2">subject</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">notes:</span><span class="hl-1"> </span><span class="hl-2">thread</span><span class="hl-1">.</span><span class="hl-2">messages</span><span class="hl-1">.</span><span class="hl-6">map</span><span class="hl-1">((</span><span class="hl-2">msg</span><span class="hl-1">) </span><span class="hl-4">=&gt;</span><span class="hl-1"> ({</span><br/><span class="hl-1"> </span><span class="hl-2">activity:</span><span class="hl-1"> { </span><span class="hl-2">source:</span><span class="hl-1"> </span><span class="hl-2">threadSource</span><span class="hl-1"> },</span><br/><span class="hl-1"> </span><span class="hl-2">key:</span><span class="hl-1"> </span><span class="hl-3">`message-</span><span class="hl-4">${</span><span class="hl-2">msg</span><span class="hl-13">.</span><span class="hl-2">id</span><span class="hl-4">}</span><span class="hl-3">`</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">content:</span><span class="hl-1"> </span><span class="hl-2">msg</span><span class="hl-1">.</span><span class="hl-2">body</span><span class="hl-1">,</span><br/><span class="hl-1"> })),</span><br/><span class="hl-1"> });</span><br/><span class="hl-1">}</span>
95
+ <h3 id="plans-user-approved-operations" class="tsd-anchor-link">Plans (User-Approved Operations)<a href="#plans-user-approved-operations" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p>With <code>requireApproval: true</code> in the Plot options, write operations on content the twist didn't create require user approval. Build a plan and attach it to a note as an action; the user can approve or deny it, and approved operations are executed by Plot:</p>
96
+ <pre><code class="typescript"><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">planAction</span><span class="hl-1"> = </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">createPlan</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">title:</span><span class="hl-1"> </span><span class="hl-3">&quot;Organize project threads&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">operations:</span><span class="hl-1"> [</span><br/><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">type:</span><span class="hl-1"> </span><span class="hl-3">&quot;updateThread&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">threadId</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">threadTitle:</span><span class="hl-1"> </span><span class="hl-3">&quot;Old thread&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">changes:</span><span class="hl-1"> { </span><span class="hl-2">archived:</span><span class="hl-1"> </span><span class="hl-4">true</span><span class="hl-1"> },</span><br/><span class="hl-1"> },</span><br/><span class="hl-1"> ],</span><br/><span class="hl-1"> </span><span class="hl-2">callback:</span><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">actionCallback</span><span class="hl-1">(</span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">onPlanResolved</span><span class="hl-1">, </span><span class="hl-2">threadId</span><span class="hl-1"> </span><span class="hl-0">as</span><span class="hl-1"> </span><span class="hl-5">string</span><span class="hl-1">),</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">createNote</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">thread:</span><span class="hl-1"> { </span><span class="hl-2">id:</span><span class="hl-1"> </span><span class="hl-2">threadId</span><span class="hl-1"> },</span><br/><span class="hl-1"> </span><span class="hl-2">content:</span><span class="hl-1"> </span><span class="hl-3">&quot;Here&#39;s my proposed cleanup:&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">actions:</span><span class="hl-1"> [</span><span class="hl-2">planAction</span><span class="hl-1">],</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-7">// Called when the user responds; approved operations are executed by Plot</span><br/><span class="hl-2">async</span><span class="hl-1"> </span><span class="hl-6">onPlanResolved</span><span class="hl-1">(</span><span class="hl-2">action</span><span class="hl-1">: </span><span class="hl-2">Action</span><span class="hl-1">, </span><span class="hl-2">threadId</span><span class="hl-1">: </span><span class="hl-2">string</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-7">// e.g. post a confirmation note</span><br/><span class="hl-1">}</span>
57
97
  </code><button type="button">Copy</button></pre>
58
98
 
59
- <p><strong>Alternative Pattern</strong> - Check existence first (for advanced cases):</p>
60
- <pre><code class="typescript"><span class="hl-2">async</span><span class="hl-1"> </span><span class="hl-6">handleEmailThreadAdvanced</span><span class="hl-1">(</span><span class="hl-2">thread</span><span class="hl-1">: </span><span class="hl-2">EmailThread</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">mappingKey</span><span class="hl-1"> = </span><span class="hl-3">`email_thread_mapping:</span><span class="hl-4">${</span><span class="hl-2">thread</span><span class="hl-13">.</span><span class="hl-2">id</span><span class="hl-4">}</span><span class="hl-3">`</span><span class="hl-1">;</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">existingActivityId</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">get</span><span class="hl-1">&lt;</span><span class="hl-5">Uuid</span><span class="hl-1">&gt;(</span><span class="hl-2">mappingKey</span><span class="hl-1">);</span><br/><br/><span class="hl-1"> </span><span class="hl-0">if</span><span class="hl-1"> (</span><span class="hl-2">existingActivityId</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-0">for</span><span class="hl-1"> (</span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">message</span><span class="hl-1"> </span><span class="hl-4">of</span><span class="hl-1"> </span><span class="hl-2">thread</span><span class="hl-1">.</span><span class="hl-2">newMessages</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">createNote</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">activity:</span><span class="hl-1"> { </span><span class="hl-2">id:</span><span class="hl-1"> </span><span class="hl-2">existingActivityId</span><span class="hl-1"> },</span><br/><span class="hl-1"> </span><span class="hl-2">content:</span><span class="hl-1"> </span><span class="hl-2">message</span><span class="hl-1">.</span><span class="hl-2">body</span><span class="hl-1">,</span><br/><span class="hl-1"> });</span><br/><span class="hl-1"> }</span><br/><span class="hl-1"> } </span><span class="hl-0">else</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">activityId</span><span class="hl-1"> = </span><span class="hl-2">Uuid</span><span class="hl-1">.</span><span class="hl-6">Generate</span><span class="hl-1">();</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">set</span><span class="hl-1">(</span><span class="hl-2">mappingKey</span><span class="hl-1">, </span><span class="hl-2">activityId</span><span class="hl-1">);</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">createActivity</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">id:</span><span class="hl-1"> </span><span class="hl-2">activityId</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">type:</span><span class="hl-1"> </span><span class="hl-2">ActivityType</span><span class="hl-1">.</span><span class="hl-2">Note</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">title:</span><span class="hl-1"> </span><span class="hl-2">thread</span><span class="hl-1">.</span><span class="hl-2">subject</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">notes:</span><span class="hl-1"> </span><span class="hl-2">thread</span><span class="hl-1">.</span><span class="hl-2">messages</span><span class="hl-1">.</span><span class="hl-6">map</span><span class="hl-1">((</span><span class="hl-2">msg</span><span class="hl-1">) </span><span class="hl-4">=&gt;</span><span class="hl-1"> ({</span><br/><span class="hl-1"> </span><span class="hl-2">content:</span><span class="hl-1"> </span><span class="hl-2">msg</span><span class="hl-1">.</span><span class="hl-2">body</span><span class="hl-1">,</span><br/><span class="hl-1"> })),</span><br/><span class="hl-1"> });</span><br/><span class="hl-1"> }</span><br/><span class="hl-1">}</span>
99
+ <h3 id="responding-to-mentions" class="tsd-anchor-link">Responding to Mentions<a href="#responding-to-mentions" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p>The <code>note</code> options let a twist respond when a user @-mentions it in a note. Declare either a fixed set of <code>intents</code> (the system matches the note against intent descriptions and examples) or a single conversational <code>handler</code> that receives every mention:</p>
100
+ <pre><code class="typescript"><span class="hl-15">plot</span><span class="hl-1">: </span><span class="hl-6">build</span><span class="hl-1">(</span><span class="hl-2">Plot</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-2">thread:</span><span class="hl-1"> { </span><span class="hl-2">access:</span><span class="hl-1"> </span><span class="hl-2">ThreadAccess</span><span class="hl-1">.</span><span class="hl-2">Create</span><span class="hl-1"> },</span><br/><span class="hl-1"> </span><span class="hl-2">note:</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">defaultMention:</span><span class="hl-1"> </span><span class="hl-4">true</span><span class="hl-1">, </span><span class="hl-7">// Auto-mention this twist on replies in its threads</span><br/><span class="hl-1"> </span><span class="hl-2">handler:</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">respond</span><span class="hl-1">, </span><span class="hl-7">// (note: Note) =&gt; Promise&lt;void&gt;</span><br/><span class="hl-1"> },</span><br/><span class="hl-1">}),</span>
61
101
  </code><button type="button">Copy</button></pre>
62
102
 
63
- <p><strong>Why this matters:</strong> A conversation with 20 messages should be one Activity with 20 Notes, not 20 separate Activities. This keeps the workspace organized and provides better context.</p>
64
- <p>See <a href="../media/SYNC_STRATEGIES.md">Sync Strategies</a> for more patterns.</p>
103
+ <p><code>handler</code> and <code>intents</code> are mutually exclusive when both are present, <code>handler</code> wins.</p>
104
+ <p>See <a href="SYNC_STRATEGIES.html">Sync Strategies</a> for comprehensive guidance on data synchronization patterns.</p>
65
105
  <hr>
66
106
  <h2 id="store" class="tsd-anchor-link">Store<a href="#store" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h2><p>Persistent key-value storage for twist state. Store methods are available directly on the twist class.</p>
67
107
  <h3 id="setup-1" class="tsd-anchor-link">Setup<a href="#setup-1" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p>Store is available automatically - no build() declaration needed!</p>
68
- <h3 id="storing-data" class="tsd-anchor-link">Storing Data<a href="#storing-data" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><pre><code class="typescript"><span class="hl-7">// Save a string</span><br/><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">set</span><span class="hl-1">(</span><span class="hl-3">&quot;last_sync&quot;</span><span class="hl-1">, </span><span class="hl-4">new</span><span class="hl-1"> </span><span class="hl-6">Date</span><span class="hl-1">().</span><span class="hl-6">toISOString</span><span class="hl-1">());</span><br/><br/><span class="hl-7">// Save an object</span><br/><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">set</span><span class="hl-1">(</span><span class="hl-3">&quot;config&quot;</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-2">enabled:</span><span class="hl-1"> </span><span class="hl-4">true</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">interval:</span><span class="hl-1"> </span><span class="hl-14">3600</span><span class="hl-1">,</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-7">// Save an array</span><br/><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">set</span><span class="hl-1">(</span><span class="hl-3">&quot;items&quot;</span><span class="hl-1">, [</span><span class="hl-3">&quot;a&quot;</span><span class="hl-1">, </span><span class="hl-3">&quot;b&quot;</span><span class="hl-1">, </span><span class="hl-3">&quot;c&quot;</span><span class="hl-1">]);</span>
108
+ <h3 id="storing-data" class="tsd-anchor-link">Storing Data<a href="#storing-data" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><pre><code class="typescript"><span class="hl-7">// Save a string</span><br/><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">set</span><span class="hl-1">(</span><span class="hl-3">&quot;last_sync&quot;</span><span class="hl-1">, </span><span class="hl-4">new</span><span class="hl-1"> </span><span class="hl-6">Date</span><span class="hl-1">().</span><span class="hl-6">toISOString</span><span class="hl-1">());</span><br/><br/><span class="hl-7">// Save an object — Dates are preserved</span><br/><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">set</span><span class="hl-1">(</span><span class="hl-3">&quot;config&quot;</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-2">enabled:</span><span class="hl-1"> </span><span class="hl-4">true</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">interval:</span><span class="hl-1"> </span><span class="hl-14">3600</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">lastRun:</span><span class="hl-1"> </span><span class="hl-4">new</span><span class="hl-1"> </span><span class="hl-6">Date</span><span class="hl-1">(),</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-7">// Save an array</span><br/><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">set</span><span class="hl-1">(</span><span class="hl-3">&quot;items&quot;</span><span class="hl-1">, [</span><span class="hl-3">&quot;a&quot;</span><span class="hl-1">, </span><span class="hl-3">&quot;b&quot;</span><span class="hl-1">, </span><span class="hl-3">&quot;c&quot;</span><span class="hl-1">]);</span>
69
109
  </code><button type="button">Copy</button></pre>
70
110
 
71
111
  <h3 id="retrieving-data" class="tsd-anchor-link">Retrieving Data<a href="#retrieving-data" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><pre><code class="typescript"><span class="hl-7">// Get with type safety</span><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">lastSync</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">get</span><span class="hl-1">&lt;</span><span class="hl-5">string</span><span class="hl-1">&gt;(</span><span class="hl-3">&quot;last_sync&quot;</span><span class="hl-1">);</span><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">config</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">get</span><span class="hl-1">&lt;{ </span><span class="hl-2">enabled</span><span class="hl-1">: </span><span class="hl-5">boolean</span><span class="hl-1">; </span><span class="hl-2">interval</span><span class="hl-1">: </span><span class="hl-5">number</span><span class="hl-1"> }&gt;(</span><span class="hl-3">&quot;config&quot;</span><span class="hl-1">);</span><br/><br/><span class="hl-7">// Handle missing data</span><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">value</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">get</span><span class="hl-1">&lt;</span><span class="hl-5">string</span><span class="hl-1">&gt;(</span><span class="hl-3">&quot;key&quot;</span><span class="hl-1">);</span><br/><span class="hl-0">if</span><span class="hl-1"> (</span><span class="hl-2">value</span><span class="hl-1"> === </span><span class="hl-4">null</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-7">// Key doesn&#39;t exist</span><br/><span class="hl-1">}</span>
@@ -74,6 +114,14 @@
74
114
  <h3 id="clearing-data" class="tsd-anchor-link">Clearing Data<a href="#clearing-data" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><pre><code class="typescript"><span class="hl-7">// Clear a specific key</span><br/><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">clear</span><span class="hl-1">(</span><span class="hl-3">&quot;last_sync&quot;</span><span class="hl-1">);</span><br/><br/><span class="hl-7">// Clear all data for this twist</span><br/><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">clearAll</span><span class="hl-1">();</span>
75
115
  </code><button type="button">Copy</button></pre>
76
116
 
117
+ <h3 id="listing-keys" class="tsd-anchor-link">Listing Keys<a href="#listing-keys" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p><code>list()</code> is available on the tool itself (<code>this.tools.store</code>):</p>
118
+ <pre><code class="typescript"><span class="hl-7">// All keys starting with a prefix</span><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">webhookKeys</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">store</span><span class="hl-1">.</span><span class="hl-6">list</span><span class="hl-1">(</span><span class="hl-3">&quot;webhook:&quot;</span><span class="hl-1">);</span>
119
+ </code><button type="button">Copy</button></pre>
120
+
121
+ <h3 id="locks" class="tsd-anchor-link">Locks<a href="#locks" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p>For operations that must not run concurrently (e.g. overlapping syncs), use the self-expiring locks on <code>this.tools.store</code> instead of hand-rolling an &quot;in progress&quot; flag. The lock auto-releases after <code>ttlMs</code>, so a crashed holder can't wedge the system:</p>
122
+ <pre><code class="typescript"><span class="hl-0">if</span><span class="hl-1"> (!(</span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">store</span><span class="hl-1">.</span><span class="hl-6">acquireLock</span><span class="hl-1">(</span><span class="hl-3">`sync_</span><span class="hl-4">${</span><span class="hl-2">id</span><span class="hl-4">}</span><span class="hl-3">`</span><span class="hl-1">, </span><span class="hl-14">30</span><span class="hl-1"> * </span><span class="hl-14">60_000</span><span class="hl-1">))) {</span><br/><span class="hl-1"> </span><span class="hl-0">return</span><span class="hl-1">; </span><span class="hl-7">// another sync is already running</span><br/><span class="hl-1">}</span><br/><span class="hl-0">try</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">runSync</span><span class="hl-1">(</span><span class="hl-2">id</span><span class="hl-1">);</span><br/><span class="hl-1">} </span><span class="hl-0">finally</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">store</span><span class="hl-1">.</span><span class="hl-6">releaseLock</span><span class="hl-1">(</span><span class="hl-3">`sync_</span><span class="hl-4">${</span><span class="hl-2">id</span><span class="hl-4">}</span><span class="hl-3">`</span><span class="hl-1">);</span><br/><span class="hl-1">}</span>
123
+ </code><button type="button">Copy</button></pre>
124
+
77
125
  <h3 id="best-practices" class="tsd-anchor-link">Best Practices<a href="#best-practices" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><h4 id="type-safety" class="tsd-anchor-link">Type Safety<a href="#type-safety" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h4><p>Define interfaces for complex stored data:</p>
78
126
  <pre><code class="typescript"><span class="hl-4">interface</span><span class="hl-1"> </span><span class="hl-5">SyncState</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">lastSync</span><span class="hl-1">: </span><span class="hl-5">string</span><span class="hl-1">;</span><br/><span class="hl-1"> </span><span class="hl-2">token</span><span class="hl-1">: </span><span class="hl-5">string</span><span class="hl-1">;</span><br/><span class="hl-1"> </span><span class="hl-2">status</span><span class="hl-1">: </span><span class="hl-3">&quot;active&quot;</span><span class="hl-1"> | </span><span class="hl-3">&quot;paused&quot;</span><span class="hl-1">;</span><br/><span class="hl-1">}</span><br/><br/><span class="hl-2">async</span><span class="hl-1"> </span><span class="hl-6">getSyncState</span><span class="hl-1">(): </span><span class="hl-5">Promise</span><span class="hl-1">&lt;</span><span class="hl-2">SyncState</span><span class="hl-1"> | </span><span class="hl-4">null</span><span class="hl-1">&gt; {</span><br/><span class="hl-1"> return await this.get&lt;SyncState&gt;(</span><span class="hl-3">&quot;sync_state&quot;</span><span class="hl-2">);</span><br/><span class="hl-2">}</span><br/><br/><span class="hl-2">async setSyncState(state:</span><span class="hl-1"> </span><span class="hl-2">SyncState</span><span class="hl-1">): </span><span class="hl-5">Promise</span><span class="hl-1">&lt;</span><span class="hl-4">void</span><span class="hl-1">&gt; {</span><br/><span class="hl-1"> await this.set(</span><span class="hl-3">&quot;sync_state&quot;</span><span class="hl-1">, state);</span><br/><span class="hl-1">}</span>
79
127
  </code><button type="button">Copy</button></pre>
@@ -82,25 +130,41 @@
82
130
  <pre><code class="typescript"><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">set</span><span class="hl-1">(</span><span class="hl-3">&quot;webhook:calendar&quot;</span><span class="hl-1">, </span><span class="hl-2">webhookUrl</span><span class="hl-1">);</span><br/><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">set</span><span class="hl-1">(</span><span class="hl-3">&quot;webhook:github&quot;</span><span class="hl-1">, </span><span class="hl-2">githubWebhookUrl</span><span class="hl-1">);</span><br/><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">set</span><span class="hl-1">(</span><span class="hl-3">&quot;config:sync_interval&quot;</span><span class="hl-1">, </span><span class="hl-14">3600</span><span class="hl-1">);</span>
83
131
  </code><button type="button">Copy</button></pre>
84
132
 
85
- <h4 id="serialization-limits" class="tsd-anchor-link">Serialization Limits<a href="#serialization-limits" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h4><p>Remember: Values must be JSON-serializable. Functions, Symbols, and undefined values cannot be stored.</p>
86
- <pre><code class="typescript"><span class="hl-7">// ❌ WRONG</span><br/><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">set</span><span class="hl-1">(</span><span class="hl-3">&quot;handler&quot;</span><span class="hl-1">, </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">myFunction</span><span class="hl-1">); </span><span class="hl-7">// Functions can&#39;t be stored</span><br/><br/><span class="hl-7">// ✅ CORRECT - Use callbacks instead</span><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">token</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">callback</span><span class="hl-1">(</span><span class="hl-3">&quot;myFunction&quot;</span><span class="hl-1">);</span><br/><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">set</span><span class="hl-1">(</span><span class="hl-3">&quot;handler_token&quot;</span><span class="hl-1">, </span><span class="hl-2">token</span><span class="hl-1">);</span>
133
+ <h4 id="serialization-limits" class="tsd-anchor-link">Serialization Limits<a href="#serialization-limits" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h4><p>Values are serialized with SuperJSON, so <code>Date</code>, <code>Map</code>, <code>Set</code>, <code>RegExp</code>, <code>URL</code>, <code>BigInt</code>, and <code>undefined</code> all round-trip correctly. Functions, Symbols, circular references, and custom class instances cannot be stored.</p>
134
+ <pre><code class="typescript"><span class="hl-7">// ❌ WRONG</span><br/><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">set</span><span class="hl-1">(</span><span class="hl-3">&quot;handler&quot;</span><span class="hl-1">, </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">myFunction</span><span class="hl-1">); </span><span class="hl-7">// Functions can&#39;t be stored</span><br/><br/><span class="hl-7">// ✅ CORRECT - Use callbacks instead</span><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">token</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">callback</span><span class="hl-1">(</span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">myFunction</span><span class="hl-1">);</span><br/><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">set</span><span class="hl-1">(</span><span class="hl-3">&quot;handler_token&quot;</span><span class="hl-1">, </span><span class="hl-2">token</span><span class="hl-1">);</span>
87
135
  </code><button type="button">Copy</button></pre>
88
136
 
89
137
  <hr>
90
- <h2 id="integrations" class="tsd-anchor-link">Integrations<a href="#integrations" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h2><p>OAuth authentication for external services (Google, Microsoft, etc.).</p>
91
- <h3 id="setup-2" class="tsd-anchor-link">Setup<a href="#setup-2" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><pre><code class="typescript"><span class="hl-0">import</span><span class="hl-1"> { </span><span class="hl-2">Integrations</span><span class="hl-1"> } </span><span class="hl-0">from</span><span class="hl-1"> </span><span class="hl-3">&quot;@plotday/twister/tools/integrations&quot;</span><span class="hl-1">;</span><br/><br/><span class="hl-6">build</span><span class="hl-1">(</span><span class="hl-2">build</span><span class="hl-1">: </span><span class="hl-2">ToolBuilder</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-0">return</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">integrations:</span><span class="hl-1"> </span><span class="hl-6">build</span><span class="hl-1">(</span><span class="hl-2">Integrations</span><span class="hl-1">),</span><br/><span class="hl-1"> };</span><br/><span class="hl-1">}</span>
138
+ <h2 id="integrations" class="tsd-anchor-link">Integrations<a href="#integrations" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h2><p>OAuth authentication and data persistence for <strong>connectors</strong> packages that extend <code>Connector</code> (a specialization of <code>Twist</code>) to sync an external service.</p>
139
+ <p>Plot owns the OAuth flow and the channel enable/disable UI. A connector declares its provider, scopes, and link types as class properties, builds the Integrations tool, and implements the channel lifecycle methods. See <a href="Building_Connectors.html">Building Connectors</a> for the full guide.</p>
140
+ <h3 id="setup-2" class="tsd-anchor-link">Setup<a href="#setup-2" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><pre><code class="typescript"><span class="hl-0">import</span><span class="hl-1"> { </span><span class="hl-2">Connector</span><span class="hl-1">, </span><span class="hl-0">type</span><span class="hl-1"> </span><span class="hl-2">ToolBuilder</span><span class="hl-1"> } </span><span class="hl-0">from</span><span class="hl-1"> </span><span class="hl-3">&quot;@plotday/twister&quot;</span><span class="hl-1">;</span><br/><span class="hl-0">import</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">AuthProvider</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-0">type</span><span class="hl-1"> </span><span class="hl-2">AuthToken</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-0">type</span><span class="hl-1"> </span><span class="hl-2">Authorization</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-0">type</span><span class="hl-1"> </span><span class="hl-2">Channel</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">Integrations</span><span class="hl-1">,</span><br/><span class="hl-1">} </span><span class="hl-0">from</span><span class="hl-1"> </span><span class="hl-3">&quot;@plotday/twister/tools/integrations&quot;</span><span class="hl-1">;</span><br/><br/><span class="hl-4">class</span><span class="hl-1"> </span><span class="hl-5">CalendarConnector</span><span class="hl-1"> </span><span class="hl-4">extends</span><span class="hl-1"> </span><span class="hl-5">Connector</span><span class="hl-1">&lt;</span><span class="hl-5">CalendarConnector</span><span class="hl-1">&gt; {</span><br/><span class="hl-1"> </span><span class="hl-4">readonly</span><span class="hl-1"> </span><span class="hl-2">provider</span><span class="hl-1"> = </span><span class="hl-2">AuthProvider</span><span class="hl-1">.</span><span class="hl-2">Google</span><span class="hl-1">;</span><br/><span class="hl-1"> </span><span class="hl-4">readonly</span><span class="hl-1"> </span><span class="hl-2">scopes</span><span class="hl-1"> = [</span><span class="hl-3">&quot;https://www.googleapis.com/auth/calendar.readonly&quot;</span><span class="hl-1">];</span><br/><br/><span class="hl-1"> </span><span class="hl-6">build</span><span class="hl-1">(</span><span class="hl-2">build</span><span class="hl-1">: </span><span class="hl-5">ToolBuilder</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-0">return</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">integrations:</span><span class="hl-1"> </span><span class="hl-6">build</span><span class="hl-1">(</span><span class="hl-2">Integrations</span><span class="hl-1">),</span><br/><span class="hl-1"> };</span><br/><span class="hl-1"> }</span><br/><br/><span class="hl-1"> </span><span class="hl-7">// List the syncable resources for a newly authorized account</span><br/><span class="hl-1"> </span><span class="hl-4">async</span><span class="hl-1"> </span><span class="hl-6">getChannels</span><span class="hl-1">(</span><span class="hl-2">auth</span><span class="hl-1">: </span><span class="hl-5">Authorization</span><span class="hl-1">, </span><span class="hl-2">token</span><span class="hl-1">: </span><span class="hl-5">AuthToken</span><span class="hl-1">): </span><span class="hl-5">Promise</span><span class="hl-1">&lt;</span><span class="hl-5">Channel</span><span class="hl-1">[]&gt; {</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">calendars</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">listCalendars</span><span class="hl-1">(</span><span class="hl-2">token</span><span class="hl-1">);</span><br/><span class="hl-1"> </span><span class="hl-0">return</span><span class="hl-1"> </span><span class="hl-2">calendars</span><span class="hl-1">.</span><span class="hl-6">map</span><span class="hl-1">((</span><span class="hl-2">c</span><span class="hl-1">) </span><span class="hl-4">=&gt;</span><span class="hl-1"> ({ </span><span class="hl-2">id:</span><span class="hl-1"> </span><span class="hl-2">c</span><span class="hl-1">.</span><span class="hl-2">id</span><span class="hl-1">, </span><span class="hl-2">title:</span><span class="hl-1"> </span><span class="hl-2">c</span><span class="hl-1">.</span><span class="hl-2">name</span><span class="hl-1"> }));</span><br/><span class="hl-1"> }</span><br/><br/><span class="hl-1"> </span><span class="hl-4">async</span><span class="hl-1"> </span><span class="hl-6">onChannelEnabled</span><span class="hl-1">(</span><span class="hl-2">channel</span><span class="hl-1">: </span><span class="hl-5">Channel</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-7">// Start syncing this channel</span><br/><span class="hl-1"> }</span><br/><br/><span class="hl-1"> </span><span class="hl-4">async</span><span class="hl-1"> </span><span class="hl-6">onChannelDisabled</span><span class="hl-1">(</span><span class="hl-2">channel</span><span class="hl-1">: </span><span class="hl-5">Channel</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-7">// Stop syncing; archive this channel&#39;s content</span><br/><span class="hl-1"> }</span><br/><span class="hl-1">}</span>
92
141
  </code><button type="button">Copy</button></pre>
93
142
 
94
- <h3 id="requesting-authentication" class="tsd-anchor-link">Requesting Authentication<a href="#requesting-authentication" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><pre><code class="typescript"><span class="hl-0">import</span><span class="hl-1"> { </span><span class="hl-2">AuthProvider</span><span class="hl-1">, </span><span class="hl-0">type</span><span class="hl-1"> </span><span class="hl-2">Authorization</span><span class="hl-1"> } </span><span class="hl-0">from</span><span class="hl-1"> </span><span class="hl-3">&quot;@plotday/twister/tools/integrations&quot;</span><span class="hl-1">;</span><br/><span class="hl-0">import</span><span class="hl-1"> { </span><span class="hl-2">ActivityLinkType</span><span class="hl-1"> } </span><span class="hl-0">from</span><span class="hl-1"> </span><span class="hl-3">&quot;@plotday/twister&quot;</span><span class="hl-1">;</span><br/><br/><span class="hl-2">async</span><span class="hl-1"> </span><span class="hl-6">activate</span><span class="hl-1">() {</span><br/><span class="hl-1"> </span><span class="hl-7">// Create callback for auth completion</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">authCallback</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">callback</span><span class="hl-1">(</span><span class="hl-3">&quot;onAuthComplete&quot;</span><span class="hl-1">);</span><br/><br/><span class="hl-1"> </span><span class="hl-7">// Request Google auth</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">authLink</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">integrations</span><span class="hl-1">.</span><span class="hl-6">request</span><span class="hl-1">(</span><br/><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">provider:</span><span class="hl-1"> </span><span class="hl-2">AuthProvider</span><span class="hl-1">.</span><span class="hl-2">Google</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">scopes:</span><span class="hl-1"> [</span><br/><span class="hl-1"> </span><span class="hl-3">&quot;https://www.googleapis.com/auth/calendar.readonly&quot;</span><br/><span class="hl-1"> ]</span><br/><span class="hl-1"> },</span><br/><span class="hl-1"> </span><span class="hl-2">authCallback</span><br/><span class="hl-1"> );</span><br/><br/><span class="hl-1"> </span><span class="hl-7">// Create activity with auth link in a note</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">createActivity</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">type:</span><span class="hl-1"> </span><span class="hl-2">ActivityType</span><span class="hl-1">.</span><span class="hl-2">Note</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">title:</span><span class="hl-1"> </span><span class="hl-3">&quot;Connect your Google Calendar&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">notes:</span><span class="hl-1"> [</span><br/><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">note:</span><span class="hl-1"> </span><span class="hl-3">&quot;Click below to connect your Google account&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">links:</span><span class="hl-1"> [</span><br/><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">type:</span><span class="hl-1"> </span><span class="hl-2">ActivityLinkType</span><span class="hl-1">.</span><span class="hl-2">auth</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">title:</span><span class="hl-1"> </span><span class="hl-3">&quot;Connect Google&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">url:</span><span class="hl-1"> </span><span class="hl-2">authLink</span><span class="hl-1">,</span><br/><span class="hl-1"> },</span><br/><span class="hl-1"> ],</span><br/><span class="hl-1"> },</span><br/><span class="hl-1"> ],</span><br/><span class="hl-1"> });</span><br/><span class="hl-1">}</span><br/><br/><span class="hl-7">// Handle auth completion</span><br/><span class="hl-2">async</span><span class="hl-1"> </span><span class="hl-6">onAuthComplete</span><span class="hl-1">(</span><span class="hl-2">authorization</span><span class="hl-1">: </span><span class="hl-2">Authorization</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-7">// Get access token</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">authToken</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">integrations</span><span class="hl-1">.</span><span class="hl-6">get</span><span class="hl-1">(</span><span class="hl-2">authorization</span><span class="hl-1">);</span><br/><br/><span class="hl-1"> </span><span class="hl-0">if</span><span class="hl-1"> (</span><span class="hl-2">authToken</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-2">console</span><span class="hl-1">.</span><span class="hl-6">log</span><span class="hl-1">(</span><span class="hl-3">&quot;Access token:&quot;</span><span class="hl-1">, </span><span class="hl-2">authToken</span><span class="hl-1">.</span><span class="hl-2">token</span><span class="hl-1">);</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">set</span><span class="hl-1">(</span><span class="hl-3">&quot;google_auth&quot;</span><span class="hl-1">, </span><span class="hl-2">authorization</span><span class="hl-1">);</span><br/><br/><span class="hl-1"> </span><span class="hl-7">// Start syncing</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">startSync</span><span class="hl-1">();</span><br/><span class="hl-1"> }</span><br/><span class="hl-1">}</span>
143
+ <h3 id="auth-providers" class="tsd-anchor-link">Auth Providers<a href="#auth-providers" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p><code>AuthProvider</code> currently includes: <code>Google</code>, <code>Microsoft</code>, <code>Notion</code>, <code>Slack</code>, <code>Atlassian</code>, <code>Linear</code>, <code>Monday</code>, <code>GitHub</code>, <code>Asana</code>, <code>HubSpot</code>, <code>Todoist</code>, and <code>Airtable</code>.</p>
144
+ <h3 id="using-auth-tokens" class="tsd-anchor-link">Using Auth Tokens<a href="#using-auth-tokens" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p><code>integrations.get(channelId)</code> returns the access token of the user who enabled sync on that channel, or <code>null</code> if the channel is not enabled or the token is expired/invalid:</p>
145
+ <pre><code class="typescript"><span class="hl-2">async</span><span class="hl-1"> </span><span class="hl-6">syncChannel</span><span class="hl-1">(</span><span class="hl-2">channelId</span><span class="hl-1">: </span><span class="hl-2">string</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">authToken</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">integrations</span><span class="hl-1">.</span><span class="hl-6">get</span><span class="hl-1">(</span><span class="hl-2">channelId</span><span class="hl-1">);</span><br/><span class="hl-1"> </span><span class="hl-0">if</span><span class="hl-1"> (!</span><span class="hl-2">authToken</span><span class="hl-1">) </span><span class="hl-0">return</span><span class="hl-1">;</span><br/><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">response</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-6">fetch</span><span class="hl-1">(</span><br/><span class="hl-1"> </span><span class="hl-3">`https://www.googleapis.com/calendar/v3/calendars/</span><span class="hl-4">${</span><span class="hl-2">channelId</span><span class="hl-4">}</span><span class="hl-3">/events`</span><span class="hl-1">,</span><br/><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">headers:</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">Authorization:</span><span class="hl-1"> </span><span class="hl-3">`Bearer </span><span class="hl-4">${</span><span class="hl-2">authToken</span><span class="hl-13">.</span><span class="hl-2">token</span><span class="hl-4">}</span><span class="hl-3">`</span><span class="hl-1">,</span><br/><span class="hl-1"> },</span><br/><span class="hl-1"> }</span><br/><span class="hl-1"> );</span><br/><span class="hl-1">}</span>
95
146
  </code><button type="button">Copy</button></pre>
96
147
 
97
- <h3 id="auth-providers" class="tsd-anchor-link">Auth Providers<a href="#auth-providers" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><ul>
98
- <li><strong>AuthProvider.Google</strong> - Google services</li>
99
- <li><strong>AuthProvider.Microsoft</strong> - Microsoft services</li>
148
+ <p>If an API call fails with a permanent auth error the runtime can't observe (e.g. Slack <code>token_revoked</code>), call <code>integrations.markNeedsReauth(channelId)</code> so the app prompts the user to reconnect.</p>
149
+ <h3 id="saving-synced-data" class="tsd-anchor-link">Saving Synced Data<a href="#saving-synced-data" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p>Connectors save external items with <code>saveLink()</code> each call upserts a thread+link pair keyed on the link's canonical <code>sources</code>:</p>
150
+ <pre><code class="typescript"><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">integrations</span><span class="hl-1">.</span><span class="hl-6">saveLink</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">sources:</span><span class="hl-1"> [</span><span class="hl-3">`linear:issue:</span><span class="hl-4">${</span><span class="hl-2">issue</span><span class="hl-13">.</span><span class="hl-2">id</span><span class="hl-4">}</span><span class="hl-3">`</span><span class="hl-1">],</span><br/><span class="hl-1"> </span><span class="hl-2">title:</span><span class="hl-1"> </span><span class="hl-2">issue</span><span class="hl-1">.</span><span class="hl-2">title</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">type:</span><span class="hl-1"> </span><span class="hl-3">&quot;issue&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">status:</span><span class="hl-1"> </span><span class="hl-2">issue</span><span class="hl-1">.</span><span class="hl-2">state</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">channelId</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">meta:</span><span class="hl-1"> { </span><span class="hl-2">url:</span><span class="hl-1"> </span><span class="hl-2">issue</span><span class="hl-1">.</span><span class="hl-2">url</span><span class="hl-1"> },</span><br/><span class="hl-1"> </span><span class="hl-2">notes:</span><span class="hl-1"> [</span><br/><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">key:</span><span class="hl-1"> </span><span class="hl-3">&quot;description&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">content:</span><span class="hl-1"> </span><span class="hl-2">issue</span><span class="hl-1">.</span><span class="hl-2">description</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">contentType:</span><span class="hl-1"> </span><span class="hl-3">&quot;markdown&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> },</span><br/><span class="hl-1"> ],</span><br/><span class="hl-1">});</span>
151
+ </code><button type="button">Copy</button></pre>
152
+
153
+ <p>Other key methods:</p>
154
+ <ul>
155
+ <li><strong><code>saveLinks(links)</code></strong> — batch version of <code>saveLink</code>. Prefer it when syncing pages of items: it collapses N saves into one runtime crossing (saving request budget), and a failure on one item doesn't abort the batch (failed items return <code>null</code>).</li>
156
+ <li><strong><code>saveNote(note)</code> / <code>saveNotes(notes)</code></strong> — attach notes to an <em>existing</em> thread (by <code>thread: { id }</code> or <code>{ source }</code>), optionally carrying a note-scoped link. Used by augmenter connectors (e.g. meeting notes attached to a calendar event).</li>
157
+ <li><strong><code>saveContacts(contacts)</code></strong> — bulk-upsert contacts (e.g. workspace members) so the recipient picker can address them.</li>
158
+ <li><strong><code>archiveLinks(filter)</code> / <code>archiveNotes(filter)</code></strong> — archive content this connector created (e.g. in <code>onChannelDisabled</code>).</li>
159
+ <li><strong><code>setThreadToDo(source, actorId, todo)</code></strong> — set or clear a user's to-do flag on a synced thread (e.g. Gmail star, Slack &quot;later&quot;).</li>
160
+ <li><strong><code>channelSyncCompleted(channelId)</code></strong> — signal that the initial backfill for a channel finished, clearing the &quot;syncing…&quot; indicator. Call exactly once per initial sync, not on incremental updates.</li>
161
+ <li><strong><code>saveCustomEmoji(emoji)</code></strong> — cache workspace custom emoji so reactions render and round-trip.</li>
100
162
  </ul>
101
- <h3 id="using-auth-tokens" class="tsd-anchor-link">Using Auth Tokens<a href="#using-auth-tokens" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><pre><code class="typescript"><span class="hl-7">// Retrieve saved authorization</span><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">authorization</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">get</span><span class="hl-1">&lt;</span><span class="hl-5">Authorization</span><span class="hl-1">&gt;(</span><span class="hl-3">&quot;google_auth&quot;</span><span class="hl-1">);</span><br/><br/><span class="hl-0">if</span><span class="hl-1"> (</span><span class="hl-2">authorization</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">authToken</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">integrations</span><span class="hl-1">.</span><span class="hl-6">get</span><span class="hl-1">(</span><span class="hl-2">authorization</span><span class="hl-1">);</span><br/><br/><span class="hl-1"> </span><span class="hl-7">// Use token with external API</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">response</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-6">fetch</span><span class="hl-1">(</span><br/><span class="hl-1"> </span><span class="hl-3">&quot;https://www.googleapis.com/calendar/v3/calendars/primary/events&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">headers:</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">Authorization:</span><span class="hl-1"> </span><span class="hl-3">`Bearer </span><span class="hl-4">${</span><span class="hl-2">authToken</span><span class="hl-13">.</span><span class="hl-2">token</span><span class="hl-4">}</span><span class="hl-3">`</span><span class="hl-1">,</span><br/><span class="hl-1"> },</span><br/><span class="hl-1"> }</span><br/><span class="hl-1"> );</span><br/><span class="hl-1">}</span>
163
+ <h3 id="auth-actions-in-twists" class="tsd-anchor-link">Auth Actions in Twists<a href="#auth-actions-in-twists" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p>Regular twists don't manage OAuth directly, but they can prompt for it by attaching an <code>ActionType.auth</code> action to a note. When the user completes the flow, the callback is invoked with the resulting <code>Authorization</code>:</p>
164
+ <pre><code class="typescript"><span class="hl-0">import</span><span class="hl-1"> { </span><span class="hl-2">ActionType</span><span class="hl-1"> } </span><span class="hl-0">from</span><span class="hl-1"> </span><span class="hl-3">&quot;@plotday/twister&quot;</span><span class="hl-1">;</span><br/><span class="hl-0">import</span><span class="hl-1"> { </span><span class="hl-2">AuthProvider</span><span class="hl-1">, </span><span class="hl-0">type</span><span class="hl-1"> </span><span class="hl-2">Authorization</span><span class="hl-1"> } </span><span class="hl-0">from</span><span class="hl-1"> </span><span class="hl-3">&quot;@plotday/twister/tools/integrations&quot;</span><span class="hl-1">;</span><br/><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">authCallback</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">callback</span><span class="hl-1">(</span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">onAuthComplete</span><span class="hl-1">);</span><br/><br/><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">createThread</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">title:</span><span class="hl-1"> </span><span class="hl-3">&quot;Connect your Google Calendar&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">notes:</span><span class="hl-1"> [</span><br/><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">content:</span><span class="hl-1"> </span><span class="hl-3">&quot;Click below to connect your Google account&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">actions:</span><span class="hl-1"> [</span><br/><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">type:</span><span class="hl-1"> </span><span class="hl-2">ActionType</span><span class="hl-1">.</span><span class="hl-2">auth</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">title:</span><span class="hl-1"> </span><span class="hl-3">&quot;Connect Google&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">provider:</span><span class="hl-1"> </span><span class="hl-2">AuthProvider</span><span class="hl-1">.</span><span class="hl-2">Google</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">scopes:</span><span class="hl-1"> [</span><span class="hl-3">&quot;https://www.googleapis.com/auth/calendar.readonly&quot;</span><span class="hl-1">],</span><br/><span class="hl-1"> </span><span class="hl-2">callback:</span><span class="hl-1"> </span><span class="hl-2">authCallback</span><span class="hl-1">,</span><br/><span class="hl-1"> },</span><br/><span class="hl-1"> ],</span><br/><span class="hl-1"> },</span><br/><span class="hl-1"> ],</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-2">async</span><span class="hl-1"> </span><span class="hl-6">onAuthComplete</span><span class="hl-1">(</span><span class="hl-2">authorization</span><span class="hl-1">: </span><span class="hl-2">Authorization</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-7">// authorization.provider, authorization.scopes, authorization.actor</span><br/><span class="hl-1">}</span>
102
165
  </code><button type="button">Copy</button></pre>
103
166
 
167
+ <p>See <a href="MULTI_USER_AUTH.html">Multi-User Auth</a> for per-user auth patterns.</p>
104
168
  <hr>
105
169
  <h2 id="tasks" class="tsd-anchor-link">Tasks<a href="#tasks" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h2><p>Queue background tasks and schedule operations. <strong>Critical for staying under request limits</strong>: each execution has ~1000 requests (HTTP requests, tool calls, database operations), and running a task creates a NEW execution with a fresh request limit.</p>
106
170
  <p><strong>Key distinction:</strong></p>
@@ -110,17 +174,18 @@
110
174
  </ul>
111
175
  <p>Tasks methods are available directly on the twist class.</p>
112
176
  <h3 id="setup-3" class="tsd-anchor-link">Setup<a href="#setup-3" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p>Tasks are available automatically - no build() declaration needed!</p>
113
- <h3 id="running-tasks-immediately" class="tsd-anchor-link">Running Tasks Immediately<a href="#running-tasks-immediately" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><pre><code class="typescript"><span class="hl-7">// Create a callback</span><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">callback</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">callback</span><span class="hl-1">(</span><span class="hl-3">&quot;processData&quot;</span><span class="hl-1">, { </span><span class="hl-2">batchId:</span><span class="hl-1"> </span><span class="hl-14">1</span><span class="hl-1"> });</span><br/><br/><span class="hl-7">// Run immediately</span><br/><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">runTask</span><span class="hl-1">(</span><span class="hl-2">callback</span><span class="hl-1">);</span><br/><br/><span class="hl-7">// The processData method will be called</span><br/><span class="hl-2">async</span><span class="hl-1"> </span><span class="hl-6">processData</span><span class="hl-1">(</span><span class="hl-2">args</span><span class="hl-1">: </span><span class="hl-2">any</span><span class="hl-1">, </span><span class="hl-2">context</span><span class="hl-1">: { </span><span class="hl-2">batchId:</span><span class="hl-1"> </span><span class="hl-2">number</span><span class="hl-1"> }) {</span><br/><span class="hl-1"> </span><span class="hl-2">console</span><span class="hl-1">.</span><span class="hl-6">log</span><span class="hl-1">(</span><span class="hl-3">&quot;Processing batch:&quot;</span><span class="hl-1">, </span><span class="hl-2">context</span><span class="hl-1">.</span><span class="hl-2">batchId</span><span class="hl-1">);</span><br/><span class="hl-1">}</span>
177
+ <h3 id="running-tasks-immediately" class="tsd-anchor-link">Running Tasks Immediately<a href="#running-tasks-immediately" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><pre><code class="typescript"><span class="hl-7">// Create a callback to a method, currying extra arguments</span><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">callback</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">callback</span><span class="hl-1">(</span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">processData</span><span class="hl-1">, </span><span class="hl-14">1</span><span class="hl-1">);</span><br/><br/><span class="hl-7">// Run immediately in a fresh execution</span><br/><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">runTask</span><span class="hl-1">(</span><span class="hl-2">callback</span><span class="hl-1">);</span><br/><br/><span class="hl-7">// The method receives the curried arguments</span><br/><span class="hl-2">async</span><span class="hl-1"> </span><span class="hl-6">processData</span><span class="hl-1">(</span><span class="hl-2">batchId</span><span class="hl-1">: </span><span class="hl-2">number</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-2">console</span><span class="hl-1">.</span><span class="hl-6">log</span><span class="hl-1">(</span><span class="hl-3">&quot;Processing batch:&quot;</span><span class="hl-1">, </span><span class="hl-2">batchId</span><span class="hl-1">);</span><br/><span class="hl-1">}</span>
114
178
  </code><button type="button">Copy</button></pre>
115
179
 
116
- <h3 id="scheduling-tasks" class="tsd-anchor-link">Scheduling Tasks<a href="#scheduling-tasks" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><pre><code class="typescript"><span class="hl-7">// Schedule for a specific time</span><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">reminderCallback</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">callback</span><span class="hl-1">(</span><span class="hl-3">&quot;sendReminder&quot;</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-2">userId:</span><span class="hl-1"> </span><span class="hl-3">&quot;123&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">message:</span><span class="hl-1"> </span><span class="hl-3">&quot;Meeting in 10 minutes&quot;</span><span class="hl-1">,</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">token</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">runTask</span><span class="hl-1">(</span><span class="hl-2">reminderCallback</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-2">runAt:</span><span class="hl-1"> </span><span class="hl-4">new</span><span class="hl-1"> </span><span class="hl-6">Date</span><span class="hl-1">(</span><span class="hl-3">&quot;2025-02-01T09:50:00Z&quot;</span><span class="hl-1">),</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-7">// Save token to cancel later if needed</span><br/><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">set</span><span class="hl-1">(</span><span class="hl-3">&quot;reminder_token&quot;</span><span class="hl-1">, </span><span class="hl-2">token</span><span class="hl-1">);</span>
180
+ <h3 id="scheduling-tasks" class="tsd-anchor-link">Scheduling Tasks<a href="#scheduling-tasks" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><pre><code class="typescript"><span class="hl-7">// Schedule for a specific time</span><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">reminderCallback</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">callback</span><span class="hl-1">(</span><br/><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">sendReminder</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-3">&quot;123&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-3">&quot;Meeting in 10 minutes&quot;</span><br/><span class="hl-1">);</span><br/><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">token</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">runTask</span><span class="hl-1">(</span><span class="hl-2">reminderCallback</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-2">runAt:</span><span class="hl-1"> </span><span class="hl-4">new</span><span class="hl-1"> </span><span class="hl-6">Date</span><span class="hl-1">(</span><span class="hl-3">&quot;2025-02-01T09:50:00Z&quot;</span><span class="hl-1">),</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-7">// Save token to cancel later if needed (returned only for scheduled tasks)</span><br/><span class="hl-0">if</span><span class="hl-1"> (</span><span class="hl-2">token</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">set</span><span class="hl-1">(</span><span class="hl-3">&quot;reminder_token&quot;</span><span class="hl-1">, </span><span class="hl-2">token</span><span class="hl-1">);</span><br/><span class="hl-1">}</span><br/><br/><span class="hl-2">async</span><span class="hl-1"> </span><span class="hl-6">sendReminder</span><span class="hl-1">(</span><span class="hl-2">userId</span><span class="hl-1">: </span><span class="hl-2">string</span><span class="hl-1">, </span><span class="hl-2">message</span><span class="hl-1">: </span><span class="hl-2">string</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-7">// ...</span><br/><span class="hl-1">}</span>
117
181
  </code><button type="button">Copy</button></pre>
118
182
 
119
- <h3 id="canceling-tasks" class="tsd-anchor-link">Canceling Tasks<a href="#canceling-tasks" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><pre><code class="typescript"><span class="hl-7">// Cancel a specific task</span><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">token</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">get</span><span class="hl-1">&lt;</span><span class="hl-5">string</span><span class="hl-1">&gt;(</span><span class="hl-3">&quot;reminder_token&quot;</span><span class="hl-1">);</span><br/><span class="hl-0">if</span><span class="hl-1"> (</span><span class="hl-2">token</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">cancelTask</span><span class="hl-1">(</span><span class="hl-2">token</span><span class="hl-1">);</span><br/><span class="hl-1">}</span><br/><br/><span class="hl-7">// Cancel all scheduled tasks for this twist</span><br/><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">cancelAllTasks</span><span class="hl-1">();</span>
183
+ <h3 id="canceling-tasks" class="tsd-anchor-link">Canceling Tasks<a href="#canceling-tasks" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><pre><code class="typescript"><span class="hl-7">// Cancel a specific scheduled task</span><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">token</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">get</span><span class="hl-1">&lt;</span><span class="hl-5">string</span><span class="hl-1">&gt;(</span><span class="hl-3">&quot;reminder_token&quot;</span><span class="hl-1">);</span><br/><span class="hl-0">if</span><span class="hl-1"> (</span><span class="hl-2">token</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">cancelTask</span><span class="hl-1">(</span><span class="hl-2">token</span><span class="hl-1">);</span><br/><span class="hl-1">}</span><br/><br/><span class="hl-7">// Cancel all scheduled tasks for this twist</span><br/><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">cancelAllTasks</span><span class="hl-1">();</span>
120
184
  </code><button type="button">Copy</button></pre>
121
185
 
186
+ <p>Immediate (non-scheduled) tasks cannot be cancelled.</p>
122
187
  <h3 id="batch-processing-pattern" class="tsd-anchor-link">Batch Processing Pattern<a href="#batch-processing-pattern" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p>Use tasks to break long operations into chunks that stay under the ~1000 request limit per execution:</p>
123
- <pre><code class="typescript"><span class="hl-2">async</span><span class="hl-1"> </span><span class="hl-6">startSync</span><span class="hl-1">() {</span><br/><span class="hl-1"> </span><span class="hl-7">// Initialize state</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">set</span><span class="hl-1">(</span><span class="hl-3">&quot;sync_state&quot;</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-2">page:</span><span class="hl-1"> </span><span class="hl-14">1</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">hasMore:</span><span class="hl-1"> </span><span class="hl-4">true</span><br/><span class="hl-1"> });</span><br/><br/><span class="hl-1"> </span><span class="hl-7">// Start first batch</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">callback</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">callback</span><span class="hl-1">(</span><span class="hl-3">&quot;syncBatch&quot;</span><span class="hl-1">);</span><br/><span class="hl-1"> </span><span class="hl-7">// runTask creates NEW execution with fresh request limit</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">runTask</span><span class="hl-1">(</span><span class="hl-2">callback</span><span class="hl-1">);</span><br/><span class="hl-1">}</span><br/><br/><span class="hl-2">async</span><span class="hl-1"> </span><span class="hl-6">syncBatch</span><span class="hl-1">() {</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">state</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">get</span><span class="hl-1">&lt;{ </span><span class="hl-2">page</span><span class="hl-1">: </span><span class="hl-5">number</span><span class="hl-1">; </span><span class="hl-2">hasMore</span><span class="hl-1">: </span><span class="hl-5">boolean</span><span class="hl-1"> }&gt;(</span><span class="hl-3">&quot;sync_state&quot;</span><span class="hl-1">);</span><br/><span class="hl-1"> </span><span class="hl-0">if</span><span class="hl-1"> (!</span><span class="hl-2">state</span><span class="hl-1"> || !</span><span class="hl-2">state</span><span class="hl-1">.</span><span class="hl-2">hasMore</span><span class="hl-1">) </span><span class="hl-0">return</span><span class="hl-1">;</span><br/><br/><span class="hl-1"> </span><span class="hl-7">// Process one page (sized to stay under request limit)</span><br/><span class="hl-1"> </span><span class="hl-7">// If each item makes ~10 requests, fetch ~100 items per page</span><br/><span class="hl-1"> </span><span class="hl-7">// 100 items × 10 requests = 1000 requests (at limit)</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">results</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">fetchPage</span><span class="hl-1">(</span><span class="hl-2">state</span><span class="hl-1">.</span><span class="hl-2">page</span><span class="hl-1">, </span><span class="hl-14">100</span><span class="hl-1">);</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">processResults</span><span class="hl-1">(</span><span class="hl-2">results</span><span class="hl-1">);</span><br/><br/><span class="hl-1"> </span><span class="hl-7">// Check if more work remains</span><br/><span class="hl-1"> </span><span class="hl-0">if</span><span class="hl-1"> (</span><span class="hl-2">results</span><span class="hl-1">.</span><span class="hl-2">hasMore</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">set</span><span class="hl-1">(</span><span class="hl-3">&quot;sync_state&quot;</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-2">page:</span><span class="hl-1"> </span><span class="hl-2">state</span><span class="hl-1">.</span><span class="hl-2">page</span><span class="hl-1"> + </span><span class="hl-14">1</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">hasMore:</span><span class="hl-1"> </span><span class="hl-4">true</span><br/><span class="hl-1"> });</span><br/><br/><span class="hl-1"> </span><span class="hl-7">// Queue next batch - creates NEW execution with fresh request limit</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">callback</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">callback</span><span class="hl-1">(</span><span class="hl-3">&quot;syncBatch&quot;</span><span class="hl-1">);</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">runTask</span><span class="hl-1">(</span><span class="hl-2">callback</span><span class="hl-1">);</span><br/><span class="hl-1"> } </span><span class="hl-0">else</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">set</span><span class="hl-1">(</span><span class="hl-3">&quot;sync_state&quot;</span><span class="hl-1">, { </span><span class="hl-2">page:</span><span class="hl-1"> </span><span class="hl-2">state</span><span class="hl-1">.</span><span class="hl-2">page</span><span class="hl-1">, </span><span class="hl-2">hasMore:</span><span class="hl-1"> </span><span class="hl-4">false</span><span class="hl-1"> });</span><br/><span class="hl-1"> }</span><br/><span class="hl-1">}</span>
188
+ <pre><code class="typescript"><span class="hl-2">async</span><span class="hl-1"> </span><span class="hl-6">startSync</span><span class="hl-1">() {</span><br/><span class="hl-1"> </span><span class="hl-7">// Initialize state</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">set</span><span class="hl-1">(</span><span class="hl-3">&quot;sync_state&quot;</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-2">page:</span><span class="hl-1"> </span><span class="hl-14">1</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">hasMore:</span><span class="hl-1"> </span><span class="hl-4">true</span><br/><span class="hl-1"> });</span><br/><br/><span class="hl-1"> </span><span class="hl-7">// Start first batch</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">callback</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">callback</span><span class="hl-1">(</span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">syncBatch</span><span class="hl-1">);</span><br/><span class="hl-1"> </span><span class="hl-7">// runTask creates NEW execution with fresh request limit</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">runTask</span><span class="hl-1">(</span><span class="hl-2">callback</span><span class="hl-1">);</span><br/><span class="hl-1">}</span><br/><br/><span class="hl-2">async</span><span class="hl-1"> </span><span class="hl-6">syncBatch</span><span class="hl-1">() {</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">state</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">get</span><span class="hl-1">&lt;{ </span><span class="hl-2">page</span><span class="hl-1">: </span><span class="hl-5">number</span><span class="hl-1">; </span><span class="hl-2">hasMore</span><span class="hl-1">: </span><span class="hl-5">boolean</span><span class="hl-1"> }&gt;(</span><span class="hl-3">&quot;sync_state&quot;</span><span class="hl-1">);</span><br/><span class="hl-1"> </span><span class="hl-0">if</span><span class="hl-1"> (!</span><span class="hl-2">state</span><span class="hl-1"> || !</span><span class="hl-2">state</span><span class="hl-1">.</span><span class="hl-2">hasMore</span><span class="hl-1">) </span><span class="hl-0">return</span><span class="hl-1">;</span><br/><br/><span class="hl-1"> </span><span class="hl-7">// Process one page (sized to stay under request limit)</span><br/><span class="hl-1"> </span><span class="hl-7">// If each item makes ~10 requests, fetch ~100 items per page</span><br/><span class="hl-1"> </span><span class="hl-7">// 100 items × 10 requests = 1000 requests (at limit)</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">results</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">fetchPage</span><span class="hl-1">(</span><span class="hl-2">state</span><span class="hl-1">.</span><span class="hl-2">page</span><span class="hl-1">, </span><span class="hl-14">100</span><span class="hl-1">);</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">processResults</span><span class="hl-1">(</span><span class="hl-2">results</span><span class="hl-1">);</span><br/><br/><span class="hl-1"> </span><span class="hl-7">// Check if more work remains</span><br/><span class="hl-1"> </span><span class="hl-0">if</span><span class="hl-1"> (</span><span class="hl-2">results</span><span class="hl-1">.</span><span class="hl-2">hasMore</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">set</span><span class="hl-1">(</span><span class="hl-3">&quot;sync_state&quot;</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-2">page:</span><span class="hl-1"> </span><span class="hl-2">state</span><span class="hl-1">.</span><span class="hl-2">page</span><span class="hl-1"> + </span><span class="hl-14">1</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">hasMore:</span><span class="hl-1"> </span><span class="hl-4">true</span><br/><span class="hl-1"> });</span><br/><br/><span class="hl-1"> </span><span class="hl-7">// Queue next batch - creates NEW execution with fresh request limit</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">callback</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">callback</span><span class="hl-1">(</span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">syncBatch</span><span class="hl-1">);</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">runTask</span><span class="hl-1">(</span><span class="hl-2">callback</span><span class="hl-1">);</span><br/><span class="hl-1"> } </span><span class="hl-0">else</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">set</span><span class="hl-1">(</span><span class="hl-3">&quot;sync_state&quot;</span><span class="hl-1">, { </span><span class="hl-2">page:</span><span class="hl-1"> </span><span class="hl-2">state</span><span class="hl-1">.</span><span class="hl-2">page</span><span class="hl-1">, </span><span class="hl-2">hasMore:</span><span class="hl-1"> </span><span class="hl-4">false</span><span class="hl-1"> });</span><br/><span class="hl-1"> }</span><br/><span class="hl-1">}</span>
124
189
  </code><button type="button">Copy</button></pre>
125
190
 
126
191
  <p>See <a href="Runtime_Environment.html">Runtime Environment</a> for more about handling long operations.</p>
@@ -129,27 +194,43 @@
129
194
  <h3 id="setup-4" class="tsd-anchor-link">Setup<a href="#setup-4" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><pre><code class="typescript"><span class="hl-0">import</span><span class="hl-1"> { </span><span class="hl-2">Network</span><span class="hl-1">, </span><span class="hl-0">type</span><span class="hl-1"> </span><span class="hl-2">WebhookRequest</span><span class="hl-1"> } </span><span class="hl-0">from</span><span class="hl-1"> </span><span class="hl-3">&quot;@plotday/twister/tools/network&quot;</span><span class="hl-1">;</span><br/><br/><span class="hl-6">build</span><span class="hl-1">(</span><span class="hl-2">build</span><span class="hl-1">: </span><span class="hl-2">ToolBuilder</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-0">return</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">network:</span><span class="hl-1"> </span><span class="hl-6">build</span><span class="hl-1">(</span><span class="hl-2">Network</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-7">// Declare which URLs you&#39;ll access</span><br/><span class="hl-1"> </span><span class="hl-2">urls:</span><span class="hl-1"> [</span><span class="hl-3">&#39;https://api.example.com/*&#39;</span><span class="hl-1">]</span><br/><span class="hl-1"> })</span><br/><span class="hl-1"> };</span><br/><span class="hl-1">}</span>
130
195
  </code><button type="button">Copy</button></pre>
131
196
 
197
+ <p>All outbound HTTP is blocked except the declared URLs. Wildcards are supported for domains (<code>https://*.example.com</code>) and paths (<code>https://api.example.com/v1/*</code>).</p>
132
198
  <h3 id="making-http-requests" class="tsd-anchor-link">Making HTTP Requests<a href="#making-http-requests" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p>Once declared in the <code>urls</code> array, you can use fetch() normally:</p>
133
199
  <pre><code class="typescript"><span class="hl-2">async</span><span class="hl-1"> </span><span class="hl-6">fetchData</span><span class="hl-1">() {</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">response</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-6">fetch</span><span class="hl-1">(</span><span class="hl-3">&quot;https://api.example.com/data&quot;</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-2">headers:</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">Authorization:</span><span class="hl-1"> </span><span class="hl-3">`Bearer </span><span class="hl-4">${</span><span class="hl-2">token</span><span class="hl-4">}</span><span class="hl-3">`</span><br/><span class="hl-1"> }</span><br/><span class="hl-1"> });</span><br/><br/><span class="hl-1"> </span><span class="hl-0">return</span><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-2">response</span><span class="hl-1">.</span><span class="hl-6">json</span><span class="hl-1">();</span><br/><span class="hl-1">}</span>
134
200
  </code><button type="button">Copy</button></pre>
135
201
 
136
- <h3 id="creating-webhooks" class="tsd-anchor-link">Creating Webhooks<a href="#creating-webhooks" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><pre><code class="typescript"><span class="hl-2">async</span><span class="hl-1"> </span><span class="hl-6">activate</span><span class="hl-1">() {</span><br/><span class="hl-1"> </span><span class="hl-7">// Create webhook endpoint</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">webhookUrl</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">network</span><span class="hl-1">.</span><span class="hl-6">createWebhook</span><span class="hl-1">(</span><br/><span class="hl-1"> </span><span class="hl-3">&quot;onCalendarUpdate&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> { </span><span class="hl-2">calendarId:</span><span class="hl-1"> </span><span class="hl-3">&quot;primary&quot;</span><span class="hl-1"> }</span><br/><span class="hl-1"> );</span><br/><br/><span class="hl-1"> </span><span class="hl-7">// Save for cleanup later</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">set</span><span class="hl-1">(</span><span class="hl-3">&quot;webhook_url&quot;</span><span class="hl-1">, </span><span class="hl-2">webhookUrl</span><span class="hl-1">);</span><br/><br/><span class="hl-1"> </span><span class="hl-7">// Register with external service</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-6">fetch</span><span class="hl-1">(</span><span class="hl-3">&quot;https://api.service.com/webhooks&quot;</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-2">method:</span><span class="hl-1"> </span><span class="hl-3">&quot;POST&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">body:</span><span class="hl-1"> </span><span class="hl-8">JSON</span><span class="hl-1">.</span><span class="hl-6">stringify</span><span class="hl-1">({ </span><span class="hl-2">url:</span><span class="hl-1"> </span><span class="hl-2">webhookUrl</span><span class="hl-1"> })</span><br/><span class="hl-1"> });</span><br/><span class="hl-1">}</span><br/><br/><span class="hl-7">// Handle webhook requests</span><br/><span class="hl-2">async</span><span class="hl-1"> </span><span class="hl-6">onCalendarUpdate</span><span class="hl-1">(</span><span class="hl-2">request</span><span class="hl-1">: </span><span class="hl-2">WebhookRequest</span><span class="hl-1">, </span><span class="hl-2">context</span><span class="hl-1">: { </span><span class="hl-2">calendarId:</span><span class="hl-1"> </span><span class="hl-2">string</span><span class="hl-1"> }) {</span><br/><span class="hl-1"> </span><span class="hl-2">console</span><span class="hl-1">.</span><span class="hl-6">log</span><span class="hl-1">(</span><span class="hl-3">&quot;Webhook received:&quot;</span><span class="hl-1">, </span><span class="hl-2">request</span><span class="hl-1">.</span><span class="hl-2">method</span><span class="hl-1">);</span><br/><span class="hl-1"> </span><span class="hl-2">console</span><span class="hl-1">.</span><span class="hl-6">log</span><span class="hl-1">(</span><span class="hl-3">&quot;Body:&quot;</span><span class="hl-1">, </span><span class="hl-2">request</span><span class="hl-1">.</span><span class="hl-2">body</span><span class="hl-1">);</span><br/><span class="hl-1"> </span><span class="hl-2">console</span><span class="hl-1">.</span><span class="hl-6">log</span><span class="hl-1">(</span><span class="hl-3">&quot;Calendar:&quot;</span><span class="hl-1">, </span><span class="hl-2">context</span><span class="hl-1">.</span><span class="hl-2">calendarId</span><span class="hl-1">);</span><br/><br/><span class="hl-1"> </span><span class="hl-7">// Process the webhook</span><br/><span class="hl-1"> </span><span class="hl-0">if</span><span class="hl-1"> (</span><span class="hl-2">request</span><span class="hl-1">.</span><span class="hl-2">body</span><span class="hl-1">.</span><span class="hl-2">type</span><span class="hl-1"> === </span><span class="hl-3">&quot;event.created&quot;</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">syncEvent</span><span class="hl-1">(</span><span class="hl-2">request</span><span class="hl-1">.</span><span class="hl-2">body</span><span class="hl-1">.</span><span class="hl-2">event</span><span class="hl-1">);</span><br/><span class="hl-1"> }</span><br/><span class="hl-1">}</span>
202
+ <h3 id="creating-webhooks" class="tsd-anchor-link">Creating Webhooks<a href="#creating-webhooks" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p><code>createWebhook(options, callback, ...extraArgs)</code> takes a method reference plus optional curried arguments, and returns a unique URL. The method is invoked with the <code>WebhookRequest</code> first, followed by the curried arguments:</p>
203
+ <pre><code class="typescript"><span class="hl-2">async</span><span class="hl-1"> </span><span class="hl-6">activate</span><span class="hl-1">() {</span><br/><span class="hl-1"> </span><span class="hl-7">// Create webhook endpoint</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">webhookUrl</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">network</span><span class="hl-1">.</span><span class="hl-6">createWebhook</span><span class="hl-1">(</span><br/><span class="hl-1"> {},</span><br/><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">onCalendarUpdate</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-3">&quot;primary&quot;</span><br/><span class="hl-1"> );</span><br/><br/><span class="hl-1"> </span><span class="hl-7">// Save for cleanup later</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">set</span><span class="hl-1">(</span><span class="hl-3">&quot;webhook_url&quot;</span><span class="hl-1">, </span><span class="hl-2">webhookUrl</span><span class="hl-1">);</span><br/><br/><span class="hl-1"> </span><span class="hl-7">// Register with external service</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-6">fetch</span><span class="hl-1">(</span><span class="hl-3">&quot;https://api.service.com/webhooks&quot;</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-2">method:</span><span class="hl-1"> </span><span class="hl-3">&quot;POST&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">body:</span><span class="hl-1"> </span><span class="hl-8">JSON</span><span class="hl-1">.</span><span class="hl-6">stringify</span><span class="hl-1">({ </span><span class="hl-2">url:</span><span class="hl-1"> </span><span class="hl-2">webhookUrl</span><span class="hl-1"> })</span><br/><span class="hl-1"> });</span><br/><span class="hl-1">}</span><br/><br/><span class="hl-7">// Handle webhook requests</span><br/><span class="hl-2">async</span><span class="hl-1"> </span><span class="hl-6">onCalendarUpdate</span><span class="hl-1">(</span><span class="hl-2">request</span><span class="hl-1">: </span><span class="hl-2">WebhookRequest</span><span class="hl-1">, </span><span class="hl-2">calendarId</span><span class="hl-1">: </span><span class="hl-2">string</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-2">console</span><span class="hl-1">.</span><span class="hl-6">log</span><span class="hl-1">(</span><span class="hl-3">&quot;Webhook received:&quot;</span><span class="hl-1">, </span><span class="hl-2">request</span><span class="hl-1">.</span><span class="hl-2">method</span><span class="hl-1">);</span><br/><span class="hl-1"> </span><span class="hl-2">console</span><span class="hl-1">.</span><span class="hl-6">log</span><span class="hl-1">(</span><span class="hl-3">&quot;Calendar:&quot;</span><span class="hl-1">, </span><span class="hl-2">calendarId</span><span class="hl-1">);</span><br/><br/><span class="hl-1"> </span><span class="hl-7">// Process the webhook (request.body is parsed JSON)</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">event</span><span class="hl-1"> = </span><span class="hl-2">request</span><span class="hl-1">.</span><span class="hl-2">body</span><span class="hl-1"> </span><span class="hl-0">as</span><span class="hl-1"> { </span><span class="hl-2">type</span><span class="hl-1">?: </span><span class="hl-5">string</span><span class="hl-1">; </span><span class="hl-2">event</span><span class="hl-1">?: </span><span class="hl-5">unknown</span><span class="hl-1"> };</span><br/><span class="hl-1"> </span><span class="hl-0">if</span><span class="hl-1"> (</span><span class="hl-2">event</span><span class="hl-1">.</span><span class="hl-2">type</span><span class="hl-1"> === </span><span class="hl-3">&quot;event.created&quot;</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">syncEvent</span><span class="hl-1">(</span><span class="hl-2">event</span><span class="hl-1">.</span><span class="hl-2">event</span><span class="hl-1">);</span><br/><span class="hl-1"> }</span><br/><span class="hl-1">}</span>
137
204
  </code><button type="button">Copy</button></pre>
138
205
 
206
+ <p><code>WebhookRequest</code> carries <code>method</code>, <code>headers</code>, <code>params</code> (query string), <code>body</code> (parsed JSON when applicable), and <code>rawBody</code> (for signature verification).</p>
207
+ <h4 id="delivery-mode" class="tsd-anchor-link">Delivery Mode<a href="#delivery-mode" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h4><p>By default webhooks are <strong>asynchronous</strong>: Plot immediately responds <code>200 { queued: true }</code> to the sender and runs your callback from a queue with at-least-once delivery — so callbacks must be idempotent. This is the right default for event notifications because slow callbacks can't trigger sender-side retry storms.</p>
208
+ <p>Pass <code>async: false</code> when the sender needs the callback's response — e.g. a subscription validation handshake that must echo a token, or a sender that retries based on status codes. In sync mode, a <code>string</code> return value is sent as <code>text/plain</code> and other values as JSON.</p>
209
+ <h4 id="provider-specific-webhooks" class="tsd-anchor-link">Provider-Specific Webhooks<a href="#provider-specific-webhooks" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h4><ul>
210
+ <li><strong>Slack</strong> (<code>provider: AuthProvider.Slack</code>): routes by team; requires the <code>authorization</code> option.</li>
211
+ <li><strong>Google Pub/Sub</strong> (<code>pubsub: &quot;gmail&quot; | &quot;workspace&quot;</code>): returns a Pub/Sub <strong>topic name</strong> instead of a URL, for Gmail <code>users.watch</code> or Google Workspace Events. Other Google products (Calendar, Drive) use standard HTTPS webhooks — don't set <code>pubsub</code> for them.</li>
212
+ </ul>
139
213
  <h3 id="deleting-webhooks" class="tsd-anchor-link">Deleting Webhooks<a href="#deleting-webhooks" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><pre><code class="typescript"><span class="hl-2">async</span><span class="hl-1"> </span><span class="hl-6">deactivate</span><span class="hl-1">() {</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">webhookUrl</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">get</span><span class="hl-1">&lt;</span><span class="hl-5">string</span><span class="hl-1">&gt;(</span><span class="hl-3">&quot;webhook_url&quot;</span><span class="hl-1">);</span><br/><br/><span class="hl-1"> </span><span class="hl-0">if</span><span class="hl-1"> (</span><span class="hl-2">webhookUrl</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-7">// Unregister from external service</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-6">fetch</span><span class="hl-1">(</span><span class="hl-3">&quot;https://api.service.com/webhooks&quot;</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-2">method:</span><span class="hl-1"> </span><span class="hl-3">&quot;DELETE&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">body:</span><span class="hl-1"> </span><span class="hl-8">JSON</span><span class="hl-1">.</span><span class="hl-6">stringify</span><span class="hl-1">({ </span><span class="hl-2">url:</span><span class="hl-1"> </span><span class="hl-2">webhookUrl</span><span class="hl-1"> })</span><br/><span class="hl-1"> });</span><br/><br/><span class="hl-1"> </span><span class="hl-7">// Delete webhook endpoint</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">network</span><span class="hl-1">.</span><span class="hl-6">deleteWebhook</span><span class="hl-1">(</span><span class="hl-2">webhookUrl</span><span class="hl-1">);</span><br/><span class="hl-1"> }</span><br/><span class="hl-1">}</span>
140
214
  </code><button type="button">Copy</button></pre>
141
215
 
216
+ <p>Always pass <code>deleteWebhook()</code> the exact value returned from <code>createWebhook()</code> (a URL, Pub/Sub topic name, or opaque Slack identifier).</p>
142
217
  <hr>
143
218
  <h2 id="callbacks" class="tsd-anchor-link">Callbacks<a href="#callbacks" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h2><p>Create persistent function references that survive worker restarts. Callbacks methods are available directly on the twist class.</p>
144
219
  <h3 id="setup-5" class="tsd-anchor-link">Setup<a href="#setup-5" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p>Callbacks are available automatically - no build() declaration needed!</p>
145
- <h3 id="creating-callbacks" class="tsd-anchor-link">Creating Callbacks<a href="#creating-callbacks" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><pre><code class="typescript"><span class="hl-7">// Create a callback to a method</span><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">callback</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">callback</span><span class="hl-1">(</span><span class="hl-3">&quot;handleEvent&quot;</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-2">eventType:</span><span class="hl-1"> </span><span class="hl-3">&quot;calendar_sync&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">priority:</span><span class="hl-1"> </span><span class="hl-3">&quot;high&quot;</span><span class="hl-1">,</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-7">// Save it for later use</span><br/><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">set</span><span class="hl-1">(</span><span class="hl-3">&quot;event_handler&quot;</span><span class="hl-1">, </span><span class="hl-2">callback</span><span class="hl-1">);</span>
220
+ <h3 id="creating-callbacks" class="tsd-anchor-link">Creating Callbacks<a href="#creating-callbacks" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p><code>this.callback()</code> takes a <strong>method reference</strong> (not a string) plus optional extra arguments to curry. The extra arguments are type-checked against the method's signature and must be serializable:</p>
221
+ <pre><code class="typescript"><span class="hl-7">// Create a callback to a method, currying two arguments</span><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">callback</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">callback</span><span class="hl-1">(</span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">handleEvent</span><span class="hl-1">, </span><span class="hl-3">&quot;calendar_sync&quot;</span><span class="hl-1">, </span><span class="hl-3">&quot;high&quot;</span><span class="hl-1">);</span><br/><br/><span class="hl-7">// Save it for later use</span><br/><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">set</span><span class="hl-1">(</span><span class="hl-3">&quot;event_handler&quot;</span><span class="hl-1">, </span><span class="hl-2">callback</span><span class="hl-1">);</span>
146
222
  </code><button type="button">Copy</button></pre>
147
223
 
148
- <h3 id="executing-callbacks" class="tsd-anchor-link">Executing Callbacks<a href="#executing-callbacks" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><pre><code class="typescript"><span class="hl-7">// Retrieve saved callback</span><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">callback</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">get</span><span class="hl-1">&lt;</span><span class="hl-5">string</span><span class="hl-1">&gt;(</span><span class="hl-3">&quot;event_handler&quot;</span><span class="hl-1">);</span><br/><br/><span class="hl-0">if</span><span class="hl-1"> (</span><span class="hl-2">callback</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-7">// Execute with additional arguments</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">result</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">run</span><span class="hl-1">(</span><span class="hl-2">callback</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-2">data:</span><span class="hl-1"> </span><span class="hl-2">eventData</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">timestamp:</span><span class="hl-1"> </span><span class="hl-4">new</span><span class="hl-1"> </span><span class="hl-6">Date</span><span class="hl-1">(),</span><br/><span class="hl-1"> });</span><br/><span class="hl-1">}</span>
224
+ <p>For interactive buttons, use <code>this.actionCallback()</code> the method receives the clicked <code>Action</code> as its first argument:</p>
225
+ <pre><code class="typescript"><span class="hl-0">import</span><span class="hl-1"> { </span><span class="hl-0">type</span><span class="hl-1"> </span><span class="hl-2">Action</span><span class="hl-1">, </span><span class="hl-2">ActionType</span><span class="hl-1"> } </span><span class="hl-0">from</span><span class="hl-1"> </span><span class="hl-3">&quot;@plotday/twister&quot;</span><span class="hl-1">;</span><br/><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">action</span><span class="hl-1">: </span><span class="hl-5">Action</span><span class="hl-1"> = {</span><br/><span class="hl-1"> </span><span class="hl-2">type:</span><span class="hl-1"> </span><span class="hl-2">ActionType</span><span class="hl-1">.</span><span class="hl-2">callback</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">title:</span><span class="hl-1"> </span><span class="hl-3">&quot;Approve&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">callback:</span><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">actionCallback</span><span class="hl-1">(</span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">onApprove</span><span class="hl-1">, </span><span class="hl-2">requestId</span><span class="hl-1">),</span><br/><span class="hl-1">};</span><br/><br/><span class="hl-2">async</span><span class="hl-1"> </span><span class="hl-6">onApprove</span><span class="hl-1">(</span><span class="hl-2">action</span><span class="hl-1">: </span><span class="hl-2">Action</span><span class="hl-1">, </span><span class="hl-2">requestId</span><span class="hl-1">: </span><span class="hl-2">string</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-7">// ...</span><br/><span class="hl-1">}</span>
149
226
  </code><button type="button">Copy</button></pre>
150
227
 
151
- <h3 id="method-signature" class="tsd-anchor-link">Method Signature<a href="#method-signature" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p>The callback method receives both the execution args and the original context:</p>
152
- <pre><code class="typescript"><span class="hl-2">async</span><span class="hl-1"> </span><span class="hl-6">handleEvent</span><span class="hl-1">(</span><br/><span class="hl-1"> </span><span class="hl-2">args</span><span class="hl-1">: { </span><span class="hl-2">data:</span><span class="hl-1"> </span><span class="hl-2">any</span><span class="hl-1">; </span><span class="hl-2">timestamp</span><span class="hl-1">: </span><span class="hl-2">Date</span><span class="hl-1"> }, </span><span class="hl-7">// From run()</span><br/><span class="hl-1"> </span><span class="hl-2">context</span><span class="hl-1">: { </span><span class="hl-2">eventType:</span><span class="hl-1"> </span><span class="hl-2">string</span><span class="hl-1">; </span><span class="hl-2">priority</span><span class="hl-1">: </span><span class="hl-2">string</span><span class="hl-1"> } </span><span class="hl-7">// From callback()</span><br/><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-2">console</span><span class="hl-1">.</span><span class="hl-6">log</span><span class="hl-1">(</span><span class="hl-3">&quot;Event type:&quot;</span><span class="hl-1">, </span><span class="hl-2">context</span><span class="hl-1">.</span><span class="hl-2">eventType</span><span class="hl-1">);</span><br/><span class="hl-1"> </span><span class="hl-2">console</span><span class="hl-1">.</span><span class="hl-6">log</span><span class="hl-1">(</span><span class="hl-3">&quot;Priority:&quot;</span><span class="hl-1">, </span><span class="hl-2">context</span><span class="hl-1">.</span><span class="hl-2">priority</span><span class="hl-1">);</span><br/><span class="hl-1"> </span><span class="hl-2">console</span><span class="hl-1">.</span><span class="hl-6">log</span><span class="hl-1">(</span><span class="hl-3">&quot;Data:&quot;</span><span class="hl-1">, </span><span class="hl-2">args</span><span class="hl-1">.</span><span class="hl-2">data</span><span class="hl-1">);</span><br/><span class="hl-1">}</span>
228
+ <h3 id="executing-callbacks" class="tsd-anchor-link">Executing Callbacks<a href="#executing-callbacks" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><pre><code class="typescript"><span class="hl-0">import</span><span class="hl-1"> { </span><span class="hl-0">type</span><span class="hl-1"> </span><span class="hl-2">Callback</span><span class="hl-1"> } </span><span class="hl-0">from</span><span class="hl-1"> </span><span class="hl-3">&quot;@plotday/twister/tools/callbacks&quot;</span><span class="hl-1">;</span><br/><br/><span class="hl-7">// Retrieve saved callback</span><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">callback</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">get</span><span class="hl-1">&lt;</span><span class="hl-5">Callback</span><span class="hl-1">&gt;(</span><span class="hl-3">&quot;event_handler&quot;</span><span class="hl-1">);</span><br/><br/><span class="hl-0">if</span><span class="hl-1"> (</span><span class="hl-2">callback</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-7">// Execute inline in the current execution</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">result</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">run</span><span class="hl-1">(</span><span class="hl-2">callback</span><span class="hl-1">);</span><br/><span class="hl-1">}</span>
229
+ </code><button type="button">Copy</button></pre>
230
+
231
+ <p><code>this.run()</code> executes inline — it shares the current execution's request count and is appropriate when you need the callback's return value. For fire-and-forget work or batch continuations, prefer <code>this.runTask()</code> (see <a href="#tasks">Tasks</a>). To pass call-time arguments, use the tool directly: <code>this.tools.callbacks.run(callback, arg1, arg2)</code>.</p>
232
+ <h3 id="method-signature" class="tsd-anchor-link">Method Signature<a href="#method-signature" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p>The callback method receives any call-time arguments first, followed by the arguments curried at creation:</p>
233
+ <pre><code class="typescript"><span class="hl-7">// Created with: this.callback(this.handleEvent, &quot;calendar_sync&quot;, &quot;high&quot;)</span><br/><span class="hl-7">// Webhooks, actions, etc. supply their own first argument(s) at call time</span><br/><span class="hl-2">async</span><span class="hl-1"> </span><span class="hl-6">handleEvent</span><span class="hl-1">(</span><br/><span class="hl-1"> </span><span class="hl-2">data</span><span class="hl-1">: </span><span class="hl-2">WebhookRequest</span><span class="hl-1">, </span><span class="hl-7">// From the caller (e.g. webhook delivery)</span><br/><span class="hl-1"> </span><span class="hl-2">eventType</span><span class="hl-1">: </span><span class="hl-2">string</span><span class="hl-1">, </span><span class="hl-7">// Curried at creation</span><br/><span class="hl-1"> </span><span class="hl-2">priority</span><span class="hl-1">: </span><span class="hl-2">string</span><span class="hl-1"> </span><span class="hl-7">// Curried at creation</span><br/><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-2">console</span><span class="hl-1">.</span><span class="hl-6">log</span><span class="hl-1">(</span><span class="hl-3">&quot;Event type:&quot;</span><span class="hl-1">, </span><span class="hl-2">eventType</span><span class="hl-1">);</span><br/><span class="hl-1"> </span><span class="hl-2">console</span><span class="hl-1">.</span><span class="hl-6">log</span><span class="hl-1">(</span><span class="hl-3">&quot;Priority:&quot;</span><span class="hl-1">, </span><span class="hl-2">priority</span><span class="hl-1">);</span><br/><span class="hl-1">}</span>
153
234
  </code><button type="button">Copy</button></pre>
154
235
 
155
236
  <h3 id="callback-versioning-and-upgrades" class="tsd-anchor-link">Callback Versioning and Upgrades<a href="#callback-versioning-and-upgrades" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p><strong>CRITICAL:</strong> Callbacks automatically upgrade to new twist versions when you deploy an update. This means:</p>
@@ -160,12 +241,12 @@
160
241
  </ul>
161
242
  <h4 id="handling-version-transitions" class="tsd-anchor-link">Handling Version Transitions<a href="#handling-version-transitions" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h4><p>You have two options when deploying a new version with callback changes:</p>
162
243
  <p><strong>Option 1: Maintain Backward Compatibility</strong> (Recommended)</p>
163
- <pre><code class="typescript"><span class="hl-7">// v1.0 - Original signature</span><br/><span class="hl-2">async</span><span class="hl-1"> </span><span class="hl-6">syncBatch</span><span class="hl-1">(</span><span class="hl-2">batchNumber</span><span class="hl-1">: </span><span class="hl-2">number</span><span class="hl-1">, </span><span class="hl-2">authToken</span><span class="hl-1">: </span><span class="hl-2">string</span><span class="hl-1">, </span><span class="hl-2">calendarId</span><span class="hl-1">: </span><span class="hl-2">string</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-7">// Process batch</span><br/><span class="hl-1">}</span><br/><br/><span class="hl-7">// v1.1 - Add optional parameter at the end</span><br/><span class="hl-2">async</span><span class="hl-1"> </span><span class="hl-6">syncBatch</span><span class="hl-1">(</span><br/><span class="hl-1"> </span><span class="hl-2">batchNumber</span><span class="hl-1">: </span><span class="hl-2">number</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">authToken</span><span class="hl-1">: </span><span class="hl-2">string</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">calendarId</span><span class="hl-1">: </span><span class="hl-2">string</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">initialSync</span><span class="hl-1">?: </span><span class="hl-2">boolean</span><span class="hl-1"> </span><span class="hl-7">// New optional parameter</span><br/><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">isInitial</span><span class="hl-1"> = </span><span class="hl-2">initialSync</span><span class="hl-1"> ?? </span><span class="hl-4">true</span><span class="hl-1">; </span><span class="hl-7">// Safe default for old calls</span><br/><span class="hl-1"> </span><span class="hl-7">// Process batch with new logic</span><br/><span class="hl-1">}</span>
244
+ <pre><code class="typescript"><span class="hl-7">// v1.0 - Original signature</span><br/><span class="hl-2">async</span><span class="hl-1"> </span><span class="hl-6">syncBatch</span><span class="hl-1">(</span><span class="hl-2">batchNumber</span><span class="hl-1">: </span><span class="hl-2">number</span><span class="hl-1">, </span><span class="hl-2">calendarId</span><span class="hl-1">: </span><span class="hl-2">string</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-7">// Process batch</span><br/><span class="hl-1">}</span><br/><br/><span class="hl-7">// v1.1 - Add optional parameter at the end</span><br/><span class="hl-2">async</span><span class="hl-1"> </span><span class="hl-6">syncBatch</span><span class="hl-1">(</span><br/><span class="hl-1"> </span><span class="hl-2">batchNumber</span><span class="hl-1">: </span><span class="hl-2">number</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">calendarId</span><span class="hl-1">: </span><span class="hl-2">string</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">initialSync</span><span class="hl-1">?: </span><span class="hl-2">boolean</span><span class="hl-1"> </span><span class="hl-7">// New optional parameter</span><br/><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">isInitial</span><span class="hl-1"> = </span><span class="hl-2">initialSync</span><span class="hl-1"> ?? </span><span class="hl-4">true</span><span class="hl-1">; </span><span class="hl-7">// Safe default for old calls</span><br/><span class="hl-1"> </span><span class="hl-7">// Process batch with new logic</span><br/><span class="hl-1">}</span>
164
245
  </code><button type="button">Copy</button></pre>
165
246
 
166
247
  <p><strong>Option 2: Maintain Old Function Temporarily</strong></p>
167
248
  <p>For breaking changes, keep the old function and create a new one:</p>
168
- <pre><code class="typescript"><span class="hl-7">// v2.0 - Keep old function for in-flight callbacks</span><br/><span class="hl-2">async</span><span class="hl-1"> </span><span class="hl-6">syncBatch</span><span class="hl-1">(</span><span class="hl-2">batchNumber</span><span class="hl-1">: </span><span class="hl-2">number</span><span class="hl-1">, </span><span class="hl-2">authToken</span><span class="hl-1">: </span><span class="hl-2">string</span><span class="hl-1">, </span><span class="hl-2">calendarId</span><span class="hl-1">: </span><span class="hl-2">string</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-7">// Old implementation still works for callbacks created in v1.x</span><br/><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">processOldBatch</span><span class="hl-1">(</span><span class="hl-2">batchNumber</span><span class="hl-1">, </span><span class="hl-2">authToken</span><span class="hl-1">, </span><span class="hl-2">calendarId</span><span class="hl-1">);</span><br/><span class="hl-1">}</span><br/><br/><span class="hl-7">// New function with better design</span><br/><span class="hl-2">async</span><span class="hl-1"> </span><span class="hl-6">syncBatchV2</span><span class="hl-1">(</span><span class="hl-2">options</span><span class="hl-1">: </span><span class="hl-2">SyncOptions</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-7">// New implementation</span><br/><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">processNewBatch</span><span class="hl-1">(</span><span class="hl-2">options</span><span class="hl-1">);</span><br/><span class="hl-1">}</span><br/><br/><span class="hl-7">// Later in v3.0 - Remove old function once all callbacks complete</span><br/><span class="hl-7">// async syncBatch - REMOVED</span>
249
+ <pre><code class="typescript"><span class="hl-7">// v2.0 - Keep old function for in-flight callbacks</span><br/><span class="hl-2">async</span><span class="hl-1"> </span><span class="hl-6">syncBatch</span><span class="hl-1">(</span><span class="hl-2">batchNumber</span><span class="hl-1">: </span><span class="hl-2">number</span><span class="hl-1">, </span><span class="hl-2">calendarId</span><span class="hl-1">: </span><span class="hl-2">string</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-7">// Old implementation still works for callbacks created in v1.x</span><br/><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">processOldBatch</span><span class="hl-1">(</span><span class="hl-2">batchNumber</span><span class="hl-1">, </span><span class="hl-2">calendarId</span><span class="hl-1">);</span><br/><span class="hl-1">}</span><br/><br/><span class="hl-7">// New function with better design</span><br/><span class="hl-2">async</span><span class="hl-1"> </span><span class="hl-6">syncBatchV2</span><span class="hl-1">(</span><span class="hl-2">options</span><span class="hl-1">: </span><span class="hl-2">SyncOptions</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-7">// New implementation</span><br/><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">processNewBatch</span><span class="hl-1">(</span><span class="hl-2">options</span><span class="hl-1">);</span><br/><span class="hl-1">}</span><br/><br/><span class="hl-7">// Later in v3.0 - Remove old function once all callbacks complete</span><br/><span class="hl-7">// async syncBatch - REMOVED</span>
169
250
  </code><button type="button">Copy</button></pre>
170
251
 
171
252
  <h4 id="affected-callback-types" class="tsd-anchor-link">Affected Callback Types<a href="#affected-callback-types" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h4><p>This versioning behavior applies to ALL callbacks:</p>
@@ -173,15 +254,15 @@
173
254
  <li><strong>Webhooks</strong> - Long-lived, called by external services</li>
174
255
  <li><strong>Scheduled tasks</strong> - Created with <code>runTask()</code>, may run days later</li>
175
256
  <li><strong>Batch operations</strong> - Multi-step processes that span upgrades</li>
176
- <li><strong>Activity link callbacks</strong> - Interactive buttons in activities</li>
257
+ <li><strong>Action callbacks</strong> - Interactive buttons on notes</li>
177
258
  <li><strong>Auth callbacks</strong> - OAuth completion handlers</li>
178
259
  </ul>
179
260
  <h4 id="migration-in-upgrade" class="tsd-anchor-link">Migration in upgrade()<a href="#migration-in-upgrade" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h4><p>For breaking changes, you can recreate callbacks in the <code>upgrade()</code> lifecycle method:</p>
180
- <pre><code class="typescript"><span class="hl-2">async</span><span class="hl-1"> </span><span class="hl-6">upgrade</span><span class="hl-1">() {</span><br/><span class="hl-1"> </span><span class="hl-7">// Get all active syncs that use old callback signature</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">syncs</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">get</span><span class="hl-1">&lt;</span><span class="hl-5">SyncState</span><span class="hl-1">[]&gt;(</span><span class="hl-3">&quot;active_syncs&quot;</span><span class="hl-1">);</span><br/><br/><span class="hl-1"> </span><span class="hl-0">for</span><span class="hl-1"> (</span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">sync</span><span class="hl-1"> </span><span class="hl-4">of</span><span class="hl-1"> </span><span class="hl-2">syncs</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-7">// Cancel old callback</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">oldCallback</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">get</span><span class="hl-1">&lt;</span><span class="hl-5">string</span><span class="hl-1">&gt;(</span><span class="hl-3">`sync_callback_</span><span class="hl-4">${</span><span class="hl-2">sync</span><span class="hl-13">.</span><span class="hl-2">id</span><span class="hl-4">}</span><span class="hl-3">`</span><span class="hl-1">);</span><br/><span class="hl-1"> </span><span class="hl-0">if</span><span class="hl-1"> (</span><span class="hl-2">oldCallback</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">deleteCallback</span><span class="hl-1">(</span><span class="hl-2">oldCallback</span><span class="hl-1">);</span><br/><span class="hl-1"> }</span><br/><br/><span class="hl-1"> </span><span class="hl-7">// Create new callback with updated signature</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">newCallback</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">callback</span><span class="hl-1">(</span><span class="hl-3">&quot;syncBatchV2&quot;</span><span class="hl-1">, </span><span class="hl-2">sync</span><span class="hl-1">.</span><span class="hl-2">id</span><span class="hl-1">);</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">set</span><span class="hl-1">(</span><span class="hl-3">`sync_callback_</span><span class="hl-4">${</span><span class="hl-2">sync</span><span class="hl-13">.</span><span class="hl-2">id</span><span class="hl-4">}</span><span class="hl-3">`</span><span class="hl-1">, </span><span class="hl-2">newCallback</span><span class="hl-1">);</span><br/><span class="hl-1"> }</span><br/><span class="hl-1">}</span>
261
+ <pre><code class="typescript"><span class="hl-2">async</span><span class="hl-1"> </span><span class="hl-6">upgrade</span><span class="hl-1">() {</span><br/><span class="hl-1"> </span><span class="hl-7">// Get all active syncs that use old callback signature</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">syncs</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">get</span><span class="hl-1">&lt;</span><span class="hl-5">SyncState</span><span class="hl-1">[]&gt;(</span><span class="hl-3">&quot;active_syncs&quot;</span><span class="hl-1">);</span><br/><br/><span class="hl-1"> </span><span class="hl-0">for</span><span class="hl-1"> (</span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">sync</span><span class="hl-1"> </span><span class="hl-4">of</span><span class="hl-1"> </span><span class="hl-2">syncs</span><span class="hl-1"> ?? []) {</span><br/><span class="hl-1"> </span><span class="hl-7">// Cancel old callback</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">oldCallback</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">get</span><span class="hl-1">&lt;</span><span class="hl-5">Callback</span><span class="hl-1">&gt;(</span><span class="hl-3">`sync_callback_</span><span class="hl-4">${</span><span class="hl-2">sync</span><span class="hl-13">.</span><span class="hl-2">id</span><span class="hl-4">}</span><span class="hl-3">`</span><span class="hl-1">);</span><br/><span class="hl-1"> </span><span class="hl-0">if</span><span class="hl-1"> (</span><span class="hl-2">oldCallback</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">deleteCallback</span><span class="hl-1">(</span><span class="hl-2">oldCallback</span><span class="hl-1">);</span><br/><span class="hl-1"> }</span><br/><br/><span class="hl-1"> </span><span class="hl-7">// Create new callback with updated signature</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">newCallback</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">callback</span><span class="hl-1">(</span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">syncBatchV2</span><span class="hl-1">, { </span><span class="hl-2">syncId:</span><span class="hl-1"> </span><span class="hl-2">sync</span><span class="hl-1">.</span><span class="hl-2">id</span><span class="hl-1"> });</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">set</span><span class="hl-1">(</span><span class="hl-3">`sync_callback_</span><span class="hl-4">${</span><span class="hl-2">sync</span><span class="hl-13">.</span><span class="hl-2">id</span><span class="hl-4">}</span><span class="hl-3">`</span><span class="hl-1">, </span><span class="hl-2">newCallback</span><span class="hl-1">);</span><br/><span class="hl-1"> }</span><br/><span class="hl-1">}</span>
181
262
  </code><button type="button">Copy</button></pre>
182
263
 
183
264
  <p><strong>Important:</strong> If you don't handle breaking changes, existing callbacks may fail when they execute with incompatible arguments.</p>
184
- <h3 id="deleting-callbacks" class="tsd-anchor-link">Deleting Callbacks<a href="#deleting-callbacks" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><pre><code class="typescript"><span class="hl-7">// Delete a specific callback</span><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">callback</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">get</span><span class="hl-1">&lt;</span><span class="hl-5">string</span><span class="hl-1">&gt;(</span><span class="hl-3">&quot;event_handler&quot;</span><span class="hl-1">);</span><br/><span class="hl-0">if</span><span class="hl-1"> (</span><span class="hl-2">callback</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">deleteCallback</span><span class="hl-1">(</span><span class="hl-2">callback</span><span class="hl-1">);</span><br/><span class="hl-1">}</span><br/><br/><span class="hl-7">// Delete all callbacks for this twist</span><br/><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">deleteAllCallbacks</span><span class="hl-1">();</span>
265
+ <h3 id="deleting-callbacks" class="tsd-anchor-link">Deleting Callbacks<a href="#deleting-callbacks" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><pre><code class="typescript"><span class="hl-7">// Delete a specific callback</span><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">callback</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">get</span><span class="hl-1">&lt;</span><span class="hl-5">Callback</span><span class="hl-1">&gt;(</span><span class="hl-3">&quot;event_handler&quot;</span><span class="hl-1">);</span><br/><span class="hl-0">if</span><span class="hl-1"> (</span><span class="hl-2">callback</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">deleteCallback</span><span class="hl-1">(</span><span class="hl-2">callback</span><span class="hl-1">);</span><br/><span class="hl-1">}</span><br/><br/><span class="hl-7">// Delete all callbacks for this twist</span><br/><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">deleteAllCallbacks</span><span class="hl-1">();</span>
185
266
  </code><button type="button">Copy</button></pre>
186
267
 
187
268
  <h3 id="use-cases" class="tsd-anchor-link">Use Cases<a href="#use-cases" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p>Callbacks are essential for:</p>
@@ -189,48 +270,70 @@
189
270
  <li><strong>Webhooks</strong> - Persistent handlers that survive restarts</li>
190
271
  <li><strong>Auth flows</strong> - Handling OAuth completion</li>
191
272
  <li><strong>Scheduled tasks</strong> - Functions to run at specific times</li>
192
- <li><strong>Activity links</strong> - Interactive buttons in activities</li>
273
+ <li><strong>Note actions</strong> - Interactive buttons on notes</li>
193
274
  </ul>
194
275
  <hr>
195
276
  <h2 id="ai" class="tsd-anchor-link">AI<a href="#ai" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h2><p>Prompt large language models with support for structured output and tool calling.</p>
196
277
  <h3 id="setup-6" class="tsd-anchor-link">Setup<a href="#setup-6" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><pre><code class="typescript"><span class="hl-0">import</span><span class="hl-1"> { </span><span class="hl-2">AI</span><span class="hl-1"> } </span><span class="hl-0">from</span><span class="hl-1"> </span><span class="hl-3">&quot;@plotday/twister/tools/ai&quot;</span><span class="hl-1">;</span><br/><br/><span class="hl-6">build</span><span class="hl-1">(</span><span class="hl-2">build</span><span class="hl-1">: </span><span class="hl-2">ToolBuilder</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-0">return</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">ai:</span><span class="hl-1"> </span><span class="hl-6">build</span><span class="hl-1">(</span><span class="hl-8">AI</span><span class="hl-1">),</span><br/><span class="hl-1"> };</span><br/><span class="hl-1">}</span>
197
278
  </code><button type="button">Copy</button></pre>
198
279
 
280
+ <p>Pass <code>build(AI, { required: false })</code> if your twist can function without AI; users can disable AI features, and a non-required AI tool then throws on <code>prompt()</code>. Check availability first:</p>
281
+ <pre><code class="typescript"><span class="hl-4">const</span><span class="hl-1"> { </span><span class="hl-2">prompt</span><span class="hl-1">: </span><span class="hl-8">canPrompt</span><span class="hl-1">, </span><span class="hl-8">webSearch</span><span class="hl-1"> } = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">ai</span><span class="hl-1">.</span><span class="hl-6">available</span><span class="hl-1">();</span>
282
+ </code><button type="button">Copy</button></pre>
283
+
199
284
  <h3 id="simple-text-generation" class="tsd-anchor-link">Simple Text Generation<a href="#simple-text-generation" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><pre><code class="typescript"><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">response</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">ai</span><span class="hl-1">.</span><span class="hl-6">prompt</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">model:</span><span class="hl-1"> { </span><span class="hl-2">speed:</span><span class="hl-1"> </span><span class="hl-3">&quot;fast&quot;</span><span class="hl-1">, </span><span class="hl-2">cost:</span><span class="hl-1"> </span><span class="hl-3">&quot;low&quot;</span><span class="hl-1"> },</span><br/><span class="hl-1"> </span><span class="hl-2">prompt:</span><span class="hl-1"> </span><span class="hl-3">&quot;Explain quantum computing in simple terms&quot;</span><span class="hl-1">,</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-2">console</span><span class="hl-1">.</span><span class="hl-6">log</span><span class="hl-1">(</span><span class="hl-2">response</span><span class="hl-1">.</span><span class="hl-2">text</span><span class="hl-1">);</span>
200
285
  </code><button type="button">Copy</button></pre>
201
286
 
202
287
  <h3 id="structured-output" class="tsd-anchor-link">Structured Output<a href="#structured-output" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p>Use Typebox schemas to get type-safe structured responses:</p>
203
- <pre><code class="typescript"><span class="hl-0">import</span><span class="hl-1"> { </span><span class="hl-2">Type</span><span class="hl-1"> } </span><span class="hl-0">from</span><span class="hl-1"> </span><span class="hl-3">&quot;typebox&quot;</span><span class="hl-1">;</span><br/><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">schema</span><span class="hl-1"> = </span><span class="hl-2">Type</span><span class="hl-1">.</span><span class="hl-6">Object</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">category:</span><span class="hl-1"> </span><span class="hl-2">Type</span><span class="hl-1">.</span><span class="hl-6">Union</span><span class="hl-1">([</span><br/><span class="hl-1"> </span><span class="hl-2">Type</span><span class="hl-1">.</span><span class="hl-6">Literal</span><span class="hl-1">(</span><span class="hl-3">&quot;work&quot;</span><span class="hl-1">),</span><br/><span class="hl-1"> </span><span class="hl-2">Type</span><span class="hl-1">.</span><span class="hl-6">Literal</span><span class="hl-1">(</span><span class="hl-3">&quot;personal&quot;</span><span class="hl-1">),</span><br/><span class="hl-1"> </span><span class="hl-2">Type</span><span class="hl-1">.</span><span class="hl-6">Literal</span><span class="hl-1">(</span><span class="hl-3">&quot;urgent&quot;</span><span class="hl-1">),</span><br/><span class="hl-1"> ]),</span><br/><span class="hl-1"> </span><span class="hl-2">priority:</span><span class="hl-1"> </span><span class="hl-2">Type</span><span class="hl-1">.</span><span class="hl-6">Number</span><span class="hl-1">({ </span><span class="hl-2">minimum:</span><span class="hl-1"> </span><span class="hl-14">1</span><span class="hl-1">, </span><span class="hl-2">maximum:</span><span class="hl-1"> </span><span class="hl-14">5</span><span class="hl-1"> }),</span><br/><span class="hl-1"> </span><span class="hl-2">summary:</span><span class="hl-1"> </span><span class="hl-2">Type</span><span class="hl-1">.</span><span class="hl-6">String</span><span class="hl-1">({ </span><span class="hl-2">description:</span><span class="hl-1"> </span><span class="hl-3">&quot;Brief summary&quot;</span><span class="hl-1"> }),</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">response</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">ai</span><span class="hl-1">.</span><span class="hl-6">prompt</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">model:</span><span class="hl-1"> { </span><span class="hl-2">speed:</span><span class="hl-1"> </span><span class="hl-3">&quot;balanced&quot;</span><span class="hl-1">, </span><span class="hl-2">cost:</span><span class="hl-1"> </span><span class="hl-3">&quot;medium&quot;</span><span class="hl-1"> },</span><br/><span class="hl-1"> </span><span class="hl-2">prompt:</span><span class="hl-1"> </span><span class="hl-3">&quot;Categorize this email: Meeting at 3pm tomorrow about Q1 planning&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">outputSchema:</span><span class="hl-1"> </span><span class="hl-2">schema</span><span class="hl-1">,</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-7">// Fully typed output!</span><br/><span class="hl-2">console</span><span class="hl-1">.</span><span class="hl-6">log</span><span class="hl-1">(</span><span class="hl-2">response</span><span class="hl-1">.</span><span class="hl-2">output</span><span class="hl-1">.</span><span class="hl-2">category</span><span class="hl-1">); </span><span class="hl-7">// &quot;work&quot; | &quot;personal&quot; | &quot;urgent&quot;</span><br/><span class="hl-2">console</span><span class="hl-1">.</span><span class="hl-6">log</span><span class="hl-1">(</span><span class="hl-2">response</span><span class="hl-1">.</span><span class="hl-2">output</span><span class="hl-1">.</span><span class="hl-2">priority</span><span class="hl-1">); </span><span class="hl-7">// number (1-5)</span><br/><span class="hl-2">console</span><span class="hl-1">.</span><span class="hl-6">log</span><span class="hl-1">(</span><span class="hl-2">response</span><span class="hl-1">.</span><span class="hl-2">output</span><span class="hl-1">.</span><span class="hl-2">summary</span><span class="hl-1">); </span><span class="hl-7">// string</span>
288
+ <pre><code class="typescript"><span class="hl-0">import</span><span class="hl-1"> { </span><span class="hl-2">Type</span><span class="hl-1"> } </span><span class="hl-0">from</span><span class="hl-1"> </span><span class="hl-3">&quot;typebox&quot;</span><span class="hl-1">;</span><br/><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">schema</span><span class="hl-1"> = </span><span class="hl-2">Type</span><span class="hl-1">.</span><span class="hl-6">Object</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">category:</span><span class="hl-1"> </span><span class="hl-2">Type</span><span class="hl-1">.</span><span class="hl-6">Union</span><span class="hl-1">([</span><br/><span class="hl-1"> </span><span class="hl-2">Type</span><span class="hl-1">.</span><span class="hl-6">Literal</span><span class="hl-1">(</span><span class="hl-3">&quot;work&quot;</span><span class="hl-1">),</span><br/><span class="hl-1"> </span><span class="hl-2">Type</span><span class="hl-1">.</span><span class="hl-6">Literal</span><span class="hl-1">(</span><span class="hl-3">&quot;personal&quot;</span><span class="hl-1">),</span><br/><span class="hl-1"> </span><span class="hl-2">Type</span><span class="hl-1">.</span><span class="hl-6">Literal</span><span class="hl-1">(</span><span class="hl-3">&quot;urgent&quot;</span><span class="hl-1">),</span><br/><span class="hl-1"> ]),</span><br/><span class="hl-1"> </span><span class="hl-2">priority:</span><span class="hl-1"> </span><span class="hl-2">Type</span><span class="hl-1">.</span><span class="hl-6">Number</span><span class="hl-1">({ </span><span class="hl-2">minimum:</span><span class="hl-1"> </span><span class="hl-14">1</span><span class="hl-1">, </span><span class="hl-2">maximum:</span><span class="hl-1"> </span><span class="hl-14">5</span><span class="hl-1"> }),</span><br/><span class="hl-1"> </span><span class="hl-2">summary:</span><span class="hl-1"> </span><span class="hl-2">Type</span><span class="hl-1">.</span><span class="hl-6">String</span><span class="hl-1">({ </span><span class="hl-2">description:</span><span class="hl-1"> </span><span class="hl-3">&quot;Brief summary&quot;</span><span class="hl-1"> }),</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">response</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">ai</span><span class="hl-1">.</span><span class="hl-6">prompt</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">model:</span><span class="hl-1"> { </span><span class="hl-2">speed:</span><span class="hl-1"> </span><span class="hl-3">&quot;balanced&quot;</span><span class="hl-1">, </span><span class="hl-2">cost:</span><span class="hl-1"> </span><span class="hl-3">&quot;medium&quot;</span><span class="hl-1"> },</span><br/><span class="hl-1"> </span><span class="hl-2">prompt:</span><span class="hl-1"> </span><span class="hl-3">&quot;Categorize this email: Meeting at 3pm tomorrow about Q1 planning&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">outputSchema:</span><span class="hl-1"> </span><span class="hl-2">schema</span><span class="hl-1">,</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-7">// Fully typed output!</span><br/><span class="hl-2">console</span><span class="hl-1">.</span><span class="hl-6">log</span><span class="hl-1">(</span><span class="hl-2">response</span><span class="hl-1">.</span><span class="hl-2">output</span><span class="hl-1">?.</span><span class="hl-2">category</span><span class="hl-1">); </span><span class="hl-7">// &quot;work&quot; | &quot;personal&quot; | &quot;urgent&quot;</span><br/><span class="hl-2">console</span><span class="hl-1">.</span><span class="hl-6">log</span><span class="hl-1">(</span><span class="hl-2">response</span><span class="hl-1">.</span><span class="hl-2">output</span><span class="hl-1">?.</span><span class="hl-2">priority</span><span class="hl-1">); </span><span class="hl-7">// number (1-5)</span><br/><span class="hl-2">console</span><span class="hl-1">.</span><span class="hl-6">log</span><span class="hl-1">(</span><span class="hl-2">response</span><span class="hl-1">.</span><span class="hl-2">output</span><span class="hl-1">?.</span><span class="hl-2">summary</span><span class="hl-1">); </span><span class="hl-7">// string</span>
204
289
  </code><button type="button">Copy</button></pre>
205
290
 
206
- <h3 id="tool-calling" class="tsd-anchor-link">Tool Calling<a href="#tool-calling" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p>Give the AI access to tools it can call:</p>
207
- <pre><code class="typescript"><span class="hl-0">import</span><span class="hl-1"> { </span><span class="hl-2">Type</span><span class="hl-1"> } </span><span class="hl-0">from</span><span class="hl-1"> </span><span class="hl-3">&quot;typebox&quot;</span><span class="hl-1">;</span><br/><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">response</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">ai</span><span class="hl-1">.</span><span class="hl-6">prompt</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">model:</span><span class="hl-1"> { </span><span class="hl-2">speed:</span><span class="hl-1"> </span><span class="hl-3">&quot;fast&quot;</span><span class="hl-1">, </span><span class="hl-2">cost:</span><span class="hl-1"> </span><span class="hl-3">&quot;medium&quot;</span><span class="hl-1"> },</span><br/><span class="hl-1"> </span><span class="hl-2">prompt:</span><span class="hl-1"> </span><span class="hl-3">&quot;What&#39;s 15% of $250?&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">tools:</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">calculate:</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">description:</span><span class="hl-1"> </span><span class="hl-3">&quot;Perform mathematical calculations&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">parameters:</span><span class="hl-1"> </span><span class="hl-2">Type</span><span class="hl-1">.</span><span class="hl-6">Object</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">expression:</span><span class="hl-1"> </span><span class="hl-2">Type</span><span class="hl-1">.</span><span class="hl-6">String</span><span class="hl-1">({ </span><span class="hl-2">description:</span><span class="hl-1"> </span><span class="hl-3">&quot;Math expression to evaluate&quot;</span><span class="hl-1"> }),</span><br/><span class="hl-1"> }),</span><br/><span class="hl-1"> </span><span class="hl-6">execute</span><span class="hl-2">:</span><span class="hl-1"> </span><span class="hl-4">async</span><span class="hl-1"> ({ </span><span class="hl-2">expression</span><span class="hl-1"> }) </span><span class="hl-4">=&gt;</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-0">return</span><span class="hl-1"> { </span><span class="hl-2">result:</span><span class="hl-1"> </span><span class="hl-6">eval</span><span class="hl-1">(</span><span class="hl-2">expression</span><span class="hl-1">) };</span><br/><span class="hl-1"> },</span><br/><span class="hl-1"> },</span><br/><span class="hl-1"> },</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-2">console</span><span class="hl-1">.</span><span class="hl-6">log</span><span class="hl-1">(</span><span class="hl-2">response</span><span class="hl-1">.</span><span class="hl-2">text</span><span class="hl-1">); </span><span class="hl-7">// &quot;15% of $250 is $37.50&quot;</span>
291
+ <h3 id="tool-calling" class="tsd-anchor-link">Tool Calling<a href="#tool-calling" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p>Give the AI access to tools it can call. Each tool declares an <code>inputSchema</code> (Typebox) and an optional <code>execute</code> function. Set <code>maxSteps</code> above 1 so tool results are fed back to the model for a final answer (the default of 1 returns the tool calls without looping):</p>
292
+ <pre><code class="typescript"><span class="hl-0">import</span><span class="hl-1"> { </span><span class="hl-2">Type</span><span class="hl-1"> } </span><span class="hl-0">from</span><span class="hl-1"> </span><span class="hl-3">&quot;typebox&quot;</span><span class="hl-1">;</span><br/><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">response</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">ai</span><span class="hl-1">.</span><span class="hl-6">prompt</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">model:</span><span class="hl-1"> { </span><span class="hl-2">speed:</span><span class="hl-1"> </span><span class="hl-3">&quot;fast&quot;</span><span class="hl-1">, </span><span class="hl-2">cost:</span><span class="hl-1"> </span><span class="hl-3">&quot;medium&quot;</span><span class="hl-1"> },</span><br/><span class="hl-1"> </span><span class="hl-2">prompt:</span><span class="hl-1"> </span><span class="hl-3">&quot;What&#39;s 15% of $250?&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">maxSteps:</span><span class="hl-1"> </span><span class="hl-14">3</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">tools:</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">calculate:</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">description:</span><span class="hl-1"> </span><span class="hl-3">&quot;Perform mathematical calculations&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">inputSchema:</span><span class="hl-1"> </span><span class="hl-2">Type</span><span class="hl-1">.</span><span class="hl-6">Object</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">expression:</span><span class="hl-1"> </span><span class="hl-2">Type</span><span class="hl-1">.</span><span class="hl-6">String</span><span class="hl-1">({ </span><span class="hl-2">description:</span><span class="hl-1"> </span><span class="hl-3">&quot;Math expression to evaluate&quot;</span><span class="hl-1"> }),</span><br/><span class="hl-1"> }),</span><br/><span class="hl-1"> </span><span class="hl-6">execute</span><span class="hl-2">:</span><span class="hl-1"> </span><span class="hl-4">async</span><span class="hl-1"> ({ </span><span class="hl-2">expression</span><span class="hl-1"> }) </span><span class="hl-4">=&gt;</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-0">return</span><span class="hl-1"> { </span><span class="hl-2">result:</span><span class="hl-1"> </span><span class="hl-6">evaluate</span><span class="hl-1">(</span><span class="hl-2">expression</span><span class="hl-1">) };</span><br/><span class="hl-1"> },</span><br/><span class="hl-1"> },</span><br/><span class="hl-1"> },</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-2">console</span><span class="hl-1">.</span><span class="hl-6">log</span><span class="hl-1">(</span><span class="hl-2">response</span><span class="hl-1">.</span><span class="hl-2">text</span><span class="hl-1">); </span><span class="hl-7">// &quot;15% of $250 is $37.50&quot;</span><br/><span class="hl-2">console</span><span class="hl-1">.</span><span class="hl-6">log</span><span class="hl-1">(</span><span class="hl-2">response</span><span class="hl-1">.</span><span class="hl-2">toolCalls</span><span class="hl-1">); </span><span class="hl-7">// Array of tool calls made</span>
208
293
  </code><button type="button">Copy</button></pre>
209
294
 
210
295
  <h3 id="multi-turn-conversations" class="tsd-anchor-link">Multi-turn Conversations<a href="#multi-turn-conversations" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p>Build conversations with message history:</p>
211
- <pre><code class="typescript"><span class="hl-0">import</span><span class="hl-1"> { </span><span class="hl-2">Type</span><span class="hl-1"> } </span><span class="hl-0">from</span><span class="hl-1"> </span><span class="hl-3">&quot;typebox&quot;</span><span class="hl-1">;</span><br/><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">messages</span><span class="hl-1"> = [</span><br/><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">role:</span><span class="hl-1"> </span><span class="hl-3">&quot;user&quot;</span><span class="hl-1"> </span><span class="hl-0">as</span><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">content:</span><span class="hl-1"> </span><span class="hl-3">&quot;What&#39;s the weather like?&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> },</span><br/><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">role:</span><span class="hl-1"> </span><span class="hl-3">&quot;assistant&quot;</span><span class="hl-1"> </span><span class="hl-0">as</span><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">content:</span><br/><span class="hl-1"> </span><span class="hl-3">&quot;I don&#39;t have access to weather data. Would you like me to help with something else?&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> },</span><br/><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">role:</span><span class="hl-1"> </span><span class="hl-3">&quot;user&quot;</span><span class="hl-1"> </span><span class="hl-0">as</span><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">content:</span><span class="hl-1"> </span><span class="hl-3">&quot;What&#39;s 2+2?&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> },</span><br/><span class="hl-1">];</span><br/><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">response</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">ai</span><span class="hl-1">.</span><span class="hl-6">prompt</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">model:</span><span class="hl-1"> { </span><span class="hl-2">speed:</span><span class="hl-1"> </span><span class="hl-3">&quot;fast&quot;</span><span class="hl-1">, </span><span class="hl-2">cost:</span><span class="hl-1"> </span><span class="hl-3">&quot;low&quot;</span><span class="hl-1"> },</span><br/><span class="hl-1"> </span><span class="hl-2">messages</span><span class="hl-1">,</span><br/><span class="hl-1">});</span>
296
+ <pre><code class="typescript"><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">messages</span><span class="hl-1"> = [</span><br/><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">role:</span><span class="hl-1"> </span><span class="hl-3">&quot;user&quot;</span><span class="hl-1"> </span><span class="hl-0">as</span><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">content:</span><span class="hl-1"> </span><span class="hl-3">&quot;What&#39;s the weather like?&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> },</span><br/><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">role:</span><span class="hl-1"> </span><span class="hl-3">&quot;assistant&quot;</span><span class="hl-1"> </span><span class="hl-0">as</span><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">content:</span><br/><span class="hl-1"> </span><span class="hl-3">&quot;I don&#39;t have access to weather data. Would you like me to help with something else?&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> },</span><br/><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">role:</span><span class="hl-1"> </span><span class="hl-3">&quot;user&quot;</span><span class="hl-1"> </span><span class="hl-0">as</span><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">content:</span><span class="hl-1"> </span><span class="hl-3">&quot;What&#39;s 2+2?&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> },</span><br/><span class="hl-1">];</span><br/><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">response</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">ai</span><span class="hl-1">.</span><span class="hl-6">prompt</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">model:</span><span class="hl-1"> { </span><span class="hl-2">speed:</span><span class="hl-1"> </span><span class="hl-3">&quot;fast&quot;</span><span class="hl-1">, </span><span class="hl-2">cost:</span><span class="hl-1"> </span><span class="hl-3">&quot;low&quot;</span><span class="hl-1"> },</span><br/><span class="hl-1"> </span><span class="hl-2">messages</span><span class="hl-1">,</span><br/><span class="hl-1">});</span>
212
297
  </code><button type="button">Copy</button></pre>
213
298
 
299
+ <h3 id="web-search" class="tsd-anchor-link">Web Search<a href="#web-search" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p>On providers with native web search (check <code>available().webSearch</code>), pass <code>webSearch: true</code> (or <code>{ maxUses: n }</code>) to let the model retrieve up-to-date information; pages used are returned in <code>response.sources</code>.</p>
214
300
  <h3 id="model-selection" class="tsd-anchor-link">Model Selection<a href="#model-selection" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p>Specify your requirements using speed and cost tiers:</p>
215
301
  <pre><code class="typescript"><span class="hl-7">// Fast and cheap - Good for simple tasks</span><br/><span class="hl-15">model</span><span class="hl-1">: { </span><span class="hl-15">speed</span><span class="hl-1">: </span><span class="hl-3">&quot;fast&quot;</span><span class="hl-1">, </span><span class="hl-15">cost</span><span class="hl-1">: </span><span class="hl-3">&quot;low&quot;</span><span class="hl-1"> }</span><br/><br/><span class="hl-7">// Balanced - Good for most tasks</span><br/><span class="hl-15">model</span><span class="hl-1">: { </span><span class="hl-15">speed</span><span class="hl-1">: </span><span class="hl-3">&quot;balanced&quot;</span><span class="hl-1">, </span><span class="hl-15">cost</span><span class="hl-1">: </span><span class="hl-3">&quot;medium&quot;</span><span class="hl-1"> }</span><br/><br/><span class="hl-7">// Most capable - Complex reasoning</span><br/><span class="hl-15">model</span><span class="hl-1">: { </span><span class="hl-15">speed</span><span class="hl-1">: </span><span class="hl-3">&quot;capable&quot;</span><span class="hl-1">, </span><span class="hl-15">cost</span><span class="hl-1">: </span><span class="hl-3">&quot;high&quot;</span><span class="hl-1"> }</span>
216
302
  </code><button type="button">Copy</button></pre>
217
303
 
218
- <p>Plot automatically selects the best available model matching your preferences.</p>
304
+ <p>Plot automatically selects the best available model matching your preferences. You can optionally suggest a specific model via <code>hint</code> (e.g. <code>hint: AIModel.CLAUDE_SONNET_46</code>), which the system may override based on user preferences.</p>
219
305
  <h3 id="typebox-schemas" class="tsd-anchor-link">Typebox Schemas<a href="#typebox-schemas" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p>Typebox provides JSON Schema with full TypeScript type inference:</p>
220
306
  <pre><code class="typescript"><span class="hl-0">import</span><span class="hl-1"> { </span><span class="hl-2">Type</span><span class="hl-1"> } </span><span class="hl-0">from</span><span class="hl-1"> </span><span class="hl-3">&quot;typebox&quot;</span><span class="hl-1">;</span><br/><br/><span class="hl-7">// Objects</span><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">PersonSchema</span><span class="hl-1"> = </span><span class="hl-2">Type</span><span class="hl-1">.</span><span class="hl-6">Object</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">name:</span><span class="hl-1"> </span><span class="hl-2">Type</span><span class="hl-1">.</span><span class="hl-6">String</span><span class="hl-1">(),</span><br/><span class="hl-1"> </span><span class="hl-2">age:</span><span class="hl-1"> </span><span class="hl-2">Type</span><span class="hl-1">.</span><span class="hl-6">Number</span><span class="hl-1">(),</span><br/><span class="hl-1"> </span><span class="hl-2">email:</span><span class="hl-1"> </span><span class="hl-2">Type</span><span class="hl-1">.</span><span class="hl-6">Optional</span><span class="hl-1">(</span><span class="hl-2">Type</span><span class="hl-1">.</span><span class="hl-6">String</span><span class="hl-1">({ </span><span class="hl-2">format:</span><span class="hl-1"> </span><span class="hl-3">&quot;email&quot;</span><span class="hl-1"> })),</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-7">// Arrays</span><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">PeopleSchema</span><span class="hl-1"> = </span><span class="hl-2">Type</span><span class="hl-1">.</span><span class="hl-6">Array</span><span class="hl-1">(</span><span class="hl-2">PersonSchema</span><span class="hl-1">);</span><br/><br/><span class="hl-7">// Unions (enums)</span><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">StatusSchema</span><span class="hl-1"> = </span><span class="hl-2">Type</span><span class="hl-1">.</span><span class="hl-6">Union</span><span class="hl-1">([</span><br/><span class="hl-1"> </span><span class="hl-2">Type</span><span class="hl-1">.</span><span class="hl-6">Literal</span><span class="hl-1">(</span><span class="hl-3">&quot;pending&quot;</span><span class="hl-1">),</span><br/><span class="hl-1"> </span><span class="hl-2">Type</span><span class="hl-1">.</span><span class="hl-6">Literal</span><span class="hl-1">(</span><span class="hl-3">&quot;active&quot;</span><span class="hl-1">),</span><br/><span class="hl-1"> </span><span class="hl-2">Type</span><span class="hl-1">.</span><span class="hl-6">Literal</span><span class="hl-1">(</span><span class="hl-3">&quot;completed&quot;</span><span class="hl-1">),</span><br/><span class="hl-1">]);</span><br/><br/><span class="hl-7">// Nested objects</span><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">ProjectSchema</span><span class="hl-1"> = </span><span class="hl-2">Type</span><span class="hl-1">.</span><span class="hl-6">Object</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">title:</span><span class="hl-1"> </span><span class="hl-2">Type</span><span class="hl-1">.</span><span class="hl-6">String</span><span class="hl-1">(),</span><br/><span class="hl-1"> </span><span class="hl-2">status:</span><span class="hl-1"> </span><span class="hl-2">StatusSchema</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">assignees:</span><span class="hl-1"> </span><span class="hl-2">Type</span><span class="hl-1">.</span><span class="hl-6">Array</span><span class="hl-1">(</span><span class="hl-2">PersonSchema</span><span class="hl-1">),</span><br/><span class="hl-1">});</span>
221
307
  </code><button type="button">Copy</button></pre>
222
308
 
223
309
  <p>See the <a href="https://github.com/sinclairzx81/typebox">Typebox documentation</a> for more schema types.</p>
224
- <h3 id="real-world-example-email-triage" class="tsd-anchor-link">Real-World Example: Email Triage<a href="#real-world-example-email-triage" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><pre><code class="typescript"><span class="hl-0">import</span><span class="hl-1"> { </span><span class="hl-2">Type</span><span class="hl-1"> } </span><span class="hl-0">from</span><span class="hl-1"> </span><span class="hl-3">&quot;typebox&quot;</span><span class="hl-1">;</span><br/><br/><span class="hl-2">async</span><span class="hl-1"> </span><span class="hl-6">triageEmail</span><span class="hl-1">(</span><span class="hl-2">emailContent</span><span class="hl-1">: </span><span class="hl-2">string</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">schema</span><span class="hl-1"> = </span><span class="hl-2">Type</span><span class="hl-1">.</span><span class="hl-6">Object</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">category:</span><span class="hl-1"> </span><span class="hl-2">Type</span><span class="hl-1">.</span><span class="hl-6">Union</span><span class="hl-1">([</span><br/><span class="hl-1"> </span><span class="hl-2">Type</span><span class="hl-1">.</span><span class="hl-6">Literal</span><span class="hl-1">(</span><span class="hl-3">&quot;urgent&quot;</span><span class="hl-1">),</span><br/><span class="hl-1"> </span><span class="hl-2">Type</span><span class="hl-1">.</span><span class="hl-6">Literal</span><span class="hl-1">(</span><span class="hl-3">&quot;important&quot;</span><span class="hl-1">),</span><br/><span class="hl-1"> </span><span class="hl-2">Type</span><span class="hl-1">.</span><span class="hl-6">Literal</span><span class="hl-1">(</span><span class="hl-3">&quot;informational&quot;</span><span class="hl-1">),</span><br/><span class="hl-1"> </span><span class="hl-2">Type</span><span class="hl-1">.</span><span class="hl-6">Literal</span><span class="hl-1">(</span><span class="hl-3">&quot;spam&quot;</span><span class="hl-1">)</span><br/><span class="hl-1"> ]),</span><br/><span class="hl-1"> </span><span class="hl-2">requiresResponse:</span><span class="hl-1"> </span><span class="hl-2">Type</span><span class="hl-1">.</span><span class="hl-6">Boolean</span><span class="hl-1">(),</span><br/><span class="hl-1"> </span><span class="hl-2">suggestedActions:</span><span class="hl-1"> </span><span class="hl-2">Type</span><span class="hl-1">.</span><span class="hl-6">Array</span><span class="hl-1">(</span><span class="hl-2">Type</span><span class="hl-1">.</span><span class="hl-6">String</span><span class="hl-1">()),</span><br/><span class="hl-1"> </span><span class="hl-2">summary:</span><span class="hl-1"> </span><span class="hl-2">Type</span><span class="hl-1">.</span><span class="hl-6">String</span><span class="hl-1">({ </span><span class="hl-2">maxLength:</span><span class="hl-1"> </span><span class="hl-14">200</span><span class="hl-1"> })</span><br/><span class="hl-1"> });</span><br/><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">response</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">ai</span><span class="hl-1">.</span><span class="hl-6">prompt</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">model:</span><span class="hl-1"> { </span><span class="hl-2">speed:</span><span class="hl-1"> </span><span class="hl-3">&quot;balanced&quot;</span><span class="hl-1">, </span><span class="hl-2">cost:</span><span class="hl-1"> </span><span class="hl-3">&quot;medium&quot;</span><span class="hl-1"> },</span><br/><span class="hl-1"> </span><span class="hl-2">prompt:</span><span class="hl-1"> </span><span class="hl-3">`Analyze this email and provide triage information:</span><span class="hl-12">\n\n</span><span class="hl-4">${</span><span class="hl-2">emailContent</span><span class="hl-4">}</span><span class="hl-3">`</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">outputSchema:</span><span class="hl-1"> </span><span class="hl-2">schema</span><br/><span class="hl-1"> });</span><br/><br/><span class="hl-1"> </span><span class="hl-7">// Create activity based on triage</span><br/><span class="hl-1"> </span><span class="hl-0">if</span><span class="hl-1"> (</span><span class="hl-2">response</span><span class="hl-1">.</span><span class="hl-2">output</span><span class="hl-1">.</span><span class="hl-2">category</span><span class="hl-1"> === </span><span class="hl-3">&quot;urgent&quot;</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">createActivity</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">type:</span><span class="hl-1"> </span><span class="hl-2">ActivityType</span><span class="hl-1">.</span><span class="hl-2">Action</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">title:</span><span class="hl-1"> </span><span class="hl-3">`URGENT: </span><span class="hl-4">${</span><span class="hl-2">response</span><span class="hl-13">.</span><span class="hl-2">output</span><span class="hl-13">.</span><span class="hl-2">summary</span><span class="hl-4">}</span><span class="hl-3">`</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">notes:</span><span class="hl-1"> [</span><br/><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">note:</span><span class="hl-1"> </span><span class="hl-3">`Actions:</span><span class="hl-12">\n</span><span class="hl-4">${</span><span class="hl-2">response</span><span class="hl-13">.</span><span class="hl-2">output</span><span class="hl-13">.</span><span class="hl-2">suggestedActions</span><span class="hl-13">.</span><span class="hl-6">join</span><span class="hl-13">(</span><span class="hl-3">&quot;</span><span class="hl-12">\n</span><span class="hl-3">&quot;</span><span class="hl-13">)</span><span class="hl-4">}</span><span class="hl-3">`</span><span class="hl-1">,</span><br/><span class="hl-1"> },</span><br/><span class="hl-1"> ],</span><br/><span class="hl-1"> });</span><br/><span class="hl-1"> }</span><br/><span class="hl-1">}</span>
310
+ <h3 id="real-world-example-email-triage" class="tsd-anchor-link">Real-World Example: Email Triage<a href="#real-world-example-email-triage" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><pre><code class="typescript"><span class="hl-0">import</span><span class="hl-1"> { </span><span class="hl-2">Type</span><span class="hl-1"> } </span><span class="hl-0">from</span><span class="hl-1"> </span><span class="hl-3">&quot;typebox&quot;</span><span class="hl-1">;</span><br/><br/><span class="hl-2">async</span><span class="hl-1"> </span><span class="hl-6">triageEmail</span><span class="hl-1">(</span><span class="hl-2">emailContent</span><span class="hl-1">: </span><span class="hl-2">string</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">schema</span><span class="hl-1"> = </span><span class="hl-2">Type</span><span class="hl-1">.</span><span class="hl-6">Object</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">category:</span><span class="hl-1"> </span><span class="hl-2">Type</span><span class="hl-1">.</span><span class="hl-6">Union</span><span class="hl-1">([</span><br/><span class="hl-1"> </span><span class="hl-2">Type</span><span class="hl-1">.</span><span class="hl-6">Literal</span><span class="hl-1">(</span><span class="hl-3">&quot;urgent&quot;</span><span class="hl-1">),</span><br/><span class="hl-1"> </span><span class="hl-2">Type</span><span class="hl-1">.</span><span class="hl-6">Literal</span><span class="hl-1">(</span><span class="hl-3">&quot;important&quot;</span><span class="hl-1">),</span><br/><span class="hl-1"> </span><span class="hl-2">Type</span><span class="hl-1">.</span><span class="hl-6">Literal</span><span class="hl-1">(</span><span class="hl-3">&quot;informational&quot;</span><span class="hl-1">),</span><br/><span class="hl-1"> </span><span class="hl-2">Type</span><span class="hl-1">.</span><span class="hl-6">Literal</span><span class="hl-1">(</span><span class="hl-3">&quot;spam&quot;</span><span class="hl-1">)</span><br/><span class="hl-1"> ]),</span><br/><span class="hl-1"> </span><span class="hl-2">requiresResponse:</span><span class="hl-1"> </span><span class="hl-2">Type</span><span class="hl-1">.</span><span class="hl-6">Boolean</span><span class="hl-1">(),</span><br/><span class="hl-1"> </span><span class="hl-2">suggestedActions:</span><span class="hl-1"> </span><span class="hl-2">Type</span><span class="hl-1">.</span><span class="hl-6">Array</span><span class="hl-1">(</span><span class="hl-2">Type</span><span class="hl-1">.</span><span class="hl-6">String</span><span class="hl-1">()),</span><br/><span class="hl-1"> </span><span class="hl-2">summary:</span><span class="hl-1"> </span><span class="hl-2">Type</span><span class="hl-1">.</span><span class="hl-6">String</span><span class="hl-1">({ </span><span class="hl-2">maxLength:</span><span class="hl-1"> </span><span class="hl-14">200</span><span class="hl-1"> })</span><br/><span class="hl-1"> });</span><br/><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">response</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">ai</span><span class="hl-1">.</span><span class="hl-6">prompt</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">model:</span><span class="hl-1"> { </span><span class="hl-2">speed:</span><span class="hl-1"> </span><span class="hl-3">&quot;balanced&quot;</span><span class="hl-1">, </span><span class="hl-2">cost:</span><span class="hl-1"> </span><span class="hl-3">&quot;medium&quot;</span><span class="hl-1"> },</span><br/><span class="hl-1"> </span><span class="hl-2">prompt:</span><span class="hl-1"> </span><span class="hl-3">`Analyze this email and provide triage information:</span><span class="hl-12">\n\n</span><span class="hl-4">${</span><span class="hl-2">emailContent</span><span class="hl-4">}</span><span class="hl-3">`</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">outputSchema:</span><span class="hl-1"> </span><span class="hl-2">schema</span><br/><span class="hl-1"> });</span><br/><br/><span class="hl-1"> </span><span class="hl-7">// Create thread based on triage</span><br/><span class="hl-1"> </span><span class="hl-0">if</span><span class="hl-1"> (</span><span class="hl-2">response</span><span class="hl-1">.</span><span class="hl-2">output</span><span class="hl-1">?.</span><span class="hl-2">category</span><span class="hl-1"> === </span><span class="hl-3">&quot;urgent&quot;</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">createThread</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">type:</span><span class="hl-1"> </span><span class="hl-3">&quot;action&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">title:</span><span class="hl-1"> </span><span class="hl-3">`URGENT: </span><span class="hl-4">${</span><span class="hl-2">response</span><span class="hl-13">.</span><span class="hl-2">output</span><span class="hl-13">.</span><span class="hl-2">summary</span><span class="hl-4">}</span><span class="hl-3">`</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">notes:</span><span class="hl-1"> [</span><br/><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">content:</span><span class="hl-1"> </span><span class="hl-3">`Actions:</span><span class="hl-12">\n</span><span class="hl-4">${</span><span class="hl-2">response</span><span class="hl-13">.</span><span class="hl-2">output</span><span class="hl-13">.</span><span class="hl-2">suggestedActions</span><span class="hl-13">.</span><span class="hl-6">join</span><span class="hl-13">(</span><span class="hl-3">&quot;</span><span class="hl-12">\n</span><span class="hl-3">&quot;</span><span class="hl-13">)</span><span class="hl-4">}</span><span class="hl-3">`</span><span class="hl-1">,</span><br/><span class="hl-1"> },</span><br/><span class="hl-1"> ],</span><br/><span class="hl-1"> });</span><br/><span class="hl-1"> }</span><br/><span class="hl-1">}</span>
225
311
  </code><button type="button">Copy</button></pre>
226
312
 
227
313
  <hr>
228
- <h2 id="link-type-safety-pattern" class="tsd-anchor-link">Link Type Safety Pattern<a href="#link-type-safety-pattern" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h2><p>When defining <code>linkTypes</code> in your connector's provider config, use <code>as const satisfies</code> to get type-safe status strings:</p>
229
- <pre><code class="typescript"><span class="hl-0">import</span><span class="hl-1"> </span><span class="hl-0">type</span><span class="hl-1"> { </span><span class="hl-2">LinkTypeConfig</span><span class="hl-1"> } </span><span class="hl-0">from</span><span class="hl-1"> </span><span class="hl-3">&quot;@plotday/twister/tools/integrations&quot;</span><span class="hl-1">;</span><br/><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">LINK_TYPES</span><span class="hl-1"> = [</span><br/><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">type:</span><span class="hl-1"> </span><span class="hl-3">&quot;issue&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">label:</span><span class="hl-1"> </span><span class="hl-3">&quot;Issue&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">logo:</span><span class="hl-1"> </span><span class="hl-3">&quot;https://api.iconify.design/simple-icons/linear.svg&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">statuses:</span><span class="hl-1"> [</span><br/><span class="hl-1"> { </span><span class="hl-2">status:</span><span class="hl-1"> </span><span class="hl-3">&quot;open&quot;</span><span class="hl-1">, </span><span class="hl-2">label:</span><span class="hl-1"> </span><span class="hl-3">&quot;Open&quot;</span><span class="hl-1"> },</span><br/><span class="hl-1"> { </span><span class="hl-2">status:</span><span class="hl-1"> </span><span class="hl-3">&quot;done&quot;</span><span class="hl-1">, </span><span class="hl-2">label:</span><span class="hl-1"> </span><span class="hl-3">&quot;Done&quot;</span><span class="hl-1"> },</span><br/><span class="hl-1"> ],</span><br/><span class="hl-1"> },</span><br/><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">type:</span><span class="hl-1"> </span><span class="hl-3">&quot;pull_request&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">label:</span><span class="hl-1"> </span><span class="hl-3">&quot;Pull Request&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">logo:</span><span class="hl-1"> </span><span class="hl-3">&quot;https://api.iconify.design/simple-icons/github.svg&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">statuses:</span><span class="hl-1"> [</span><br/><span class="hl-1"> { </span><span class="hl-2">status:</span><span class="hl-1"> </span><span class="hl-3">&quot;open&quot;</span><span class="hl-1">, </span><span class="hl-2">label:</span><span class="hl-1"> </span><span class="hl-3">&quot;Open&quot;</span><span class="hl-1"> },</span><br/><span class="hl-1"> { </span><span class="hl-2">status:</span><span class="hl-1"> </span><span class="hl-3">&quot;merged&quot;</span><span class="hl-1">, </span><span class="hl-2">label:</span><span class="hl-1"> </span><span class="hl-3">&quot;Merged&quot;</span><span class="hl-1"> },</span><br/><span class="hl-1"> { </span><span class="hl-2">status:</span><span class="hl-1"> </span><span class="hl-3">&quot;closed&quot;</span><span class="hl-1">, </span><span class="hl-2">label:</span><span class="hl-1"> </span><span class="hl-3">&quot;Closed&quot;</span><span class="hl-1"> },</span><br/><span class="hl-1"> ],</span><br/><span class="hl-1"> },</span><br/><span class="hl-1">] </span><span class="hl-0">as</span><span class="hl-1"> </span><span class="hl-5">const</span><span class="hl-1"> </span><span class="hl-0">satisfies</span><span class="hl-1"> </span><span class="hl-5">LinkTypeConfig</span><span class="hl-1">[];</span><br/><br/><span class="hl-7">// Derive type-safe union types from the config</span><br/><span class="hl-4">type</span><span class="hl-1"> </span><span class="hl-5">IssueStatus</span><span class="hl-1"> = (</span><span class="hl-4">typeof</span><span class="hl-1"> </span><span class="hl-8">LINK_TYPES</span><span class="hl-1">)[</span><span class="hl-14">0</span><span class="hl-1">][</span><span class="hl-3">&quot;statuses&quot;</span><span class="hl-1">][</span><span class="hl-5">number</span><span class="hl-1">][</span><span class="hl-3">&quot;status&quot;</span><span class="hl-1">]; </span><span class="hl-7">// &quot;open&quot; | &quot;done&quot;</span><br/><span class="hl-4">type</span><span class="hl-1"> </span><span class="hl-5">PRStatus</span><span class="hl-1"> = (</span><span class="hl-4">typeof</span><span class="hl-1"> </span><span class="hl-8">LINK_TYPES</span><span class="hl-1">)[</span><span class="hl-14">1</span><span class="hl-1">][</span><span class="hl-3">&quot;statuses&quot;</span><span class="hl-1">][</span><span class="hl-5">number</span><span class="hl-1">][</span><span class="hl-3">&quot;status&quot;</span><span class="hl-1">]; </span><span class="hl-7">// &quot;open&quot; | &quot;merged&quot; | &quot;closed&quot;</span>
314
+ <h2 id="files" class="tsd-anchor-link">Files<a href="#files" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h2><p>Read files that users attach to notes. Uploaded files appear on notes as <code>ActionType.file</code> actions; connectors call <code>read()</code> during outbound sync (e.g. <code>onNoteCreated</code>) to retrieve the bytes and send them to the source system.</p>
315
+ <h3 id="setup-7" class="tsd-anchor-link">Setup<a href="#setup-7" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><pre><code class="typescript"><span class="hl-0">import</span><span class="hl-1"> { </span><span class="hl-2">Files</span><span class="hl-1"> } </span><span class="hl-0">from</span><span class="hl-1"> </span><span class="hl-3">&quot;@plotday/twister/tools/files&quot;</span><span class="hl-1">;</span><br/><br/><span class="hl-6">build</span><span class="hl-1">(</span><span class="hl-2">build</span><span class="hl-1">: </span><span class="hl-2">ToolBuilder</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-0">return</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">files:</span><span class="hl-1"> </span><span class="hl-6">build</span><span class="hl-1">(</span><span class="hl-2">Files</span><span class="hl-1">),</span><br/><span class="hl-1"> };</span><br/><span class="hl-1">}</span>
316
+ </code><button type="button">Copy</button></pre>
317
+
318
+ <h3 id="reading-files" class="tsd-anchor-link">Reading Files<a href="#reading-files" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><pre><code class="typescript"><span class="hl-0">import</span><span class="hl-1"> { </span><span class="hl-2">ActionType</span><span class="hl-1"> } </span><span class="hl-0">from</span><span class="hl-1"> </span><span class="hl-3">&quot;@plotday/twister&quot;</span><span class="hl-1">;</span><br/><br/><span class="hl-2">async</span><span class="hl-1"> </span><span class="hl-6">onNoteCreated</span><span class="hl-1">(</span><span class="hl-2">note</span><span class="hl-1">: </span><span class="hl-2">Note</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-0">for</span><span class="hl-1"> (</span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">action</span><span class="hl-1"> </span><span class="hl-4">of</span><span class="hl-1"> </span><span class="hl-2">note</span><span class="hl-1">.</span><span class="hl-2">actions</span><span class="hl-1"> ?? []) {</span><br/><span class="hl-1"> </span><span class="hl-0">if</span><span class="hl-1"> (</span><span class="hl-2">action</span><span class="hl-1">.</span><span class="hl-2">type</span><span class="hl-1"> === </span><span class="hl-2">ActionType</span><span class="hl-1">.</span><span class="hl-2">file</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">file</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">files</span><span class="hl-1">.</span><span class="hl-6">read</span><span class="hl-1">(</span><span class="hl-2">action</span><span class="hl-1">.</span><span class="hl-2">fileId</span><span class="hl-1">);</span><br/><span class="hl-1"> </span><span class="hl-7">// file.data: Uint8Array, plus fileName, mimeType, fileSize</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">uploadToSource</span><span class="hl-1">(</span><span class="hl-2">file</span><span class="hl-1">);</span><br/><span class="hl-1"> }</span><br/><span class="hl-1"> }</span><br/><span class="hl-1">}</span>
319
+ </code><button type="button">Copy</button></pre>
320
+
321
+ <p><code>read()</code> throws <code>FileNotFoundError</code> if the file is missing or out of scope.</p>
322
+ <p>For <strong>inbound</strong> attachments (files that live in the external system), connectors emit <code>ActionType.fileRef</code> actions and implement <code>Connector.downloadAttachment()</code> — the bytes are fetched on demand and never stored in Plot.</p>
323
+ <hr>
324
+ <h2 id="other-built-in-tools" class="tsd-anchor-link">Other Built-in Tools<a href="#other-built-in-tools" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h2><p>A few specialized tools are also available — see their type definitions for full APIs:</p>
325
+ <ul>
326
+ <li><strong>Imap</strong> (<code>@plotday/twister/tools/imap</code>) and <strong>Smtp</strong> (<code>@plotday/twister/tools/smtp</code>) — raw IMAP/SMTP sessions for password-based email connectors (connect, list mailboxes, search/fetch messages, set flags, send).</li>
327
+ <li><strong>Twists</strong> (<code>@plotday/twister/tools/twists</code>) — programmatically create, generate, and deploy twists, and subscribe to their logs. Used by twist-builder twists.</li>
328
+ </ul>
329
+ <hr>
330
+ <h2 id="link-type-safety-pattern" class="tsd-anchor-link">Link Type Safety Pattern<a href="#link-type-safety-pattern" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h2><p>When defining <code>linkTypes</code> in your connector, use <code>as const satisfies LinkTypeConfig[]</code> to get type-safe status strings:</p>
331
+ <pre><code class="typescript"><span class="hl-0">import</span><span class="hl-1"> </span><span class="hl-0">type</span><span class="hl-1"> { </span><span class="hl-2">LinkTypeConfig</span><span class="hl-1"> } </span><span class="hl-0">from</span><span class="hl-1"> </span><span class="hl-3">&quot;@plotday/twister/tools/integrations&quot;</span><span class="hl-1">;</span><br/><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">LINK_TYPES</span><span class="hl-1"> = [</span><br/><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">type:</span><span class="hl-1"> </span><span class="hl-3">&quot;issue&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">label:</span><span class="hl-1"> </span><span class="hl-3">&quot;Issue&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">logo:</span><span class="hl-1"> </span><span class="hl-3">&quot;https://api.iconify.design/simple-icons/linear.svg&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">statuses:</span><span class="hl-1"> [</span><br/><span class="hl-1"> { </span><span class="hl-2">status:</span><span class="hl-1"> </span><span class="hl-3">&quot;open&quot;</span><span class="hl-1">, </span><span class="hl-2">label:</span><span class="hl-1"> </span><span class="hl-3">&quot;Open&quot;</span><span class="hl-1">, </span><span class="hl-2">icon:</span><span class="hl-1"> </span><span class="hl-3">&quot;todo&quot;</span><span class="hl-1"> },</span><br/><span class="hl-1"> { </span><span class="hl-2">status:</span><span class="hl-1"> </span><span class="hl-3">&quot;done&quot;</span><span class="hl-1">, </span><span class="hl-2">label:</span><span class="hl-1"> </span><span class="hl-3">&quot;Done&quot;</span><span class="hl-1">, </span><span class="hl-2">icon:</span><span class="hl-1"> </span><span class="hl-3">&quot;done&quot;</span><span class="hl-1">, </span><span class="hl-2">done:</span><span class="hl-1"> </span><span class="hl-4">true</span><span class="hl-1"> },</span><br/><span class="hl-1"> ],</span><br/><span class="hl-1"> },</span><br/><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">type:</span><span class="hl-1"> </span><span class="hl-3">&quot;pull_request&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">label:</span><span class="hl-1"> </span><span class="hl-3">&quot;Pull Request&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">logo:</span><span class="hl-1"> </span><span class="hl-3">&quot;https://api.iconify.design/simple-icons/github.svg&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">statuses:</span><span class="hl-1"> [</span><br/><span class="hl-1"> { </span><span class="hl-2">status:</span><span class="hl-1"> </span><span class="hl-3">&quot;open&quot;</span><span class="hl-1">, </span><span class="hl-2">label:</span><span class="hl-1"> </span><span class="hl-3">&quot;Open&quot;</span><span class="hl-1">, </span><span class="hl-2">icon:</span><span class="hl-1"> </span><span class="hl-3">&quot;inProgress&quot;</span><span class="hl-1"> },</span><br/><span class="hl-1"> { </span><span class="hl-2">status:</span><span class="hl-1"> </span><span class="hl-3">&quot;merged&quot;</span><span class="hl-1">, </span><span class="hl-2">label:</span><span class="hl-1"> </span><span class="hl-3">&quot;Merged&quot;</span><span class="hl-1">, </span><span class="hl-2">icon:</span><span class="hl-1"> </span><span class="hl-3">&quot;done&quot;</span><span class="hl-1">, </span><span class="hl-2">done:</span><span class="hl-1"> </span><span class="hl-4">true</span><span class="hl-1"> },</span><br/><span class="hl-1"> { </span><span class="hl-2">status:</span><span class="hl-1"> </span><span class="hl-3">&quot;closed&quot;</span><span class="hl-1">, </span><span class="hl-2">label:</span><span class="hl-1"> </span><span class="hl-3">&quot;Closed&quot;</span><span class="hl-1">, </span><span class="hl-2">icon:</span><span class="hl-1"> </span><span class="hl-3">&quot;cancelled&quot;</span><span class="hl-1">, </span><span class="hl-2">done:</span><span class="hl-1"> </span><span class="hl-4">true</span><span class="hl-1"> },</span><br/><span class="hl-1"> ],</span><br/><span class="hl-1"> },</span><br/><span class="hl-1">] </span><span class="hl-0">as</span><span class="hl-1"> </span><span class="hl-5">const</span><span class="hl-1"> </span><span class="hl-0">satisfies</span><span class="hl-1"> </span><span class="hl-5">LinkTypeConfig</span><span class="hl-1">[];</span><br/><br/><span class="hl-7">// Derive type-safe union types from the config</span><br/><span class="hl-4">type</span><span class="hl-1"> </span><span class="hl-5">IssueStatus</span><span class="hl-1"> = (</span><span class="hl-4">typeof</span><span class="hl-1"> </span><span class="hl-8">LINK_TYPES</span><span class="hl-1">)[</span><span class="hl-14">0</span><span class="hl-1">][</span><span class="hl-3">&quot;statuses&quot;</span><span class="hl-1">][</span><span class="hl-5">number</span><span class="hl-1">][</span><span class="hl-3">&quot;status&quot;</span><span class="hl-1">]; </span><span class="hl-7">// &quot;open&quot; | &quot;done&quot;</span><br/><span class="hl-4">type</span><span class="hl-1"> </span><span class="hl-5">PRStatus</span><span class="hl-1"> = (</span><span class="hl-4">typeof</span><span class="hl-1"> </span><span class="hl-8">LINK_TYPES</span><span class="hl-1">)[</span><span class="hl-14">1</span><span class="hl-1">][</span><span class="hl-3">&quot;statuses&quot;</span><span class="hl-1">][</span><span class="hl-5">number</span><span class="hl-1">][</span><span class="hl-3">&quot;status&quot;</span><span class="hl-1">]; </span><span class="hl-7">// &quot;open&quot; | &quot;merged&quot; | &quot;closed&quot;</span>
230
332
  </code><button type="button">Copy</button></pre>
231
333
 
232
- <p>Then reference <code>LINK_TYPES</code> in your provider config:</p>
233
- <pre><code class="typescript"><span class="hl-6">build</span><span class="hl-1">(</span><span class="hl-2">build</span><span class="hl-1">: </span><span class="hl-2">ConnectorBuilder</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-0">return</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">integrations:</span><span class="hl-1"> </span><span class="hl-6">build</span><span class="hl-1">(</span><span class="hl-2">Integrations</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-2">providers:</span><span class="hl-1"> [{</span><br/><span class="hl-1"> </span><span class="hl-2">provider:</span><span class="hl-1"> </span><span class="hl-2">MyConnector</span><span class="hl-1">.</span><span class="hl-8">PROVIDER</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">scopes:</span><span class="hl-1"> </span><span class="hl-2">MyConnector</span><span class="hl-1">.</span><span class="hl-8">SCOPES</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">linkTypes:</span><span class="hl-1"> [...</span><span class="hl-8">LINK_TYPES</span><span class="hl-1">],</span><br/><span class="hl-1"> </span><span class="hl-7">// ...</span><br/><span class="hl-1"> }],</span><br/><span class="hl-1"> }),</span><br/><span class="hl-1"> };</span><br/><span class="hl-1">}</span>
334
+ <p>Note that every status requires a curated <code>icon</code> (<code>StatusIcon</code>) so the UI always has a glyph to render.</p>
335
+ <p>Then declare <code>linkTypes</code> as a class property on your connector:</p>
336
+ <pre><code class="typescript"><span class="hl-4">class</span><span class="hl-1"> </span><span class="hl-5">MyConnector</span><span class="hl-1"> </span><span class="hl-4">extends</span><span class="hl-1"> </span><span class="hl-5">Connector</span><span class="hl-1">&lt;</span><span class="hl-5">MyConnector</span><span class="hl-1">&gt; {</span><br/><span class="hl-1"> </span><span class="hl-4">readonly</span><span class="hl-1"> </span><span class="hl-2">provider</span><span class="hl-1"> = </span><span class="hl-2">MyConnector</span><span class="hl-1">.</span><span class="hl-8">PROVIDER</span><span class="hl-1">;</span><br/><span class="hl-1"> </span><span class="hl-4">readonly</span><span class="hl-1"> </span><span class="hl-2">scopes</span><span class="hl-1"> = </span><span class="hl-2">MyConnector</span><span class="hl-1">.</span><span class="hl-8">SCOPES</span><span class="hl-1">;</span><br/><span class="hl-1"> </span><span class="hl-4">readonly</span><span class="hl-1"> </span><span class="hl-2">linkTypes</span><span class="hl-1"> = [...</span><span class="hl-8">LINK_TYPES</span><span class="hl-1">];</span><br/><br/><span class="hl-1"> </span><span class="hl-6">build</span><span class="hl-1">(</span><span class="hl-2">build</span><span class="hl-1">: </span><span class="hl-5">ToolBuilder</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-0">return</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">integrations:</span><span class="hl-1"> </span><span class="hl-6">build</span><span class="hl-1">(</span><span class="hl-2">Integrations</span><span class="hl-1">),</span><br/><span class="hl-1"> </span><span class="hl-7">// ...</span><br/><span class="hl-1"> };</span><br/><span class="hl-1"> }</span><br/><span class="hl-1">}</span>
234
337
  </code><button type="button">Copy</button></pre>
235
338
 
236
339
  <hr>
@@ -239,4 +342,4 @@
239
342
  <li><strong><a href="Runtime_Environment.html">Runtime Environment</a></strong> - Understanding execution constraints</li>
240
343
  <li><strong>API Reference</strong> - Explore detailed API docs in the sidebar</li>
241
344
  </ul>
242
- </div></div><div class="col-sidebar"><div class="page-menu"><div class="tsd-navigation settings"><details class="tsd-accordion"><summary class="tsd-accordion-summary"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-chevronDown"></use></svg><h3>Settings</h3></summary><div class="tsd-accordion-details"><div class="tsd-filter-visibility"><span class="settings-label">Member Visibility</span><ul id="tsd-filter-options"><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-protected" name="protected"/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>Protected</span></label></li><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-inherited" name="inherited" checked/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>Inherited</span></label></li></ul></div><div class="tsd-theme-toggle"><label class="settings-label" for="tsd-theme">Theme</label><select id="tsd-theme"><option value="os">OS</option><option value="light">Light</option><option value="dark">Dark</option></select></div></div></details></div><details open class="tsd-accordion tsd-page-navigation"><summary class="tsd-accordion-summary"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-chevronDown"></use></svg><h3>On This Page</h3></summary><div class="tsd-accordion-details"><a href="#built-in-tools"><span>Built-<wbr/>in <wbr/>Tools</span></a><ul><li><a href="#table-of-contents"><span>Table of <wbr/>Contents</span></a></li><li><a href="#plot"><span>Plot</span></a></li><li><ul><li><a href="#workspace-level-twists"><span>Workspace-<wbr/>Level <wbr/>Twists</span></a></li><li><a href="#understanding-activities-and-notes"><span>Understanding <wbr/>Activities and <wbr/>Notes</span></a></li><li><a href="#setup"><span>Setup</span></a></li><li><a href="#creating-activities"><span>Creating <wbr/>Activities</span></a></li><li><ul><li><a href="#action-scheduling-states"><span>Action <wbr/>Scheduling <wbr/>States</span></a></li></ul></li><li><a href="#updating-activities"><span>Updating <wbr/>Activities</span></a></li><li><a href="#deleting-activities"><span>Deleting <wbr/>Activities</span></a></li><li><a href="#managing-priorities"><span>Managing <wbr/>Priorities</span></a></li><li><a href="#activity-data-synchronization"><span>Activity <wbr/>Data <wbr/>Synchronization</span></a></li><li><a href="#creating-and-managing-notes"><span>Creating and <wbr/>Managing <wbr/>Notes</span></a></li><li><ul><li><a href="#creating-notes-on-new-activities"><span>Creating <wbr/>Notes on <wbr/>New <wbr/>Activities</span></a></li><li><a href="#adding-notes-to-existing-activities"><span>Adding <wbr/>Notes to <wbr/>Existing <wbr/>Activities</span></a></li><li><a href="#pattern-email-threads-and-conversations"><span>Pattern: <wbr/>Email <wbr/>Threads and <wbr/>Conversations</span></a></li></ul></li></ul></li><li><a href="#store"><span>Store</span></a></li><li><ul><li><a href="#setup-1"><span>Setup</span></a></li><li><a href="#storing-data"><span>Storing <wbr/>Data</span></a></li><li><a href="#retrieving-data"><span>Retrieving <wbr/>Data</span></a></li><li><a href="#clearing-data"><span>Clearing <wbr/>Data</span></a></li><li><a href="#best-practices"><span>Best <wbr/>Practices</span></a></li><li><ul><li><a href="#type-safety"><span>Type <wbr/>Safety</span></a></li><li><a href="#namespacing"><span>Namespacing</span></a></li><li><a href="#serialization-limits"><span>Serialization <wbr/>Limits</span></a></li></ul></li></ul></li><li><a href="#integrations"><span>Integrations</span></a></li><li><ul><li><a href="#setup-2"><span>Setup</span></a></li><li><a href="#requesting-authentication"><span>Requesting <wbr/>Authentication</span></a></li><li><a href="#auth-providers"><span>Auth <wbr/>Providers</span></a></li><li><a href="#using-auth-tokens"><span>Using <wbr/>Auth <wbr/>Tokens</span></a></li></ul></li><li><a href="#tasks"><span>Tasks</span></a></li><li><ul><li><a href="#setup-3"><span>Setup</span></a></li><li><a href="#running-tasks-immediately"><span>Running <wbr/>Tasks <wbr/>Immediately</span></a></li><li><a href="#scheduling-tasks"><span>Scheduling <wbr/>Tasks</span></a></li><li><a href="#canceling-tasks"><span>Canceling <wbr/>Tasks</span></a></li><li><a href="#batch-processing-pattern"><span>Batch <wbr/>Processing <wbr/>Pattern</span></a></li></ul></li><li><a href="#network"><span>Network</span></a></li><li><ul><li><a href="#setup-4"><span>Setup</span></a></li><li><a href="#making-http-requests"><span>Making <wbr/>HTTP <wbr/>Requests</span></a></li><li><a href="#creating-webhooks"><span>Creating <wbr/>Webhooks</span></a></li><li><a href="#deleting-webhooks"><span>Deleting <wbr/>Webhooks</span></a></li></ul></li><li><a href="#callbacks"><span>Callbacks</span></a></li><li><ul><li><a href="#setup-5"><span>Setup</span></a></li><li><a href="#creating-callbacks"><span>Creating <wbr/>Callbacks</span></a></li><li><a href="#executing-callbacks"><span>Executing <wbr/>Callbacks</span></a></li><li><a href="#method-signature"><span>Method <wbr/>Signature</span></a></li><li><a href="#callback-versioning-and-upgrades"><span>Callback <wbr/>Versioning and <wbr/>Upgrades</span></a></li><li><ul><li><a href="#handling-version-transitions"><span>Handling <wbr/>Version <wbr/>Transitions</span></a></li><li><a href="#affected-callback-types"><span>Affected <wbr/>Callback <wbr/>Types</span></a></li><li><a href="#migration-in-upgrade"><span>Migration in upgrade()</span></a></li></ul></li><li><a href="#deleting-callbacks"><span>Deleting <wbr/>Callbacks</span></a></li><li><a href="#use-cases"><span>Use <wbr/>Cases</span></a></li></ul></li><li><a href="#ai"><span>AI</span></a></li><li><ul><li><a href="#setup-6"><span>Setup</span></a></li><li><a href="#simple-text-generation"><span>Simple <wbr/>Text <wbr/>Generation</span></a></li><li><a href="#structured-output"><span>Structured <wbr/>Output</span></a></li><li><a href="#tool-calling"><span>Tool <wbr/>Calling</span></a></li><li><a href="#multi-turn-conversations"><span>Multi-<wbr/>turn <wbr/>Conversations</span></a></li><li><a href="#model-selection"><span>Model <wbr/>Selection</span></a></li><li><a href="#typebox-schemas"><span>Typebox <wbr/>Schemas</span></a></li><li><a href="#real-world-example-email-triage"><span>Real-<wbr/>World <wbr/>Example: <wbr/>Email <wbr/>Triage</span></a></li></ul></li><li><a href="#link-type-safety-pattern"><span>Link <wbr/>Type <wbr/>Safety <wbr/>Pattern</span></a></li><li><a href="#next-steps"><span>Next <wbr/>Steps</span></a></li></ul></div></details></div><div class="site-menu"><nav id="tsd-sidebar-links" class="tsd-navigation"><a href="https://plot.day" class="tsd-nav-link">Plot</a><a href="https://github.com/plotday/plot" class="tsd-nav-link">GitHub</a><a href="https://www.npmjs.com/package/@plotday/twister" class="tsd-nav-link">NPM</a></nav><nav class="tsd-navigation"><a href="../modules.html">Creating Plot Twists</a><ul class="tsd-small-nested-navigation" id="tsd-nav-container"><li>Loading...</li></ul></nav></div></div></div><footer><p class="tsd-generator">Generated using <a href="https://typedoc.org/" target="_blank">TypeDoc</a></p></footer><div class="overlay"></div></body></html>
345
+ </div></div><div class="col-sidebar"><div class="page-menu"><div class="tsd-navigation settings"><details class="tsd-accordion"><summary class="tsd-accordion-summary"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-chevronDown"></use></svg><h3>Settings</h3></summary><div class="tsd-accordion-details"><div class="tsd-filter-visibility"><span class="settings-label">Member Visibility</span><ul id="tsd-filter-options"><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-protected" name="protected"/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>Protected</span></label></li><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-inherited" name="inherited" checked/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>Inherited</span></label></li></ul></div><div class="tsd-theme-toggle"><label class="settings-label" for="tsd-theme">Theme</label><select id="tsd-theme"><option value="os">OS</option><option value="light">Light</option><option value="dark">Dark</option></select></div></div></details></div><details open class="tsd-accordion tsd-page-navigation"><summary class="tsd-accordion-summary"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-chevronDown"></use></svg><h3>On This Page</h3></summary><div class="tsd-accordion-details"><a href="#built-in-tools"><span>Built-<wbr/>in <wbr/>Tools</span></a><ul><li><a href="#table-of-contents"><span>Table of <wbr/>Contents</span></a></li><li><a href="#plot"><span>Plot</span></a></li><li><ul><li><a href="#workspace-level-twists"><span>Workspace-<wbr/>Level <wbr/>Twists</span></a></li><li><a href="#understanding-threads-and-notes"><span>Understanding <wbr/>Threads and <wbr/>Notes</span></a></li><li><a href="#setup"><span>Setup</span></a></li><li><a href="#creating-threads"><span>Creating <wbr/>Threads</span></a></li><li><a href="#updating-threads"><span>Updating <wbr/>Threads</span></a></li><li><a href="#creating-and-managing-notes"><span>Creating and <wbr/>Managing <wbr/>Notes</span></a></li><li><ul><li><a href="#creating-notes-on-new-threads"><span>Creating <wbr/>Notes on <wbr/>New <wbr/>Threads</span></a></li><li><a href="#adding-notes-to-existing-threads"><span>Adding <wbr/>Notes to <wbr/>Existing <wbr/>Threads</span></a></li><li><a href="#pattern-conversations-and-message-threads"><span>Pattern: <wbr/>Conversations and <wbr/>Message <wbr/>Threads</span></a></li></ul></li><li><a href="#reading-threads-and-notes"><span>Reading <wbr/>Threads and <wbr/>Notes</span></a></li><li><a href="#managing-focuses"><span>Managing <wbr/>Focuses</span></a></li><li><a href="#schedules"><span>Schedules</span></a></li><li><a href="#contacts"><span>Contacts</span></a></li><li><a href="#links-from-connected-channels"><span>Links from <wbr/>Connected <wbr/>Channels</span></a></li><li><a href="#semantic-search"><span>Semantic <wbr/>Search</span></a></li><li><a href="#plans-user-approved-operations"><span>Plans (<wbr/>User-<wbr/>Approved <wbr/>Operations)</span></a></li><li><a href="#responding-to-mentions"><span>Responding to <wbr/>Mentions</span></a></li></ul></li><li><a href="#store"><span>Store</span></a></li><li><ul><li><a href="#setup-1"><span>Setup</span></a></li><li><a href="#storing-data"><span>Storing <wbr/>Data</span></a></li><li><a href="#retrieving-data"><span>Retrieving <wbr/>Data</span></a></li><li><a href="#clearing-data"><span>Clearing <wbr/>Data</span></a></li><li><a href="#listing-keys"><span>Listing <wbr/>Keys</span></a></li><li><a href="#locks"><span>Locks</span></a></li><li><a href="#best-practices"><span>Best <wbr/>Practices</span></a></li><li><ul><li><a href="#type-safety"><span>Type <wbr/>Safety</span></a></li><li><a href="#namespacing"><span>Namespacing</span></a></li><li><a href="#serialization-limits"><span>Serialization <wbr/>Limits</span></a></li></ul></li></ul></li><li><a href="#integrations"><span>Integrations</span></a></li><li><ul><li><a href="#setup-2"><span>Setup</span></a></li><li><a href="#auth-providers"><span>Auth <wbr/>Providers</span></a></li><li><a href="#using-auth-tokens"><span>Using <wbr/>Auth <wbr/>Tokens</span></a></li><li><a href="#saving-synced-data"><span>Saving <wbr/>Synced <wbr/>Data</span></a></li><li><a href="#auth-actions-in-twists"><span>Auth <wbr/>Actions in <wbr/>Twists</span></a></li></ul></li><li><a href="#tasks"><span>Tasks</span></a></li><li><ul><li><a href="#setup-3"><span>Setup</span></a></li><li><a href="#running-tasks-immediately"><span>Running <wbr/>Tasks <wbr/>Immediately</span></a></li><li><a href="#scheduling-tasks"><span>Scheduling <wbr/>Tasks</span></a></li><li><a href="#canceling-tasks"><span>Canceling <wbr/>Tasks</span></a></li><li><a href="#batch-processing-pattern"><span>Batch <wbr/>Processing <wbr/>Pattern</span></a></li></ul></li><li><a href="#network"><span>Network</span></a></li><li><ul><li><a href="#setup-4"><span>Setup</span></a></li><li><a href="#making-http-requests"><span>Making <wbr/>HTTP <wbr/>Requests</span></a></li><li><a href="#creating-webhooks"><span>Creating <wbr/>Webhooks</span></a></li><li><ul><li><a href="#delivery-mode"><span>Delivery <wbr/>Mode</span></a></li><li><a href="#provider-specific-webhooks"><span>Provider-<wbr/>Specific <wbr/>Webhooks</span></a></li></ul></li><li><a href="#deleting-webhooks"><span>Deleting <wbr/>Webhooks</span></a></li></ul></li><li><a href="#callbacks"><span>Callbacks</span></a></li><li><ul><li><a href="#setup-5"><span>Setup</span></a></li><li><a href="#creating-callbacks"><span>Creating <wbr/>Callbacks</span></a></li><li><a href="#executing-callbacks"><span>Executing <wbr/>Callbacks</span></a></li><li><a href="#method-signature"><span>Method <wbr/>Signature</span></a></li><li><a href="#callback-versioning-and-upgrades"><span>Callback <wbr/>Versioning and <wbr/>Upgrades</span></a></li><li><ul><li><a href="#handling-version-transitions"><span>Handling <wbr/>Version <wbr/>Transitions</span></a></li><li><a href="#affected-callback-types"><span>Affected <wbr/>Callback <wbr/>Types</span></a></li><li><a href="#migration-in-upgrade"><span>Migration in upgrade()</span></a></li></ul></li><li><a href="#deleting-callbacks"><span>Deleting <wbr/>Callbacks</span></a></li><li><a href="#use-cases"><span>Use <wbr/>Cases</span></a></li></ul></li><li><a href="#ai"><span>AI</span></a></li><li><ul><li><a href="#setup-6"><span>Setup</span></a></li><li><a href="#simple-text-generation"><span>Simple <wbr/>Text <wbr/>Generation</span></a></li><li><a href="#structured-output"><span>Structured <wbr/>Output</span></a></li><li><a href="#tool-calling"><span>Tool <wbr/>Calling</span></a></li><li><a href="#multi-turn-conversations"><span>Multi-<wbr/>turn <wbr/>Conversations</span></a></li><li><a href="#web-search"><span>Web <wbr/>Search</span></a></li><li><a href="#model-selection"><span>Model <wbr/>Selection</span></a></li><li><a href="#typebox-schemas"><span>Typebox <wbr/>Schemas</span></a></li><li><a href="#real-world-example-email-triage"><span>Real-<wbr/>World <wbr/>Example: <wbr/>Email <wbr/>Triage</span></a></li></ul></li><li><a href="#files"><span>Files</span></a></li><li><ul><li><a href="#setup-7"><span>Setup</span></a></li><li><a href="#reading-files"><span>Reading <wbr/>Files</span></a></li></ul></li><li><a href="#other-built-in-tools"><span>Other <wbr/>Built-<wbr/>in <wbr/>Tools</span></a></li><li><a href="#link-type-safety-pattern"><span>Link <wbr/>Type <wbr/>Safety <wbr/>Pattern</span></a></li><li><a href="#next-steps"><span>Next <wbr/>Steps</span></a></li></ul></div></details></div><div class="site-menu"><nav id="tsd-sidebar-links" class="tsd-navigation"><a href="https://plot.day" class="tsd-nav-link">Plot</a><a href="https://github.com/plotday/plot" class="tsd-nav-link">GitHub</a><a href="https://www.npmjs.com/package/@plotday/twister" class="tsd-nav-link">NPM</a></nav><nav class="tsd-navigation"><a href="../modules.html">Creating Plot Twists</a><ul class="tsd-small-nested-navigation" id="tsd-nav-container"><li>Loading...</li></ul></nav></div></div></div><footer><p class="tsd-generator">Generated using <a href="https://typedoc.org/" target="_blank">TypeDoc</a></p></footer><div class="overlay"></div></body></html>