@plotday/twister 0.27.0 → 0.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (201) hide show
  1. package/README.md +10 -4
  2. package/bin/templates/AGENTS.template.md +91 -29
  3. package/cli/templates/AGENTS.template.md +91 -29
  4. package/dist/common/calendar.d.ts +35 -28
  5. package/dist/common/calendar.d.ts.map +1 -1
  6. package/dist/common/messaging.d.ts +24 -10
  7. package/dist/common/messaging.d.ts.map +1 -1
  8. package/dist/common/projects.d.ts +30 -15
  9. package/dist/common/projects.d.ts.map +1 -1
  10. package/dist/common/serializable.d.ts +40 -0
  11. package/dist/common/serializable.d.ts.map +1 -0
  12. package/dist/common/serializable.js +2 -0
  13. package/dist/common/serializable.js.map +1 -0
  14. package/dist/docs/assets/hierarchy.js +1 -1
  15. package/dist/docs/assets/navigation.js +1 -1
  16. package/dist/docs/assets/search.js +1 -1
  17. package/dist/docs/classes/tool.ITool.html +1 -1
  18. package/dist/docs/classes/tool.Tool.html +32 -28
  19. package/dist/docs/classes/tools_ai.AI.html +2 -2
  20. package/dist/docs/classes/tools_callbacks.Callbacks.html +6 -6
  21. package/dist/docs/classes/tools_integrations.Integrations.html +3 -3
  22. package/dist/docs/classes/tools_network.Network.html +12 -12
  23. package/dist/docs/classes/tools_plot.Plot.html +41 -31
  24. package/dist/docs/classes/tools_store.Store.html +31 -11
  25. package/dist/docs/classes/tools_tasks.Tasks.html +8 -8
  26. package/dist/docs/classes/tools_twists.Twists.html +5 -5
  27. package/dist/docs/classes/twist.Twist.html +28 -24
  28. package/dist/docs/documents/Building_Custom_Tools.html +8 -2
  29. package/dist/docs/documents/Built-in_Tools.html +19 -8
  30. package/dist/docs/documents/Core_Concepts.html +14 -6
  31. package/dist/docs/documents/Getting_Started.html +11 -4
  32. package/dist/docs/enums/plot.ActivityLinkType.html +5 -5
  33. package/dist/docs/enums/plot.ActivityType.html +4 -4
  34. package/dist/docs/enums/plot.ActorType.html +4 -4
  35. package/dist/docs/enums/plot.ConferencingProvider.html +6 -6
  36. package/dist/docs/enums/tag.Tag.html +2 -2
  37. package/dist/docs/enums/tools_ai.AIModel.html +2 -2
  38. package/dist/docs/enums/tools_integrations.AuthLevel.html +3 -3
  39. package/dist/docs/enums/tools_integrations.AuthProvider.html +11 -11
  40. package/dist/docs/enums/tools_plot.ActivityAccess.html +3 -3
  41. package/dist/docs/enums/tools_plot.ContactAccess.html +4 -4
  42. package/dist/docs/enums/tools_plot.PriorityAccess.html +3 -3
  43. package/dist/docs/functions/index.Uuid.Generate.html +1 -1
  44. package/dist/docs/functions/utils_hash.quickHash.html +1 -1
  45. package/dist/docs/hierarchy.html +1 -1
  46. package/dist/docs/index.html +10 -0
  47. package/dist/docs/interfaces/common_calendar.Calendar.html +5 -5
  48. package/dist/docs/interfaces/common_calendar.CalendarTool.html +29 -15
  49. package/dist/docs/interfaces/common_calendar.SyncOptions.html +3 -3
  50. package/dist/docs/interfaces/index.SerializableArray.html +2 -0
  51. package/dist/docs/interfaces/index.SerializableMap.html +2 -0
  52. package/dist/docs/interfaces/index.SerializableObject.html +2 -0
  53. package/dist/docs/interfaces/index.SerializableSet.html +2 -0
  54. package/dist/docs/interfaces/tools_ai.AIRequest.html +11 -11
  55. package/dist/docs/interfaces/tools_ai.AIResponse.html +9 -9
  56. package/dist/docs/interfaces/tools_ai.FilePart.html +5 -5
  57. package/dist/docs/interfaces/tools_ai.ImagePart.html +4 -4
  58. package/dist/docs/interfaces/tools_ai.ReasoningPart.html +4 -4
  59. package/dist/docs/interfaces/tools_ai.RedactedReasoningPart.html +3 -3
  60. package/dist/docs/interfaces/tools_ai.TextPart.html +3 -3
  61. package/dist/docs/interfaces/tools_ai.ToolCallPart.html +5 -5
  62. package/dist/docs/interfaces/tools_ai.ToolExecutionOptions.html +4 -4
  63. package/dist/docs/interfaces/tools_ai.ToolResultPart.html +5 -5
  64. package/dist/docs/interfaces/tools_twists.TwistSource.html +3 -3
  65. package/dist/docs/interfaces/utils_types.ToolShed.html +5 -5
  66. package/dist/docs/media/SYNC_STRATEGIES.md +651 -0
  67. package/dist/docs/modules/index.html +1 -1
  68. package/dist/docs/modules/plot.html +1 -1
  69. package/dist/docs/modules/utils_types.html +1 -1
  70. package/dist/docs/types/common_calendar.CalendarAuth.html +2 -2
  71. package/dist/docs/types/index.Serializable.html +17 -0
  72. package/dist/docs/types/index.Uuid.html +1 -1
  73. package/dist/docs/types/plot.Activity.html +17 -8
  74. package/dist/docs/types/plot.ActivityCommon.html +10 -10
  75. package/dist/docs/types/plot.ActivityLink.html +1 -1
  76. package/dist/docs/types/plot.ActivityMeta.html +5 -3
  77. package/dist/docs/types/plot.ActivityUpdate.html +4 -2
  78. package/dist/docs/types/plot.ActivityWithNotes.html +1 -1
  79. package/dist/docs/types/plot.Actor.html +15 -5
  80. package/dist/docs/types/plot.ActorId.html +1 -1
  81. package/dist/docs/types/plot.ContentType.html +1 -1
  82. package/dist/docs/types/plot.NewActivity.html +26 -12
  83. package/dist/docs/types/plot.NewActivityWithNotes.html +1 -1
  84. package/dist/docs/types/plot.NewActor.html +1 -1
  85. package/dist/docs/types/plot.NewContact.html +4 -4
  86. package/dist/docs/types/plot.NewNote.html +9 -4
  87. package/dist/docs/types/plot.NewPriority.html +15 -5
  88. package/dist/docs/types/plot.NewTags.html +1 -1
  89. package/dist/docs/types/plot.Note.html +6 -3
  90. package/dist/docs/types/plot.NoteUpdate.html +4 -3
  91. package/dist/docs/types/plot.PickPriorityConfig.html +3 -3
  92. package/dist/docs/types/plot.Priority.html +9 -4
  93. package/dist/docs/types/plot.PriorityUpdate.html +3 -0
  94. package/dist/docs/types/plot.SyncUpdate.html +1 -1
  95. package/dist/docs/types/plot.Tags.html +1 -1
  96. package/dist/docs/types/tools_ai.AIAssistantMessage.html +2 -2
  97. package/dist/docs/types/tools_ai.AIMessage.html +1 -1
  98. package/dist/docs/types/tools_ai.AISource.html +1 -1
  99. package/dist/docs/types/tools_ai.AISystemMessage.html +2 -2
  100. package/dist/docs/types/tools_ai.AITool.html +1 -1
  101. package/dist/docs/types/tools_ai.AIToolMessage.html +2 -2
  102. package/dist/docs/types/tools_ai.AIToolSet.html +1 -1
  103. package/dist/docs/types/tools_ai.AIUsage.html +5 -5
  104. package/dist/docs/types/tools_ai.AIUserMessage.html +2 -2
  105. package/dist/docs/types/tools_ai.DataContent.html +1 -1
  106. package/dist/docs/types/tools_ai.ModelPreferences.html +4 -4
  107. package/dist/docs/types/tools_callbacks.Callback.html +1 -1
  108. package/dist/docs/types/tools_integrations.AuthToken.html +4 -4
  109. package/dist/docs/types/tools_integrations.Authorization.html +4 -4
  110. package/dist/docs/types/tools_network.WebhookRequest.html +6 -6
  111. package/dist/docs/types/tools_plot.NoteIntentHandler.html +4 -4
  112. package/dist/docs/types/tools_twists.Log.html +2 -2
  113. package/dist/docs/types/tools_twists.TwistPermissions.html +1 -1
  114. package/dist/docs/types/utils_types.BuiltInTools.html +2 -2
  115. package/dist/docs/types/utils_types.CallbackMethods.html +1 -1
  116. package/dist/docs/types/utils_types.ExtractBuildReturn.html +1 -1
  117. package/dist/docs/types/utils_types.InferOptions.html +1 -1
  118. package/dist/docs/types/utils_types.InferTools.html +1 -1
  119. package/dist/docs/types/utils_types.JSONValue.html +7 -0
  120. package/dist/docs/types/utils_types.NoFunctions.html +1 -1
  121. package/dist/docs/types/utils_types.NonFunction.html +1 -1
  122. package/dist/docs/types/utils_types.PromiseValues.html +1 -1
  123. package/dist/docs/types/utils_types.ToolBuilder.html +1 -1
  124. package/dist/index.d.ts +1 -0
  125. package/dist/index.d.ts.map +1 -1
  126. package/dist/index.js +1 -0
  127. package/dist/index.js.map +1 -1
  128. package/dist/llm-docs/common/calendar.d.ts +1 -1
  129. package/dist/llm-docs/common/calendar.d.ts.map +1 -1
  130. package/dist/llm-docs/common/calendar.js +1 -1
  131. package/dist/llm-docs/common/calendar.js.map +1 -1
  132. package/dist/llm-docs/common/messaging.d.ts +1 -1
  133. package/dist/llm-docs/common/messaging.d.ts.map +1 -1
  134. package/dist/llm-docs/common/messaging.js +1 -1
  135. package/dist/llm-docs/common/messaging.js.map +1 -1
  136. package/dist/llm-docs/common/projects.d.ts +1 -1
  137. package/dist/llm-docs/common/projects.d.ts.map +1 -1
  138. package/dist/llm-docs/common/projects.js +1 -1
  139. package/dist/llm-docs/common/projects.js.map +1 -1
  140. package/dist/llm-docs/plot.d.ts +1 -1
  141. package/dist/llm-docs/plot.d.ts.map +1 -1
  142. package/dist/llm-docs/plot.js +1 -1
  143. package/dist/llm-docs/plot.js.map +1 -1
  144. package/dist/llm-docs/tool.d.ts +1 -1
  145. package/dist/llm-docs/tool.d.ts.map +1 -1
  146. package/dist/llm-docs/tool.js +1 -1
  147. package/dist/llm-docs/tool.js.map +1 -1
  148. package/dist/llm-docs/tools/network.d.ts +1 -1
  149. package/dist/llm-docs/tools/network.d.ts.map +1 -1
  150. package/dist/llm-docs/tools/network.js +1 -1
  151. package/dist/llm-docs/tools/network.js.map +1 -1
  152. package/dist/llm-docs/tools/plot.d.ts +1 -1
  153. package/dist/llm-docs/tools/plot.d.ts.map +1 -1
  154. package/dist/llm-docs/tools/plot.js +1 -1
  155. package/dist/llm-docs/tools/plot.js.map +1 -1
  156. package/dist/llm-docs/tools/store.d.ts +1 -1
  157. package/dist/llm-docs/tools/store.d.ts.map +1 -1
  158. package/dist/llm-docs/tools/store.js +1 -1
  159. package/dist/llm-docs/tools/store.js.map +1 -1
  160. package/dist/llm-docs/tools/tasks.d.ts +1 -1
  161. package/dist/llm-docs/tools/tasks.d.ts.map +1 -1
  162. package/dist/llm-docs/tools/tasks.js +1 -1
  163. package/dist/llm-docs/tools/tasks.js.map +1 -1
  164. package/dist/llm-docs/twist-guide-template.d.ts +1 -1
  165. package/dist/llm-docs/twist-guide-template.d.ts.map +1 -1
  166. package/dist/llm-docs/twist-guide-template.js +1 -1
  167. package/dist/llm-docs/twist-guide-template.js.map +1 -1
  168. package/dist/llm-docs/twist.d.ts +1 -1
  169. package/dist/llm-docs/twist.d.ts.map +1 -1
  170. package/dist/llm-docs/twist.js +1 -1
  171. package/dist/llm-docs/twist.js.map +1 -1
  172. package/dist/plot.d.ts +232 -56
  173. package/dist/plot.d.ts.map +1 -1
  174. package/dist/plot.js.map +1 -1
  175. package/dist/tool.d.ts +30 -8
  176. package/dist/tool.d.ts.map +1 -1
  177. package/dist/tool.js +29 -7
  178. package/dist/tool.js.map +1 -1
  179. package/dist/tools/network.d.ts +19 -16
  180. package/dist/tools/network.d.ts.map +1 -1
  181. package/dist/tools/network.js +6 -4
  182. package/dist/tools/network.js.map +1 -1
  183. package/dist/tools/plot.d.ts +93 -24
  184. package/dist/tools/plot.d.ts.map +1 -1
  185. package/dist/tools/plot.js +46 -1
  186. package/dist/tools/plot.js.map +1 -1
  187. package/dist/tools/store.d.ts +49 -8
  188. package/dist/tools/store.d.ts.map +1 -1
  189. package/dist/tools/store.js +12 -1
  190. package/dist/tools/store.js.map +1 -1
  191. package/dist/tools/tasks.d.ts +4 -4
  192. package/dist/tools/tasks.js +4 -4
  193. package/dist/twist-guide.d.ts +1 -1
  194. package/dist/twist-guide.d.ts.map +1 -1
  195. package/dist/twist.d.ts +24 -10
  196. package/dist/twist.d.ts.map +1 -1
  197. package/dist/twist.js +19 -5
  198. package/dist/twist.js.map +1 -1
  199. package/dist/utils/types.d.ts +19 -0
  200. package/dist/utils/types.d.ts.map +1 -1
  201. package/package.json +1 -1
