@plotday/twister 0.57.0 → 0.59.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -44
- package/bin/commands/create.js +9 -14
- package/bin/commands/create.js.map +1 -1
- package/bin/commands/deploy.js +2 -0
- package/bin/commands/deploy.js.map +1 -1
- package/bin/commands/generate.js +8 -5
- package/bin/commands/generate.js.map +1 -1
- package/bin/index.js +2 -2
- package/bin/index.js.map +1 -1
- package/bin/templates/AGENTS.template.md +110 -94
- package/bin/templates/README.template.md +36 -33
- package/cli/templates/AGENTS.template.md +110 -94
- package/cli/templates/README.template.md +36 -33
- package/dist/connector.d.ts +58 -17
- package/dist/connector.d.ts.map +1 -1
- package/dist/connector.js +51 -13
- package/dist/connector.js.map +1 -1
- package/dist/docs/assets/hierarchy.js +1 -1
- package/dist/docs/assets/navigation.js +1 -1
- package/dist/docs/assets/search.js +1 -1
- package/dist/docs/classes/index.Connector.html +90 -60
- package/dist/docs/classes/index.FileNotFoundError.html +2 -2
- package/dist/docs/classes/index.Files.html +4 -4
- package/dist/docs/classes/index.Imap.html +10 -10
- package/dist/docs/classes/index.Options.html +2 -2
- package/dist/docs/classes/index.Smtp.html +6 -6
- package/dist/docs/classes/tool.ITool.html +2 -2
- package/dist/docs/classes/tool.Tool.html +23 -23
- package/dist/docs/classes/tools_ai.AI.html +5 -5
- package/dist/docs/classes/tools_callbacks.Callbacks.html +8 -8
- package/dist/docs/classes/tools_integrations.Integrations.html +15 -15
- package/dist/docs/classes/tools_network.Network.html +9 -9
- package/dist/docs/classes/tools_plot.Plot.html +34 -33
- package/dist/docs/classes/tools_store.Store.html +8 -8
- package/dist/docs/classes/tools_tasks.Tasks.html +6 -6
- package/dist/docs/classes/tools_twists.Twists.html +12 -11
- package/dist/docs/classes/twist.Twist.html +28 -28
- package/dist/docs/documents/Building_Connectors.html +42 -28
- package/dist/docs/documents/Built-in_Tools.html +170 -67
- package/dist/docs/documents/CLI_Reference.html +68 -47
- package/dist/docs/documents/Core_Concepts.html +52 -81
- package/dist/docs/documents/Getting_Started.html +28 -31
- package/dist/docs/documents/MULTI_USER_AUTH.html +45 -0
- package/dist/docs/documents/Runtime_Environment.html +13 -12
- package/dist/docs/documents/SYNC_STRATEGIES.html +373 -0
- package/dist/docs/enums/plot.ActionType.html +9 -9
- package/dist/docs/enums/plot.ActorType.html +4 -4
- package/dist/docs/enums/plot.ConferencingProvider.html +6 -6
- package/dist/docs/enums/plot.ThemeColor.html +9 -9
- package/dist/docs/enums/tag.Tag.html +3 -3
- package/dist/docs/enums/tools_ai.AIModel.html +3 -3
- package/dist/docs/enums/tools_integrations.AuthProvider.html +13 -13
- package/dist/docs/enums/tools_plot.ContactAccess.html +2 -2
- package/dist/docs/enums/tools_plot.FocusAccess.html +3 -3
- package/dist/docs/enums/tools_plot.LinkAccess.html +3 -3
- package/dist/docs/enums/tools_plot.ThreadAccess.html +4 -4
- package/dist/docs/functions/index.Uuid.Generate.html +1 -1
- package/dist/docs/functions/utils_hash.quickHash.html +1 -1
- package/dist/docs/hierarchy.html +1 -1
- package/dist/docs/index.html +7 -8
- package/dist/docs/interfaces/tools_ai.AIRequest.html +13 -13
- package/dist/docs/interfaces/tools_ai.AIResponse.html +9 -9
- package/dist/docs/interfaces/tools_ai.FilePart.html +5 -5
- package/dist/docs/interfaces/tools_ai.ImagePart.html +4 -4
- package/dist/docs/interfaces/tools_ai.ReasoningPart.html +4 -4
- package/dist/docs/interfaces/tools_ai.RedactedReasoningPart.html +3 -3
- package/dist/docs/interfaces/tools_ai.TextPart.html +3 -3
- package/dist/docs/interfaces/tools_ai.ToolCallPart.html +5 -5
- package/dist/docs/interfaces/tools_ai.ToolExecutionOptions.html +4 -4
- package/dist/docs/interfaces/tools_ai.ToolResultPart.html +5 -5
- package/dist/docs/interfaces/tools_twists.TwistSource.html +3 -3
- package/dist/docs/interfaces/utils_types.ToolShed.html +5 -5
- package/dist/docs/media/AGENTS.md +101 -74
- package/dist/docs/modules.html +1 -1
- package/dist/docs/types/index.BooleanDef.html +2 -2
- package/dist/docs/types/index.CreateLinkDraft.html +9 -9
- package/dist/docs/types/index.ImapAddress.html +3 -3
- package/dist/docs/types/index.ImapConnectOptions.html +6 -6
- package/dist/docs/types/index.ImapFetchOptions.html +4 -4
- package/dist/docs/types/index.ImapFlagOperation.html +1 -1
- package/dist/docs/types/index.ImapMailbox.html +5 -5
- package/dist/docs/types/index.ImapMailboxStatus.html +7 -7
- package/dist/docs/types/index.ImapMessage.html +14 -14
- package/dist/docs/types/index.ImapSearchCriteria.html +9 -9
- package/dist/docs/types/index.ImapSession.html +1 -1
- package/dist/docs/types/index.NewSchedule.html +13 -13
- package/dist/docs/types/index.NewScheduleContact.html +2 -2
- package/dist/docs/types/index.NewScheduleOccurrence.html +1 -1
- package/dist/docs/types/index.NoteWriteBackResult.html +3 -3
- package/dist/docs/types/index.NumberDef.html +2 -2
- package/dist/docs/types/index.OptionDef.html +1 -1
- package/dist/docs/types/index.OptionalScopeGroup.html +6 -6
- package/dist/docs/types/index.OptionsSchema.html +1 -1
- package/dist/docs/types/index.ReactionCapabilities.html +1 -1
- package/dist/docs/types/index.ResolvedOptions.html +1 -1
- package/dist/docs/types/index.ResolvedRecipient.html +5 -5
- package/dist/docs/types/index.Schedule.html +12 -12
- package/dist/docs/types/index.ScheduleContact.html +2 -2
- package/dist/docs/types/index.ScheduleContactRole.html +1 -1
- package/dist/docs/types/index.ScheduleContactStatus.html +1 -1
- package/dist/docs/types/index.ScheduleOccurrence.html +6 -6
- package/dist/docs/types/index.ScheduleOccurrenceUpdate.html +1 -1
- package/dist/docs/types/index.ScopeConfig.html +3 -3
- package/dist/docs/types/index.SelectDef.html +2 -2
- package/dist/docs/types/index.Serializable.html +1 -1
- package/dist/docs/types/index.SmtpAddress.html +3 -3
- package/dist/docs/types/index.SmtpConnectOptions.html +7 -7
- package/dist/docs/types/index.SmtpMessage.html +12 -12
- package/dist/docs/types/index.SmtpSendResult.html +4 -4
- package/dist/docs/types/index.SmtpSession.html +1 -1
- package/dist/docs/types/index.TextDef.html +2 -2
- package/dist/docs/types/index.Uuid.html +1 -1
- package/dist/docs/types/plot.Action.html +1 -1
- package/dist/docs/types/plot.Actor.html +5 -5
- package/dist/docs/types/plot.ActorId.html +4 -4
- package/dist/docs/types/plot.Contact.html +4 -4
- package/dist/docs/types/plot.ContentType.html +1 -1
- package/dist/docs/types/plot.Focus.html +8 -8
- package/dist/docs/types/plot.FocusUpdate.html +1 -1
- package/dist/docs/types/plot.Link.html +17 -17
- package/dist/docs/types/plot.LinkUpdate.html +1 -1
- package/dist/docs/types/plot.NewActor.html +1 -1
- package/dist/docs/types/plot.NewContact.html +1 -1
- package/dist/docs/types/plot.NewFocus.html +1 -1
- package/dist/docs/types/plot.NewLink.html +5 -2
- package/dist/docs/types/plot.NewLinkWithNotes.html +1 -1
- package/dist/docs/types/plot.NewNote.html +1 -1
- package/dist/docs/types/plot.NewReactions.html +1 -1
- package/dist/docs/types/plot.NewTags.html +1 -1
- package/dist/docs/types/plot.NewThread.html +1 -1
- package/dist/docs/types/plot.NewThreadWithNotes.html +1 -1
- package/dist/docs/types/plot.Note.html +1 -1
- package/dist/docs/types/plot.NoteUpdate.html +1 -1
- package/dist/docs/types/plot.PlanOperation.html +1 -1
- package/dist/docs/types/plot.Reaction.html +3 -3
- package/dist/docs/types/plot.Reactions.html +1 -1
- package/dist/docs/types/plot.Tags.html +1 -1
- package/dist/docs/types/plot.Thread.html +1 -1
- package/dist/docs/types/plot.ThreadAccessLevel.html +1 -1
- package/dist/docs/types/plot.ThreadCommon.html +6 -6
- package/dist/docs/types/plot.ThreadFilter.html +2 -2
- package/dist/docs/types/plot.ThreadMeta.html +1 -1
- package/dist/docs/types/plot.ThreadType.html +1 -1
- package/dist/docs/types/plot.ThreadUpdate.html +1 -1
- package/dist/docs/types/plot.ThreadWithNotes.html +1 -1
- package/dist/docs/types/tools_ai.AIAssistantMessage.html +2 -2
- package/dist/docs/types/tools_ai.AICapabilities.html +4 -4
- package/dist/docs/types/tools_ai.AIMessage.html +1 -1
- package/dist/docs/types/tools_ai.AIOptions.html +2 -2
- package/dist/docs/types/tools_ai.AISource.html +1 -1
- package/dist/docs/types/tools_ai.AISystemMessage.html +2 -2
- package/dist/docs/types/tools_ai.AITool.html +1 -1
- package/dist/docs/types/tools_ai.AIToolMessage.html +2 -2
- package/dist/docs/types/tools_ai.AIToolSet.html +1 -1
- package/dist/docs/types/tools_ai.AIUsage.html +5 -5
- package/dist/docs/types/tools_ai.AIUserMessage.html +2 -2
- package/dist/docs/types/tools_ai.DataContent.html +1 -1
- package/dist/docs/types/tools_ai.ModelPreferences.html +5 -5
- package/dist/docs/types/tools_callbacks.Callback.html +2 -2
- package/dist/docs/types/tools_integrations.ArchiveLinkFilter.html +5 -5
- package/dist/docs/types/tools_integrations.ArchiveNotesFilter.html +2 -2
- package/dist/docs/types/tools_integrations.AuthToken.html +6 -5
- package/dist/docs/types/tools_integrations.Authorization.html +4 -4
- package/dist/docs/types/tools_integrations.Channel.html +6 -6
- package/dist/docs/types/tools_integrations.ComposeConfig.html +4 -4
- package/dist/docs/types/tools_integrations.ContactRoleConfig.html +5 -5
- package/dist/docs/types/tools_integrations.LinkTypeConfig.html +21 -21
- package/dist/docs/types/tools_integrations.NewCustomEmoji.html +8 -8
- package/dist/docs/types/tools_integrations.StatusIcon.html +1 -1
- package/dist/docs/types/tools_integrations.SyncContext.html +4 -4
- package/dist/docs/types/tools_network.WebhookRequest.html +6 -6
- package/dist/docs/types/tools_plot.LinkFilter.html +5 -5
- package/dist/docs/types/tools_plot.LinkSearchResult.html +1 -1
- package/dist/docs/types/tools_plot.NoteIntentHandler.html +4 -4
- package/dist/docs/types/tools_plot.NoteSearchResult.html +1 -1
- package/dist/docs/types/tools_plot.SearchOptions.html +4 -4
- package/dist/docs/types/tools_plot.SearchResult.html +1 -1
- package/dist/docs/types/tools_twists.Log.html +2 -2
- package/dist/docs/types/tools_twists.TwistPermissions.html +1 -1
- package/dist/docs/types/utils_types.BuiltInTools.html +2 -2
- package/dist/docs/types/utils_types.ExtractBuildReturn.html +1 -1
- package/dist/docs/types/utils_types.InferOptions.html +1 -1
- package/dist/docs/types/utils_types.InferTools.html +1 -1
- package/dist/docs/types/utils_types.JSONValue.html +1 -1
- package/dist/docs/types/utils_types.PromiseValues.html +1 -1
- package/dist/docs/types/utils_types.ToolBuilder.html +1 -1
- package/dist/docs/variables/tools_plot.SEARCH_DEFAULT_LIMIT.html +1 -1
- package/dist/docs/variables/tools_plot.SEARCH_MAX_LIMIT.html +1 -1
- package/dist/facets.d.ts +30 -0
- package/dist/facets.d.ts.map +1 -0
- package/dist/facets.js +16 -0
- package/dist/facets.js.map +1 -0
- package/dist/llm-docs/connector.d.ts +1 -1
- package/dist/llm-docs/connector.d.ts.map +1 -1
- package/dist/llm-docs/connector.js +1 -1
- package/dist/llm-docs/connector.js.map +1 -1
- package/dist/llm-docs/facets.d.ts +9 -0
- package/dist/llm-docs/facets.d.ts.map +1 -0
- package/dist/llm-docs/facets.js +8 -0
- package/dist/llm-docs/facets.js.map +1 -0
- package/dist/llm-docs/index.d.ts.map +1 -1
- package/dist/llm-docs/index.js +2 -0
- package/dist/llm-docs/index.js.map +1 -1
- package/dist/llm-docs/plot.d.ts +1 -1
- package/dist/llm-docs/plot.d.ts.map +1 -1
- package/dist/llm-docs/plot.js +1 -1
- package/dist/llm-docs/plot.js.map +1 -1
- package/dist/llm-docs/tool.d.ts +1 -1
- package/dist/llm-docs/tool.d.ts.map +1 -1
- package/dist/llm-docs/tool.js +1 -1
- package/dist/llm-docs/tool.js.map +1 -1
- package/dist/llm-docs/tools/ai.d.ts +1 -1
- package/dist/llm-docs/tools/ai.d.ts.map +1 -1
- package/dist/llm-docs/tools/ai.js +1 -1
- package/dist/llm-docs/tools/ai.js.map +1 -1
- package/dist/llm-docs/tools/callbacks.d.ts +1 -1
- package/dist/llm-docs/tools/callbacks.d.ts.map +1 -1
- package/dist/llm-docs/tools/callbacks.js +1 -1
- package/dist/llm-docs/tools/callbacks.js.map +1 -1
- package/dist/llm-docs/tools/files.d.ts +1 -1
- package/dist/llm-docs/tools/files.d.ts.map +1 -1
- package/dist/llm-docs/tools/files.js +1 -1
- package/dist/llm-docs/tools/files.js.map +1 -1
- package/dist/llm-docs/tools/imap.d.ts +1 -1
- package/dist/llm-docs/tools/imap.d.ts.map +1 -1
- package/dist/llm-docs/tools/imap.js +1 -1
- package/dist/llm-docs/tools/imap.js.map +1 -1
- package/dist/llm-docs/tools/integrations.d.ts +1 -1
- package/dist/llm-docs/tools/integrations.d.ts.map +1 -1
- package/dist/llm-docs/tools/integrations.js +1 -1
- package/dist/llm-docs/tools/integrations.js.map +1 -1
- package/dist/llm-docs/tools/network.d.ts +1 -1
- package/dist/llm-docs/tools/network.d.ts.map +1 -1
- package/dist/llm-docs/tools/network.js +1 -1
- package/dist/llm-docs/tools/network.js.map +1 -1
- package/dist/llm-docs/tools/plot.d.ts +1 -1
- package/dist/llm-docs/tools/plot.d.ts.map +1 -1
- package/dist/llm-docs/tools/plot.js +1 -1
- package/dist/llm-docs/tools/plot.js.map +1 -1
- package/dist/llm-docs/tools/smtp.d.ts +1 -1
- package/dist/llm-docs/tools/smtp.d.ts.map +1 -1
- package/dist/llm-docs/tools/smtp.js +1 -1
- package/dist/llm-docs/tools/smtp.js.map +1 -1
- package/dist/llm-docs/tools/tasks.d.ts +1 -1
- package/dist/llm-docs/tools/tasks.d.ts.map +1 -1
- package/dist/llm-docs/tools/tasks.js +1 -1
- package/dist/llm-docs/tools/tasks.js.map +1 -1
- package/dist/llm-docs/tools/twists.d.ts +1 -1
- package/dist/llm-docs/tools/twists.d.ts.map +1 -1
- package/dist/llm-docs/tools/twists.js +1 -1
- package/dist/llm-docs/tools/twists.js.map +1 -1
- package/dist/llm-docs/twist-guide-template.d.ts +1 -1
- package/dist/llm-docs/twist-guide-template.d.ts.map +1 -1
- package/dist/llm-docs/twist-guide-template.js +1 -1
- package/dist/llm-docs/twist-guide-template.js.map +1 -1
- package/dist/llm-docs/twist.d.ts +1 -1
- package/dist/llm-docs/twist.d.ts.map +1 -1
- package/dist/llm-docs/twist.js +1 -1
- package/dist/llm-docs/twist.js.map +1 -1
- package/dist/plot.d.ts +15 -8
- package/dist/plot.d.ts.map +1 -1
- package/dist/plot.js.map +1 -1
- package/dist/tool.d.ts +4 -4
- package/dist/tool.js +4 -4
- package/dist/tools/ai.d.ts +12 -13
- package/dist/tools/ai.d.ts.map +1 -1
- package/dist/tools/ai.js +8 -9
- package/dist/tools/ai.js.map +1 -1
- package/dist/tools/callbacks.d.ts +1 -1
- package/dist/tools/files.d.ts +2 -2
- package/dist/tools/imap.d.ts +1 -1
- package/dist/tools/imap.js +1 -1
- package/dist/tools/integrations.d.ts +2 -1
- package/dist/tools/integrations.d.ts.map +1 -1
- package/dist/tools/network.d.ts +5 -5
- package/dist/tools/plot.d.ts +42 -37
- package/dist/tools/plot.d.ts.map +1 -1
- package/dist/tools/plot.js +16 -12
- package/dist/tools/plot.js.map +1 -1
- package/dist/tools/smtp.d.ts +1 -1
- package/dist/tools/smtp.js +1 -1
- package/dist/tools/tasks.d.ts +6 -8
- package/dist/tools/tasks.d.ts.map +1 -1
- package/dist/tools/tasks.js +5 -7
- package/dist/tools/tasks.js.map +1 -1
- package/dist/tools/twists.d.ts +15 -14
- package/dist/tools/twists.d.ts.map +1 -1
- package/dist/tools/twists.js +2 -2
- package/dist/tools/twists.js.map +1 -1
- package/dist/twist-guide.d.ts +1 -1
- package/dist/twist-guide.d.ts.map +1 -1
- package/dist/twist.d.ts +2 -2
- package/dist/twist.js +2 -2
- package/package.json +6 -1
- package/src/connector.ts +56 -16
- package/src/facets.ts +40 -0
- package/src/llm-docs/connector.ts +1 -1
- package/src/llm-docs/facets.ts +8 -0
- package/src/llm-docs/index.ts +2 -0
- package/src/llm-docs/plot.ts +1 -1
- package/src/llm-docs/tool.ts +1 -1
- package/src/llm-docs/tools/ai.ts +1 -1
- package/src/llm-docs/tools/callbacks.ts +1 -1
- package/src/llm-docs/tools/files.ts +1 -1
- package/src/llm-docs/tools/imap.ts +1 -1
- package/src/llm-docs/tools/integrations.ts +1 -1
- package/src/llm-docs/tools/network.ts +1 -1
- package/src/llm-docs/tools/plot.ts +1 -1
- package/src/llm-docs/tools/smtp.ts +1 -1
- package/src/llm-docs/tools/tasks.ts +1 -1
- package/src/llm-docs/tools/twists.ts +1 -1
- package/src/llm-docs/twist-guide-template.ts +1 -1
- package/src/llm-docs/twist.ts +1 -1
- package/src/plot.ts +15 -8
- package/src/tool.ts +4 -4
- package/src/tools/ai.ts +12 -13
- package/src/tools/callbacks.ts +1 -1
- package/src/tools/files.ts +2 -2
- package/src/tools/imap.ts +1 -1
- package/src/tools/integrations.ts +2 -1
- package/src/tools/network.ts +5 -5
- package/src/tools/plot.ts +42 -37
- package/src/tools/smtp.ts +1 -1
- package/src/tools/tasks.ts +6 -8
- package/src/tools/twists.ts +15 -14
- package/src/twist.ts +2 -2
- package/dist/docs/media/MULTI_USER_AUTH.md +0 -116
- 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
|
|
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
|
|
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
|
|
14
|
-
<p>When a twist creates a thread
|
|
15
|
-
<h3 id="understanding-
|
|
16
|
-
<p><strong>Think of
|
|
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><
|
|
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">"@plotday/twister/tools/plot"</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
|
-
<
|
|
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">"@plotday/twister"</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">"Q1 Planning Meeting Notes"</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">"Discussed goals for Q1 and assigned action items."</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">"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">"Review pull request #123"</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">"description"</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">"Please review the changes and provide feedback."</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">"View PR"</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">"https://github.com/org/repo/pull/123"</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">"Team standup"</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">"Daily standup meeting to sync on progress."</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">"2025-02-01T10:00:00Z"</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">"2025-02-01T10:30:00Z"</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>"action"</code>, <code>"notes"</code>, <code>"idea"</code>, <code>"goal"</code>, <code>"decision"</code>, and (in shared focuses) <code>"discussion"</code>, <code>"announcement"</code>, <code>"ask"</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">"Updated title"</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's own tags without touching other actors' 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">"@plotday/twister"</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">"TEAM"</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">"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">"Customer feedback: Login issues"</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">"description"</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">"Customer reported:</span><span class="hl-12">\n\n\"</span><span class="hl-3">I'm unable to log in using Google SSO.</span><span class="hl-12">\"\n\n</span><span class="hl-3">Priority: High</span><span class="hl-12">\n</span><span class="hl-3">Affected users: ~15 reports"</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">"View Support Ticket"</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">"https://support.example.com/tickets/12345"</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="
|
|
24
|
-
<pre><code class="typescript"><span class="hl-7">//
|
|
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">"Update: Engineering team has identified the root cause. Fix will be deployed in the next release."</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">"View PR Fix"</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">"https://github.com/org/repo/pull/789"</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
|
|
28
|
-
<
|
|
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: "text"</code> or <code>"html"</code> to have the server convert; <code>"markdown"</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">"Updated content with more details"</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"><</span><span class="hl-5">Uuid</span><span class="hl-1">>(</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">=></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">=></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
|
-
<
|
|
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'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">"linear:issue:ABC-123"</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">"description"</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's threads (requires ThreadAccess.Full).</span><br/><span class="hl-7">// Defaults to the owner'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-
|
|
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">"Work"</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">"work"</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">"work"</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">"work"</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">"Work Projects"</span><span class="hl-1">,</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-7">// List the owner'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="
|
|
38
|
-
<pre><code class="typescript"><span class="hl-
|
|
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">"Team standup"</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">"2025-01-15T10:00:00Z"</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">"2025-01-15T10:30:00Z"</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">"FREQ=DAILY;BYDAY=MO,TU,WE,TH,FR"</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
|
|
42
|
-
<
|
|
83
|
+
<p>For all-day events, pass <code>"YYYY-MM-DD"</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
|
-
<
|
|
46
|
-
<
|
|
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">"https://support.example.com/tickets/12345"</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">"Customer feedback: Login issues"</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">"https://support.example.com/tickets/12345"</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">"description"</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">"Customer reported:</span><span class="hl-12">\n\n\"</span><span class="hl-3">I'm unable to log in using Google SSO. The page redirects but then shows an error 'Invalid state parameter'.</span><span class="hl-12">\"\n\n</span><span class="hl-3">Priority: High</span><span class="hl-12">\n</span><span class="hl-3">Affected users: ~15 reports"</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">"View Support Ticket"</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">"https://support.example.com/tickets/12345"</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">"event"</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
|
-
<
|
|
51
|
-
<pre><code class="typescript"><span class="hl-
|
|
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">"budget discussion with finance"</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 "note" or "link"</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
|
-
<
|
|
55
|
-
<
|
|
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'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">=></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">"Organize project threads"</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">"updateThread"</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">"Old thread"</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">"Here's my proposed cleanup:"</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
|
|
60
|
-
<pre><code class="typescript"><span class="hl-
|
|
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) => Promise<void></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><
|
|
64
|
-
<p>See <a href="
|
|
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">"last_sync"</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">"config"</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">"items"</span><span class="hl-1">, [</span><span class="hl-3">"a"</span><span class="hl-1">, </span><span class="hl-3">"b"</span><span class="hl-1">, </span><span class="hl-3">"c"</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">"last_sync"</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">"config"</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">"items"</span><span class="hl-1">, [</span><span class="hl-3">"a"</span><span class="hl-1">, </span><span class="hl-3">"b"</span><span class="hl-1">, </span><span class="hl-3">"c"</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"><</span><span class="hl-5">string</span><span class="hl-1">>(</span><span class="hl-3">"last_sync"</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"><{ </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"> }>(</span><span class="hl-3">"config"</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"><</span><span class="hl-5">string</span><span class="hl-1">>(</span><span class="hl-3">"key"</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'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">"last_sync"</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">"webhook:"</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 "in progress" 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">"active"</span><span class="hl-1"> | </span><span class="hl-3">"paused"</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"><</span><span class="hl-2">SyncState</span><span class="hl-1"> | </span><span class="hl-4">null</span><span class="hl-1">> {</span><br/><span class="hl-1"> return await this.get<SyncState>(</span><span class="hl-3">"sync_state"</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"><</span><span class="hl-4">void</span><span class="hl-1">> {</span><br/><span class="hl-1"> await this.set(</span><span class="hl-3">"sync_state"</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">"webhook:calendar"</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">"webhook:github"</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">"config:sync_interval"</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>
|
|
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">"handler"</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'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-
|
|
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">"handler"</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'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">"handler_token"</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
|
|
91
|
-
<
|
|
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">"@plotday/twister"</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">"@plotday/twister/tools/integrations"</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"><</span><span class="hl-5">CalendarConnector</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">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">"https://www.googleapis.com/auth/calendar.readonly"</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"><</span><span class="hl-5">Channel</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">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">=></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'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="
|
|
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
|
-
<
|
|
98
|
-
<
|
|
99
|
-
<
|
|
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">"issue"</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">"description"</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">"markdown"</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 "later").</li>
|
|
160
|
+
<li><strong><code>channelSyncCompleted(channelId)</code></strong> — signal that the initial backfill for a channel finished, clearing the "syncing…" 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="
|
|
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">"@plotday/twister"</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">"@plotday/twister/tools/integrations"</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">"Connect your Google Calendar"</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">"Click below to connect your Google account"</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">"Connect Google"</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">"https://www.googleapis.com/auth/calendar.readonly"</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-
|
|
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">"Processing batch:"</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-
|
|
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">"123"</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-3">"Meeting in 10 minutes"</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">"2025-02-01T09:50:00Z"</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">"reminder_token"</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"><</span><span class="hl-5">string</span><span class="hl-1">>(</span><span class="hl-3">"reminder_token"</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"><</span><span class="hl-5">string</span><span class="hl-1">>(</span><span class="hl-3">"reminder_token"</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">"sync_state"</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-
|
|
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">"sync_state"</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"><{ </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"> }>(</span><span class="hl-3">"sync_state"</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">"sync_state"</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">"sync_state"</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">"@plotday/twister/tools/network"</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'll access</span><br/><span class="hl-1"> </span><span class="hl-2">urls:</span><span class="hl-1"> [</span><span class="hl-3">'https://api.example.com/*'</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">"https://api.example.com/data"</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><
|
|
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">"primary"</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">"webhook_url"</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">"https://api.service.com/webhooks"</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">"POST"</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">"Webhook received:"</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">"Calendar:"</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">"event.created"</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: "gmail" | "workspace"</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"><</span><span class="hl-5">string</span><span class="hl-1">>(</span><span class="hl-3">"webhook_url"</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">"https://api.service.com/webhooks"</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">"DELETE"</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><
|
|
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">"calendar_sync"</span><span class="hl-1">, </span><span class="hl-3">"high"</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">"event_handler"</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
|
-
<
|
|
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">"@plotday/twister"</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">"Approve"</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="
|
|
152
|
-
|
|
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">"@plotday/twister/tools/callbacks"</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"><</span><span class="hl-5">Callback</span><span class="hl-1">>(</span><span class="hl-3">"event_handler"</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, "calendar_sync", "high")</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">"Event type:"</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">"Priority:"</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">
|
|
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">
|
|
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>
|
|
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"><</span><span class="hl-5">SyncState</span><span class="hl-1">[]>(</span><span class="hl-3">"active_syncs"</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"><</span><span class="hl-5">
|
|
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"><</span><span class="hl-5">SyncState</span><span class="hl-1">[]>(</span><span class="hl-3">"active_syncs"</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"><</span><span class="hl-5">Callback</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><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"><</span><span class="hl-5">
|
|
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"><</span><span class="hl-5">Callback</span><span class="hl-1">>(</span><span class="hl-3">"event_handler"</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>
|
|
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">"@plotday/twister/tools/ai"</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">"fast"</span><span class="hl-1">, </span><span class="hl-2">cost:</span><span class="hl-1"> </span><span class="hl-3">"low"</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">"Explain quantum computing in simple terms"</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">"typebox"</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">"work"</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">"personal"</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">"urgent"</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">"Brief summary"</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">"balanced"</span><span class="hl-1">, </span><span class="hl-2">cost:</span><span class="hl-1"> </span><span class="hl-3">"medium"</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">"Categorize this email: Meeting at 3pm tomorrow about Q1 planning"</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"
|
|
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">"typebox"</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">"work"</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">"personal"</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">"urgent"</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">"Brief summary"</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">"balanced"</span><span class="hl-1">, </span><span class="hl-2">cost:</span><span class="hl-1"> </span><span class="hl-3">"medium"</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">"Categorize this email: Meeting at 3pm tomorrow about Q1 planning"</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">// "work" | "personal" | "urgent"</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">"typebox"</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">"fast"</span><span class="hl-1">, </span><span class="hl-2">cost:</span><span class="hl-1"> </span><span class="hl-3">"medium"</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">"What's 15% of $250?"</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">"Perform mathematical calculations"</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">
|
|
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">"typebox"</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">"fast"</span><span class="hl-1">, </span><span class="hl-2">cost:</span><span class="hl-1"> </span><span class="hl-3">"medium"</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">"What's 15% of $250?"</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">"Perform mathematical calculations"</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">"Math expression to evaluate"</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">=></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">// "15% of $250 is $37.50"</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-
|
|
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">"user"</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">"What's the weather like?"</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">"assistant"</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">"I don't have access to weather data. Would you like me to help with something else?"</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">"user"</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">"What's 2+2?"</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">"fast"</span><span class="hl-1">, </span><span class="hl-2">cost:</span><span class="hl-1"> </span><span class="hl-3">"low"</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">"fast"</span><span class="hl-1">, </span><span class="hl-15">cost</span><span class="hl-1">: </span><span class="hl-3">"low"</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">"balanced"</span><span class="hl-1">, </span><span class="hl-15">cost</span><span class="hl-1">: </span><span class="hl-3">"medium"</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">"capable"</span><span class="hl-1">, </span><span class="hl-15">cost</span><span class="hl-1">: </span><span class="hl-3">"high"</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">"typebox"</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">"email"</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">"pending"</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">"active"</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">"completed"</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">"typebox"</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">"urgent"</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">"important"</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">"informational"</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">"spam"</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">"balanced"</span><span class="hl-1">, </span><span class="hl-2">cost:</span><span class="hl-1"> </span><span class="hl-3">"medium"</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
|
|
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">"typebox"</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">"urgent"</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">"important"</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">"informational"</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">"spam"</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">"balanced"</span><span class="hl-1">, </span><span class="hl-2">cost:</span><span class="hl-1"> </span><span class="hl-3">"medium"</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">"urgent"</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">"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">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">"</span><span class="hl-12">\n</span><span class="hl-3">"</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="
|
|
229
|
-
<
|
|
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">"@plotday/twister/tools/files"</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">"@plotday/twister"</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">"@plotday/twister/tools/integrations"</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">"issue"</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">"Issue"</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">"https://api.iconify.design/simple-icons/linear.svg"</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">"open"</span><span class="hl-1">, </span><span class="hl-2">label:</span><span class="hl-1"> </span><span class="hl-3">"Open"</span><span class="hl-1">, </span><span class="hl-2">icon:</span><span class="hl-1"> </span><span class="hl-3">"todo"</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">"done"</span><span class="hl-1">, </span><span class="hl-2">label:</span><span class="hl-1"> </span><span class="hl-3">"Done"</span><span class="hl-1">, </span><span class="hl-2">icon:</span><span class="hl-1"> </span><span class="hl-3">"done"</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">"pull_request"</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">"Pull Request"</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">"https://api.iconify.design/simple-icons/github.svg"</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">"open"</span><span class="hl-1">, </span><span class="hl-2">label:</span><span class="hl-1"> </span><span class="hl-3">"Open"</span><span class="hl-1">, </span><span class="hl-2">icon:</span><span class="hl-1"> </span><span class="hl-3">"inProgress"</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">"merged"</span><span class="hl-1">, </span><span class="hl-2">label:</span><span class="hl-1"> </span><span class="hl-3">"Merged"</span><span class="hl-1">, </span><span class="hl-2">icon:</span><span class="hl-1"> </span><span class="hl-3">"done"</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">"closed"</span><span class="hl-1">, </span><span class="hl-2">label:</span><span class="hl-1"> </span><span class="hl-3">"Closed"</span><span class="hl-1">, </span><span class="hl-2">icon:</span><span class="hl-1"> </span><span class="hl-3">"cancelled"</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">"statuses"</span><span class="hl-1">][</span><span class="hl-5">number</span><span class="hl-1">][</span><span class="hl-3">"status"</span><span class="hl-1">]; </span><span class="hl-7">// "open" | "done"</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">"statuses"</span><span class="hl-1">][</span><span class="hl-5">number</span><span class="hl-1">][</span><span class="hl-3">"status"</span><span class="hl-1">]; </span><span class="hl-7">// "open" | "merged" | "closed"</span>
|
|
230
332
|
</code><button type="button">Copy</button></pre>
|
|
231
333
|
|
|
232
|
-
<p>
|
|
233
|
-
<
|
|
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"><</span><span class="hl-5">MyConnector</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">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-
|
|
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>
|