@plotday/twister 0.35.0 → 0.37.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 +188 -271
- package/bin/templates/README.template.md +2 -23
- package/cli/templates/AGENTS.template.md +188 -271
- 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 +10 -0
- package/dist/docs/classes/index.Source.html +184 -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 +38 -16
- package/dist/docs/classes/tools_network.Network.html +1 -1
- package/dist/docs/classes/tools_plot.Plot.html +86 -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} +8 -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/interfaces/utils_types.ToolShed.html +5 -5
- 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.BooleanDef.html +7 -0
- 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.NumberDef.html +9 -0
- package/dist/docs/types/index.OptionDef.html +2 -0
- package/dist/docs/types/index.OptionsSchema.html +3 -0
- package/dist/docs/types/index.ResolvedOptions.html +4 -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/index.SelectDef.html +8 -0
- package/dist/docs/types/index.TextDef.html +8 -0
- package/dist/docs/types/plot.Action.html +26 -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.NoteIntentHandler.html +6 -6
- 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 +2 -2
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/llm-docs/index.d.ts.map +1 -1
- package/dist/llm-docs/index.js +6 -8
- package/dist/llm-docs/index.js.map +1 -1
- package/dist/llm-docs/options.d.ts +9 -0
- package/dist/llm-docs/options.d.ts.map +1 -0
- package/dist/llm-docs/options.js +8 -0
- package/dist/llm-docs/options.js.map +1 -0
- 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/options.d.ts +104 -0
- package/dist/options.d.ts.map +1 -0
- package/dist/options.js +40 -0
- package/dist/options.js.map +1 -0
- package/dist/plot.d.ts +254 -588
- package/dist/plot.d.ts.map +1 -1
- package/dist/plot.js +16 -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 +133 -0
- package/dist/source.d.ts.map +1 -0
- package/dist/source.js +116 -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 +107 -50
- 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 +133 -117
- package/dist/tools/plot.d.ts.map +1 -1
- package/dist/tools/plot.js +19 -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/dist/utils/types.d.ts +5 -1
- package/dist/utils/types.d.ts.map +1 -1
- package/package.json +16 -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 -7
- 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,79 +19,78 @@ 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
|
|
72
|
+
type Thread,
|
|
73
|
+
type NewThreadWithNotes,
|
|
74
|
+
type ThreadFilter,
|
|
73
75
|
type Priority,
|
|
74
76
|
type ToolBuilder,
|
|
75
|
-
|
|
77
|
+
Twist,
|
|
78
|
+
ThreadType,
|
|
76
79
|
} from "@plotday/twister";
|
|
77
|
-
import { Plot } from "@plotday/twister/tools/plot";
|
|
78
|
-
|
|
80
|
+
import { ThreadAccess, Plot } from "@plotday/twister/tools/plot";
|
|
81
|
+
// Import your sources or tools as needed
|
|
79
82
|
|
|
80
83
|
export default class MyTwist extends Twist<MyTwist> {
|
|
81
84
|
build(build: ToolBuilder) {
|
|
82
85
|
return {
|
|
83
|
-
plot: build(Plot
|
|
86
|
+
plot: build(Plot, {
|
|
87
|
+
thread: { access: ThreadAccess.Create },
|
|
88
|
+
}),
|
|
84
89
|
};
|
|
85
90
|
}
|
|
86
91
|
|
|
87
|
-
async activate(
|
|
88
|
-
//
|
|
89
|
-
// Common actions: request auth, create setup activities
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
async activity(activity: Activity) {
|
|
93
|
-
// Called when an activity is routed to this twist
|
|
94
|
-
// Common actions: process external events, update activities
|
|
92
|
+
async activate(_priority: Pick<Priority, "id">) {
|
|
93
|
+
// Auth and resource selection handled in the twist edit modal.
|
|
95
94
|
}
|
|
96
95
|
}
|
|
97
96
|
```
|
|
@@ -132,238 +131,197 @@ For complete API documentation of built-in tools including all methods, types, a
|
|
|
132
131
|
|
|
133
132
|
**Critical**: Never use instance variables for state. They are lost after function execution. Always use Store methods.
|
|
134
133
|
|
|
135
|
-
### External Tools (Add to package.json)
|
|
136
|
-
|
|
137
|
-
Add tool dependencies to `package.json`:
|
|
138
|
-
|
|
139
|
-
```json
|
|
140
|
-
{
|
|
141
|
-
"dependencies": {
|
|
142
|
-
"@plotday/twister": "workspace:^",
|
|
143
|
-
"@plotday/tool-google-calendar": "workspace:^"
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
#### Common External Tools
|
|
149
|
-
|
|
150
|
-
- `@plotday/tool-google-calendar`: Google Calendar integration
|
|
151
|
-
- `@plotday/tool-outlook-calendar`: Outlook Calendar integration
|
|
152
|
-
- `@plotday/tool-google-contacts`: Google Contacts integration
|
|
153
|
-
|
|
154
134
|
## Lifecycle Methods
|
|
155
135
|
|
|
156
136
|
### activate(priority: Pick<Priority, "id">)
|
|
157
137
|
|
|
158
|
-
Called when the twist is enabled for a priority.
|
|
138
|
+
Called when the twist is enabled for a priority. Auth and resource selection are handled automatically via the twist edit modal when using external tools with Integrations.
|
|
159
139
|
|
|
160
|
-
|
|
140
|
+
Most twists have an empty or minimal `activate()`:
|
|
161
141
|
|
|
162
142
|
```typescript
|
|
163
143
|
async activate(_priority: Pick<Priority, "id">) {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
"google"
|
|
167
|
-
);
|
|
168
|
-
|
|
169
|
-
await this.tools.plot.createActivity({
|
|
170
|
-
type: ActivityType.Note,
|
|
171
|
-
title: "Connect your account",
|
|
172
|
-
notes: [
|
|
173
|
-
{
|
|
174
|
-
content: "Click the link below to connect your account and start syncing.",
|
|
175
|
-
links: [authLink],
|
|
176
|
-
},
|
|
177
|
-
],
|
|
178
|
-
});
|
|
144
|
+
// Auth and resource selection are handled in the twist edit modal.
|
|
145
|
+
// Only add custom initialization here if needed.
|
|
179
146
|
}
|
|
180
147
|
```
|
|
181
148
|
|
|
182
|
-
**Store Parent
|
|
149
|
+
**Store Parent Thread for Later (optional):**
|
|
183
150
|
|
|
184
151
|
```typescript
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
{
|
|
190
|
-
content: "Your twist is
|
|
191
|
-
},
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
await this.set("setup_activity_id", activity.id);
|
|
152
|
+
async activate(_priority: Pick<Priority, "id">) {
|
|
153
|
+
const threadId = await this.tools.plot.createThread({
|
|
154
|
+
type: ThreadType.Note,
|
|
155
|
+
title: "Setup complete",
|
|
156
|
+
notes: [{
|
|
157
|
+
content: "Your twist is ready. Threads will appear as they sync.",
|
|
158
|
+
}],
|
|
159
|
+
});
|
|
160
|
+
await this.set("setup_thread_id", threadId);
|
|
161
|
+
}
|
|
196
162
|
```
|
|
197
163
|
|
|
198
|
-
###
|
|
164
|
+
### Event Callbacks (via build options)
|
|
199
165
|
|
|
200
|
-
|
|
166
|
+
Twists respond to events through callbacks declared in `build()`:
|
|
201
167
|
|
|
202
|
-
**
|
|
168
|
+
**React to thread changes (for two-way sync):**
|
|
203
169
|
|
|
204
170
|
```typescript
|
|
205
|
-
|
|
206
|
-
|
|
171
|
+
plot: build(Plot, {
|
|
172
|
+
thread: {
|
|
173
|
+
access: ThreadAccess.Create,
|
|
174
|
+
updated: this.onThreadUpdated,
|
|
175
|
+
},
|
|
176
|
+
note: {
|
|
177
|
+
created: this.onNoteCreated,
|
|
178
|
+
},
|
|
179
|
+
}),
|
|
180
|
+
|
|
181
|
+
async onThreadUpdated(thread: Thread, changes: { tagsAdded, tagsRemoved }): Promise<void> {
|
|
182
|
+
const tool = this.getToolForThread(thread);
|
|
183
|
+
if (tool?.updateIssue) await tool.updateIssue(thread);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async onNoteCreated(note: Note): Promise<void> {
|
|
187
|
+
if (note.author.type === ActorType.Twist) return; // Prevent loops
|
|
188
|
+
// Sync note to external service as a comment
|
|
207
189
|
}
|
|
208
190
|
```
|
|
209
191
|
|
|
210
|
-
**
|
|
192
|
+
**Respond to mentions (AI twist pattern):**
|
|
211
193
|
|
|
212
194
|
```typescript
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
195
|
+
plot: build(Plot, {
|
|
196
|
+
thread: { access: ThreadAccess.Respond },
|
|
197
|
+
note: {
|
|
198
|
+
intents: [{
|
|
199
|
+
description: "Respond to general questions",
|
|
200
|
+
examples: ["What's the weather?", "Help me plan my week"],
|
|
201
|
+
handler: this.respond,
|
|
202
|
+
}],
|
|
203
|
+
},
|
|
204
|
+
}),
|
|
218
205
|
```
|
|
219
206
|
|
|
220
|
-
##
|
|
207
|
+
## Actions
|
|
221
208
|
|
|
222
|
-
|
|
209
|
+
Actions enable user interaction:
|
|
223
210
|
|
|
224
211
|
```typescript
|
|
225
|
-
import { type
|
|
212
|
+
import { type Action, ActionType } from "@plotday/twister";
|
|
226
213
|
|
|
227
|
-
// URL
|
|
228
|
-
const
|
|
214
|
+
// External URL action
|
|
215
|
+
const urlAction: Action = {
|
|
229
216
|
title: "Open website",
|
|
230
|
-
type:
|
|
217
|
+
type: ActionType.external,
|
|
231
218
|
url: "https://example.com",
|
|
232
219
|
};
|
|
233
220
|
|
|
234
|
-
// Callback
|
|
235
|
-
const token = await this.
|
|
236
|
-
const
|
|
221
|
+
// Callback action (uses Callbacks tool — use linkCallback, not callback)
|
|
222
|
+
const token = await this.linkCallback(this.onActionClicked, "context");
|
|
223
|
+
const callbackAction: Action = {
|
|
237
224
|
title: "Click me",
|
|
238
|
-
type:
|
|
239
|
-
|
|
225
|
+
type: ActionType.callback,
|
|
226
|
+
callback: token,
|
|
240
227
|
};
|
|
241
228
|
|
|
242
|
-
// Add to
|
|
243
|
-
await this.tools.plot.
|
|
244
|
-
type:
|
|
245
|
-
title: "Task with
|
|
229
|
+
// Add to thread note
|
|
230
|
+
await this.tools.plot.createThread({
|
|
231
|
+
type: ThreadType.Note,
|
|
232
|
+
title: "Task with actions",
|
|
246
233
|
notes: [
|
|
247
234
|
{
|
|
248
|
-
content: "Click the
|
|
249
|
-
|
|
235
|
+
content: "Click the actions below to take action.",
|
|
236
|
+
actions: [urlAction, callbackAction],
|
|
250
237
|
},
|
|
251
238
|
],
|
|
252
239
|
});
|
|
240
|
+
|
|
241
|
+
// Callback handler receives the Action as first argument
|
|
242
|
+
async onActionClicked(action: Action, context: string): Promise<void> {
|
|
243
|
+
// Handle action click
|
|
244
|
+
}
|
|
253
245
|
```
|
|
254
246
|
|
|
255
247
|
## Authentication Pattern
|
|
256
248
|
|
|
257
|
-
|
|
249
|
+
Auth is handled automatically via the Integrations tool. Tools declare their OAuth provider in `build()`, and users connect in the twist edit modal. **You do not need to create auth activities manually.**
|
|
258
250
|
|
|
259
251
|
```typescript
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
links: [authLink],
|
|
275
|
-
},
|
|
276
|
-
],
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
// Store for later use
|
|
280
|
-
await this.set("auth_activity_id", activity.id);
|
|
252
|
+
// In your tool's build() method:
|
|
253
|
+
build(build: ToolBuilder) {
|
|
254
|
+
return {
|
|
255
|
+
integrations: build(Integrations, {
|
|
256
|
+
providers: [{
|
|
257
|
+
provider: AuthProvider.Google,
|
|
258
|
+
scopes: ["https://www.googleapis.com/auth/calendar"],
|
|
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
|
|
262
|
+
}],
|
|
263
|
+
}),
|
|
264
|
+
// ...
|
|
265
|
+
};
|
|
281
266
|
}
|
|
282
267
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
268
|
+
// Get a token for API calls:
|
|
269
|
+
const token = await this.tools.integrations.get(AuthProvider.Google, channelId);
|
|
270
|
+
if (!token) throw new Error("No auth token available");
|
|
271
|
+
const client = new ApiClient({ accessToken: token.token });
|
|
272
|
+
```
|
|
286
273
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
274
|
+
For per-user write-backs (e.g., RSVP, comments attributed to the acting user):
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
await this.tools.integrations.actAs(
|
|
278
|
+
AuthProvider.Google,
|
|
279
|
+
actorId, // The user who performed the action
|
|
280
|
+
threadId, // Thread to prompt for auth if needed
|
|
281
|
+
this.performWriteBack,
|
|
282
|
+
...extraArgs
|
|
283
|
+
);
|
|
290
284
|
```
|
|
291
285
|
|
|
292
286
|
## Sync Pattern
|
|
293
287
|
|
|
294
|
-
###
|
|
288
|
+
### Upsert via Source/Key (Strategy 2)
|
|
295
289
|
|
|
296
|
-
|
|
290
|
+
Use source/key for automatic upserts:
|
|
297
291
|
|
|
298
292
|
```typescript
|
|
299
|
-
async
|
|
300
|
-
const
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
authToken,
|
|
304
|
-
calendarId,
|
|
305
|
-
this.handleEvent,
|
|
306
|
-
calendarId
|
|
307
|
-
);
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
async handleEvent(
|
|
311
|
-
event: ExternalEvent,
|
|
312
|
-
calendarId: string
|
|
313
|
-
): Promise<void> {
|
|
314
|
-
// Use the event's canonical URL as the source for automatic deduplication
|
|
315
|
-
const activity: NewActivityWithNotes = {
|
|
316
|
-
source: event.htmlLink, // or event.url, depending on your external system
|
|
317
|
-
type: ActivityType.Event,
|
|
293
|
+
async handleEvent(event: ExternalEvent): Promise<void> {
|
|
294
|
+
const thread: NewThreadWithNotes = {
|
|
295
|
+
source: event.htmlLink, // Canonical URL for automatic deduplication
|
|
296
|
+
type: ThreadType.Event,
|
|
318
297
|
title: event.summary || "(No title)",
|
|
319
|
-
start: event.start?.dateTime || event.start?.date || null,
|
|
320
|
-
end: event.end?.dateTime || event.end?.date || null,
|
|
321
298
|
notes: [],
|
|
322
299
|
};
|
|
323
300
|
|
|
324
|
-
// Add description as an upsertable note
|
|
325
301
|
if (event.description) {
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
key: "description",
|
|
302
|
+
thread.notes.push({
|
|
303
|
+
thread: { source: event.htmlLink },
|
|
304
|
+
key: "description", // This key enables note-level upserts
|
|
329
305
|
content: event.description,
|
|
330
306
|
});
|
|
331
307
|
}
|
|
332
308
|
|
|
333
|
-
//
|
|
334
|
-
|
|
335
|
-
const attendeeList = event.attendees
|
|
336
|
-
.map(a => `- ${a.email}${a.displayName ? ` (${a.displayName})` : ''}`)
|
|
337
|
-
.join('\n');
|
|
338
|
-
|
|
339
|
-
activity.notes.push({
|
|
340
|
-
activity: { source: event.htmlLink },
|
|
341
|
-
key: "attendees", // Different key for different note types
|
|
342
|
-
content: `## Attendees\n${attendeeList}`,
|
|
343
|
-
});
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
// Create or update - Plot automatically handles deduplication based on source
|
|
347
|
-
await this.tools.plot.createActivity(activity);
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
async stopSync(calendarId: string): Promise<void> {
|
|
351
|
-
const authToken = await this.get<string>("auth_token");
|
|
352
|
-
await this.tools.calendarTool.stopSync(authToken, calendarId);
|
|
309
|
+
// Create or update — Plot handles deduplication automatically
|
|
310
|
+
await this.tools.plot.createThread(thread);
|
|
353
311
|
}
|
|
354
312
|
```
|
|
355
313
|
|
|
356
314
|
### Advanced: Generate and Store IDs (Strategy 3)
|
|
357
315
|
|
|
358
|
-
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.
|
|
359
317
|
|
|
360
318
|
```typescript
|
|
361
319
|
async handleEventAdvanced(
|
|
362
|
-
|
|
320
|
+
incomingThread: NewThreadWithNotes,
|
|
363
321
|
calendarId: string
|
|
364
322
|
): Promise<void> {
|
|
365
323
|
// Extract external event ID from meta (adapt based on your tool's data)
|
|
366
|
-
const externalId =
|
|
324
|
+
const externalId = incomingThread.meta?.eventId;
|
|
367
325
|
|
|
368
326
|
if (!externalId) {
|
|
369
327
|
console.error("Event missing external ID");
|
|
@@ -372,86 +330,45 @@ async handleEventAdvanced(
|
|
|
372
330
|
|
|
373
331
|
// Check if we've already synced this event
|
|
374
332
|
const mappingKey = `event_mapping:${calendarId}:${externalId}`;
|
|
375
|
-
const
|
|
333
|
+
const existingThreadId = await this.get<Uuid>(mappingKey);
|
|
376
334
|
|
|
377
|
-
if (
|
|
335
|
+
if (existingThreadId) {
|
|
378
336
|
// Event already exists - add update as a Note (add message to thread)
|
|
379
|
-
if (
|
|
337
|
+
if (incomingThread.notes?.[0]?.content) {
|
|
380
338
|
await this.tools.plot.createNote({
|
|
381
|
-
|
|
382
|
-
content:
|
|
339
|
+
thread: { id: existingThreadId },
|
|
340
|
+
content: incomingThread.notes[0].content,
|
|
383
341
|
});
|
|
384
342
|
}
|
|
385
343
|
return;
|
|
386
344
|
}
|
|
387
345
|
|
|
388
346
|
// New event - generate UUID and store mapping
|
|
389
|
-
const
|
|
390
|
-
await this.set(mappingKey,
|
|
347
|
+
const threadId = Uuid.Generate();
|
|
348
|
+
await this.set(mappingKey, threadId);
|
|
391
349
|
|
|
392
|
-
// Create new
|
|
393
|
-
await this.tools.plot.
|
|
394
|
-
...
|
|
395
|
-
id:
|
|
350
|
+
// Create new Thread with initial Note (new thread with first message)
|
|
351
|
+
await this.tools.plot.createThread({
|
|
352
|
+
...incomingThread,
|
|
353
|
+
id: threadId,
|
|
396
354
|
});
|
|
397
355
|
}
|
|
398
356
|
```
|
|
399
357
|
|
|
400
|
-
##
|
|
358
|
+
## Resource Selection
|
|
401
359
|
|
|
402
|
-
|
|
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.
|
|
403
361
|
|
|
404
362
|
```typescript
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
this.onCalendarSelected,
|
|
415
|
-
provider,
|
|
416
|
-
calendar.id,
|
|
417
|
-
calendar.name,
|
|
418
|
-
authToken
|
|
419
|
-
);
|
|
420
|
-
|
|
421
|
-
links.push({
|
|
422
|
-
title: `📅 ${calendar.name}${calendar.primary ? " (Primary)" : ""}`,
|
|
423
|
-
type: ActivityLinkType.callback,
|
|
424
|
-
token: token,
|
|
425
|
-
});
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
await this.tools.plot.createActivity({
|
|
429
|
-
type: ActivityType.Note,
|
|
430
|
-
title: "Which calendars would you like to connect?",
|
|
431
|
-
notes: [
|
|
432
|
-
{
|
|
433
|
-
content: "Select the calendars you want to sync:",
|
|
434
|
-
links,
|
|
435
|
-
},
|
|
436
|
-
],
|
|
437
|
-
});
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
async onCalendarSelected(
|
|
441
|
-
link: ActivityLink,
|
|
442
|
-
provider: string,
|
|
443
|
-
calendarId: string,
|
|
444
|
-
calendarName: string,
|
|
445
|
-
authToken: string
|
|
446
|
-
): Promise<void> {
|
|
447
|
-
// Start sync for selected calendar
|
|
448
|
-
await this.tools.tool.startSync(
|
|
449
|
-
authToken,
|
|
450
|
-
calendarId,
|
|
451
|
-
this.handleEvent,
|
|
452
|
-
provider,
|
|
453
|
-
calendarId
|
|
454
|
-
);
|
|
363
|
+
// In your tool:
|
|
364
|
+
async getChannels(_auth: Authorization, token: AuthToken): Promise<Channel[]> {
|
|
365
|
+
const client = new ApiClient({ accessToken: token.token });
|
|
366
|
+
const calendars = await client.listCalendars();
|
|
367
|
+
return calendars.map(c => ({
|
|
368
|
+
id: c.id,
|
|
369
|
+
title: c.name,
|
|
370
|
+
children: c.subCalendars?.map(sc => ({ id: sc.id, title: sc.name })),
|
|
371
|
+
}));
|
|
455
372
|
}
|
|
456
373
|
```
|
|
457
374
|
|
|
@@ -486,7 +403,7 @@ async startSync(resourceId: string): Promise<void> {
|
|
|
486
403
|
await this.runTask(callback);
|
|
487
404
|
}
|
|
488
405
|
|
|
489
|
-
async syncBatch(
|
|
406
|
+
async syncBatch(resourceId: string): Promise<void> {
|
|
490
407
|
// Load state from Store (set by previous execution)
|
|
491
408
|
const state = await this.get(`sync_state_${resourceId}`);
|
|
492
409
|
|
|
@@ -496,17 +413,17 @@ async syncBatch(args: any, resourceId: string): Promise<void> {
|
|
|
496
413
|
// Process results using source/key pattern (automatic upserts, no manual tracking)
|
|
497
414
|
// If each item makes ~10 requests, keep batch size ≤ 100 items to stay under limit
|
|
498
415
|
for (const item of result.items) {
|
|
499
|
-
// Each
|
|
500
|
-
await this.tools.plot.
|
|
416
|
+
// Each createThread may make ~5-10 requests depending on notes/links
|
|
417
|
+
await this.tools.plot.createThread({
|
|
501
418
|
source: item.url, // Use item's canonical URL for automatic deduplication
|
|
502
|
-
type:
|
|
419
|
+
type: ThreadType.Note,
|
|
503
420
|
title: item.title,
|
|
504
421
|
notes: [{
|
|
505
422
|
activity: { source: item.url },
|
|
506
423
|
key: "description", // Use key for upsertable notes
|
|
507
424
|
content: item.description,
|
|
508
425
|
}],
|
|
509
|
-
unread:
|
|
426
|
+
...(state.initialSync ? { unread: false } : {}), // false for initial, omit for incremental
|
|
510
427
|
...(state.initialSync ? { archived: false } : {}), // unarchive on initial only
|
|
511
428
|
});
|
|
512
429
|
}
|
|
@@ -528,8 +445,8 @@ async syncBatch(args: any, resourceId: string): Promise<void> {
|
|
|
528
445
|
await this.clear(`sync_state_${resourceId}`);
|
|
529
446
|
|
|
530
447
|
// Optionally notify user of completion
|
|
531
|
-
await this.tools.plot.
|
|
532
|
-
type:
|
|
448
|
+
await this.tools.plot.createThread({
|
|
449
|
+
type: ThreadType.Note,
|
|
533
450
|
title: "Sync complete",
|
|
534
451
|
notes: [
|
|
535
452
|
{
|
|
@@ -541,9 +458,9 @@ async syncBatch(args: any, resourceId: string): Promise<void> {
|
|
|
541
458
|
}
|
|
542
459
|
```
|
|
543
460
|
|
|
544
|
-
##
|
|
461
|
+
## Thread Sync Best Practices
|
|
545
462
|
|
|
546
|
-
When syncing
|
|
463
|
+
When syncing threads from external systems, follow these patterns for optimal user experience:
|
|
547
464
|
|
|
548
465
|
### The `initialSync` Flag
|
|
549
466
|
|
|
@@ -551,30 +468,30 @@ All sync-based tools should distinguish between initial sync (first import) and
|
|
|
551
468
|
|
|
552
469
|
| Field | Initial Sync | Incremental Sync | Reason |
|
|
553
470
|
|-------|--------------|------------------|---------|
|
|
554
|
-
| `unread` | `false` |
|
|
471
|
+
| `unread` | `false` | *omit* | Initial: mark read for all. Incremental: auto-mark read for author only |
|
|
555
472
|
| `archived` | `false` | *omit* | Unarchive on install, preserve user choice on updates |
|
|
556
473
|
|
|
557
474
|
**Example:**
|
|
558
475
|
```typescript
|
|
559
|
-
const
|
|
560
|
-
type:
|
|
476
|
+
const thread: NewThread = {
|
|
477
|
+
type: ThreadType.Event,
|
|
561
478
|
source: event.url,
|
|
562
479
|
title: event.title,
|
|
563
|
-
unread:
|
|
564
|
-
...(initialSync ? { archived: false } : {}),
|
|
480
|
+
...(initialSync ? { unread: false } : {}), // false for initial, omit for incremental
|
|
481
|
+
...(initialSync ? { archived: false } : {}), // unarchive on initial only
|
|
565
482
|
};
|
|
566
483
|
```
|
|
567
484
|
|
|
568
485
|
**Why this matters:**
|
|
569
|
-
- **Initial sync**: Activities are unarchived and marked as read, preventing spam from bulk historical imports
|
|
570
|
-
- **Incremental sync**:
|
|
486
|
+
- **Initial sync**: Activities are unarchived and marked as read for all users, preventing spam from bulk historical imports
|
|
487
|
+
- **Incremental sync**: Activities are auto-marked read for the author (twist owner), unread for everyone else. Archived state is preserved
|
|
571
488
|
- **Reinstall**: Acts as initial sync, so previously archived activities are unarchived (fresh start)
|
|
572
489
|
|
|
573
490
|
### Two-Way Sync: Avoiding Race Conditions
|
|
574
491
|
|
|
575
|
-
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.
|
|
576
493
|
|
|
577
|
-
**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.
|
|
578
495
|
|
|
579
496
|
```typescript
|
|
580
497
|
async pushNoteAsComment(note: Note, externalItemId: string): Promise<void> {
|
|
@@ -617,8 +534,8 @@ try {
|
|
|
617
534
|
} catch (error) {
|
|
618
535
|
console.error("Operation failed:", error);
|
|
619
536
|
|
|
620
|
-
await this.tools.plot.
|
|
621
|
-
type:
|
|
537
|
+
await this.tools.plot.createThread({
|
|
538
|
+
type: ThreadType.Note,
|
|
622
539
|
title: "Operation failed",
|
|
623
540
|
notes: [
|
|
624
541
|
{
|
|
@@ -632,11 +549,11 @@ try {
|
|
|
632
549
|
## Common Pitfalls
|
|
633
550
|
|
|
634
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.
|
|
635
|
-
- **Processing self-created
|
|
636
|
-
- **Always create
|
|
637
|
-
- **Use correct
|
|
638
|
-
- **Use
|
|
639
|
-
- **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.
|
|
640
557
|
- Tools are declared in the `build` method and accessed via `this.tools.toolName` in twist methods.
|
|
641
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).
|
|
642
559
|
- **Always use Callbacks tool for persistent references** - Direct function references don't survive worker restarts.
|