package/README.md CHANGED
@@ -136,17 +136,21 @@ Twist tools provide capabilities to twists. They are usually unopinionated and d
136
136
 
137
137
  Think of an **Activity as a thread** and **Notes as messages in that thread**. Always create activities with an initial note, and add notes for updates rather than creating new activities.
138
138
 
139
+ **Data sync:** When syncing from external systems, use `Activity.source` (canonical URL) and `Note.key` for automatic upserts without manual ID tracking. See the [Sync Strategies guide](https://twist.plot.day/documents/Sync_Strategies.html) for detailed patterns.
140
+
139
141
  **Action scheduling:** When creating Actions (tasks), omitting the `start` field defaults to "Do Now" (current time). For most integrations, explicitly set `start: null` to create backlog items ("Do Someday"), only using "Do Now" for urgent or in-progress tasks.
140
142
 
141
143
  ```typescript
142
- // Create an activity with an initial note (thread with first message)
144
+ // Create an activity with source for automatic deduplication
143
145
  await this.tools.plot.createActivity({
146
+ source: "https://github.com/org/repo/pull/123", // Enables automatic upserts
144
147
  type: ActivityType.Action,
145
148
  title: "Review pull request",
146
149
  start: null, // "Do Someday" - backlog item (recommended default)
147
- // Tracked via UUID mapping
148
150
  notes: [
149
151
  {
152
+ activity: { source: "https://github.com/org/repo/pull/123" },
153
+ key: "description", // Use key for upsertable notes
150
154
  content: "New PR ready for review",
151
155
  links: [
152
156
  {
@@ -159,9 +163,10 @@ await this.tools.plot.createActivity({
159
163
  ],
160
164
  });
161
165
 
162
- // Add a note to existing activity (add message to thread)
166
+ // Add or update a note using key (upserts if key exists)
163
167
  await this.tools.plot.createNote({
164
- activity: { id: activityId },
168
+ activity: { source: "https://github.com/org/repo/pull/123" },
169
+ key: "approval", // Using key enables upserts
165
170
  content: "LGTM! Approved ✅",
166
171
  });
167
172
  ```
@@ -199,6 +204,7 @@ plot priority create # Create new priority
199
204
 
200
205
  - [Getting Started](https://twist.plot.day/documents/Getting_Started.html) - Complete walkthrough
201
206
  - [Core Concepts](https://twist.plot.day/documents/Core_Concepts.html) - Twists, tools, and architecture
207
+ - [Sync Strategies](https://twist.plot.day/documents/Sync_Strategies.html) - Data synchronization patterns (upserts, deduplication, ID management)
202
208
  - [Built-in Tools](https://twist.plot.day/documents/Built-in_Tools.html) - Plot, Store, AI, and more
203
209
  - [Building Custom Tools](https://twist.plot.day/documents/Building_Custom_Tools.html) - Create reusable twist tools
204
210
  - [Runtime Environment](https://twist.plot.day/documents/Runtime_Environment.html) - Execution constraints and optimization
@@ -27,10 +27,30 @@ Plot Twists are TypeScript classes that extend the `Twist` base class. Twists in
27
27
 
28
28
  1. **Always create Activities with an initial Note** - The title is just a summary; detailed content goes in Notes
29
29
  2. **Add Notes to existing Activities for updates** - Don't create a new Activity for each related message
30
- 3. **Track external items using generated UUIDs** - Store mappings between external IDs and Plot UUIDs for deduplication
31
- 4. **Most Activities should be `ActivityType.Note`** - Use `Action` only for tasks with `doneAt`, use `Event` only for items with `start`/`end`
30
+ 3. **Use Activity.source and Note.key for automatic upserts (Recommended)** - Set Activity.source to the external item's URL for deduplication, and use Note.key for upsertable note content. No manual ID tracking needed.
31
+ 4. **For advanced cases, use generated UUIDs** - Only when you need multiple Plot activities per external item (see SYNC_STRATEGIES.md)
32
+ 5. **Most Activities should be `ActivityType.Note`** - Use `Action` only for tasks with `done`, use `Event` only for items with `start`/`end`
32
33
 
33
- ### Decision Tree
34
+ ### Recommended Decision Tree (Strategy 2: Upsert via Source/Key)
35
+
36
+ ```
37
+ New event/task/conversation from external system?
38
+ ├─ Has stable URL or ID?
39
+ │ └─ Yes → Set Activity.source to the canonical URL/ID
40
+ │ Create Activity (Plot handles deduplication automatically)
41
+ │ Use Note.key for different note types:
42
+ │ - "description" for main content
43
+ │ - "metadata" for status/priority/assignee
44
+ │ - "comment-{id}" for individual comments
45
+
46
+ └─ No stable identifier OR need multiple Plot activities per external item?
47
+ └─ Use Advanced Pattern (Strategy 3: Generate and Store IDs)
48
+ See SYNC_STRATEGIES.md for details
49
+ ```
50
+
51
+ ### Advanced Decision Tree (Strategy 3: Generate and Store IDs)
52
+
53
+ Only use when source/key upserts aren't sufficient (e.g., creating multiple activities from one external item):
34
54
 
35
55
  ```
36
56
  New event/task/conversation?
@@ -269,7 +289,9 @@ async onAuthComplete(authResult: { authToken: string }, provider: string) {
269
289
 
270
290
  ## Sync Pattern
271
291
 
272
- Pattern for syncing external data - demonstrates adding Notes to existing Activities:
292
+ ### Recommended: Upsert via Source/Key (Strategy 2)
293
+
294
+ Pattern for syncing external data using automatic upserts - **no manual ID tracking needed**:
273
295
 
274
296
  ```typescript
275
297
  async startSync(calendarId: string): Promise<void> {
@@ -284,6 +306,57 @@ async startSync(calendarId: string): Promise<void> {
284
306
  }
285
307
 
286
308
  async handleEvent(
309
+ event: ExternalEvent,
310
+ calendarId: string
311
+ ): Promise<void> {
312
+ // Use the event's canonical URL as the source for automatic deduplication
313
+ const activity: NewActivityWithNotes = {
314
+ source: event.htmlLink, // or event.url, depending on your external system
315
+ type: ActivityType.Event,
316
+ title: event.summary || "(No title)",
317
+ start: event.start?.dateTime || event.start?.date || null,
318
+ end: event.end?.dateTime || event.end?.date || null,
319
+ notes: [],
320
+ };
321
+
322
+ // Add description as an upsertable note
323
+ if (event.description) {
324
+ activity.notes.push({
325
+ activity: { source: event.htmlLink },
326
+ key: "description", // This key enables upserts - same key updates the note
327
+ content: event.description,
328
+ });
329
+ }
330
+
331
+ // Add attendees as an upsertable note
332
+ if (event.attendees?.length) {
333
+ const attendeeList = event.attendees
334
+ .map(a => `- ${a.email}${a.displayName ? ` (${a.displayName})` : ''}`)
335
+ .join('\n');
336
+
337
+ activity.notes.push({
338
+ activity: { source: event.htmlLink },
339
+ key: "attendees", // Different key for different note types
340
+ content: `## Attendees\n${attendeeList}`,
341
+ });
342
+ }
343
+
344
+ // Create or update - Plot automatically handles deduplication based on source
345
+ await this.tools.plot.createActivity(activity);
346
+ }
347
+
348
+ async stopSync(calendarId: string): Promise<void> {
349
+ const authToken = await this.get<string>("auth_token");
350
+ await this.tools.calendarTool.stopSync(authToken, calendarId);
351
+ }
352
+ ```
353
+
354
+ ### Advanced: Generate and Store IDs (Strategy 3)
355
+
356
+ Only use this pattern when you need to create multiple Plot activities from a single external item, or when the external system doesn't provide stable identifiers. See SYNC_STRATEGIES.md for details.
357
+
358
+ ```typescript
359
+ async handleEventAdvanced(
287
360
  incomingActivity: NewActivityWithNotes,
288
361
  calendarId: string
289
362
  ): Promise<void> {
@@ -320,11 +393,6 @@ async handleEvent(
320
393
  id: activityId,
321
394
  });
322
395
  }
323
-
324
- async stopSync(calendarId: string): Promise<void> {
325
- const authToken = await this.get<string>("auth_token");
326
- await this.tools.calendarTool.stopSync(authToken, calendarId);
327
- }
328
396
  ```
329
397
 
330
398
  ## Calendar Selection Pattern
@@ -419,24 +487,18 @@ async syncBatch(args: any, resourceId: string): Promise<void> {
419
487
  // Process one batch (keep under time limit)
420
488
  const result = await this.fetchBatch(state.nextPageToken);
421
489
 
422
- // Process results (create activities with Notes)
490
+ // Process results using source/key pattern (automatic upserts, no manual tracking)
423
491
  for (const item of result.items) {
424
- // Check if already synced
425
- const mappingKey = `item_mapping:${resourceId}:${item.id}`;
426
- const existingId = await this.get<Uuid>(mappingKey);
427
-
428
- if (!existingId) {
429
- // New item - generate UUID and store mapping
430
- const activityId = Uuid.Generate();
431
- await this.set(mappingKey, activityId);
432
-
433
- await this.tools.plot.createActivity({
434
- id: activityId,
435
- type: ActivityType.Note,
436
- title: item.title,
437
- notes: [{ id: Uuid.Generate(), content: item.description }],
438
- });
439
- }
492
+ await this.tools.plot.createActivity({
493
+ source: item.url, // Use item's canonical URL for automatic deduplication
494
+ type: ActivityType.Note,
495
+ title: item.title,
496
+ notes: [{
497
+ activity: { source: item.url },
498
+ key: "description", // Use key for upsertable notes
499
+ content: item.description,
500
+ }],
501
+ });
440
502
  }
441
503
 
442
504
  if (result.nextPageToken) {
@@ -495,9 +557,9 @@ try {
495
557
  - **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.
496
558
  - **Processing self-created activities** - Other users may change an Activity created by the twist, resulting in an \`activity\` call. Be sure to check the \`changes === null\` and/or \`activity.author.id !== this.id\` to avoid re-processing.
497
559
  - **Always create Activities with Notes** - See "Understanding Activities and Notes" section above for the thread/message pattern and decision tree.
498
- - **Use correct Activity types** - Most should be `ActivityType.Note`. Only use `Action` for tasks with `doneAt`, and `Event` for items with `start`/`end`.
499
- - **Track external items with UUID mappings** - Generate UUIDs with `Uuid.Generate()` and store mappings (`external_id uuid`) for deduplication. Never rely on the `source` field.
500
- - **Add Notes to existing Activities** - Look up stored UUID mappings before creating new Activities. Think thread replies, not new threads.
560
+ - **Use correct Activity types** - Most should be `ActivityType.Note`. Only use `Action` for tasks with `done`, and `Event` for items with `start`/`end`.
561
+ - **Use Activity.source and Note.key for automatic upserts (Recommended)** - Set Activity.source to the external item's URL for automatic deduplication. Only use UUID generation and storage for advanced cases (see SYNC_STRATEGIES.md).
562
+ - **Add Notes to existing Activities** - For source/key pattern, reference activities by source. For UUID pattern, look up stored mappings before creating new Activities. Think thread replies, not new threads.
501
563
  - Tools are declared in the `build` method and accessed via `this.tools.toolName` in twist methods.
502
564
  - **Don't forget runtime limits** - Each execution has ~10 seconds. Break long operations into batches with the Tasks tool. Process enough items per batch to be efficient, but few enough to stay under time limits.
503
565
  - **Always use Callbacks tool for persistent references** - Direct function references don't survive worker restarts.
@@ -27,10 +27,30 @@ Plot Twists are TypeScript classes that extend the `Twist` base class. Twists in
27
27
 
28
28
  1. **Always create Activities with an initial Note** - The title is just a summary; detailed content goes in Notes
29
29
  2. **Add Notes to existing Activities for updates** - Don't create a new Activity for each related message
30
- 3. **Track external items using generated UUIDs** - Store mappings between external IDs and Plot UUIDs for deduplication
31
- 4. **Most Activities should be `ActivityType.Note`** - Use `Action` only for tasks with `doneAt`, use `Event` only for items with `start`/`end`
30
+ 3. **Use Activity.source and Note.key for automatic upserts (Recommended)** - Set Activity.source to the external item's URL for deduplication, and use Note.key for upsertable note content. No manual ID tracking needed.
31
+ 4. **For advanced cases, use generated UUIDs** - Only when you need multiple Plot activities per external item (see SYNC_STRATEGIES.md)
32
+ 5. **Most Activities should be `ActivityType.Note`** - Use `Action` only for tasks with `done`, use `Event` only for items with `start`/`end`
32
33
 
33
- ### Decision Tree
34
+ ### Recommended Decision Tree (Strategy 2: Upsert via Source/Key)
35
+
36
+ ```
37
+ New event/task/conversation from external system?
38
+ ├─ Has stable URL or ID?
39
+ │ └─ Yes → Set Activity.source to the canonical URL/ID
40
+ │ Create Activity (Plot handles deduplication automatically)
41
+ │ Use Note.key for different note types:
42
+ │ - "description" for main content
43
+ │ - "metadata" for status/priority/assignee
44
+ │ - "comment-{id}" for individual comments
45
+
46
+ └─ No stable identifier OR need multiple Plot activities per external item?
47
+ └─ Use Advanced Pattern (Strategy 3: Generate and Store IDs)
48
+ See SYNC_STRATEGIES.md for details
49
+ ```
50
+
51
+ ### Advanced Decision Tree (Strategy 3: Generate and Store IDs)
52
+
53
+ Only use when source/key upserts aren't sufficient (e.g., creating multiple activities from one external item):
34
54
 
35
55
  ```
36
56
  New event/task/conversation?
@@ -269,7 +289,9 @@ async onAuthComplete(authResult: { authToken: string }, provider: string) {
269
289
 
270
290
  ## Sync Pattern
271
291
 
272
- Pattern for syncing external data - demonstrates adding Notes to existing Activities:
292
+ ### Recommended: Upsert via Source/Key (Strategy 2)
293
+
294
+ Pattern for syncing external data using automatic upserts - **no manual ID tracking needed**:
273
295
 
274
296
  ```typescript
275
297
  async startSync(calendarId: string): Promise<void> {
@@ -284,6 +306,57 @@ async startSync(calendarId: string): Promise<void> {
284
306
  }
285
307
 
286
308
  async handleEvent(
309
+ event: ExternalEvent,
310
+ calendarId: string
311
+ ): Promise<void> {
312
+ // Use the event's canonical URL as the source for automatic deduplication
313
+ const activity: NewActivityWithNotes = {
314
+ source: event.htmlLink, // or event.url, depending on your external system
315
+ type: ActivityType.Event,
316
+ title: event.summary || "(No title)",
317
+ start: event.start?.dateTime || event.start?.date || null,
318
+ end: event.end?.dateTime || event.end?.date || null,
319
+ notes: [],
320
+ };
321
+
322
+ // Add description as an upsertable note
323
+ if (event.description) {
324
+ activity.notes.push({
325
+ activity: { source: event.htmlLink },
326
+ key: "description", // This key enables upserts - same key updates the note
327
+ content: event.description,
328
+ });
329
+ }
330
+
331
+ // Add attendees as an upsertable note
332
+ if (event.attendees?.length) {
333
+ const attendeeList = event.attendees
334
+ .map(a => `- ${a.email}${a.displayName ? ` (${a.displayName})` : ''}`)
335
+ .join('\n');
336
+
337
+ activity.notes.push({
338
+ activity: { source: event.htmlLink },
339
+ key: "attendees", // Different key for different note types
340
+ content: `## Attendees\n${attendeeList}`,
341
+ });
342
+ }
343
+
344
+ // Create or update - Plot automatically handles deduplication based on source
345
+ await this.tools.plot.createActivity(activity);
346
+ }
347
+
348
+ async stopSync(calendarId: string): Promise<void> {
349
+ const authToken = await this.get<string>("auth_token");
350
+ await this.tools.calendarTool.stopSync(authToken, calendarId);
351
+ }
352
+ ```
353
+
354
+ ### Advanced: Generate and Store IDs (Strategy 3)
355
+
356
+ Only use this pattern when you need to create multiple Plot activities from a single external item, or when the external system doesn't provide stable identifiers. See SYNC_STRATEGIES.md for details.
357
+
358
+ ```typescript
359
+ async handleEventAdvanced(
287
360
  incomingActivity: NewActivityWithNotes,
288
361
  calendarId: string
289
362
  ): Promise<void> {
@@ -320,11 +393,6 @@ async handleEvent(
320
393
  id: activityId,
321
394
  });
322
395
  }
323
-
324
- async stopSync(calendarId: string): Promise<void> {
325
- const authToken = await this.get<string>("auth_token");
326
- await this.tools.calendarTool.stopSync(authToken, calendarId);
327
- }
328
396
  ```
329
397
 
330
398
  ## Calendar Selection Pattern
@@ -419,24 +487,18 @@ async syncBatch(args: any, resourceId: string): Promise<void> {
419
487
  // Process one batch (keep under time limit)
420
488
  const result = await this.fetchBatch(state.nextPageToken);
421
489
 
422
- // Process results (create activities with Notes)
490
+ // Process results using source/key pattern (automatic upserts, no manual tracking)
423
491
  for (const item of result.items) {
424
- // Check if already synced
425
- const mappingKey = `item_mapping:${resourceId}:${item.id}`;
426
- const existingId = await this.get<Uuid>(mappingKey);
427
-
428
- if (!existingId) {
429
- // New item - generate UUID and store mapping
430
- const activityId = Uuid.Generate();
431
- await this.set(mappingKey, activityId);
432
-
433
- await this.tools.plot.createActivity({
434
- id: activityId,
435
- type: ActivityType.Note,
436
- title: item.title,
437
- notes: [{ id: Uuid.Generate(), content: item.description }],
438
- });
439
- }
492
+ await this.tools.plot.createActivity({
493
+ source: item.url, // Use item's canonical URL for automatic deduplication
494
+ type: ActivityType.Note,
495
+ title: item.title,
496
+ notes: [{
497
+ activity: { source: item.url },
498
+ key: "description", // Use key for upsertable notes
499
+ content: item.description,
500
+ }],
501
+ });
440
502
  }
441
503
 
442
504
  if (result.nextPageToken) {
@@ -495,9 +557,9 @@ try {
495
557
  - **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.
496
558
  - **Processing self-created activities** - Other users may change an Activity created by the twist, resulting in an \`activity\` call. Be sure to check the \`changes === null\` and/or \`activity.author.id !== this.id\` to avoid re-processing.
497
559
  - **Always create Activities with Notes** - See "Understanding Activities and Notes" section above for the thread/message pattern and decision tree.
498
- - **Use correct Activity types** - Most should be `ActivityType.Note`. Only use `Action` for tasks with `doneAt`, and `Event` for items with `start`/`end`.
499
- - **Track external items with UUID mappings** - Generate UUIDs with `Uuid.Generate()` and store mappings (`external_id uuid`) for deduplication. Never rely on the `source` field.
500
- - **Add Notes to existing Activities** - Look up stored UUID mappings before creating new Activities. Think thread replies, not new threads.
560
+ - **Use correct Activity types** - Most should be `ActivityType.Note`. Only use `Action` for tasks with `done`, and `Event` for items with `start`/`end`.
561
+ - **Use Activity.source and Note.key for automatic upserts (Recommended)** - Set Activity.source to the external item's URL for automatic deduplication. Only use UUID generation and storage for advanced cases (see SYNC_STRATEGIES.md).
562
+ - **Add Notes to existing Activities** - For source/key pattern, reference activities by source. For UUID pattern, look up stored mappings before creating new Activities. Think thread replies, not new threads.
501
563
  - Tools are declared in the `build` method and accessed via `this.tools.toolName` in twist methods.
502
564
  - **Don't forget runtime limits** - Each execution has ~10 seconds. Break long operations into batches with the Tasks tool. Process enough items per batch to be efficient, but few enough to stay under time limits.
503
565
  - **Always use Callbacks tool for persistent references** - Direct function references don't survive worker restarts.
@@ -1,4 +1,5 @@
1
1
  import type { ActivityLink, SyncUpdate } from "../index";
2
+ import type { NoFunctions } from "../utils/types";
2
3
  /**
3
4
  * Represents successful calendar authorization.
4
5
  *
@@ -54,9 +55,13 @@ export interface SyncOptions {
54
55
  * 5. Start sync for selected calendars
55
56
  * 6. Process incoming events via callbacks
56
57
  *
58
+ * **Recommended Data Sync Strategy:**
59
+ * Use Activity.source and Note.key for automatic upserts without manual ID tracking.
60
+ * See SYNC_STRATEGIES.md for detailed patterns and when to use alternative approaches.
61
+ *
57
62
  * @example
58
63
  * ```typescript
59
- * // Typical calendar integration flow
64
+ * // Typical calendar integration flow using source/key upserts
60
65
  * class MyCalendarTwist extends Twist {
61
66
  * private googleCalendar: GoogleCalendar;
62
67
  *
@@ -78,9 +83,12 @@ export interface SyncOptions {
78
83
  * const primaryCalendar = calendars.find(c => c.primary);
79
84
  * if (primaryCalendar) {
80
85
  * await this.googleCalendar.startSync(
81
- * auth.authToken,
82
- * primaryCalendar.id,
83
- * "onCalendarEvent"
86
+ * {
87
+ * authToken: auth.authToken,
88
+ * calendarId: primaryCalendar.id
89
+ * },
90
+ * this.onCalendarEvent,
91
+ * { initialSync: true }
84
92
  * );
85
93
  * }
86
94
  * }
@@ -89,21 +97,10 @@ export interface SyncOptions {
89
97
  * syncUpdate: SyncUpdate,
90
98
  * syncMeta: { initialSync: boolean }
91
99
  * ) {
92
- * // Step 4: Process synced events
93
- * if ('activityId' in syncUpdate) {
94
- * // Update existing activity
95
- * if (syncUpdate.update) {
96
- * await this.plot.updateActivity(syncUpdate.update);
97
- * }
98
- * if (syncUpdate.notes) {
99
- * for (const note of syncUpdate.notes) {
100
- * await this.plot.createNote(note);
101
- * }
102
- * }
103
- * } else {
104
- * // Create new activity
105
- * await this.plot.createActivity(syncUpdate);
106
- * }
100
+ * // Step 4: Process synced events using source/key pattern
101
+ * // The sync update will automatically use the event's URL as the source
102
+ * // for deduplication - no manual ID tracking needed
103
+ * await this.plot.createActivity(syncUpdate);
107
104
  * }
108
105
  * }
109
106
  * ```
@@ -136,21 +133,31 @@ export interface CalendarTool {
136
133
  * event import and ongoing change notifications. The callback function
137
134
  * will be invoked for each synced event.
138
135
  *
139
- * Tools implementing this should:
140
- * - Generate UUIDs for new activities and notes using Uuid.Generate()
141
- * - Track activity IDs locally to detect updates vs new items
142
- * - Send NewActivityWithNotes for new events
143
- * - Send update object with activityId for changed events
136
+ * **Recommended Implementation** (Strategy 2 - Upsert via Source/Key):
137
+ * - Set Activity.source to the event's canonical URL (e.g., event.htmlLink)
138
+ * - Use Note.key for event details (description, attendees, etc.) to enable upserts
139
+ * - No manual ID tracking needed - Plot handles deduplication automatically
140
+ * - Send NewActivityWithNotes for all events (creates new or updates existing)
144
141
  * - Set activity.unread = false for initial sync, true for incremental updates
145
142
  *
146
- * @param authToken - Authorization token for calendar access
147
- * @param calendarId - ID of the calendar to sync
143
+ * **Alternative** (Strategy 3 - Advanced cases):
144
+ * - Use Uuid.Generate() and store ID mappings when creating multiple activities per event
145
+ * - See SYNC_STRATEGIES.md for when this is appropriate
146
+ *
147
+ * @param options - Sync configuration options
148
+ * @param options.authToken - Authorization token for calendar access
149
+ * @param options.calendarId - ID of the calendar to sync
150
+ * @param options.timeMin - Earliest date to sync events from (inclusive)
151
+ * @param options.timeMax - Latest date to sync events to (exclusive)
148
152
  * @param callback - Function receiving (syncUpdate, ...extraArgs) for each synced event
149
- * @param extraArgs - Additional arguments to pass to the callback (type-checked)
153
+ * @param extraArgs - Additional arguments to pass to the callback (type-checked, no functions allowed)
150
154
  * @returns Promise that resolves when sync setup is complete
151
155
  * @throws When auth token is invalid or calendar doesn't exist
152
156
  */
153
- startSync<TCallback extends (syncUpdate: SyncUpdate, ...args: any[]) => any>(authToken: string, calendarId: string, callback: TCallback, ...extraArgs: TCallback extends (syncUpdate: any, ...rest: infer R) => any ? R : []): Promise<void>;
157
+ startSync<TCallback extends (syncUpdate: SyncUpdate, ...args: any[]) => any>(options: {
158
+ authToken: string;
159
+ calendarId: string;
160
+ } & SyncOptions, callback: TCallback, ...extraArgs: TCallback extends (syncUpdate: any, ...rest: infer R) => any ? NoFunctions<R> : []): Promise<void>;
154
161
  /**
155
162
  * Stops synchronizing events from a specific calendar.
156
163
  *
@@ -1 +1 @@
1
- {"version":3,"file":"calendar.d.ts","sourceRoot":"","sources":["../../src/common/calendar.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAwB,UAAU,EAAE,MAAM,UAAU,CAAC;AAE/E;;;;;;GAMG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,2CAA2C;IAC3C,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,WAAW,QAAQ;IACvB,6DAA6D;IAC7D,EAAE,EAAE,MAAM,CAAC;IACX,0CAA0C;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,oEAAoE;IACpE,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,0DAA0D;IAC1D,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC1B,oDAAoD;IACpD,OAAO,CAAC,EAAE,IAAI,CAAC;IACf,gDAAgD;IAChD,OAAO,CAAC,EAAE,IAAI,CAAC;CAChB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoEG;AACH,MAAM,WAAW,YAAY;IAC3B;;;;;;OAMG;IACH,WAAW,CAAC,SAAS,SAAS,CAAC,IAAI,EAAE,YAAY,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,EACvE,QAAQ,EAAE,SAAS,EACnB,GAAG,SAAS,EAAE,SAAS,SAAS,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,EAAE,MAAM,CAAC,KAAK,GAAG,GAChE,CAAC,GACD,EAAE,GACL,OAAO,CAAC,YAAY,CAAC,CAAC;IAEzB;;;;;;;;;;OAUG;IACH,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;IAErD;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,SAAS,CACP,SAAS,SAAS,CAChB,UAAU,EAAE,UAAU,EACtB,GAAG,IAAI,EAAE,GAAG,EAAE,KACX,GAAG,EAER,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,SAAS,EACnB,GAAG,SAAS,EAAE,SAAS,SAAS,CAC9B,UAAU,EAAE,GAAG,EACf,GAAG,IAAI,EAAE,MAAM,CAAC,KACb,GAAG,GACJ,CAAC,GACD,EAAE,GACL,OAAO,CAAC,IAAI,CAAC,CAAC;IAEjB;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAChE"}
1
+ {"version":3,"file":"calendar.d.ts","sourceRoot":"","sources":["../../src/common/calendar.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAwB,UAAU,EAAE,MAAM,UAAU,CAAC;AAC/E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAElD;;;;;;GAMG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,2CAA2C;IAC3C,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,WAAW,QAAQ;IACvB,6DAA6D;IAC7D,EAAE,EAAE,MAAM,CAAC;IACX,0CAA0C;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,oEAAoE;IACpE,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,0DAA0D;IAC1D,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC1B,oDAAoD;IACpD,OAAO,CAAC,EAAE,IAAI,CAAC;IACf,gDAAgD;IAChD,OAAO,CAAC,EAAE,IAAI,CAAC;CAChB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgEG;AACH,MAAM,WAAW,YAAY;IAC3B;;;;;;OAMG;IACH,WAAW,CAAC,SAAS,SAAS,CAAC,IAAI,EAAE,YAAY,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,EACvE,QAAQ,EAAE,SAAS,EACnB,GAAG,SAAS,EAAE,SAAS,SAAS,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,EAAE,MAAM,CAAC,KAAK,GAAG,GAChE,CAAC,GACD,EAAE,GACL,OAAO,CAAC,YAAY,CAAC,CAAC;IAEzB;;;;;;;;;;OAUG;IACH,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;IAErD;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;IACH,SAAS,CAAC,SAAS,SAAS,CAAC,UAAU,EAAE,UAAU,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,EACzE,OAAO,EAAE;QACP,SAAS,EAAE,MAAM,CAAC;QAClB,UAAU,EAAE,MAAM,CAAC;KACpB,GAAG,WAAW,EACf,QAAQ,EAAE,SAAS,EACnB,GAAG,SAAS,EAAE,SAAS,SAAS,CAAC,UAAU,EAAE,GAAG,EAAE,GAAG,IAAI,EAAE,MAAM,CAAC,KAAK,GAAG,GACtE,WAAW,CAAC,CAAC,CAAC,GACd,EAAE,GACL,OAAO,CAAC,IAAI,CAAC,CAAC;IAEjB;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAChE"}
@@ -1,4 +1,5 @@
1
1
  import type { ActivityLink, SyncUpdate } from "../index";
2
+ import type { NoFunctions } from "../utils/types";
2
3
  /**
3
4
  * Represents a successful messaging service authorization.
4
5
  *
@@ -41,6 +42,10 @@ export interface MessageSyncOptions {
41
42
  *
42
43
  * All synced messages/emails are converted to ActivityWithNotes objects.
43
44
  * Each email thread or chat conversation becomes an Activity with Notes for each message.
45
+ *
46
+ * **Recommended Data Sync Strategy:**
47
+ * Use Activity.source (thread URL or ID) and Note.key (message ID) for automatic upserts.
48
+ * See SYNC_STRATEGIES.md for detailed patterns.
44
49
  */
45
50
  export interface MessagingTool {
46
51
  /**
@@ -64,21 +69,30 @@ export interface MessagingTool {
64
69
  * Email threads and chat conversations are converted to SyncUpdate objects,
65
70
  * which can be either new items or updates to existing items.
66
71
  *
67
- * Tools implementing this should:
68
- * - Generate UUIDs for new activities and notes using Uuid.Generate()
69
- * - Track activity IDs locally to detect updates vs new threads
70
- * - Send NewActivityWithNotes for new threads
71
- * - Send update object with activityId and new notes for new messages in existing threads
72
+ * **Recommended Implementation** (Strategy 2 - Upsert via Source/Key):
73
+ * - Set Activity.source to the thread/conversation URL or stable ID (e.g., "slack:{channelId}:{threadTs}")
74
+ * - Use Note.key for individual messages (e.g., "message-{messageId}")
75
+ * - Each message becomes a separate note with unique key for upserts
76
+ * - No manual ID tracking needed - Plot handles deduplication automatically
77
+ * - Send NewActivityWithNotes for all threads (creates new or updates existing)
72
78
  * - Set activity.unread = false for initial sync, true for incremental updates
73
79
  *
74
- * @param authToken - Authorization token for access
75
- * @param channelId - ID of the channel (e.g., channel, inbox) to sync
80
+ * **Alternative** (Strategy 3 - Advanced cases):
81
+ * - Use Uuid.Generate() and store ID mappings when creating multiple activities per thread
82
+ * - See SYNC_STRATEGIES.md for when this is appropriate
83
+ *
84
+ * @param options - Sync configuration options
85
+ * @param options.authToken - Authorization token for access
86
+ * @param options.channelId - ID of the channel (e.g., channel, inbox) to sync
87
+ * @param options.timeMin - Earliest date to sync events from (inclusive)
76
88
  * @param callback - Function receiving (syncUpdate, ...extraArgs) for each synced conversation
77
- * @param options - Optional configuration for limiting the sync scope (e.g., time range)
78
- * @param extraArgs - Additional arguments to pass to the callback (type-checked)
89
+ * @param extraArgs - Additional arguments to pass to the callback (type-checked, no functions allowed)
79
90
  * @returns Promise that resolves when sync setup is complete
80
91
  */
81
- startSync<TCallback extends (syncUpdate: SyncUpdate, ...args: any[]) => any>(authToken: string, channelId: string, callback: TCallback, options?: MessageSyncOptions, ...extraArgs: TCallback extends (syncUpdate: any, ...rest: infer R) => any ? R : []): Promise<void>;
92
+ startSync<TCallback extends (syncUpdate: SyncUpdate, ...args: any[]) => any>(options: {
93
+ authToken: string;
94
+ channelId: string;
95
+ } & MessageSyncOptions, callback: TCallback, ...extraArgs: TCallback extends (syncUpdate: any, ...rest: infer R) => any ? NoFunctions<R> : []): Promise<void>;
82
96
  /**
83
97
  * Stops synchronizing messages from a specific channel.
84
98
  *
@@ -1 +1 @@
1
- {"version":3,"file":"messaging.d.ts","sourceRoot":"","sources":["../../src/common/messaging.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAwB,UAAU,EAAE,MAAM,UAAU,CAAC;AAE/E;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,4CAA4C;IAC5C,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,4DAA4D;IAC5D,EAAE,EAAE,MAAM,CAAC;IACX,uFAAuF;IACvF,IAAI,EAAE,MAAM,CAAC;IACb,mEAAmE;IACnE,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,4EAA4E;IAC5E,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;;;;GAKG;AACH,MAAM,WAAW,kBAAkB;IACjC,oDAAoD;IACpD,OAAO,CAAC,EAAE,IAAI,CAAC;CAChB;AAED;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B;;;;;;OAMG;IACH,WAAW,CAAC,SAAS,SAAS,CAAC,IAAI,EAAE,aAAa,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,EACxE,QAAQ,EAAE,SAAS,EACnB,GAAG,SAAS,EAAE,SAAS,SAAS,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,EAAE,MAAM,CAAC,KAAK,GAAG,GAChE,CAAC,GACD,EAAE,GACL,OAAO,CAAC,YAAY,CAAC,CAAC;IAEzB;;;;;OAKG;IACH,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CAAC;IAE1D;;;;;;;;;;;;;;;;;;;OAmBG;IACH,SAAS,CACP,SAAS,SAAS,CAAC,UAAU,EAAE,UAAU,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,EAEjE,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,SAAS,EACnB,OAAO,CAAC,EAAE,kBAAkB,EAC5B,GAAG,SAAS,EAAE,SAAS,SAAS,CAAC,UAAU,EAAE,GAAG,EAAE,GAAG,IAAI,EAAE,MAAM,CAAC,KAAK,GAAG,GACtE,CAAC,GACD,EAAE,GACL,OAAO,CAAC,IAAI,CAAC,CAAC;IAEjB;;;;;;OAMG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/D"}
1
+ {"version":3,"file":"messaging.d.ts","sourceRoot":"","sources":["../../src/common/messaging.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAwB,UAAU,EAAE,MAAM,UAAU,CAAC;AAC/E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAElD;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,4CAA4C;IAC5C,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,4DAA4D;IAC5D,EAAE,EAAE,MAAM,CAAC;IACX,uFAAuF;IACvF,IAAI,EAAE,MAAM,CAAC;IACb,mEAAmE;IACnE,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,4EAA4E;IAC5E,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;;;;GAKG;AACH,MAAM,WAAW,kBAAkB;IACjC,oDAAoD;IACpD,OAAO,CAAC,EAAE,IAAI,CAAC;CAChB;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,aAAa;IAC5B;;;;;;OAMG;IACH,WAAW,CAAC,SAAS,SAAS,CAAC,IAAI,EAAE,aAAa,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,EACxE,QAAQ,EAAE,SAAS,EACnB,GAAG,SAAS,EAAE,SAAS,SAAS,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,EAAE,MAAM,CAAC,KAAK,GAAG,GAChE,CAAC,GACD,EAAE,GACL,OAAO,CAAC,YAAY,CAAC,CAAC;IAEzB;;;;;OAKG;IACH,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CAAC;IAE1D;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACH,SAAS,CAAC,SAAS,SAAS,CAAC,UAAU,EAAE,UAAU,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,EACzE,OAAO,EAAE;QACP,SAAS,EAAE,MAAM,CAAC;QAClB,SAAS,EAAE,MAAM,CAAC;KACnB,GAAG,kBAAkB,EACtB,QAAQ,EAAE,SAAS,EACnB,GAAG,SAAS,EAAE,SAAS,SAAS,CAAC,UAAU,EAAE,GAAG,EAAE,GAAG,IAAI,EAAE,MAAM,CAAC,KAAK,GAAG,GACtE,WAAW,CAAC,CAAC,CAAC,GACd,EAAE,GACL,OAAO,CAAC,IAAI,CAAC,CAAC;IAEjB;;;;;;OAMG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/D"}
@@ -1,4 +1,5 @@
1
1
  import type { ActivityLink, ActivityUpdate, SyncUpdate } from "../index";
2
+ import type { NoFunctions } from "../utils/types";
2
3
  /**
3
4
  * Represents a successful project management service authorization.
4
5
  *
@@ -41,6 +42,10 @@ export interface ProjectSyncOptions {
41
42
  *
42
43
  * All synced issues/tasks are converted to ActivityWithNotes objects.
43
44
  * Each issue becomes an Activity with Notes for the description and comments.
45
+ *
46
+ * **Recommended Data Sync Strategy:**
47
+ * Use Activity.source (issue URL) and Note.key for automatic upserts.
48
+ * See SYNC_STRATEGIES.md for detailed patterns.
44
49
  */
45
50
  export interface ProjectTool {
46
51
  /**
@@ -64,22 +69,32 @@ export interface ProjectTool {
64
69
  * Issues and tasks are converted to SyncUpdate objects, which can be either
65
70
  * new items or updates to existing items.
66
71
  *
67
- * Tools implementing this should:
68
- * - Generate UUIDs for new activities and notes using Uuid.Generate()
69
- * - Track activity IDs locally to detect updates vs new items
70
- * - Send NewActivityWithNotes for new issues
71
- * - Send update object with activityId for changed issues or new comments
72
- * - Track description note ID and hash to detect description changes
72
+ * **Recommended Implementation** (Strategy 2 - Upsert via Source/Key):
73
+ * - Set Activity.source to the issue's canonical URL (e.g., Linear issue URL, Jira issue URL)
74
+ * - Use Note.key for issue details:
75
+ * - key: "description" for issue description (upserts on changes)
76
+ * - key: "metadata" for status, priority, assignee, etc.
77
+ * - key: "comment-{commentId}" for individual comments (unique per comment)
78
+ * - No manual ID tracking needed - Plot handles deduplication automatically
79
+ * - Send NewActivityWithNotes for all issues (creates new or updates existing)
73
80
  * - Set activity.unread = false for initial sync, true for incremental updates
74
81
  *
75
- * @param authToken - Authorization token for access
76
- * @param projectId - ID of the project to sync
82
+ * **Alternative** (Strategy 3 - Advanced cases):
83
+ * - Use Uuid.Generate() and store ID mappings when creating multiple activities per issue
84
+ * - See SYNC_STRATEGIES.md for when this is appropriate
85
+ *
86
+ * @param options - Sync configuration options
87
+ * @param options.authToken - Authorization token for access
88
+ * @param options.projectId - ID of the project to sync
89
+ * @param options.timeMin - Earliest date to sync issues from (inclusive)
77
90
  * @param callback - Function receiving (syncUpdate, ...extraArgs) for each synced issue
78
- * @param options - Optional configuration for limiting the sync scope (e.g., time range)
79
- * @param extraArgs - Additional arguments to pass to the callback (type-checked)
91
+ * @param extraArgs - Additional arguments to pass to the callback (type-checked, no functions allowed)
80
92
  * @returns Promise that resolves when sync setup is complete
81
93
  */
82
- startSync<TCallback extends (syncUpdate: SyncUpdate, ...args: any[]) => any>(authToken: string, projectId: string, callback: TCallback, options?: ProjectSyncOptions, ...extraArgs: TCallback extends (syncUpdate: any, ...rest: infer R) => any ? R : []): Promise<void>;
94
+ startSync<TCallback extends (syncUpdate: SyncUpdate, ...args: any[]) => any>(options: {
95
+ authToken: string;
96
+ projectId: string;
97
+ } & ProjectSyncOptions, callback: TCallback, ...extraArgs: TCallback extends (syncUpdate: any, ...rest: infer R) => any ? NoFunctions<R> : []): Promise<void>;
83
98
  /**
84
99
  * Stops synchronizing issues from a specific project.
85
100
  *
@@ -95,10 +110,10 @@ export interface ProjectTool {
95
110
  * sync activity updates back to the external service.
96
111
  *
97
112
  * The update object contains only the fields that changed, plus id and source.
98
- * Uses the combination of start and doneAt to determine workflow state:
99
- * - doneAt set → Completed/Done state
100
- * - doneAt null + start set → In Progress/Active state
101
- * - doneAt null + start null → Backlog/Todo state
113
+ * Uses the combination of start and done to determine workflow state:
114
+ * - done set → Completed/Done state
115
+ * - done null + start set → In Progress/Active state
116
+ * - done null + start null → Backlog/Todo state
102
117
  *
103
118
  * @param authToken - Authorization token for access
104
119
  * @param update - ActivityUpdate with changed fields (includes id and source)