@plotday/twister 0.36.0 → 0.38.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 +4 -2
- package/bin/commands/create.js +37 -3
- package/bin/commands/create.js.map +1 -1
- package/bin/commands/deploy.js +4 -0
- package/bin/commands/deploy.js.map +1 -1
- package/bin/index.js +1 -0
- package/bin/index.js.map +1 -1
- package/bin/templates/AGENTS.template.md +101 -189
- package/bin/templates/README.template.md +2 -23
- package/cli/templates/AGENTS.template.md +101 -189
- package/cli/templates/README.template.md +2 -23
- 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.Options.html +1 -1
- package/dist/docs/classes/index.Source.html +200 -0
- package/dist/docs/classes/tool.ITool.html +1 -1
- package/dist/docs/classes/tool.Tool.html +21 -21
- package/dist/docs/classes/tools_ai.AI.html +1 -1
- package/dist/docs/classes/tools_callbacks.Callbacks.html +2 -2
- package/dist/docs/classes/tools_integrations.Integrations.html +45 -16
- package/dist/docs/classes/tools_network.Network.html +1 -1
- package/dist/docs/classes/tools_plot.Plot.html +93 -60
- package/dist/docs/classes/tools_store.Store.html +1 -1
- package/dist/docs/classes/tools_tasks.Tasks.html +1 -1
- package/dist/docs/classes/tools_twists.Twists.html +1 -1
- package/dist/docs/classes/twist.Twist.html +42 -10
- package/dist/docs/documents/Building_Sources.html +137 -0
- package/dist/docs/documents/Built-in_Tools.html +11 -2
- package/dist/docs/documents/Core_Concepts.html +5 -10
- package/dist/docs/documents/Getting_Started.html +1 -1
- package/dist/docs/enums/{plot.ActivityLinkType.html → plot.ActionType.html} +10 -8
- package/dist/docs/enums/plot.ActorType.html +7 -7
- 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 -10
- package/dist/docs/enums/tools_integrations.AuthProvider.html +11 -11
- package/dist/docs/enums/tools_plot.ContactAccess.html +3 -3
- package/dist/docs/enums/tools_plot.PriorityAccess.html +3 -3
- package/dist/docs/enums/{tools_plot.ActivityAccess.html → tools_plot.ThreadAccess.html} +6 -6
- package/dist/docs/hierarchy.html +1 -1
- package/dist/docs/index.html +5 -6
- package/dist/docs/media/AGENTS.md +910 -0
- package/dist/docs/media/MULTI_USER_AUTH.md +111 -0
- package/dist/docs/media/SYNC_STRATEGIES.md +7 -7
- package/dist/docs/modules/index.html +1 -1
- package/dist/docs/modules/plot.html +1 -1
- package/dist/docs/modules/tool.html +1 -1
- package/dist/docs/modules/tools_integrations.html +1 -1
- package/dist/docs/modules/tools_plot.html +1 -1
- package/dist/docs/modules.html +1 -1
- package/dist/docs/types/index.NewSchedule.html +33 -0
- package/dist/docs/types/index.NewScheduleContact.html +5 -0
- package/dist/docs/types/index.NewScheduleOccurrence.html +6 -0
- package/dist/docs/types/index.Schedule.html +37 -0
- package/dist/docs/types/index.ScheduleContact.html +5 -0
- package/dist/docs/types/index.ScheduleContactRole.html +1 -0
- package/dist/docs/types/index.ScheduleContactStatus.html +1 -0
- package/dist/docs/types/index.ScheduleOccurrence.html +17 -0
- package/dist/docs/types/index.ScheduleOccurrenceUpdate.html +2 -0
- package/dist/docs/types/plot.Action.html +28 -0
- package/dist/docs/types/plot.Actor.html +6 -6
- package/dist/docs/types/plot.ActorId.html +1 -1
- package/dist/docs/types/plot.ContentType.html +1 -1
- package/dist/docs/types/plot.Link.html +36 -0
- package/dist/docs/types/plot.NewActor.html +1 -1
- package/dist/docs/types/plot.NewContact.html +5 -5
- package/dist/docs/types/plot.NewLink.html +26 -0
- package/dist/docs/types/plot.NewLinkWithNotes.html +7 -0
- package/dist/docs/types/plot.NewNote.html +10 -10
- package/dist/docs/types/plot.NewPriority.html +1 -1
- package/dist/docs/types/plot.NewTags.html +1 -1
- package/dist/docs/types/plot.NewThread.html +27 -0
- package/dist/docs/types/plot.NewThreadWithNotes.html +1 -0
- package/dist/docs/types/plot.Note.html +9 -8
- package/dist/docs/types/plot.NoteUpdate.html +2 -2
- package/dist/docs/types/plot.PickPriorityConfig.html +8 -10
- package/dist/docs/types/plot.Priority.html +6 -6
- package/dist/docs/types/plot.PriorityUpdate.html +1 -1
- package/dist/docs/types/plot.Tags.html +1 -1
- package/dist/docs/types/plot.Thread.html +1 -0
- package/dist/docs/types/plot.ThreadCommon.html +17 -0
- package/dist/docs/types/plot.ThreadFilter.html +2 -0
- package/dist/docs/types/plot.ThreadMeta.html +11 -0
- package/dist/docs/types/plot.ThreadUpdate.html +3 -0
- package/dist/docs/types/plot.ThreadWithNotes.html +1 -0
- package/dist/docs/types/tools_integrations.ArchiveLinkFilter.html +11 -0
- package/dist/docs/types/tools_integrations.AuthToken.html +4 -4
- package/dist/docs/types/tools_integrations.Authorization.html +4 -4
- package/dist/docs/types/tools_integrations.Channel.html +11 -0
- package/dist/docs/types/tools_integrations.LinkTypeConfig.html +17 -0
- package/dist/docs/types/tools_plot.LinkFilter.html +10 -0
- package/dist/docs/types/tools_plot.LinkSearchResult.html +1 -0
- package/dist/docs/types/tools_plot.NoteIntentHandler.html +6 -6
- package/dist/docs/types/tools_plot.NoteSearchResult.html +1 -0
- package/dist/docs/types/tools_plot.SearchOptions.html +7 -0
- package/dist/docs/types/tools_plot.SearchResult.html +1 -0
- package/dist/docs/types/tools_twists.TwistPermissions.html +1 -1
- package/dist/docs/variables/tools_plot.SEARCH_DEFAULT_LIMIT.html +2 -0
- package/dist/docs/variables/tools_plot.SEARCH_MAX_LIMIT.html +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/llm-docs/index.d.ts.map +1 -1
- package/dist/llm-docs/index.js +4 -8
- 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/schedule.d.ts +9 -0
- package/dist/llm-docs/schedule.d.ts.map +1 -0
- package/dist/llm-docs/schedule.js +8 -0
- package/dist/llm-docs/schedule.js.map +1 -0
- package/dist/llm-docs/source.d.ts +9 -0
- package/dist/llm-docs/source.d.ts.map +1 -0
- package/dist/llm-docs/source.js +8 -0
- package/dist/llm-docs/source.js.map +1 -0
- package/dist/llm-docs/tag.d.ts +1 -1
- package/dist/llm-docs/tag.d.ts.map +1 -1
- package/dist/llm-docs/tag.js +1 -1
- package/dist/llm-docs/tag.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/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/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/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/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 +264 -589
- package/dist/plot.d.ts.map +1 -1
- package/dist/plot.js +18 -49
- package/dist/plot.js.map +1 -1
- package/dist/schedule.d.ts +172 -0
- package/dist/schedule.d.ts.map +1 -0
- package/dist/schedule.js +2 -0
- package/dist/schedule.js.map +1 -0
- package/dist/source.d.ts +158 -0
- package/dist/source.d.ts.map +1 -0
- package/dist/source.js +144 -0
- package/dist/source.js.map +1 -0
- package/dist/tag.d.ts +3 -10
- package/dist/tag.d.ts.map +1 -1
- package/dist/tag.js +2 -11
- package/dist/tag.js.map +1 -1
- package/dist/tool.d.ts +1 -15
- package/dist/tool.d.ts.map +1 -1
- package/dist/tool.js.map +1 -1
- package/dist/tools/callbacks.d.ts +1 -1
- package/dist/tools/callbacks.js +1 -1
- package/dist/tools/integrations.d.ts +119 -52
- package/dist/tools/integrations.d.ts.map +1 -1
- package/dist/tools/integrations.js +23 -22
- package/dist/tools/integrations.js.map +1 -1
- package/dist/tools/plot.d.ts +182 -117
- package/dist/tools/plot.d.ts.map +1 -1
- package/dist/tools/plot.js +23 -21
- package/dist/tools/plot.js.map +1 -1
- package/dist/tools/twists.d.ts +1 -1
- package/dist/twist-guide.d.ts +1 -1
- package/dist/twist-guide.d.ts.map +1 -1
- package/dist/twist.d.ts +66 -11
- package/dist/twist.d.ts.map +1 -1
- package/dist/twist.js +79 -10
- package/dist/twist.js.map +1 -1
- package/package.json +11 -41
- package/dist/common/calendar.d.ts +0 -135
- package/dist/common/calendar.d.ts.map +0 -1
- package/dist/common/calendar.js +0 -2
- package/dist/common/calendar.js.map +0 -1
- package/dist/common/documents.d.ts +0 -122
- package/dist/common/documents.d.ts.map +0 -1
- package/dist/common/documents.js +0 -2
- package/dist/common/documents.js.map +0 -1
- package/dist/common/messaging.d.ts +0 -84
- package/dist/common/messaging.d.ts.map +0 -1
- package/dist/common/messaging.js +0 -2
- package/dist/common/messaging.js.map +0 -1
- package/dist/common/projects.d.ts +0 -112
- package/dist/common/projects.d.ts.map +0 -1
- package/dist/common/projects.js +0 -2
- package/dist/common/projects.js.map +0 -1
- package/dist/docs/documents/Building_Custom_Tools.html +0 -215
- package/dist/docs/enums/plot.ActivityKind.html +0 -14
- package/dist/docs/enums/plot.ActivityType.html +0 -10
- package/dist/docs/modules/common_calendar.html +0 -1
- package/dist/docs/types/common_calendar.Calendar.html +0 -13
- package/dist/docs/types/common_calendar.CalendarTool.html +0 -81
- package/dist/docs/types/common_calendar.SyncOptions.html +0 -24
- package/dist/docs/types/plot.Activity.html +0 -2
- package/dist/docs/types/plot.ActivityCommon.html +0 -20
- package/dist/docs/types/plot.ActivityFilter.html +0 -3
- package/dist/docs/types/plot.ActivityLink.html +0 -26
- package/dist/docs/types/plot.ActivityMeta.html +0 -11
- package/dist/docs/types/plot.ActivityOccurrence.html +0 -22
- package/dist/docs/types/plot.ActivityOccurrenceUpdate.html +0 -3
- package/dist/docs/types/plot.ActivityUpdate.html +0 -3
- package/dist/docs/types/plot.ActivityWithNotes.html +0 -1
- package/dist/docs/types/plot.NewActivity.html +0 -81
- package/dist/docs/types/plot.NewActivityOccurrence.html +0 -24
- package/dist/docs/types/plot.NewActivityWithNotes.html +0 -1
- package/dist/docs/types/tool.SyncToolOptions.html +0 -9
- package/dist/docs/types/tools_integrations.IntegrationOptions.html +0 -4
- package/dist/docs/types/tools_integrations.IntegrationProviderConfig.html +0 -13
- package/dist/docs/types/tools_integrations.Syncable.html +0 -9
- package/dist/llm-docs/common/calendar.d.ts +0 -9
- package/dist/llm-docs/common/calendar.d.ts.map +0 -1
- package/dist/llm-docs/common/calendar.js +0 -8
- package/dist/llm-docs/common/calendar.js.map +0 -1
- package/dist/llm-docs/common/documents.d.ts +0 -9
- package/dist/llm-docs/common/documents.d.ts.map +0 -1
- package/dist/llm-docs/common/documents.js +0 -8
- package/dist/llm-docs/common/documents.js.map +0 -1
- package/dist/llm-docs/common/messaging.d.ts +0 -9
- package/dist/llm-docs/common/messaging.d.ts.map +0 -1
- package/dist/llm-docs/common/messaging.js +0 -8
- package/dist/llm-docs/common/messaging.js.map +0 -1
- package/dist/llm-docs/common/projects.d.ts +0 -9
- package/dist/llm-docs/common/projects.d.ts.map +0 -1
- package/dist/llm-docs/common/projects.js +0 -8
- package/dist/llm-docs/common/projects.js.map +0 -1
|
@@ -19,78 +19,72 @@ Plot Twists are TypeScript classes that extend the `Twist` base class. Twists in
|
|
|
19
19
|
- **Store intermediate state**: Use the Store tool to persist state between batches
|
|
20
20
|
- **Examples**: Syncing large datasets, processing many API calls, or performing batch operations
|
|
21
21
|
|
|
22
|
-
## Understanding
|
|
22
|
+
## Understanding Threads and Notes
|
|
23
23
|
|
|
24
|
-
**CRITICAL CONCEPT**:
|
|
24
|
+
**CRITICAL CONCEPT**: A **Thread** represents something done or to be done (a task, event, or conversation), while **Notes** represent the updates and details on that thread.
|
|
25
25
|
|
|
26
|
-
**Think of
|
|
26
|
+
**Think of a Thread as a thread** on a messaging platform, and **Notes as the messages in that thread**.
|
|
27
27
|
|
|
28
28
|
### Key Guidelines
|
|
29
29
|
|
|
30
|
-
1. **Always create
|
|
31
|
-
2. **Add Notes to existing
|
|
32
|
-
3. **Use
|
|
33
|
-
4. **For advanced cases, use generated UUIDs** - Only when you need multiple Plot
|
|
34
|
-
5. **Most
|
|
30
|
+
1. **Always create Threads with an initial Note** - The title is just a summary; detailed content goes in Notes
|
|
31
|
+
2. **Add Notes to existing Threads for updates** - Don't create a new Thread for each related message
|
|
32
|
+
3. **Use Thread.source and Note.key for automatic upserts (Recommended)** - Set Thread.source to the external item's URL for deduplication, and use Note.key for upsertable note content. No manual ID tracking needed.
|
|
33
|
+
4. **For advanced cases, use generated UUIDs** - Only when you need multiple Plot threads per external item (see SYNC_STRATEGIES.md)
|
|
34
|
+
5. **Most Threads should be `ThreadType.Note`** - Use `Action` only for tasks with `done`, use `Event` only for items with `start`/`end`
|
|
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 → Set
|
|
42
|
-
│ Create
|
|
41
|
+
│ └─ Yes → Set Thread.source to the canonical URL/ID
|
|
42
|
+
│ Create Thread (Plot handles deduplication automatically)
|
|
43
43
|
│ Use Note.key for different note types:
|
|
44
44
|
│ - "description" for main content
|
|
45
45
|
│ - "metadata" for status/priority/assignee
|
|
46
46
|
│ - "comment-{id}" for individual comments
|
|
47
47
|
│
|
|
48
|
-
└─ No stable identifier OR need multiple Plot
|
|
48
|
+
└─ No stable identifier OR need multiple Plot threads per external item?
|
|
49
49
|
└─ Use Advanced Pattern (Strategy 3: Generate and Store IDs)
|
|
50
50
|
See SYNC_STRATEGIES.md for details
|
|
51
51
|
```
|
|
52
52
|
|
|
53
53
|
### Advanced Decision Tree (Strategy 3: Generate and Store IDs)
|
|
54
54
|
|
|
55
|
-
Only use when source/key upserts aren't sufficient (e.g., creating multiple
|
|
55
|
+
Only use when source/key upserts aren't sufficient (e.g., creating multiple threads from one external item):
|
|
56
56
|
|
|
57
57
|
```
|
|
58
58
|
New event/task/conversation?
|
|
59
59
|
├─ Yes → Generate UUID with Uuid.Generate()
|
|
60
|
-
│ Create new
|
|
61
|
-
│ Store mapping: external_id →
|
|
60
|
+
│ Create new Thread with that UUID
|
|
61
|
+
│ Store mapping: external_id → thread_uuid
|
|
62
62
|
│
|
|
63
63
|
└─ No (update/reply/comment) → Look up mapping by external_id
|
|
64
|
-
├─ Found → Add Note to existing
|
|
65
|
-
└─ Not found → Create new
|
|
64
|
+
├─ Found → Add Note to existing Thread using stored UUID
|
|
65
|
+
└─ Not found → Create new Thread with UUID + store mapping
|
|
66
66
|
```
|
|
67
67
|
|
|
68
68
|
## Twist Structure Pattern
|
|
69
69
|
|
|
70
70
|
```typescript
|
|
71
71
|
import {
|
|
72
|
-
type
|
|
73
|
-
type
|
|
74
|
-
type
|
|
72
|
+
type Thread,
|
|
73
|
+
type NewThreadWithNotes,
|
|
74
|
+
type ThreadFilter,
|
|
75
75
|
type Priority,
|
|
76
76
|
type ToolBuilder,
|
|
77
77
|
Twist,
|
|
78
|
-
|
|
78
|
+
ThreadType,
|
|
79
79
|
} from "@plotday/twister";
|
|
80
|
-
import {
|
|
81
|
-
// Import your tools
|
|
82
|
-
// import { GoogleCalendar } from "@plotday/tool-google-calendar";
|
|
83
|
-
// import { Linear } from "@plotday/tool-linear";
|
|
80
|
+
import { ThreadAccess, Plot } from "@plotday/twister/tools/plot";
|
|
81
|
+
// Import your sources or tools as needed
|
|
84
82
|
|
|
85
83
|
export default class MyTwist extends Twist<MyTwist> {
|
|
86
84
|
build(build: ToolBuilder) {
|
|
87
85
|
return {
|
|
88
|
-
// myTool: build(MyTool, {
|
|
89
|
-
// onItem: this.handleItem,
|
|
90
|
-
// onSyncableDisabled: this.onSyncableDisabled,
|
|
91
|
-
// }),
|
|
92
86
|
plot: build(Plot, {
|
|
93
|
-
|
|
87
|
+
thread: { access: ThreadAccess.Create },
|
|
94
88
|
}),
|
|
95
89
|
};
|
|
96
90
|
}
|
|
@@ -98,14 +92,6 @@ export default class MyTwist extends Twist<MyTwist> {
|
|
|
98
92
|
async activate(_priority: Pick<Priority, "id">) {
|
|
99
93
|
// Auth and resource selection handled in the twist edit modal.
|
|
100
94
|
}
|
|
101
|
-
|
|
102
|
-
async handleItem(activity: NewActivityWithNotes): Promise<void> {
|
|
103
|
-
await this.tools.plot.createActivity(activity);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
async onSyncableDisabled(filter: ActivityFilter): Promise<void> {
|
|
107
|
-
await this.tools.plot.updateActivity({ match: filter, archived: true });
|
|
108
|
-
}
|
|
109
95
|
}
|
|
110
96
|
```
|
|
111
97
|
|
|
@@ -145,31 +131,6 @@ For complete API documentation of built-in tools including all methods, types, a
|
|
|
145
131
|
|
|
146
132
|
**Critical**: Never use instance variables for state. They are lost after function execution. Always use Store methods.
|
|
147
133
|
|
|
148
|
-
### External Tools (Add to package.json)
|
|
149
|
-
|
|
150
|
-
Add tool dependencies to `package.json`:
|
|
151
|
-
|
|
152
|
-
```json
|
|
153
|
-
{
|
|
154
|
-
"dependencies": {
|
|
155
|
-
"@plotday/twister": "workspace:^",
|
|
156
|
-
"@plotday/tool-google-calendar": "workspace:^"
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
#### Available External Tools
|
|
162
|
-
|
|
163
|
-
- `@plotday/tool-google-calendar`: Google Calendar sync (CalendarTool)
|
|
164
|
-
- `@plotday/tool-outlook-calendar`: Outlook Calendar sync (CalendarTool)
|
|
165
|
-
- `@plotday/tool-google-contacts`: Google Contacts sync (supporting tool)
|
|
166
|
-
- `@plotday/tool-google-drive`: Google Drive sync (DocumentTool)
|
|
167
|
-
- `@plotday/tool-gmail`: Gmail sync (MessagingTool)
|
|
168
|
-
- `@plotday/tool-slack`: Slack sync (MessagingTool)
|
|
169
|
-
- `@plotday/tool-linear`: Linear sync (ProjectTool)
|
|
170
|
-
- `@plotday/tool-jira`: Jira sync (ProjectTool)
|
|
171
|
-
- `@plotday/tool-asana`: Asana sync (ProjectTool)
|
|
172
|
-
|
|
173
134
|
## Lifecycle Methods
|
|
174
135
|
|
|
175
136
|
### activate(priority: Pick<Priority, "id">)
|
|
@@ -185,18 +146,18 @@ async activate(_priority: Pick<Priority, "id">) {
|
|
|
185
146
|
}
|
|
186
147
|
```
|
|
187
148
|
|
|
188
|
-
**Store Parent
|
|
149
|
+
**Store Parent Thread for Later (optional):**
|
|
189
150
|
|
|
190
151
|
```typescript
|
|
191
152
|
async activate(_priority: Pick<Priority, "id">) {
|
|
192
|
-
const
|
|
193
|
-
type:
|
|
153
|
+
const threadId = await this.tools.plot.createThread({
|
|
154
|
+
type: ThreadType.Note,
|
|
194
155
|
title: "Setup complete",
|
|
195
156
|
notes: [{
|
|
196
|
-
content: "Your twist is ready.
|
|
157
|
+
content: "Your twist is ready. Threads will appear as they sync.",
|
|
197
158
|
}],
|
|
198
159
|
});
|
|
199
|
-
await this.set("
|
|
160
|
+
await this.set("setup_thread_id", threadId);
|
|
200
161
|
}
|
|
201
162
|
```
|
|
202
163
|
|
|
@@ -204,44 +165,22 @@ async activate(_priority: Pick<Priority, "id">) {
|
|
|
204
165
|
|
|
205
166
|
Twists respond to events through callbacks declared in `build()`:
|
|
206
167
|
|
|
207
|
-
**
|
|
208
|
-
|
|
209
|
-
```typescript
|
|
210
|
-
build(build: ToolBuilder) {
|
|
211
|
-
return {
|
|
212
|
-
myTool: build(MyTool, {
|
|
213
|
-
onItem: this.handleItem,
|
|
214
|
-
onSyncableDisabled: this.onSyncableDisabled,
|
|
215
|
-
}),
|
|
216
|
-
plot: build(Plot, { activity: { access: ActivityAccess.Create } }),
|
|
217
|
-
};
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
async handleItem(activity: NewActivityWithNotes): Promise<void> {
|
|
221
|
-
await this.tools.plot.createActivity(activity);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
async onSyncableDisabled(filter: ActivityFilter): Promise<void> {
|
|
225
|
-
await this.tools.plot.updateActivity({ match: filter, archived: true });
|
|
226
|
-
}
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
**React to activity changes (for two-way sync):**
|
|
168
|
+
**React to thread changes (for two-way sync):**
|
|
230
169
|
|
|
231
170
|
```typescript
|
|
232
171
|
plot: build(Plot, {
|
|
233
|
-
|
|
234
|
-
access:
|
|
235
|
-
updated: this.
|
|
172
|
+
thread: {
|
|
173
|
+
access: ThreadAccess.Create,
|
|
174
|
+
updated: this.onThreadUpdated,
|
|
236
175
|
},
|
|
237
176
|
note: {
|
|
238
177
|
created: this.onNoteCreated,
|
|
239
178
|
},
|
|
240
179
|
}),
|
|
241
180
|
|
|
242
|
-
async
|
|
243
|
-
const tool = this.
|
|
244
|
-
if (tool?.updateIssue) await tool.updateIssue(
|
|
181
|
+
async onThreadUpdated(thread: Thread, changes: { tagsAdded, tagsRemoved }): Promise<void> {
|
|
182
|
+
const tool = this.getToolForThread(thread);
|
|
183
|
+
if (tool?.updateIssue) await tool.updateIssue(thread);
|
|
245
184
|
}
|
|
246
185
|
|
|
247
186
|
async onNoteCreated(note: Note): Promise<void> {
|
|
@@ -254,7 +193,7 @@ async onNoteCreated(note: Note): Promise<void> {
|
|
|
254
193
|
|
|
255
194
|
```typescript
|
|
256
195
|
plot: build(Plot, {
|
|
257
|
-
|
|
196
|
+
thread: { access: ThreadAccess.Respond },
|
|
258
197
|
note: {
|
|
259
198
|
intents: [{
|
|
260
199
|
description: "Respond to general questions",
|
|
@@ -265,43 +204,43 @@ plot: build(Plot, {
|
|
|
265
204
|
}),
|
|
266
205
|
```
|
|
267
206
|
|
|
268
|
-
##
|
|
207
|
+
## Actions
|
|
269
208
|
|
|
270
|
-
|
|
209
|
+
Actions enable user interaction:
|
|
271
210
|
|
|
272
211
|
```typescript
|
|
273
|
-
import { type
|
|
212
|
+
import { type Action, ActionType } from "@plotday/twister";
|
|
274
213
|
|
|
275
|
-
// External URL
|
|
276
|
-
const
|
|
214
|
+
// External URL action
|
|
215
|
+
const urlAction: Action = {
|
|
277
216
|
title: "Open website",
|
|
278
|
-
type:
|
|
217
|
+
type: ActionType.external,
|
|
279
218
|
url: "https://example.com",
|
|
280
219
|
};
|
|
281
220
|
|
|
282
|
-
// Callback
|
|
283
|
-
const token = await this.linkCallback(this.
|
|
284
|
-
const
|
|
221
|
+
// Callback action (uses Callbacks tool — use linkCallback, not callback)
|
|
222
|
+
const token = await this.linkCallback(this.onActionClicked, "context");
|
|
223
|
+
const callbackAction: Action = {
|
|
285
224
|
title: "Click me",
|
|
286
|
-
type:
|
|
225
|
+
type: ActionType.callback,
|
|
287
226
|
callback: token,
|
|
288
227
|
};
|
|
289
228
|
|
|
290
|
-
// Add to
|
|
291
|
-
await this.tools.plot.
|
|
292
|
-
type:
|
|
293
|
-
title: "Task with
|
|
229
|
+
// Add to thread note
|
|
230
|
+
await this.tools.plot.createThread({
|
|
231
|
+
type: ThreadType.Note,
|
|
232
|
+
title: "Task with actions",
|
|
294
233
|
notes: [
|
|
295
234
|
{
|
|
296
|
-
content: "Click the
|
|
297
|
-
|
|
235
|
+
content: "Click the actions below to take action.",
|
|
236
|
+
actions: [urlAction, callbackAction],
|
|
298
237
|
},
|
|
299
238
|
],
|
|
300
239
|
});
|
|
301
240
|
|
|
302
|
-
// Callback handler receives the
|
|
303
|
-
async
|
|
304
|
-
// Handle
|
|
241
|
+
// Callback handler receives the Action as first argument
|
|
242
|
+
async onActionClicked(action: Action, context: string): Promise<void> {
|
|
243
|
+
// Handle action click
|
|
305
244
|
}
|
|
306
245
|
```
|
|
307
246
|
|
|
@@ -317,9 +256,9 @@ build(build: ToolBuilder) {
|
|
|
317
256
|
providers: [{
|
|
318
257
|
provider: AuthProvider.Google,
|
|
319
258
|
scopes: ["https://www.googleapis.com/auth/calendar"],
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
259
|
+
getChannels: this.getChannels, // List available resources after auth
|
|
260
|
+
onChannelEnabled: this.onChannelEnabled, // User enabled a resource
|
|
261
|
+
onChannelDisabled: this.onChannelDisabled, // User disabled a resource
|
|
323
262
|
}],
|
|
324
263
|
}),
|
|
325
264
|
// ...
|
|
@@ -327,7 +266,7 @@ build(build: ToolBuilder) {
|
|
|
327
266
|
}
|
|
328
267
|
|
|
329
268
|
// Get a token for API calls:
|
|
330
|
-
const token = await this.tools.integrations.get(AuthProvider.Google,
|
|
269
|
+
const token = await this.tools.integrations.get(AuthProvider.Google, channelId);
|
|
331
270
|
if (!token) throw new Error("No auth token available");
|
|
332
271
|
const client = new ApiClient({ accessToken: token.token });
|
|
333
272
|
```
|
|
@@ -338,7 +277,7 @@ For per-user write-backs (e.g., RSVP, comments attributed to the acting user):
|
|
|
338
277
|
await this.tools.integrations.actAs(
|
|
339
278
|
AuthProvider.Google,
|
|
340
279
|
actorId, // The user who performed the action
|
|
341
|
-
|
|
280
|
+
threadId, // Thread to prompt for auth if needed
|
|
342
281
|
this.performWriteBack,
|
|
343
282
|
...extraArgs
|
|
344
283
|
);
|
|
@@ -346,70 +285,43 @@ await this.tools.integrations.actAs(
|
|
|
346
285
|
|
|
347
286
|
## Sync Pattern
|
|
348
287
|
|
|
349
|
-
###
|
|
350
|
-
|
|
351
|
-
Most twists use external tools (CalendarTool, ProjectTool, etc.) that handle sync internally. The twist just receives `NewActivityWithNotes` objects and saves them:
|
|
352
|
-
|
|
353
|
-
```typescript
|
|
354
|
-
build(build: ToolBuilder) {
|
|
355
|
-
return {
|
|
356
|
-
calendarTool: build(GoogleCalendar, {
|
|
357
|
-
onItem: this.handleEvent, // Receives synced items
|
|
358
|
-
onSyncableDisabled: this.onSyncableDisabled, // Clean up when disabled
|
|
359
|
-
}),
|
|
360
|
-
plot: build(Plot, { activity: { access: ActivityAccess.Create } }),
|
|
361
|
-
};
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
// Tools deliver NewActivityWithNotes — twist saves them
|
|
365
|
-
async handleEvent(activity: NewActivityWithNotes): Promise<void> {
|
|
366
|
-
await this.tools.plot.createActivity(activity);
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
async onSyncableDisabled(filter: ActivityFilter): Promise<void> {
|
|
370
|
-
await this.tools.plot.updateActivity({ match: filter, archived: true });
|
|
371
|
-
}
|
|
372
|
-
```
|
|
373
|
-
|
|
374
|
-
### Custom Sync: Upsert via Source/Key (Strategy 2)
|
|
288
|
+
### Upsert via Source/Key (Strategy 2)
|
|
375
289
|
|
|
376
|
-
|
|
290
|
+
Use source/key for automatic upserts:
|
|
377
291
|
|
|
378
292
|
```typescript
|
|
379
293
|
async handleEvent(event: ExternalEvent): Promise<void> {
|
|
380
|
-
const
|
|
294
|
+
const thread: NewThreadWithNotes = {
|
|
381
295
|
source: event.htmlLink, // Canonical URL for automatic deduplication
|
|
382
|
-
type:
|
|
296
|
+
type: ThreadType.Event,
|
|
383
297
|
title: event.summary || "(No title)",
|
|
384
|
-
start: event.start?.dateTime || event.start?.date || null,
|
|
385
|
-
end: event.end?.dateTime || event.end?.date || null,
|
|
386
298
|
notes: [],
|
|
387
299
|
};
|
|
388
300
|
|
|
389
301
|
if (event.description) {
|
|
390
|
-
|
|
391
|
-
|
|
302
|
+
thread.notes.push({
|
|
303
|
+
thread: { source: event.htmlLink },
|
|
392
304
|
key: "description", // This key enables note-level upserts
|
|
393
305
|
content: event.description,
|
|
394
306
|
});
|
|
395
307
|
}
|
|
396
308
|
|
|
397
309
|
// Create or update — Plot handles deduplication automatically
|
|
398
|
-
await this.tools.plot.
|
|
310
|
+
await this.tools.plot.createThread(thread);
|
|
399
311
|
}
|
|
400
312
|
```
|
|
401
313
|
|
|
402
314
|
### Advanced: Generate and Store IDs (Strategy 3)
|
|
403
315
|
|
|
404
|
-
Only use this pattern when you need to create multiple Plot
|
|
316
|
+
Only use this pattern when you need to create multiple Plot threads from a single external item, or when the external system doesn't provide stable identifiers. See SYNC_STRATEGIES.md for details.
|
|
405
317
|
|
|
406
318
|
```typescript
|
|
407
319
|
async handleEventAdvanced(
|
|
408
|
-
|
|
320
|
+
incomingThread: NewThreadWithNotes,
|
|
409
321
|
calendarId: string
|
|
410
322
|
): Promise<void> {
|
|
411
323
|
// Extract external event ID from meta (adapt based on your tool's data)
|
|
412
|
-
const externalId =
|
|
324
|
+
const externalId = incomingThread.meta?.eventId;
|
|
413
325
|
|
|
414
326
|
if (!externalId) {
|
|
415
327
|
console.error("Event missing external ID");
|
|
@@ -418,38 +330,38 @@ async handleEventAdvanced(
|
|
|
418
330
|
|
|
419
331
|
// Check if we've already synced this event
|
|
420
332
|
const mappingKey = `event_mapping:${calendarId}:${externalId}`;
|
|
421
|
-
const
|
|
333
|
+
const existingThreadId = await this.get<Uuid>(mappingKey);
|
|
422
334
|
|
|
423
|
-
if (
|
|
335
|
+
if (existingThreadId) {
|
|
424
336
|
// Event already exists - add update as a Note (add message to thread)
|
|
425
|
-
if (
|
|
337
|
+
if (incomingThread.notes?.[0]?.content) {
|
|
426
338
|
await this.tools.plot.createNote({
|
|
427
|
-
|
|
428
|
-
content:
|
|
339
|
+
thread: { id: existingThreadId },
|
|
340
|
+
content: incomingThread.notes[0].content,
|
|
429
341
|
});
|
|
430
342
|
}
|
|
431
343
|
return;
|
|
432
344
|
}
|
|
433
345
|
|
|
434
346
|
// New event - generate UUID and store mapping
|
|
435
|
-
const
|
|
436
|
-
await this.set(mappingKey,
|
|
347
|
+
const threadId = Uuid.Generate();
|
|
348
|
+
await this.set(mappingKey, threadId);
|
|
437
349
|
|
|
438
|
-
// Create new
|
|
439
|
-
await this.tools.plot.
|
|
440
|
-
...
|
|
441
|
-
id:
|
|
350
|
+
// Create new Thread with initial Note (new thread with first message)
|
|
351
|
+
await this.tools.plot.createThread({
|
|
352
|
+
...incomingThread,
|
|
353
|
+
id: threadId,
|
|
442
354
|
});
|
|
443
355
|
}
|
|
444
356
|
```
|
|
445
357
|
|
|
446
358
|
## Resource Selection
|
|
447
359
|
|
|
448
|
-
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 tool's `
|
|
360
|
+
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 tool's `getChannels()` method and toggle them on/off. You do **not** need to build custom selection UI.
|
|
449
361
|
|
|
450
362
|
```typescript
|
|
451
363
|
// In your tool:
|
|
452
|
-
async
|
|
364
|
+
async getChannels(_auth: Authorization, token: AuthToken): Promise<Channel[]> {
|
|
453
365
|
const client = new ApiClient({ accessToken: token.token });
|
|
454
366
|
const calendars = await client.listCalendars();
|
|
455
367
|
return calendars.map(c => ({
|
|
@@ -501,10 +413,10 @@ async syncBatch(resourceId: string): Promise<void> {
|
|
|
501
413
|
// Process results using source/key pattern (automatic upserts, no manual tracking)
|
|
502
414
|
// If each item makes ~10 requests, keep batch size ≤ 100 items to stay under limit
|
|
503
415
|
for (const item of result.items) {
|
|
504
|
-
// Each
|
|
505
|
-
await this.tools.plot.
|
|
416
|
+
// Each createThread may make ~5-10 requests depending on notes/links
|
|
417
|
+
await this.tools.plot.createThread({
|
|
506
418
|
source: item.url, // Use item's canonical URL for automatic deduplication
|
|
507
|
-
type:
|
|
419
|
+
type: ThreadType.Note,
|
|
508
420
|
title: item.title,
|
|
509
421
|
notes: [{
|
|
510
422
|
activity: { source: item.url },
|
|
@@ -533,8 +445,8 @@ async syncBatch(resourceId: string): Promise<void> {
|
|
|
533
445
|
await this.clear(`sync_state_${resourceId}`);
|
|
534
446
|
|
|
535
447
|
// Optionally notify user of completion
|
|
536
|
-
await this.tools.plot.
|
|
537
|
-
type:
|
|
448
|
+
await this.tools.plot.createThread({
|
|
449
|
+
type: ThreadType.Note,
|
|
538
450
|
title: "Sync complete",
|
|
539
451
|
notes: [
|
|
540
452
|
{
|
|
@@ -546,9 +458,9 @@ async syncBatch(resourceId: string): Promise<void> {
|
|
|
546
458
|
}
|
|
547
459
|
```
|
|
548
460
|
|
|
549
|
-
##
|
|
461
|
+
## Thread Sync Best Practices
|
|
550
462
|
|
|
551
|
-
When syncing
|
|
463
|
+
When syncing threads from external systems, follow these patterns for optimal user experience:
|
|
552
464
|
|
|
553
465
|
### The `initialSync` Flag
|
|
554
466
|
|
|
@@ -561,8 +473,8 @@ All sync-based tools should distinguish between initial sync (first import) and
|
|
|
561
473
|
|
|
562
474
|
**Example:**
|
|
563
475
|
```typescript
|
|
564
|
-
const
|
|
565
|
-
type:
|
|
476
|
+
const thread: NewThread = {
|
|
477
|
+
type: ThreadType.Event,
|
|
566
478
|
source: event.url,
|
|
567
479
|
title: event.title,
|
|
568
480
|
...(initialSync ? { unread: false } : {}), // false for initial, omit for incremental
|
|
@@ -577,9 +489,9 @@ const activity: NewActivity = {
|
|
|
577
489
|
|
|
578
490
|
### Two-Way Sync: Avoiding Race Conditions
|
|
579
491
|
|
|
580
|
-
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
|
|
492
|
+
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.
|
|
581
493
|
|
|
582
|
-
**Solution:** Embed the Plot `
|
|
494
|
+
**Solution:** Embed the Plot `Thread.id` / `Note.id` in the external item's metadata when creating it, and update `Thread.source` / `Note.key` after creation. When processing webhooks, check for the Plot ID in metadata first.
|
|
583
495
|
|
|
584
496
|
```typescript
|
|
585
497
|
async pushNoteAsComment(note: Note, externalItemId: string): Promise<void> {
|
|
@@ -622,8 +534,8 @@ try {
|
|
|
622
534
|
} catch (error) {
|
|
623
535
|
console.error("Operation failed:", error);
|
|
624
536
|
|
|
625
|
-
await this.tools.plot.
|
|
626
|
-
type:
|
|
537
|
+
await this.tools.plot.createThread({
|
|
538
|
+
type: ThreadType.Note,
|
|
627
539
|
title: "Operation failed",
|
|
628
540
|
notes: [
|
|
629
541
|
{
|
|
@@ -637,11 +549,11 @@ try {
|
|
|
637
549
|
## Common Pitfalls
|
|
638
550
|
|
|
639
551
|
- **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.
|
|
640
|
-
- **Processing self-created
|
|
641
|
-
- **Always create
|
|
642
|
-
- **Use correct
|
|
643
|
-
- **Use
|
|
644
|
-
- **Add Notes to existing
|
|
552
|
+
- **Processing self-created threads** - Other users may change a Thread created by the twist, resulting in a callback. Be sure to check the `changes === null` and/or `thread.author.id !== this.id` to avoid re-processing.
|
|
553
|
+
- **Always create Threads with Notes** - See "Understanding Threads and Notes" section above for the thread/message pattern and decision tree.
|
|
554
|
+
- **Use correct Thread types** - Most should be `ThreadType.Note`. Only use `Action` for tasks with `done`, and `Event` for items with `start`/`end`.
|
|
555
|
+
- **Use Thread.source and Note.key for automatic upserts (Recommended)** - Set Thread.source to the external item's URL for automatic deduplication. Only use UUID generation and storage for advanced cases (see SYNC_STRATEGIES.md).
|
|
556
|
+
- **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.
|
|
645
557
|
- Tools are declared in the `build` method and accessed via `this.tools.toolName` in twist methods.
|
|
646
558
|
- **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).
|
|
647
559
|
- **Always use Callbacks tool for persistent references** - Direct function references don't survive worker restarts.
|
|
@@ -68,30 +68,9 @@ build(build: ToolBuilder) {
|
|
|
68
68
|
- **Callbacks**: Create persistent function references for webhooks
|
|
69
69
|
- **Network**: HTTP access permissions and webhook management
|
|
70
70
|
|
|
71
|
-
####
|
|
71
|
+
#### Sources
|
|
72
72
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
```json
|
|
76
|
-
{
|
|
77
|
-
"dependencies": {
|
|
78
|
-
"@plotday/twister": "workspace:^",
|
|
79
|
-
"@plotday/tool-google-calendar": "workspace:^"
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
Then use them in your twist:
|
|
85
|
-
|
|
86
|
-
```typescript
|
|
87
|
-
import GoogleCalendarTool from "@plotday/tool-google-calendar";
|
|
88
|
-
|
|
89
|
-
build(build: ToolBuilder) {
|
|
90
|
-
return {
|
|
91
|
-
googleCalendar: build(GoogleCalendarTool),
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
```
|
|
73
|
+
External service integrations (Google Calendar, Slack, Linear, etc.) are built as Sources. See the [Building Sources](https://twist.plot.day/documents/Building_Sources.html) guide.
|
|
95
74
|
|
|
96
75
|
### Activity Types
|
|
97
76
|
|