@plotday/twister 0.56.0 → 0.58.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +24 -17
- package/dist/connector.d.ts.map +1 -1
- package/dist/connector.js +19 -12
- 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 +66 -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 +26 -12
- 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/index.html +1 -1
- package/dist/docs/modules/tools_integrations.html +1 -1
- 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 +5 -0
- 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 +29 -2
- package/dist/tools/integrations.d.ts.map +1 -1
- package/dist/tools/integrations.js.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 +23 -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 +34 -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
|
@@ -29,17 +29,18 @@ Plot Twists are TypeScript classes that extend the `Twist` base class. Twists in
|
|
|
29
29
|
|
|
30
30
|
1. **Always create Threads with an initial Note** - The title is just a summary; detailed content goes in Notes
|
|
31
31
|
2. **Add Notes to existing Threads for updates** - Don't create a new Thread for each related message
|
|
32
|
-
3. **Use
|
|
32
|
+
3. **Use source/key for automatic upserts (Recommended)** - External items are saved as links keyed by `source` (connectors call `integrations.saveLink()` with the item's canonical URL/ID), and `Note.key` enables upsertable note content. Reference an already-synced thread with `thread: { source }`. No manual ID tracking needed.
|
|
33
33
|
4. **For advanced cases, use generated UUIDs** - Only when you need multiple Plot threads per external item (see SYNC_STRATEGIES.md)
|
|
34
|
-
5. **
|
|
34
|
+
5. **Thread `type` is an optional display sub-type** - One of `"action"`, `"notes"`, `"idea"`, `"goal"`, `"decision"`, `"discussion"`, `"announcement"`, `"ask"`. Omit it for the default (`"notes"` in private focuses, `"discussion"` in shared ones). Use `"action"` for tasks; events are threads with `schedules`.
|
|
35
35
|
|
|
36
36
|
### Recommended Decision Tree (Strategy 2: Upsert via Source/Key)
|
|
37
37
|
|
|
38
38
|
```
|
|
39
39
|
New event/task/conversation from external system?
|
|
40
40
|
├─ Has stable URL or ID?
|
|
41
|
-
│ └─ Yes →
|
|
42
|
-
│
|
|
41
|
+
│ └─ Yes → Save a link with `source` set to the canonical URL/ID
|
|
42
|
+
│ (connectors: integrations.saveLink() — Plot handles
|
|
43
|
+
│ deduplication automatically)
|
|
43
44
|
│ Use Note.key for different note types:
|
|
44
45
|
│ - "description" for main content
|
|
45
46
|
│ - "metadata" for status/priority/assignee
|
|
@@ -71,11 +72,9 @@ New event/task/conversation?
|
|
|
71
72
|
import {
|
|
72
73
|
type Thread,
|
|
73
74
|
type NewThreadWithNotes,
|
|
74
|
-
type
|
|
75
|
-
type Priority,
|
|
75
|
+
type Note,
|
|
76
76
|
type ToolBuilder,
|
|
77
77
|
Twist,
|
|
78
|
-
ThreadType,
|
|
79
78
|
} from "@plotday/twister";
|
|
80
79
|
import { ThreadAccess, Plot } from "@plotday/twister/tools/plot";
|
|
81
80
|
// Import your sources or tools as needed
|
|
@@ -89,7 +88,7 @@ export default class MyTwist extends Twist<MyTwist> {
|
|
|
89
88
|
};
|
|
90
89
|
}
|
|
91
90
|
|
|
92
|
-
async activate(
|
|
91
|
+
async activate() {
|
|
93
92
|
// Auth and resource selection handled in the twist edit modal.
|
|
94
93
|
}
|
|
95
94
|
}
|
|
@@ -119,11 +118,11 @@ For complete API documentation of built-in tools including all methods, types, a
|
|
|
119
118
|
|
|
120
119
|
**Quick reference - Available tools:**
|
|
121
120
|
|
|
122
|
-
- `@plotday/twister/tools/plot` - Core data layer (create/update
|
|
121
|
+
- `@plotday/twister/tools/plot` - Core data layer (create/update threads, notes, focuses, contacts)
|
|
123
122
|
- `@plotday/twister/tools/ai` - LLM integration (text generation, structured output, reasoning)
|
|
124
123
|
- Use ModelPreferences to specify `speed` (fast/balanced/capable) and `cost` (low/medium/high)
|
|
125
124
|
- `@plotday/twister/tools/store` - Persistent key-value storage (also via `this.set()`, `this.get()`)
|
|
126
|
-
- `@plotday/twister/tools/tasks` - Queue batched work (also via `this.
|
|
125
|
+
- `@plotday/twister/tools/tasks` - Queue batched work (also via `this.runTask()`)
|
|
127
126
|
- `@plotday/twister/tools/callbacks` - Persistent function references (also via `this.callback()`)
|
|
128
127
|
- `@plotday/twister/tools/integrations` - OAuth2 authentication flows
|
|
129
128
|
- `@plotday/twister/tools/network` - HTTP access permissions and webhook management
|
|
@@ -133,25 +132,24 @@ For complete API documentation of built-in tools including all methods, types, a
|
|
|
133
132
|
|
|
134
133
|
## Lifecycle Methods
|
|
135
134
|
|
|
136
|
-
### activate(
|
|
135
|
+
### activate(context?: { actor: Actor })
|
|
137
136
|
|
|
138
|
-
Called when the twist is
|
|
137
|
+
Called when the twist is installed by a user. Auth and resource selection are handled automatically via the twist edit modal when using external tools with Integrations.
|
|
139
138
|
|
|
140
139
|
Most twists have an empty or minimal `activate()`:
|
|
141
140
|
|
|
142
141
|
```typescript
|
|
143
|
-
async activate(
|
|
142
|
+
async activate() {
|
|
144
143
|
// Auth and resource selection are handled in the twist edit modal.
|
|
145
144
|
// Only add custom initialization here if needed.
|
|
146
145
|
}
|
|
147
146
|
```
|
|
148
147
|
|
|
149
|
-
**Store
|
|
148
|
+
**Store Setup Thread for Later (optional):**
|
|
150
149
|
|
|
151
150
|
```typescript
|
|
152
|
-
async activate(
|
|
151
|
+
async activate() {
|
|
153
152
|
const threadId = await this.tools.plot.createThread({
|
|
154
|
-
type: ThreadType.Note,
|
|
155
153
|
title: "Setup complete",
|
|
156
154
|
notes: [{
|
|
157
155
|
content: "Your twist is ready. Threads will appear as they sync.",
|
|
@@ -161,9 +159,9 @@ async activate(_priority: Pick<Priority, "id">) {
|
|
|
161
159
|
}
|
|
162
160
|
```
|
|
163
161
|
|
|
164
|
-
### Event Callbacks (
|
|
162
|
+
### Event Callbacks (lifecycle overrides)
|
|
165
163
|
|
|
166
|
-
Twists respond to events
|
|
164
|
+
Twists respond to events by overriding lifecycle methods inherited from the `Twist` base class. No registration is needed — declare Plot access in `build()` and the runtime routes events for threads your twist created:
|
|
167
165
|
|
|
168
166
|
**React to thread changes (for two-way sync):**
|
|
169
167
|
|
|
@@ -171,20 +169,19 @@ Twists respond to events through callbacks declared in `build()`:
|
|
|
171
169
|
plot: build(Plot, {
|
|
172
170
|
thread: {
|
|
173
171
|
access: ThreadAccess.Create,
|
|
174
|
-
updated: this.onThreadUpdated,
|
|
175
|
-
},
|
|
176
|
-
note: {
|
|
177
|
-
created: this.onNoteCreated,
|
|
178
172
|
},
|
|
179
173
|
}),
|
|
180
174
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
175
|
+
// Called when a thread created by this twist is updated
|
|
176
|
+
async onThreadUpdated(thread: Thread, changes: { tagsAdded: Record<Tag, ActorId[]>; tagsRemoved: Record<Tag, ActorId[]> }): Promise<void> {
|
|
177
|
+
// Push the change to the external system
|
|
178
|
+
await externalApi.updateItem(thread.meta?.externalId, { title: thread.title });
|
|
184
179
|
}
|
|
185
180
|
|
|
181
|
+
// Called when a note is created on a thread created by this twist.
|
|
182
|
+
// Notes created by the twist itself are filtered out automatically.
|
|
186
183
|
async onNoteCreated(note: Note, thread: Thread): Promise<NoteWriteBackResult | void> {
|
|
187
|
-
if (note.author.type === ActorType.Twist) return; // Prevent loops
|
|
184
|
+
if (note.author.type === ActorType.Twist) return; // Prevent loops with other twists
|
|
188
185
|
// Sync note to external service as a comment. Return the external
|
|
189
186
|
// system's id + stored content so the runtime can set note.key AND
|
|
190
187
|
// record a sync baseline that preserves Plot's content on round-trip.
|
|
@@ -210,6 +207,8 @@ plot: build(Plot, {
|
|
|
210
207
|
}),
|
|
211
208
|
```
|
|
212
209
|
|
|
210
|
+
For a fully conversational twist, set `note.handler` instead of `intents` — every mention is then routed directly to that one method (`(note: Note) => Promise<void>`), skipping intent matching. `handler` and `intents` are mutually exclusive.
|
|
211
|
+
|
|
213
212
|
**Default mention on replies:**
|
|
214
213
|
|
|
215
214
|
When your twist processes replies (two-way comment sync or conversational AI), set `defaultMention: true` so the user doesn't have to manually re-mention your twist on every note:
|
|
@@ -218,15 +217,11 @@ When your twist processes replies (two-way comment sync or conversational AI), s
|
|
|
218
217
|
- `note.defaultMention` — Auto-mention on follow-up notes in threads where your twist was @-mentioned (e.g., conversational agents)
|
|
219
218
|
|
|
220
219
|
```typescript
|
|
221
|
-
// Connector with two-way comment sync
|
|
220
|
+
// Connector with two-way comment sync (override onThreadUpdated / onNoteCreated)
|
|
222
221
|
plot: build(Plot, {
|
|
223
222
|
thread: {
|
|
224
223
|
access: ThreadAccess.Create,
|
|
225
224
|
defaultMention: true, // Users replying to synced threads will mention this twist by default
|
|
226
|
-
updated: this.onThreadUpdated,
|
|
227
|
-
},
|
|
228
|
-
note: {
|
|
229
|
-
created: this.onNoteCreated,
|
|
230
225
|
},
|
|
231
226
|
}),
|
|
232
227
|
|
|
@@ -256,8 +251,8 @@ const urlAction: Action = {
|
|
|
256
251
|
url: "https://example.com",
|
|
257
252
|
};
|
|
258
253
|
|
|
259
|
-
// Callback action (uses Callbacks tool — use
|
|
260
|
-
const token = await this.
|
|
254
|
+
// Callback action (uses Callbacks tool — use actionCallback, not callback)
|
|
255
|
+
const token = await this.actionCallback(this.onActionClicked, "context");
|
|
261
256
|
const callbackAction: Action = {
|
|
262
257
|
title: "Click me",
|
|
263
258
|
type: ActionType.callback,
|
|
@@ -266,7 +261,6 @@ const callbackAction: Action = {
|
|
|
266
261
|
|
|
267
262
|
// Add to thread note
|
|
268
263
|
await this.tools.plot.createThread({
|
|
269
|
-
type: ThreadType.Note,
|
|
270
264
|
title: "Task with actions",
|
|
271
265
|
notes: [
|
|
272
266
|
{
|
|
@@ -284,27 +278,40 @@ async onActionClicked(action: Action, context: string): Promise<void> {
|
|
|
284
278
|
|
|
285
279
|
## Authentication Pattern
|
|
286
280
|
|
|
287
|
-
Auth is handled automatically via the Integrations tool.
|
|
281
|
+
Auth is handled automatically via the Integrations tool. Connectors (twists that extend `Connector`) declare their OAuth provider and scopes as class properties, and users connect in the twist edit modal. **You do not need to create auth activities manually.**
|
|
288
282
|
|
|
289
283
|
```typescript
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
284
|
+
import { Connector, type ToolBuilder } from "@plotday/twister";
|
|
285
|
+
import {
|
|
286
|
+
AuthProvider,
|
|
287
|
+
type AuthToken,
|
|
288
|
+
type Authorization,
|
|
289
|
+
type Channel,
|
|
290
|
+
Integrations,
|
|
291
|
+
} from "@plotday/twister/tools/integrations";
|
|
292
|
+
|
|
293
|
+
export default class MyConnector extends Connector<MyConnector> {
|
|
294
|
+
readonly provider = AuthProvider.Google;
|
|
295
|
+
readonly scopes = ["https://www.googleapis.com/auth/calendar"];
|
|
296
|
+
|
|
297
|
+
build(build: ToolBuilder) {
|
|
298
|
+
return {
|
|
299
|
+
integrations: build(Integrations),
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// List available resources after auth
|
|
304
|
+
async getChannels(auth: Authorization, token: AuthToken): Promise<Channel[]> { ... }
|
|
305
|
+
|
|
306
|
+
// User enabled a resource
|
|
307
|
+
async onChannelEnabled(channel: Channel): Promise<void> { ... }
|
|
308
|
+
|
|
309
|
+
// User disabled a resource
|
|
310
|
+
async onChannelDisabled(channel: Channel): Promise<void> { ... }
|
|
304
311
|
}
|
|
305
312
|
|
|
306
313
|
// Get a token for API calls:
|
|
307
|
-
const token = await this.tools.integrations.get(
|
|
314
|
+
const token = await this.tools.integrations.get(channelId);
|
|
308
315
|
if (!token) throw new Error("No auth token available");
|
|
309
316
|
const client = new ApiClient({ accessToken: token.token });
|
|
310
317
|
```
|
|
@@ -321,28 +328,33 @@ is not dispatched.
|
|
|
321
328
|
|
|
322
329
|
### Upsert via Source/Key (Strategy 2)
|
|
323
330
|
|
|
324
|
-
Use source/key for automatic upserts:
|
|
331
|
+
Use source/key for automatic upserts. Connectors save external items as links — `source` is the upsert key:
|
|
325
332
|
|
|
326
333
|
```typescript
|
|
327
334
|
async handleEvent(event: ExternalEvent): Promise<void> {
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
335
|
+
// Create or update — Plot handles deduplication automatically
|
|
336
|
+
await this.tools.integrations.saveLink({
|
|
337
|
+
source: event.htmlLink, // Canonical URL/ID for automatic deduplication
|
|
338
|
+
type: "event",
|
|
331
339
|
title: event.summary || "(No title)",
|
|
332
|
-
notes:
|
|
333
|
-
|
|
340
|
+
notes: event.description
|
|
341
|
+
? [{
|
|
342
|
+
key: "description", // This key enables note-level upserts
|
|
343
|
+
content: event.description,
|
|
344
|
+
}]
|
|
345
|
+
: [],
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
```
|
|
334
349
|
|
|
335
|
-
|
|
336
|
-
thread.notes.push({
|
|
337
|
-
thread: { source: event.htmlLink },
|
|
338
|
-
key: "description", // This key enables note-level upserts
|
|
339
|
-
content: event.description,
|
|
340
|
-
});
|
|
341
|
-
}
|
|
350
|
+
`saveLink()` is available to Connectors only. A regular twist adding notes to an already-synced thread references it by source instead:
|
|
342
351
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
}
|
|
352
|
+
```typescript
|
|
353
|
+
await this.tools.plot.createNote({
|
|
354
|
+
thread: { source: event.htmlLink }, // Reference the synced thread
|
|
355
|
+
key: "summary",
|
|
356
|
+
content: "...",
|
|
357
|
+
});
|
|
346
358
|
```
|
|
347
359
|
|
|
348
360
|
### Advanced: Generate and Store IDs (Strategy 3)
|
|
@@ -391,10 +403,10 @@ async handleEventAdvanced(
|
|
|
391
403
|
|
|
392
404
|
## Resource Selection
|
|
393
405
|
|
|
394
|
-
Resource selection (calendars, projects, channels) is handled automatically in the twist edit modal via the Integrations tool. Users see a list of available resources returned by your
|
|
406
|
+
Resource selection (calendars, projects, channels) is handled automatically in the twist edit modal via the Integrations tool. Users see a list of available resources returned by your connector's `getChannels()` method and toggle them on/off. You do **not** need to build custom selection UI.
|
|
395
407
|
|
|
396
408
|
```typescript
|
|
397
|
-
// In your
|
|
409
|
+
// In your connector:
|
|
398
410
|
async getChannels(_auth: Authorization, token: AuthToken): Promise<Channel[]> {
|
|
399
411
|
const client = new ApiClient({ accessToken: token.token });
|
|
400
412
|
const calendars = await client.listCalendars();
|
|
@@ -447,13 +459,11 @@ async syncBatch(resourceId: string): Promise<void> {
|
|
|
447
459
|
// Process results using source/key pattern (automatic upserts, no manual tracking)
|
|
448
460
|
// If each item makes ~10 requests, keep batch size ≤ 100 items to stay under limit
|
|
449
461
|
for (const item of result.items) {
|
|
450
|
-
|
|
451
|
-
await this.tools.plot.createThread({
|
|
462
|
+
await this.tools.integrations.saveLink({
|
|
452
463
|
source: item.url, // Use item's canonical URL for automatic deduplication
|
|
453
|
-
type:
|
|
464
|
+
type: "item",
|
|
454
465
|
title: item.title,
|
|
455
466
|
notes: [{
|
|
456
|
-
activity: { source: item.url },
|
|
457
467
|
key: "description", // Use key for upsertable notes
|
|
458
468
|
content: item.description,
|
|
459
469
|
}],
|
|
@@ -461,6 +471,8 @@ async syncBatch(resourceId: string): Promise<void> {
|
|
|
461
471
|
...(state.initialSync ? { archived: false } : {}), // unarchive on initial only
|
|
462
472
|
});
|
|
463
473
|
}
|
|
474
|
+
// Tip: integrations.saveLinks([...]) saves a whole page in one call,
|
|
475
|
+
// which counts as a single request against the execution budget.
|
|
464
476
|
|
|
465
477
|
if (result.nextPageToken) {
|
|
466
478
|
// Update state in Store for next batch
|
|
@@ -480,7 +492,6 @@ async syncBatch(resourceId: string): Promise<void> {
|
|
|
480
492
|
|
|
481
493
|
// Optionally notify user of completion
|
|
482
494
|
await this.tools.plot.createThread({
|
|
483
|
-
type: ThreadType.Note,
|
|
484
495
|
title: "Sync complete",
|
|
485
496
|
notes: [
|
|
486
497
|
{
|
|
@@ -507,25 +518,25 @@ All sync-based tools should distinguish between initial sync (first import) and
|
|
|
507
518
|
|
|
508
519
|
**Example:**
|
|
509
520
|
```typescript
|
|
510
|
-
|
|
511
|
-
type: ThreadType.Event,
|
|
521
|
+
await this.tools.integrations.saveLink({
|
|
512
522
|
source: event.url,
|
|
523
|
+
type: "event",
|
|
513
524
|
title: event.title,
|
|
514
525
|
...(initialSync ? { unread: false } : {}), // false for initial, omit for incremental
|
|
515
526
|
...(initialSync ? { archived: false } : {}), // unarchive on initial only
|
|
516
|
-
};
|
|
527
|
+
});
|
|
517
528
|
```
|
|
518
529
|
|
|
519
530
|
**Why this matters:**
|
|
520
|
-
- **Initial sync**:
|
|
521
|
-
- **Incremental sync**:
|
|
522
|
-
- **Reinstall**: Acts as initial sync, so previously archived
|
|
531
|
+
- **Initial sync**: Threads are unarchived and marked as read for all users, preventing spam from bulk historical imports
|
|
532
|
+
- **Incremental sync**: Threads are auto-marked read for the author (twist owner), unread for everyone else. Archived state is preserved
|
|
533
|
+
- **Reinstall**: Acts as initial sync, so previously archived threads are unarchived (fresh start)
|
|
523
534
|
|
|
524
535
|
### Two-Way Sync: Avoiding Race Conditions
|
|
525
536
|
|
|
526
537
|
When implementing two-way sync where items created in Plot are pushed to an external system (e.g. Notes becoming comments), a race condition can occur: the external system may send a webhook for the newly created item before you've updated the Thread/Note with the external key. The webhook handler won't find the item by external key and may create a duplicate.
|
|
527
538
|
|
|
528
|
-
**Solution:** Embed the Plot `Thread.id` / `Note.id` in the external item's metadata when creating it, and update `
|
|
539
|
+
**Solution:** Embed the Plot `Thread.id` / `Note.id` in the external item's metadata when creating it, and update `Note.key` after creation. When processing webhooks, check for the Plot ID in metadata first.
|
|
529
540
|
|
|
530
541
|
```typescript
|
|
531
542
|
async pushNoteAsComment(note: Note, externalItemId: string): Promise<void> {
|
|
@@ -546,15 +557,21 @@ async pushNoteAsComment(note: Note, externalItemId: string): Promise<void> {
|
|
|
546
557
|
async onWebhook(payload: WebhookPayload): Promise<void> {
|
|
547
558
|
const comment = payload.comment;
|
|
548
559
|
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
:
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
560
|
+
if (comment.metadata?.plotNoteId) {
|
|
561
|
+
// Plot ID in metadata: the note originated in Plot (handles the race)
|
|
562
|
+
await this.tools.plot.updateNote({
|
|
563
|
+
id: comment.metadata.plotNoteId,
|
|
564
|
+
key: `comment-${comment.id}`,
|
|
565
|
+
content: comment.body,
|
|
566
|
+
});
|
|
567
|
+
} else {
|
|
568
|
+
// Otherwise upsert by thread source and note key
|
|
569
|
+
await this.tools.plot.createNote({
|
|
570
|
+
thread: { source: payload.itemUrl },
|
|
571
|
+
key: `comment-${comment.id}`,
|
|
572
|
+
content: comment.body,
|
|
573
|
+
});
|
|
574
|
+
}
|
|
558
575
|
}
|
|
559
576
|
```
|
|
560
577
|
|
|
@@ -569,7 +586,6 @@ try {
|
|
|
569
586
|
console.error("Operation failed:", error);
|
|
570
587
|
|
|
571
588
|
await this.tools.plot.createThread({
|
|
572
|
-
type: ThreadType.Note,
|
|
573
589
|
title: "Operation failed",
|
|
574
590
|
notes: [
|
|
575
591
|
{
|
|
@@ -583,15 +599,15 @@ try {
|
|
|
583
599
|
## Common Pitfalls
|
|
584
600
|
|
|
585
601
|
- **Don't use instance variables for state** - Anything stored in memory is lost after function execution. Always use the Store tool for data that needs to persist.
|
|
586
|
-
- **Processing self-created
|
|
602
|
+
- **Processing self-created content** - `onNoteCreated` filters out notes created by the twist itself, but still guard against reacting to other automated actors (`note.author.type === ActorType.Twist`) to avoid loops.
|
|
587
603
|
- **Always create Threads with Notes** - See "Understanding Threads and Notes" section above for the thread/message pattern and decision tree.
|
|
588
|
-
- **
|
|
589
|
-
- **Use
|
|
604
|
+
- **Thread `type` is optional** - It's a display sub-type (`"action"`, `"notes"`, `"idea"`, `"goal"`, `"decision"`, `"discussion"`, `"announcement"`, `"ask"`). Omit it for the default; use `"action"` for tasks.
|
|
605
|
+
- **Use source/key for automatic upserts (Recommended)** - Save external items as links keyed by their canonical URL/ID (connectors: `integrations.saveLink()`) for automatic deduplication. Only use UUID generation and storage for advanced cases (see SYNC_STRATEGIES.md).
|
|
590
606
|
- **Add Notes to existing Threads** - For source/key pattern, reference threads by source. For UUID pattern, look up stored mappings before creating new Threads. Think thread replies, not new threads.
|
|
591
607
|
- Tools are declared in the `build` method and accessed via `this.tools.toolName` in twist methods.
|
|
592
608
|
- **Don't forget request limits** - Each execution has ~1000 requests (HTTP requests, tool calls). Break long loops into batches with `this.runTask()` to get fresh request limits. Calculate requests per item to determine safe batch size (e.g., if each item needs ~10 requests, batch size = ~100 items).
|
|
593
609
|
- **Always use Callbacks tool for persistent references** - Direct function references don't survive worker restarts.
|
|
594
|
-
- **
|
|
610
|
+
- **Don't cache auth tokens** - Fetch tokens with `this.tools.integrations.get(channelId)` when needed; the Integrations tool manages storage and refresh.
|
|
595
611
|
- **Clean up callbacks and stored state** - Delete callbacks and Store entries when no longer needed.
|
|
596
612
|
- **Handle missing auth gracefully** - Check for stored auth before operations.
|
|
597
613
|
- **CRITICAL: Maintain callback backward compatibility** - All callbacks (webhooks, tasks, batch operations) automatically upgrade to new twist versions. You **must** maintain backward compatibility in callback method signatures. Only add optional parameters at the end, never remove or reorder parameters. For breaking changes, implement migration logic in the `upgrade()` lifecycle method to recreate affected callbacks.
|
|
@@ -5,9 +5,6 @@ A Plot twist that [describe what your twist does].
|
|
|
5
5
|
## Quick Start
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
# Install dependencies
|
|
9
|
-
{{packageManager}} install
|
|
10
|
-
|
|
11
8
|
# Lint your code
|
|
12
9
|
{{packageManager}} lint
|
|
13
10
|
|
|
@@ -15,6 +12,8 @@ A Plot twist that [describe what your twist does].
|
|
|
15
12
|
{{packageManager}} deploy
|
|
16
13
|
```
|
|
17
14
|
|
|
15
|
+
Dependencies are installed automatically when the project is created.
|
|
16
|
+
|
|
18
17
|
## Development
|
|
19
18
|
|
|
20
19
|
### Project Structure
|
|
@@ -25,26 +24,28 @@ A Plot twist that [describe what your twist does].
|
|
|
25
24
|
│ └── index.ts # Main twist implementation
|
|
26
25
|
├── package.json # twist metadata and dependencies
|
|
27
26
|
├── tsconfig.json # TypeScript configuration
|
|
27
|
+
├── AGENTS.md # Implementation guide for AI assistants
|
|
28
|
+
├── CLAUDE.md # Claude Code entry point (includes AGENTS.md)
|
|
28
29
|
└── README.md # This file
|
|
29
30
|
```
|
|
30
31
|
|
|
31
32
|
### twist Lifecycle
|
|
32
33
|
|
|
33
|
-
Your twist
|
|
34
|
+
Your twist overrides lifecycle methods from the `Twist` base class:
|
|
34
35
|
|
|
35
|
-
#### `activate(
|
|
36
|
+
#### `activate(context?: { actor: Actor })`
|
|
36
37
|
|
|
37
|
-
Called when the twist is
|
|
38
|
-
-
|
|
39
|
-
- Create initial setup activities
|
|
38
|
+
Called when the twist is installed by a user. Authentication and resource selection are handled automatically in the twist edit modal, so this is where you typically:
|
|
39
|
+
- Seed initial threads
|
|
40
40
|
- Initialize twist state
|
|
41
41
|
|
|
42
|
-
#### `
|
|
42
|
+
#### `onThreadUpdated(thread, changes)` / `onNoteCreated(note, thread)`
|
|
43
|
+
|
|
44
|
+
Called when a thread created by this twist is updated, or a note is added to one. Use these to:
|
|
45
|
+
- Implement two-way sync with external systems
|
|
46
|
+
- React to user replies and tag changes
|
|
43
47
|
|
|
44
|
-
|
|
45
|
-
- Process incoming activities
|
|
46
|
-
- Create new activities based on external events
|
|
47
|
-
- Update existing activities
|
|
48
|
+
Other hooks include `upgrade()` (new version deployed) and `deactivate()` (twist uninstalled).
|
|
48
49
|
|
|
49
50
|
### Using Tools
|
|
50
51
|
|
|
@@ -61,24 +62,25 @@ build(build: ToolBuilder) {
|
|
|
61
62
|
|
|
62
63
|
#### Built-in Tools
|
|
63
64
|
|
|
64
|
-
- **Plot**: Create
|
|
65
|
+
- **Plot**: Create and update threads, notes, and focuses
|
|
65
66
|
- **Store**: Persist data across twist invocations
|
|
66
|
-
- **Integrations**:
|
|
67
|
+
- **Integrations**: OAuth authentication and channel management for external services
|
|
67
68
|
- **Tasks**: Queue background tasks and batch operations
|
|
68
69
|
- **Callbacks**: Create persistent function references for webhooks
|
|
69
70
|
- **Network**: HTTP access permissions and webhook management
|
|
71
|
+
- **AI**: LLM text generation and structured output
|
|
70
72
|
|
|
71
73
|
#### Connectors
|
|
72
74
|
|
|
73
75
|
External service integrations (Google Calendar, Slack, Linear, etc.) are built as Connectors. See the [Building Connectors](https://twist.plot.day/documents/Building_Connectors.html) guide.
|
|
74
76
|
|
|
75
|
-
###
|
|
77
|
+
### Thread Types
|
|
76
78
|
|
|
77
|
-
|
|
79
|
+
A thread's `type` is an optional display sub-type: `"action"`, `"notes"`, `"idea"`, `"goal"`, `"decision"`, `"discussion"`, `"announcement"`, or `"ask"`.
|
|
78
80
|
|
|
79
|
-
-
|
|
80
|
-
-
|
|
81
|
-
-
|
|
81
|
+
- Omit it for the default (`"notes"` in private focuses, `"discussion"` in shared ones)
|
|
82
|
+
- Use `"action"` for tasks
|
|
83
|
+
- Scheduled events are threads with `schedules`
|
|
82
84
|
|
|
83
85
|
### State Management
|
|
84
86
|
|
|
@@ -99,8 +101,9 @@ const token = await this.get<string>("sync_token");
|
|
|
99
101
|
**Important**: All twist and tool functions are executed in a sandboxed, ephemeral environment with limited resources:
|
|
100
102
|
|
|
101
103
|
- **Memory is temporary**: Anything stored in memory (e.g. as a variable in the twist/tool object) is lost after the function completes. Use the Store tool instead. Only use memory for temporary caching.
|
|
102
|
-
- **Limited
|
|
103
|
-
- **
|
|
104
|
+
- **Limited requests per execution**: Each execution has ~1000 requests (HTTP requests, tool calls, database operations)
|
|
105
|
+
- **Limited CPU time**: Each execution has limited CPU time (typically ~60 seconds) and memory (128MB)
|
|
106
|
+
- **Use the Tasks tool**: Queue separate chunks of work with `this.runTask(callback)` — each task runs in a new execution with a fresh request limit
|
|
104
107
|
- **Break long operations**: Split large operations into smaller batches that can be processed independently
|
|
105
108
|
- **Store intermediate state**: Use the Store tool to persist state between batches
|
|
106
109
|
- **Examples**: Syncing large datasets, processing many API calls, or performing batch operations
|
|
@@ -115,12 +118,12 @@ async startSync(calendarId: string): Promise<void> {
|
|
|
115
118
|
batchNumber: 1,
|
|
116
119
|
});
|
|
117
120
|
|
|
118
|
-
// Queue first batch
|
|
119
|
-
const callback = await this.callback(
|
|
120
|
-
await this.
|
|
121
|
+
// Queue first batch as a task (new execution, fresh request limit)
|
|
122
|
+
const callback = await this.callback(this.syncBatch, calendarId);
|
|
123
|
+
await this.runTask(callback);
|
|
121
124
|
}
|
|
122
125
|
|
|
123
|
-
async syncBatch(
|
|
126
|
+
async syncBatch(calendarId: string): Promise<void> {
|
|
124
127
|
// Load state from Store
|
|
125
128
|
const state = await this.get(`sync_state_${calendarId}`);
|
|
126
129
|
|
|
@@ -129,16 +132,16 @@ async syncBatch(args: any, context: { calendarId: string }): Promise<void> {
|
|
|
129
132
|
|
|
130
133
|
if (result.hasMore) {
|
|
131
134
|
// Update state and queue next batch
|
|
132
|
-
await this.set(`sync_state_${
|
|
135
|
+
await this.set(`sync_state_${calendarId}`, {
|
|
133
136
|
nextPageToken: result.nextPageToken,
|
|
134
137
|
batchNumber: state.batchNumber + 1,
|
|
135
138
|
});
|
|
136
139
|
|
|
137
|
-
const nextCallback = await this.callback(
|
|
138
|
-
await this.
|
|
140
|
+
const nextCallback = await this.callback(this.syncBatch, calendarId);
|
|
141
|
+
await this.runTask(nextCallback);
|
|
139
142
|
} else {
|
|
140
143
|
// Cleanup when done
|
|
141
|
-
await this.clear(`sync_state_${
|
|
144
|
+
await this.clear(`sync_state_${calendarId}`);
|
|
142
145
|
}
|
|
143
146
|
}
|
|
144
147
|
```
|
|
@@ -157,9 +160,9 @@ Test your twist locally before deploying:
|
|
|
157
160
|
|
|
158
161
|
## Resources
|
|
159
162
|
|
|
160
|
-
- [Plot
|
|
161
|
-
- [
|
|
162
|
-
- [Tool
|
|
163
|
+
- [Plot Twist Creator Documentation](https://twist.plot.day)
|
|
164
|
+
- [Connector Examples](https://github.com/plotday/plot/tree/main/connectors)
|
|
165
|
+
- [Tool Type Definitions](https://github.com/plotday/plot/tree/main/twister/src/tools)
|
|
163
166
|
|
|
164
167
|
## Support
|
|
165
168
|
|
package/dist/connector.d.ts
CHANGED
|
@@ -208,8 +208,8 @@ export type ScopeConfig = {
|
|
|
208
208
|
* type: "issue",
|
|
209
209
|
* label: "Issue",
|
|
210
210
|
* statuses: [
|
|
211
|
-
* { status: "open", label: "Open" },
|
|
212
|
-
* { status: "done", label: "Done" },
|
|
211
|
+
* { status: "open", label: "Open", icon: "todo" },
|
|
212
|
+
* { status: "done", label: "Done", icon: "done", done: true },
|
|
213
213
|
* ],
|
|
214
214
|
* }];
|
|
215
215
|
*
|
|
@@ -481,17 +481,24 @@ export declare abstract class Connector<TSelf> extends Twist<TSelf> {
|
|
|
481
481
|
*/
|
|
482
482
|
onThreadRead(thread: Thread, actor: Actor, unread: boolean): Promise<void>;
|
|
483
483
|
/**
|
|
484
|
-
* Called when a user
|
|
485
|
-
*
|
|
486
|
-
*
|
|
487
|
-
*
|
|
488
|
-
*
|
|
489
|
-
* `
|
|
490
|
-
*
|
|
491
|
-
*
|
|
492
|
-
*
|
|
493
|
-
*
|
|
494
|
-
*
|
|
484
|
+
* Called when a user changes the **thread-level sharing** of a thread owned
|
|
485
|
+
* by this connector — adding or removing a contact, or (for connectors with
|
|
486
|
+
* roles) changing a contact's role. Override on connectors whose external
|
|
487
|
+
* source can reflect that membership change, e.g. a group DM / multi-party
|
|
488
|
+
* chat (`LinkTypeConfig.sharingModel: "thread"`) or an email's To/Cc/Bcc
|
|
489
|
+
* recipients (`sharingModel: "message"`). Connectors backed by an immutable
|
|
490
|
+
* roster (most group DMs today) or by channel-level membership
|
|
491
|
+
* (`sharingModel: "channel"`) leave this as the default no-op.
|
|
492
|
+
*
|
|
493
|
+
* `role`/`from`/`to` are **null** for connectors without roles (group DMs
|
|
494
|
+
* have no roles); they carry a `contactRoles` id only for connectors that
|
|
495
|
+
* declare roles (e.g. email `to`/`cc`/`bcc`).
|
|
496
|
+
*
|
|
497
|
+
* The dispatch fires after Plot has persisted the change. A connector may
|
|
498
|
+
* reflect it actively (e.g. add/remove a participant on the external chat)
|
|
499
|
+
* or passively on the next outbound note (e.g. building To/Cc/Bcc headers
|
|
500
|
+
* from the current `thread.contacts` × `thread.contactMeta`) — this callback
|
|
501
|
+
* is not the right place to send a standalone notification.
|
|
495
502
|
*
|
|
496
503
|
* @param thread - The thread whose contacts changed
|
|
497
504
|
* @param changes - The added/removed contacts and any role transitions on existing contacts
|
|
@@ -499,16 +506,16 @@ export declare abstract class Connector<TSelf> extends Twist<TSelf> {
|
|
|
499
506
|
onContactsChanged(thread: Thread, changes: {
|
|
500
507
|
added: Array<{
|
|
501
508
|
contact: Contact;
|
|
502
|
-
role: string;
|
|
509
|
+
role: string | null;
|
|
503
510
|
}>;
|
|
504
511
|
removed: Array<{
|
|
505
512
|
contact: Contact;
|
|
506
|
-
role: string;
|
|
513
|
+
role: string | null;
|
|
507
514
|
}>;
|
|
508
515
|
changed: Array<{
|
|
509
516
|
contact: Contact;
|
|
510
|
-
from: string;
|
|
511
|
-
to: string;
|
|
517
|
+
from: string | null;
|
|
518
|
+
to: string | null;
|
|
512
519
|
}>;
|
|
513
520
|
}): Promise<void>;
|
|
514
521
|
/**
|