@plotday/twister 0.49.0 → 0.51.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 (90) hide show
  1. package/dist/connector.d.ts +30 -3
  2. package/dist/connector.d.ts.map +1 -1
  3. package/dist/connector.js.map +1 -1
  4. package/dist/docs/assets/hierarchy.js +1 -1
  5. package/dist/docs/assets/search.js +1 -1
  6. package/dist/docs/classes/index.Connector.html +37 -13
  7. package/dist/docs/classes/index.Imap.html +1 -1
  8. package/dist/docs/classes/index.Options.html +1 -1
  9. package/dist/docs/classes/index.Smtp.html +1 -1
  10. package/dist/docs/classes/tools_integrations.Integrations.html +40 -10
  11. package/dist/docs/classes/tools_network.Network.html +1 -1
  12. package/dist/docs/classes/tools_plot.Plot.html +11 -19
  13. package/dist/docs/classes/tools_store.Store.html +25 -2
  14. package/dist/docs/classes/tools_tasks.Tasks.html +1 -1
  15. package/dist/docs/documents/Built-in_Tools.html +1 -1
  16. package/dist/docs/documents/Runtime_Environment.html +25 -1
  17. package/dist/docs/enums/plot.ActorType.html +4 -4
  18. package/dist/docs/enums/tools_integrations.AuthProvider.html +13 -13
  19. package/dist/docs/hierarchy.html +1 -1
  20. package/dist/docs/types/index.NewScheduleOccurrence.html +15 -2
  21. package/dist/docs/types/index.ScheduleOccurrence.html +2 -4
  22. package/dist/docs/types/index.ScheduleOccurrenceUpdate.html +1 -1
  23. package/dist/docs/types/plot.Actor.html +5 -5
  24. package/dist/docs/types/plot.Contact.html +4 -4
  25. package/dist/docs/types/plot.ContentType.html +1 -1
  26. package/dist/docs/types/plot.Link.html +24 -19
  27. package/dist/docs/types/plot.LinkUpdate.html +1 -1
  28. package/dist/docs/types/plot.NewActor.html +1 -1
  29. package/dist/docs/types/plot.NewContact.html +1 -1
  30. package/dist/docs/types/plot.NewLink.html +11 -4
  31. package/dist/docs/types/plot.NewLinkWithNotes.html +1 -1
  32. package/dist/docs/types/plot.NewNote.html +1 -1
  33. package/dist/docs/types/plot.Note.html +7 -1
  34. package/dist/docs/types/plot.NoteUpdate.html +1 -1
  35. package/dist/docs/types/plot.PlanOperation.html +1 -1
  36. package/dist/docs/types/tools_integrations.ArchiveLinkFilter.html +5 -5
  37. package/dist/docs/types/tools_integrations.AuthToken.html +4 -4
  38. package/dist/docs/types/tools_integrations.Authorization.html +4 -4
  39. package/dist/docs/types/tools_integrations.SyncContext.html +21 -2
  40. package/dist/llm-docs/connector.d.ts +1 -1
  41. package/dist/llm-docs/connector.d.ts.map +1 -1
  42. package/dist/llm-docs/connector.js +1 -1
  43. package/dist/llm-docs/connector.js.map +1 -1
  44. package/dist/llm-docs/plot.d.ts +1 -1
  45. package/dist/llm-docs/plot.d.ts.map +1 -1
  46. package/dist/llm-docs/plot.js +1 -1
  47. package/dist/llm-docs/plot.js.map +1 -1
  48. package/dist/llm-docs/schedule.d.ts +1 -1
  49. package/dist/llm-docs/schedule.d.ts.map +1 -1
  50. package/dist/llm-docs/schedule.js +1 -1
  51. package/dist/llm-docs/schedule.js.map +1 -1
  52. package/dist/llm-docs/tools/integrations.d.ts +1 -1
  53. package/dist/llm-docs/tools/integrations.d.ts.map +1 -1
  54. package/dist/llm-docs/tools/integrations.js +1 -1
  55. package/dist/llm-docs/tools/integrations.js.map +1 -1
  56. package/dist/llm-docs/tools/plot.d.ts +1 -1
  57. package/dist/llm-docs/tools/plot.d.ts.map +1 -1
  58. package/dist/llm-docs/tools/plot.js +1 -1
  59. package/dist/llm-docs/tools/plot.js.map +1 -1
  60. package/dist/llm-docs/tools/store.d.ts +1 -1
  61. package/dist/llm-docs/tools/store.d.ts.map +1 -1
  62. package/dist/llm-docs/tools/store.js +1 -1
  63. package/dist/llm-docs/tools/store.js.map +1 -1
  64. package/dist/plot.d.ts +33 -7
  65. package/dist/plot.d.ts.map +1 -1
  66. package/dist/plot.js.map +1 -1
  67. package/dist/schedule.d.ts +18 -2
  68. package/dist/schedule.d.ts.map +1 -1
  69. package/dist/tools/integrations.d.ts +64 -0
  70. package/dist/tools/integrations.d.ts.map +1 -1
  71. package/dist/tools/integrations.js.map +1 -1
  72. package/dist/tools/plot.d.ts +0 -11
  73. package/dist/tools/plot.d.ts.map +1 -1
  74. package/dist/tools/plot.js.map +1 -1
  75. package/dist/tools/store.d.ts +40 -0
  76. package/dist/tools/store.d.ts.map +1 -1
  77. package/dist/tools/store.js.map +1 -1
  78. package/package.json +1 -1
  79. package/src/connector.ts +30 -3
  80. package/src/llm-docs/connector.ts +1 -1
  81. package/src/llm-docs/plot.ts +1 -1
  82. package/src/llm-docs/schedule.ts +1 -1
  83. package/src/llm-docs/tools/integrations.ts +1 -1
  84. package/src/llm-docs/tools/plot.ts +1 -1
  85. package/src/llm-docs/tools/store.ts +1 -1
  86. package/src/plot.ts +40 -16
  87. package/src/schedule.ts +19 -3
  88. package/src/tools/integrations.ts +69 -0
  89. package/src/tools/plot.ts +0 -12
  90. package/src/tools/store.ts +44 -0
@@ -5,4 +5,4 @@
5
5
  * Generated from: prebuild.ts
6
6
  */
7
7
 
8
- export default "import type { NewSchedule, NewScheduleOccurrence, Schedule } from \"./schedule\";\nimport { type Tag } from \"./tag\";\nimport { type Callback } from \"./tools/callbacks\";\nimport { type AuthProvider } from \"./tools/integrations\";\nimport { type JSONValue } from \"./utils/types\";\nimport { Uuid } from \"./utils/uuid\";\n\nexport { Tag } from \"./tag\";\nexport { Uuid } from \"./utils/uuid\";\nexport { type JSONValue } from \"./utils/types\";\nexport { type AuthProvider } from \"./tools/integrations\";\n\n/**\n * @fileoverview\n * Core Plot entity types for working with threads, notes, priorities, and contacts.\n *\n * ## Type Pattern: Null vs Undefined Semantics\n *\n * Plot entity types use a consistent pattern to distinguish between missing, unset, and explicitly cleared values:\n *\n * ### Entity Types (Thread, Priority, Note, Actor)\n * - **Required fields**: No `?`, cannot be `undefined`\n * - Example: `id: Uuid`, `title: string`\n * - **Nullable fields**: Use `| null` to allow explicit clearing\n * - Example: `assignee: ActorId | null`, `done: Date | null`\n * - `null` = field is explicitly unset/cleared\n * - Non-null value = field has a value\n * - **Optional nullable fields**: Use `?` with `| null` for permission-based access\n * - Example: `email?: string | null`, `name?: string | null`\n * - `undefined` = field not included (e.g., no permission to access)\n * - `null` = field included but not set\n * - Value = field has a value\n *\n * ### New* Types (NewThread, NewNote, NewPriority)\n * Used for creating or updating entities. Support partial updates by distinguishing omitted vs cleared fields:\n * - **Required fields**: Must be provided (no `?`)\n * - Example: `title: string` in NewPriority\n * - **Optional fields**: Use `?` to make them optional\n * - Example: `title?: string`, `author?: NewActor`\n * - `undefined` (omitted) = don't set/update this field\n * - Provided value = set/update this field\n * - **Optional nullable fields**: Use `?` with `| null` to support clearing\n * - Example: `assignee?: NewActor | null`\n * - `undefined` (omitted) = don't change assignee\n * - `null` = clear the assignee\n * - NewActor = set/update the assignee\n *\n * This pattern allows API consumers to:\n * 1. Omit fields they don't want to change (undefined)\n * 2. Explicitly clear fields by setting to null\n * 3. Set or update fields by providing values\n *\n * @example\n * ```typescript\n * // Creating a new thread\n * const newThread: NewThread = {\n * title: \"Review pull request\",\n * };\n *\n * // Updating a thread - only change what's specified\n * const update: ThreadUpdate = {\n * id: threadId,\n * archived: true,\n * };\n * ```\n */\n\n/**\n * Represents a unique user, contact, or twist in Plot.\n *\n * ActorIds are used throughout Plot for:\n * - Activity authors and assignees\n * - Tag creators (actor_id in activity_tag/note_tag)\n * - Mentions in activities and notes\n * - Any entity that can perform actions in Plot\n */\nexport type ActorId = string & { readonly __brand: \"ActorId\" };\n\n/**\n * Theme colors for priorities.\n */\nexport enum ThemeColor {\n /** Catalyst - Green */\n Catalyst = 0,\n /** Call to Adventure - Blue */\n CallToAdventure = 1,\n /** Rising Action - Purple */\n RisingAction = 2,\n /** Momentum - Pink-Purple */\n Momentum = 3,\n /** Turning Point - Pink */\n TurningPoint = 4,\n /** Breakthrough - Orange */\n Breakthrough = 5,\n /** Climax - Olive */\n Climax = 6,\n /** Resolution - Blue-Gray */\n Resolution = 7,\n}\n\n/**\n * Represents a priority context within Plot.\n *\n * Priorities are similar to projects in other apps. All Activity is in a Priority.\n * Priorities can be nested.\n */\nexport type Priority = {\n /** Unique identifier for the priority */\n id: Uuid;\n /** Human-readable title for the priority */\n title: string;\n /** Whether this priority has been archived */\n archived: boolean;\n /**\n * Optional key for referencing this priority.\n * Keys are unique per priority tree (a user's personal priorities or the root of a shared priority).\n */\n key: string | null;\n /** Optional theme color for the priority (0-7). If not set, inherits from parent or defaults to 7 (Resolution). */\n color: ThemeColor | null;\n};\n\n/**\n * Type for creating new priorities.\n *\n * Supports multiple creation patterns:\n * - Provide a specific UUID for the priority\n * - Provide a key for upsert within the user's priorities\n * - Omit both to auto-generate a new UUID\n *\n * Optionally specify a parent priority by ID or key for hierarchical structures.\n */\nexport type NewPriority = Pick<Priority, \"title\"> &\n Partial<Omit<Priority, \"id\" | \"title\">> &\n (\n | {\n /**\n * Unique identifier for the priority, generated by Uuid.Generate().\n * Specifying an ID allows tools to track and upsert priorities.\n */\n id: Uuid;\n }\n | {\n /**\n * Unique key for the priority within the user's priorities.\n * Can be used to upsert without knowing the UUID.\n * For example, \"@plot\" identifies the Plot priority.\n */\n key: string;\n }\n | {\n /* Neither id nor key is required. An id will be generated and returned. */\n }\n ) & {\n /** Add the new priority as the child of another priority */\n parent?: { id: Uuid } | { key: string };\n };\n\n/**\n * Type for updating existing priorities.\n * Must provide either id or key to identify the priority to update.\n * Set `parent` to move the priority under a new parent (requires PriorityAccess.Full).\n */\nexport type PriorityUpdate = ({ id: Uuid } | { key: string }) &\n Partial<Pick<Priority, \"title\" | \"archived\">> & {\n /** Move the priority under a new parent. Requires PriorityAccess.Full. */\n parent?: { id: Uuid } | { key: string };\n };\n\n/**\n * Enumeration of supported action types.\n *\n * Different action types have different behaviors when clicked by users\n * and may require different rendering approaches.\n */\nexport enum ActionType {\n /** External web links that open in browser */\n external = \"external\",\n /** Authentication flows for connecting services */\n auth = \"auth\",\n /** Callback actions that trigger twist methods when clicked */\n callback = \"callback\",\n /** Video conferencing links with provider-specific handling */\n conferencing = \"conferencing\",\n /** File attachment links stored in R2 */\n file = \"file\",\n /** Thread reference links for navigating to related threads */\n thread = \"thread\",\n /** Structured plan of operations for user approval */\n plan = \"plan\",\n}\n\n/**\n * Video conferencing providers for conferencing links.\n *\n * Used to identify the conferencing platform and provide\n * provider-specific UI elements (titles, icons, etc.).\n */\nexport enum ConferencingProvider {\n /** Google Meet */\n googleMeet = \"googleMeet\",\n /** Zoom */\n zoom = \"zoom\",\n /** Microsoft Teams */\n microsoftTeams = \"microsoftTeams\",\n /** Cisco Webex */\n webex = \"webex\",\n /** Other or unknown conferencing provider */\n other = \"other\",\n}\n\n/**\n * Represents a clickable action attached to a thread.\n *\n * Thread actions are rendered as buttons that enable user interaction with threads.\n * Different action types have specific behaviors and required fields for proper functionality.\n *\n * @example\n * ```typescript\n * // External action - opens URL in browser\n * const externalAction: Action = {\n * type: ActionType.external,\n * title: \"Open in Google Calendar\",\n * url: \"https://calendar.google.com/event/123\",\n * };\n *\n * // Conferencing action - opens video conference with provider info\n * const conferencingAction: Action = {\n * type: ActionType.conferencing,\n * url: \"https://meet.google.com/abc-defg-hij\",\n * provider: ConferencingProvider.googleMeet,\n * };\n *\n * // Integrations action - initiates OAuth flow\n * const authAction: Action = {\n * type: ActionType.auth,\n * title: \"Continue with Google\",\n * provider: AuthProvider.Google,\n * scopes: [\"https://www.googleapis.com/auth/calendar.readonly\"],\n * callback: \"callback-token-for-auth-completion\"\n * };\n *\n * // Callback action - triggers a twist method\n * const callbackAction: Action = {\n * type: ActionType.callback,\n * title: \"📅 Primary Calendar\",\n * token: \"callback-token-here\"\n * };\n * ```\n */\nexport type Action =\n | {\n /** External web link that opens in browser */\n type: ActionType.external;\n /** Display text for the action button */\n title: string;\n /** URL to open when clicked */\n url: string;\n }\n | {\n /** Video conferencing action with provider-specific handling */\n type: ActionType.conferencing;\n /** URL to join the conference */\n url: string;\n /** Conferencing provider for UI customization */\n provider: ConferencingProvider;\n }\n | {\n /** Authentication action that initiates an OAuth flow */\n type: ActionType.auth;\n /** Display text for the auth button */\n title: string;\n /** OAuth provider (e.g., \"google\", \"microsoft\") */\n provider: string;\n /** Array of OAuth scopes to request */\n scopes: string[];\n /** Callback token for auth completion notification */\n callback: Callback;\n }\n | {\n /** Callback action that triggers a twist method when clicked */\n type: ActionType.callback;\n /** Display text for the callback button */\n title: string;\n /** Token identifying the callback to execute */\n callback: Callback;\n }\n | {\n /** File attachment action stored in R2 */\n type: ActionType.file;\n /** Unique identifier for the stored file */\n fileId: string;\n /** Original filename */\n fileName: string;\n /** File size in bytes */\n fileSize: number;\n /** MIME type of the file */\n mimeType: string;\n /** Intrinsic width of the image in pixels (only for image files) */\n imageWidth?: number | null;\n /** Intrinsic height of the image in pixels (only for image files) */\n imageHeight?: number | null;\n }\n | {\n /** Thread reference action for navigating to a related thread */\n type: ActionType.thread;\n /** UUID of the referenced thread */\n threadId: Uuid;\n }\n | {\n /** Structured plan of operations for user approval */\n type: ActionType.plan;\n /** Human-readable summary of the plan */\n title: string;\n /** Operations to execute on approval */\n operations: PlanOperation[];\n /** Callback invoked with (action, approved: boolean) */\n callback: Callback;\n };\n\n/**\n * Represents metadata about a thread, typically from an external system.\n *\n * Thread metadata enables storing additional information about threads,\n * which is useful for synchronization, linking back to external systems,\n * and storing tool-specific data.\n *\n * Must be valid JSON data (strings, numbers, booleans, null, objects, arrays).\n * Functions and other non-JSON values are not supported.\n *\n * @example\n * ```typescript\n * // Calendar event metadata\n * await plot.createThread({\n * title: \"Team Meeting\",\n * meta: {\n * calendarId: \"primary\",\n * htmlLink: \"https://calendar.google.com/event/abc123\",\n * conferenceData: { ... }\n * }\n * });\n *\n * // Project issue metadata\n * await plot.createThread({\n * title: \"Fix login bug\",\n * meta: {\n * projectId: \"TEAM\",\n * issueNumber: 123,\n * url: \"https://linear.app/team/issue/TEAM-123\"\n * }\n * });\n * ```\n */\nexport type ThreadMeta = {\n /** Source-specific properties and metadata */\n [key: string]: JSONValue;\n};\n\n/**\n * Thread sub-type that determines the thread's icon and category.\n * Available types depend on whether the priority is shared:\n * - Private priorities: \"action\" (default for tasks), \"notes\" (default), \"idea\", \"goal\", \"decision\"\n * - Shared priorities: all above plus \"discussion\" (default), \"announcement\", \"ask\"\n */\nexport type ThreadType =\n | \"action\"\n | \"notes\"\n | \"idea\"\n | \"goal\"\n | \"decision\"\n | \"discussion\"\n | \"announcement\"\n | \"ask\";\n\n/**\n * Tags on an item, along with the actors who added each tag.\n */\nexport type Tags = { [K in Tag]?: ActorId[] };\n\n/**\n * A set of tags to add to an item, along with the actors adding each tag.\n */\nexport type NewTags = { [K in Tag]?: NewActor[] };\n\n/**\n * Thread access level determining visibility.\n * - \"public\": Visible to all users with priority access\n * - \"members\": Visible to priority members (default for shared priorities)\n * - \"private\": Visible only to creator and contacts listed in accessContacts\n */\nexport type ThreadAccessLevel = \"public\" | \"members\" | \"private\";\n\n/**\n * Common fields shared by both Thread and Note entities.\n */\nexport type ThreadCommon = {\n /** Unique identifier for the thread */\n id: Uuid;\n /**\n * When this item was created.\n *\n * **For sources:** Set this to the external system's timestamp (e.g., email\n * sent date, comment creation date), NOT the sync time. If omitted, defaults\n * to the current time, which is almost never correct for synced data.\n */\n created: Date;\n /** Whether this thread has been archived */\n archived: boolean;\n /** Tags attached to this thread. Maps tag ID to array of actor IDs who added that tag. */\n tags: Tags;\n};\n\n/**\n * Fields on a Thread entity.\n * Threads are simple containers for links and notes.\n */\ntype ThreadFields = ThreadCommon & {\n /** The display title/summary of the thread */\n title: string;\n /** The priority context this thread belongs to */\n priority: Priority;\n /** The thread's sub-type/category. Determines the displayed icon. */\n type: ThreadType | null;\n /** Thread access level: \"public\", \"members\", or \"private\" */\n access: ThreadAccessLevel;\n /** Contacts who can see a private thread (empty array for creator-only). Only meaningful when access is \"private\". */\n accessContacts: Contact[];\n /** The schedule associated with this thread, if any */\n schedule?: Schedule;\n /** Source-specific metadata from the thread's link, populated on callbacks */\n meta?: ThreadMeta;\n};\n\nexport type Thread = ThreadFields;\n\nexport type ThreadWithNotes = Thread & {\n notes: Note[];\n};\n\nexport type NewThreadWithNotes = NewThread & {\n notes: Omit<NewNote, \"thread\">[];\n};\n\n/**\n * Type for creating new threads.\n *\n * Threads are simple containers. All other fields are optional.\n *\n * @example\n * ```typescript\n * const thread: NewThread = {\n * title: \"Review pull request\"\n * };\n * ```\n */\nexport type NewThread = Partial<\n Omit<ThreadFields, \"priority\" | \"tags\" | \"id\" | \"accessContacts\">\n> &\n (\n | {\n /** Unique identifier for the thread, generated by Uuid.Generate(). */\n id: Uuid;\n }\n | {\n /* id is optional. An id will be generated and returned. */\n }\n ) &\n {\n /** Explicit priority - disables automatic priority matching. When omitted, the server classifies the thread using the user's priority rules. */\n priority?: Pick<Priority, \"id\">;\n } & {\n /**\n * All tags to set on the new thread.\n */\n tags?: NewTags;\n\n /**\n * The thread's sub-type/category. Sets the thread's icon.\n * If omitted, defaults to \"notes\" (private) or \"discussion\" (shared).\n */\n type?: ThreadType;\n\n /**\n * Contacts who can see a private thread.\n * Pass email-based NewContact objects; they are resolved to contact IDs by the API.\n * If omitted for a private thread, defaults to the connection owner.\n */\n accessContacts?: NewContact[];\n\n /**\n * Whether the thread should be marked as unread for users.\n * - undefined/omitted (default): Thread is unread for users, except auto-marked\n * as read for the author if they are the twist owner (user)\n * - true: Thread is explicitly unread for ALL users (use sparingly)\n * - false: Thread is marked as read for all users in the priority at creation time\n */\n unread?: boolean;\n\n /**\n * Whether the thread is archived.\n * - true: Archive the thread\n * - false: Unarchive the thread\n * - undefined (default): Preserve current archive state\n */\n archived?: boolean;\n\n /**\n * Optional preview content for the thread. Can be Markdown formatted.\n * The preview will be automatically generated from this content (truncated to 100 chars).\n */\n preview?: string | null;\n\n /**\n * Optional schedules to create alongside the thread.\n */\n schedules?: Array<Omit<NewSchedule, \"threadId\">>;\n\n /**\n * Optional schedule occurrence overrides.\n */\n scheduleOccurrences?: NewScheduleOccurrence[];\n };\n\nexport type ThreadFilter = {\n meta?: {\n [key: string]: JSONValue;\n };\n};\n\n/**\n * Fields supported by bulk updates via `match`. Only simple scalar fields\n * that can be applied uniformly across many threads are included.\n */\ntype ThreadBulkUpdateFields = Partial<\n Pick<ThreadFields, \"title\" | \"access\" | \"archived\">\n> & {\n /** Contacts who can see a private thread. Pass NewContact objects (email-based); resolved by the API. */\n accessContacts?: NewContact[];\n};\n\n/**\n * Fields supported by single-thread updates via `id` or `source`.\n * Includes all bulk fields plus tags and preview.\n */\ntype ThreadSingleUpdateFields = ThreadBulkUpdateFields & {\n /**\n * Tags to change on the thread. Use an empty array of NewActor to remove a tag.\n * Use twistTags to add/remove the twist from tags to avoid clearing other actors' tags.\n */\n tags?: NewTags;\n\n /**\n * Add or remove the twist's tags.\n * Maps tag ID to boolean: true = add tag, false = remove tag.\n * This is allowed on all threads the twist has access to.\n */\n twistTags?: Partial<Record<Tag, boolean>>;\n\n /**\n * Update the thread's sub-type/category.\n */\n type?: ThreadType;\n\n /**\n * Optional preview content for the thread. Can be Markdown formatted.\n * The preview will be automatically generated from this content (truncated to 100 chars).\n *\n * - string: Use this content for preview generation\n * - null: Explicitly disable preview (no preview will be shown)\n * - undefined (omitted): Preserve current preview value\n *\n * This field is write-only and won't be returned when reading threads.\n */\n preview?: string | null;\n\n /**\n * Move the thread to a different priority. Requires ThreadAccess.Full.\n * The target priority must be owned by the twist's user.\n */\n priority?: Pick<Priority, \"id\">;\n};\n\nexport type ThreadUpdate =\n | (({ id: Uuid } | { source: string }) & ThreadSingleUpdateFields)\n | ({\n /**\n * Update all threads matching the specified criteria. Only threads\n * that match all provided fields and were created by the twist will be updated.\n */\n match: ThreadFilter;\n } & ThreadBulkUpdateFields);\n\n/**\n * Represents a note within a thread.\n *\n * Notes contain the detailed content (note text, actions) associated with a thread.\n * They are always ordered by creation time within their parent thread.\n */\nexport type Note = ThreadCommon & {\n /** The author of this note */\n author: Actor;\n /**\n * Globally unique, stable identifier for the note within its thread.\n * Can be used to upsert without knowing the id.\n *\n * Use one of these patterns:\n * - Hardcoded semantic keys for fixed note types: \"description\", \"cancellation\"\n * - External service IDs for dynamic collections: `comment:${immutableId}`\n *\n * Examples:\n * - `\"description\"` (for a Jira issue's description note)\n * - `\"comment:12345\"` (for a specific comment by ID)\n * - `\"gmail:msg:18d4e5f2a3b1c9d7\"` (for a Gmail message within a thread)\n *\n * Ensure IDs are immutable - avoid human-readable slugs or titles.\n */\n key: string | null;\n /** The parent thread this note belongs to */\n thread: Thread;\n /** Primary content for the note (markdown) */\n content: string | null;\n /** Array of interactive actions attached to the note */\n actions: Array<Action> | null;\n /** The note this is a reply to, or null if not a reply */\n reNote: { id: Uuid } | null;\n /**\n * Contacts who can see this note, or null if the note inherits thread visibility.\n * When set (even to []), the note is private to the listed contacts plus the creator.\n */\n accessContacts: ActorId[] | null;\n /** Priority twist IDs (twists/connectors) mentioned for dispatch routing. Does not include user contacts. */\n mentions: ActorId[];\n};\n\n/**\n * Type for creating new notes.\n *\n * Requires the thread reference, with all other fields optional.\n * Can provide id, key, or neither for note identification:\n * - id: Provide a specific UUID for the note\n * - key: Provide an external identifier for upsert within the thread\n * - neither: A new note with auto-generated UUID will be created\n */\nexport type NewNote = Partial<\n Omit<\n Note,\n \"author\" | \"thread\" | \"tags\" | \"mentions\" | \"accessContacts\" | \"id\" | \"key\" | \"reNote\"\n >\n> &\n ({ id: Uuid } | { key: string } | {}) & {\n /** Reference to the parent thread (required) */\n thread:\n | Pick<Thread, \"id\">\n | {\n source: string;\n };\n\n /**\n * The person that created the item, or leave undefined to use the twist as author.\n */\n author?: NewActor;\n\n /**\n * Format of the note content. Determines how the note is processed:\n * - 'text': Plain text that will be converted to markdown (auto-links URLs, preserves line breaks)\n * - 'markdown': Already in markdown format (default, no conversion)\n * - 'html': HTML content that will be converted to markdown\n */\n contentType?: ContentType;\n\n /**\n * Tags to change on the thread. Use an empty array of NewActor to remove a tag.\n * Use twistTags to add/remove the twist from tags to avoid clearing other actors' tags.\n */\n tags?: NewTags;\n\n /**\n * Contacts who can see this note, or null/undefined to inherit thread visibility.\n * Accepts resolved ActorId UUIDs or email-based NewContact objects (resolved server-side).\n * Include all participants who should see the note (sender + recipients).\n * The note author is NOT implicitly included — add them explicitly.\n * When set (even to []), the note is private to the listed contacts plus the creator.\n */\n accessContacts?: (ActorId | NewContact)[] | null;\n\n /**\n * Twist/connector IDs to mention for dispatch routing.\n * Does not include user contacts — use accessContacts for visibility.\n */\n mentions?: NewActor[];\n\n /**\n * Whether the note should mark the parent thread as unread for users.\n * - undefined/omitted (default): Thread is unread for users, except auto-marked\n * as read for the author if they are the twist owner (user)\n * - true: Thread is explicitly unread for ALL users (use sparingly)\n * - false: Thread is marked as read for all users in the priority at note creation time\n *\n * For the default behavior, omit this field entirely.\n * Use false for initial sync to avoid marking historical items as unread.\n */\n unread?: boolean;\n\n /**\n * When true, the server will use AI to detect tasks in this note's content\n * and create separate Plot-authored reply notes for each detected task.\n * Use for messaging connectors (email, chat) where tasks are implicit\n * in conversation rather than explicitly structured.\n */\n checkForTasks?: boolean;\n\n /**\n * Reference to a parent note this note is a reply to.\n * - `{ id }`: reply by UUID\n * - `{ key }`: reply by key, resolved after creation (for batch ops)\n * - `null`: explicitly not a reply\n * - `undefined` (omitted): not a reply\n */\n reNote?: { id: Uuid } | { key: string } | null;\n };\n\n/**\n * Type for updating existing notes.\n * Must provide either id or key to identify the note to update.\n */\nexport type NoteUpdate = ({ id: Uuid; key?: string } | { key: string }) &\n Partial<\n Pick<Note, \"accessContacts\" | \"archived\" | \"content\" | \"actions\" | \"reNote\">\n > & {\n /**\n * Format of the note content. Determines how the note is processed:\n * - 'text': Plain text that will be converted to markdown (auto-links URLs, preserves line breaks)\n * - 'markdown': Already in markdown format (default, no conversion)\n * - 'html': HTML content that will be converted to markdown\n */\n contentType?: ContentType;\n\n /**\n * Tags to change on the note. Use an empty array of NewActor to remove a tag.\n * Use twistTags to add/remove the twist from tags to avoid clearing other actors' tags.\n */\n tags?: NewTags;\n\n /**\n * Add or remove the twist's tags.\n * Maps tag ID to boolean: true = add tag, false = remove tag.\n * This is allowed on all notes the twist has access to.\n */\n twistTags?: Partial<Record<Tag, boolean>>;\n\n /**\n * Twist/connector IDs to mention for dispatch routing.\n * Does not include user contacts — use accessContacts for visibility.\n */\n mentions?: NewActor[];\n };\n\n/**\n * Represents an actor in Plot - a user, contact, or twist.\n *\n * Actors can be associated with threads as authors, assignees, or mentions.\n * The email field is only included when ContactAccess.Read permission is granted.\n *\n * @example\n * ```typescript\n * const actor: Actor = {\n * id: \"f0ffd5f8-1635-4b13-9532-35f97446db90\" as ActorId,\n * type: ActorType.Contact,\n * email: \"john.doe@example.com\", // Only if ContactAccess.Read\n * name: \"John Doe\"\n * };\n * ```\n */\nexport type Actor = {\n /** Unique identifier for the actor */\n id: ActorId;\n /** Type of actor (User, Contact, or Twist) */\n type: ActorType;\n /**\n * Email address (only included with ContactAccess.Read permission).\n * - `undefined`: No permission to read email\n * - `null`: Permission granted but email not set\n * - `string`: Email address\n */\n email?: string | null;\n /**\n * Display name.\n * - `undefined`: Not included due to permissions\n * - `null`: Not set\n * - `string`: Display name\n */\n name?: string | null;\n};\n\n/**\n * A resolved contact with identity info. Used for access control lists\n * where only human contacts (not twists) are relevant.\n */\nexport type Contact = {\n /** Unique identifier for the contact */\n id: ActorId;\n /** Email address, or null if not set */\n email: string | null;\n /** Display name, or null if not set */\n name: string | null;\n};\n\n/**\n * An existing or new contact.\n */\nexport type NewActor =\n | {\n /** Unique identifier for the actor */\n id: ActorId;\n }\n | NewContact;\n\n/**\n * Enumeration of author types that can create threads.\n *\n * The author type affects how threads are displayed and processed\n * within the Plot system.\n */\nexport enum ActorType {\n /** Threads created by human users */\n User,\n /** Threads created by external contacts */\n Contact,\n /** Threads created by automated twists */\n Twist,\n}\n\n/**\n * Represents contact information for creating a new contact.\n *\n * Contacts are used throughout Plot for representing people associated\n * with activities, such as event attendees or task assignees.\n *\n * @example\n * ```typescript\n * const newContact: NewContact = {\n * email: \"john.doe@example.com\",\n * name: \"John Doe\",\n * avatar: \"https://avatar.example.com/john.jpg\"\n * };\n * ```\n */\n/**\n * Common fields shared by all NewContact variants.\n */\ntype NewContactBase = {\n /** Optional avatar image URL for the contact */\n avatar?: string;\n /**\n * External provider account source. Used for identity resolution\n * when email is unavailable and for privacy compliance reporting.\n */\n source?: { provider: AuthProvider; accountId: string };\n};\n\n/**\n * At least one of `email` or `name` must be provided so the contact can be\n * identified in the UI. Contacts with neither would display as \"Unknown\".\n */\nexport type NewContact = NewContactBase &\n ({ email: string; name?: string } | { email?: string; name: string });\n\nexport type ContentType = \"text\" | \"markdown\" | \"html\";\n\n/**\n * Represents an external entity linked to a thread.\n *\n * Links are created by sources to represent external entities (issues, emails, calendar events)\n * attached to a thread container. A thread can have multiple links (1:many).\n * Links store source-specific data like type, status, metadata, and embeddings.\n *\n * @example\n * ```typescript\n * // A link representing a Linear issue\n * const link: Link = {\n * threadId: \"...\" as Uuid,\n * source: \"linear:issue:549dd8bd-2bc9-43d1-95d5-4b4af0c5af1b\",\n * created: new Date(),\n * author: { id: \"...\" as ActorId, type: ActorType.Contact, name: \"Alice\" },\n * title: \"Fix login bug\",\n * type: \"issue\",\n * status: \"open\",\n * meta: { projectId: \"TEAM\", url: \"https://linear.app/team/TEAM-123\" },\n * assignee: null,\n * actions: null,\n * };\n * ```\n */\nexport type Link = {\n /** The thread this link belongs to */\n threadId: Uuid;\n /** External source identifier for dedup/upsert */\n source: string | null;\n /** When this link was originally created in its source system */\n created: Date;\n /** The actor credited with creating this link */\n author: Actor | null;\n /** Display title */\n title: string;\n /** Truncated preview */\n preview: string | null;\n /** The actor assigned to this link */\n assignee: Actor | null;\n /** Source-defined type string (e.g., issue, pull_request, email, event) */\n type: string | null;\n /** Source-defined status string (e.g., open, done, closed) */\n status: string | null;\n /** Interactive action buttons */\n actions: Array<Action> | null;\n /** Source metadata */\n meta: ThreadMeta | null;\n /** URL to open the original item in its source application (e.g., \"Open in Linear\") */\n sourceUrl: string | null;\n /** Channel ID that produced this link (matches source_channel.channel_id) */\n channelId: string | null;\n /**\n * Cross-connector thread bundling key.\n * When set, this link shares a thread with any link whose `source` matches\n * this value (or whose `relatedSource` matches this link's `source`).\n * Works regardless of creation order.\n */\n relatedSource: string | null;\n};\n\n/**\n * Type for creating new links.\n *\n * Links are created by sources to represent external entities.\n * Requires a source identifier for dedup/upsert.\n */\nexport type NewLink = (\n | {\n /**\n * Canonical ID for the item in an external system.\n * When set, uniquely identifies the link within a priority tree. This performs\n * an upsert.\n */\n source: string;\n }\n | {}\n) &\n Partial<Omit<Link, \"source\" | \"author\" | \"assignee\" | \"threadId\">> & {\n /** The person that created the item. By default, it will be the twist itself. */\n author?: NewActor;\n /** The person assigned to the item. */\n assignee?: NewActor | null;\n /**\n * Thread access level: \"public\", \"members\" (default), or \"private\".\n * When \"private\", thread visibility is limited to the creator and contacts in accessContacts.\n */\n access?: ThreadAccessLevel;\n /**\n * Contacts who can see a private thread.\n * Pass email-based NewContact objects; they are resolved to contact IDs by the API.\n * If omitted for a private thread, defaults to the connection owner.\n */\n accessContacts?: NewContact[];\n /**\n * Whether the thread should be marked as unread for users.\n * - undefined/omitted (default): Thread is unread for users, except auto-marked\n * as read for the author if they are the twist owner (user)\n * - false: Thread is marked as read for all users in the priority at creation time\n */\n unread?: boolean;\n /**\n * Whether the thread is archived.\n * - true: Archive the thread\n * - false: Unarchive the thread\n * - undefined (default): Preserve current archive state\n */\n archived?: boolean;\n /**\n * Explicit priority (disables automatic priority matching).\n * Only used when the link creates a new thread. When omitted, the\n * server classifies the thread using the user's priority rules.\n */\n priority?: Pick<Priority, \"id\">;\n };\n\n/**\n * A new link with notes to save via integrations.saveLink().\n * Creates a thread+link pair, with notes attached to the thread.\n */\nexport type NewLinkWithNotes = NewLink & {\n /**\n * Title for the link and its thread container.\n * Must be the real entity title (e.g. issue title, message subject),\n * never a placeholder or ID. This value overwrites the existing title on upsert.\n * Omit to preserve the existing title (e.g. for cancelled events where the\n * title may not be available in the webhook payload).\n */\n title?: string;\n /** Notes to attach to the thread */\n notes?: Omit<NewNote, \"thread\">[];\n /** Schedules to create for the link */\n schedules?: Array<Omit<NewSchedule, \"threadId\">>;\n /** Schedule occurrence overrides */\n scheduleOccurrences?: NewScheduleOccurrence[];\n};\n\n/**\n * Type for updating existing links.\n *\n * Set `threadId` to move the link to a different thread.\n * Requires LinkAccess.Full.\n */\nexport type LinkUpdate = { id: Uuid } & {\n /** Move the link to a different thread owned by the twist's user. */\n threadId?: Uuid;\n};\n\n/**\n * A single operation within a plan submitted for user approval.\n *\n * Operations include display metadata (titles) so the app can render\n * a human-readable summary without additional lookups.\n */\nexport type PlanOperation =\n | {\n type: \"updateThread\";\n threadId: Uuid;\n /** Current thread title for display */\n threadTitle: string;\n changes: Partial<Pick<ThreadFields, \"archived\" | \"title\" | \"type\">> & {\n /** Move to this priority */\n priority?: { id: Uuid; title: string };\n };\n }\n | {\n type: \"updateLink\";\n linkId: Uuid;\n /** Current link title for display */\n linkTitle: string;\n changes: {\n /** Move to this thread */\n threadId?: Uuid;\n threadTitle?: string;\n };\n }\n | {\n type: \"createThread\";\n title: string;\n priorityId: Uuid;\n /** Priority title for display */\n priorityTitle: string;\n }\n | {\n type: \"createNote\";\n threadId: Uuid;\n /** Thread title for display */\n threadTitle: string;\n content: string;\n }\n | {\n type: \"updatePriority\";\n priorityId: Uuid;\n /** Current priority title for display */\n priorityTitle: string;\n changes: Partial<Pick<Priority, \"title\" | \"archived\">> & {\n /** Move under this parent */\n parent?: { id: Uuid; title: string };\n };\n };\n";
8
+ export default "import type { NewSchedule, NewScheduleOccurrence, Schedule } from \"./schedule\";\nimport { type Tag } from \"./tag\";\nimport { type Callback } from \"./tools/callbacks\";\nimport { type AuthProvider } from \"./tools/integrations\";\nimport { type JSONValue } from \"./utils/types\";\nimport { Uuid } from \"./utils/uuid\";\n\nexport { Tag } from \"./tag\";\nexport { Uuid } from \"./utils/uuid\";\nexport { type JSONValue } from \"./utils/types\";\nexport { type AuthProvider } from \"./tools/integrations\";\n\n/**\n * @fileoverview\n * Core Plot entity types for working with threads, notes, priorities, and contacts.\n *\n * ## Type Pattern: Null vs Undefined Semantics\n *\n * Plot entity types use a consistent pattern to distinguish between missing, unset, and explicitly cleared values:\n *\n * ### Entity Types (Thread, Priority, Note, Actor)\n * - **Required fields**: No `?`, cannot be `undefined`\n * - Example: `id: Uuid`, `title: string`\n * - **Nullable fields**: Use `| null` to allow explicit clearing\n * - Example: `assignee: ActorId | null`, `done: Date | null`\n * - `null` = field is explicitly unset/cleared\n * - Non-null value = field has a value\n * - **Optional nullable fields**: Use `?` with `| null` for permission-based access\n * - Example: `email?: string | null`, `name?: string | null`\n * - `undefined` = field not included (e.g., no permission to access)\n * - `null` = field included but not set\n * - Value = field has a value\n *\n * ### New* Types (NewThread, NewNote, NewPriority)\n * Used for creating or updating entities. Support partial updates by distinguishing omitted vs cleared fields:\n * - **Required fields**: Must be provided (no `?`)\n * - Example: `title: string` in NewPriority\n * - **Optional fields**: Use `?` to make them optional\n * - Example: `title?: string`, `author?: NewActor`\n * - `undefined` (omitted) = don't set/update this field\n * - Provided value = set/update this field\n * - **Optional nullable fields**: Use `?` with `| null` to support clearing\n * - Example: `assignee?: NewActor | null`\n * - `undefined` (omitted) = don't change assignee\n * - `null` = clear the assignee\n * - NewActor = set/update the assignee\n *\n * This pattern allows API consumers to:\n * 1. Omit fields they don't want to change (undefined)\n * 2. Explicitly clear fields by setting to null\n * 3. Set or update fields by providing values\n *\n * @example\n * ```typescript\n * // Creating a new thread\n * const newThread: NewThread = {\n * title: \"Review pull request\",\n * };\n *\n * // Updating a thread - only change what's specified\n * const update: ThreadUpdate = {\n * id: threadId,\n * archived: true,\n * };\n * ```\n */\n\n/**\n * Represents a unique user, contact, or twist in Plot.\n *\n * ActorIds are used throughout Plot for:\n * - Activity authors and assignees\n * - Tag creators (actor_id in activity_tag/note_tag)\n * - Mentions in activities and notes\n * - Any entity that can perform actions in Plot\n */\nexport type ActorId = string & { readonly __brand: \"ActorId\" };\n\n/**\n * Theme colors for priorities.\n */\nexport enum ThemeColor {\n /** Catalyst - Green */\n Catalyst = 0,\n /** Call to Adventure - Blue */\n CallToAdventure = 1,\n /** Rising Action - Purple */\n RisingAction = 2,\n /** Momentum - Pink-Purple */\n Momentum = 3,\n /** Turning Point - Pink */\n TurningPoint = 4,\n /** Breakthrough - Orange */\n Breakthrough = 5,\n /** Climax - Olive */\n Climax = 6,\n /** Resolution - Blue-Gray */\n Resolution = 7,\n}\n\n/**\n * Represents a priority context within Plot.\n *\n * Priorities are similar to projects in other apps. All Activity is in a Priority.\n * Priorities can be nested.\n */\nexport type Priority = {\n /** Unique identifier for the priority */\n id: Uuid;\n /** Human-readable title for the priority */\n title: string;\n /** Whether this priority has been archived */\n archived: boolean;\n /**\n * Optional key for referencing this priority.\n * Keys are unique per priority tree (a user's personal priorities or the root of a shared priority).\n */\n key: string | null;\n /** Optional theme color for the priority (0-7). If not set, inherits from parent or defaults to 7 (Resolution). */\n color: ThemeColor | null;\n};\n\n/**\n * Type for creating new priorities.\n *\n * Supports multiple creation patterns:\n * - Provide a specific UUID for the priority\n * - Provide a key for upsert within the user's priorities\n * - Omit both to auto-generate a new UUID\n *\n * Optionally specify a parent priority by ID or key for hierarchical structures.\n */\nexport type NewPriority = Pick<Priority, \"title\"> &\n Partial<Omit<Priority, \"id\" | \"title\">> &\n (\n | {\n /**\n * Unique identifier for the priority, generated by Uuid.Generate().\n * Specifying an ID allows tools to track and upsert priorities.\n */\n id: Uuid;\n }\n | {\n /**\n * Unique key for the priority within the user's priorities.\n * Can be used to upsert without knowing the UUID.\n * For example, \"@plot\" identifies the Plot priority.\n */\n key: string;\n }\n | {\n /* Neither id nor key is required. An id will be generated and returned. */\n }\n ) & {\n /** Add the new priority as the child of another priority */\n parent?: { id: Uuid } | { key: string };\n };\n\n/**\n * Type for updating existing priorities.\n * Must provide either id or key to identify the priority to update.\n * Set `parent` to move the priority under a new parent (requires PriorityAccess.Full).\n */\nexport type PriorityUpdate = ({ id: Uuid } | { key: string }) &\n Partial<Pick<Priority, \"title\" | \"archived\">> & {\n /** Move the priority under a new parent. Requires PriorityAccess.Full. */\n parent?: { id: Uuid } | { key: string };\n };\n\n/**\n * Enumeration of supported action types.\n *\n * Different action types have different behaviors when clicked by users\n * and may require different rendering approaches.\n */\nexport enum ActionType {\n /** External web links that open in browser */\n external = \"external\",\n /** Authentication flows for connecting services */\n auth = \"auth\",\n /** Callback actions that trigger twist methods when clicked */\n callback = \"callback\",\n /** Video conferencing links with provider-specific handling */\n conferencing = \"conferencing\",\n /** File attachment links stored in R2 */\n file = \"file\",\n /** Thread reference links for navigating to related threads */\n thread = \"thread\",\n /** Structured plan of operations for user approval */\n plan = \"plan\",\n}\n\n/**\n * Video conferencing providers for conferencing links.\n *\n * Used to identify the conferencing platform and provide\n * provider-specific UI elements (titles, icons, etc.).\n */\nexport enum ConferencingProvider {\n /** Google Meet */\n googleMeet = \"googleMeet\",\n /** Zoom */\n zoom = \"zoom\",\n /** Microsoft Teams */\n microsoftTeams = \"microsoftTeams\",\n /** Cisco Webex */\n webex = \"webex\",\n /** Other or unknown conferencing provider */\n other = \"other\",\n}\n\n/**\n * Represents a clickable action attached to a thread.\n *\n * Thread actions are rendered as buttons that enable user interaction with threads.\n * Different action types have specific behaviors and required fields for proper functionality.\n *\n * @example\n * ```typescript\n * // External action - opens URL in browser\n * const externalAction: Action = {\n * type: ActionType.external,\n * title: \"Open in Google Calendar\",\n * url: \"https://calendar.google.com/event/123\",\n * };\n *\n * // Conferencing action - opens video conference with provider info\n * const conferencingAction: Action = {\n * type: ActionType.conferencing,\n * url: \"https://meet.google.com/abc-defg-hij\",\n * provider: ConferencingProvider.googleMeet,\n * };\n *\n * // Integrations action - initiates OAuth flow\n * const authAction: Action = {\n * type: ActionType.auth,\n * title: \"Continue with Google\",\n * provider: AuthProvider.Google,\n * scopes: [\"https://www.googleapis.com/auth/calendar.readonly\"],\n * callback: \"callback-token-for-auth-completion\"\n * };\n *\n * // Callback action - triggers a twist method\n * const callbackAction: Action = {\n * type: ActionType.callback,\n * title: \"📅 Primary Calendar\",\n * token: \"callback-token-here\"\n * };\n * ```\n */\nexport type Action =\n | {\n /** External web link that opens in browser */\n type: ActionType.external;\n /** Display text for the action button */\n title: string;\n /** URL to open when clicked */\n url: string;\n }\n | {\n /** Video conferencing action with provider-specific handling */\n type: ActionType.conferencing;\n /** URL to join the conference */\n url: string;\n /** Conferencing provider for UI customization */\n provider: ConferencingProvider;\n }\n | {\n /** Authentication action that initiates an OAuth flow */\n type: ActionType.auth;\n /** Display text for the auth button */\n title: string;\n /** OAuth provider (e.g., \"google\", \"microsoft\") */\n provider: string;\n /** Array of OAuth scopes to request */\n scopes: string[];\n /** Callback token for auth completion notification */\n callback: Callback;\n }\n | {\n /** Callback action that triggers a twist method when clicked */\n type: ActionType.callback;\n /** Display text for the callback button */\n title: string;\n /** Token identifying the callback to execute */\n callback: Callback;\n }\n | {\n /** File attachment action stored in R2 */\n type: ActionType.file;\n /** Unique identifier for the stored file */\n fileId: string;\n /** Original filename */\n fileName: string;\n /** File size in bytes */\n fileSize: number;\n /** MIME type of the file */\n mimeType: string;\n /** Intrinsic width of the image in pixels (only for image files) */\n imageWidth?: number | null;\n /** Intrinsic height of the image in pixels (only for image files) */\n imageHeight?: number | null;\n }\n | {\n /** Thread reference action for navigating to a related thread */\n type: ActionType.thread;\n /** UUID of the referenced thread */\n threadId: Uuid;\n }\n | {\n /** Structured plan of operations for user approval */\n type: ActionType.plan;\n /** Human-readable summary of the plan */\n title: string;\n /** Operations to execute on approval */\n operations: PlanOperation[];\n /** Callback invoked with (action, approved: boolean) */\n callback: Callback;\n };\n\n/**\n * Represents metadata about a thread, typically from an external system.\n *\n * Thread metadata enables storing additional information about threads,\n * which is useful for synchronization, linking back to external systems,\n * and storing tool-specific data.\n *\n * Must be valid JSON data (strings, numbers, booleans, null, objects, arrays).\n * Functions and other non-JSON values are not supported.\n *\n * @example\n * ```typescript\n * // Calendar event metadata\n * await plot.createThread({\n * title: \"Team Meeting\",\n * meta: {\n * calendarId: \"primary\",\n * htmlLink: \"https://calendar.google.com/event/abc123\",\n * conferenceData: { ... }\n * }\n * });\n *\n * // Project issue metadata\n * await plot.createThread({\n * title: \"Fix login bug\",\n * meta: {\n * projectId: \"TEAM\",\n * issueNumber: 123,\n * url: \"https://linear.app/team/issue/TEAM-123\"\n * }\n * });\n * ```\n */\nexport type ThreadMeta = {\n /** Source-specific properties and metadata */\n [key: string]: JSONValue;\n};\n\n/**\n * Thread sub-type that determines the thread's icon and category.\n * Available types depend on whether the priority is shared:\n * - Private priorities: \"action\" (default for tasks), \"notes\" (default), \"idea\", \"goal\", \"decision\"\n * - Shared priorities: all above plus \"discussion\" (default), \"announcement\", \"ask\"\n */\nexport type ThreadType =\n | \"action\"\n | \"notes\"\n | \"idea\"\n | \"goal\"\n | \"decision\"\n | \"discussion\"\n | \"announcement\"\n | \"ask\";\n\n/**\n * Tags on an item, along with the actors who added each tag.\n */\nexport type Tags = { [K in Tag]?: ActorId[] };\n\n/**\n * A set of tags to add to an item, along with the actors adding each tag.\n */\nexport type NewTags = { [K in Tag]?: NewActor[] };\n\n/**\n * Thread access level determining visibility.\n * - \"public\": Visible to all users with priority access\n * - \"members\": Visible to priority members (default for shared priorities)\n * - \"private\": Visible only to creator and contacts listed in accessContacts\n */\nexport type ThreadAccessLevel = \"public\" | \"members\" | \"private\";\n\n/**\n * Common fields shared by both Thread and Note entities.\n */\nexport type ThreadCommon = {\n /** Unique identifier for the thread */\n id: Uuid;\n /**\n * When this item was created.\n *\n * **For sources:** Set this to the external system's timestamp (e.g., email\n * sent date, comment creation date), NOT the sync time. If omitted, defaults\n * to the current time, which is almost never correct for synced data.\n */\n created: Date;\n /** Whether this thread has been archived */\n archived: boolean;\n /** Tags attached to this thread. Maps tag ID to array of actor IDs who added that tag. */\n tags: Tags;\n};\n\n/**\n * Fields on a Thread entity.\n * Threads are simple containers for links and notes.\n */\ntype ThreadFields = ThreadCommon & {\n /** The display title/summary of the thread */\n title: string;\n /** The priority context this thread belongs to */\n priority: Priority;\n /** The thread's sub-type/category. Determines the displayed icon. */\n type: ThreadType | null;\n /** Thread access level: \"public\", \"members\", or \"private\" */\n access: ThreadAccessLevel;\n /** Contacts who can see a private thread (empty array for creator-only). Only meaningful when access is \"private\". */\n accessContacts: Contact[];\n /** The schedule associated with this thread, if any */\n schedule?: Schedule;\n /** Source-specific metadata from the thread's link, populated on callbacks */\n meta?: ThreadMeta;\n};\n\nexport type Thread = ThreadFields;\n\nexport type ThreadWithNotes = Thread & {\n notes: Note[];\n};\n\nexport type NewThreadWithNotes = NewThread & {\n notes: Omit<NewNote, \"thread\">[];\n};\n\n/**\n * Type for creating new threads.\n *\n * Threads are simple containers. All other fields are optional.\n *\n * @example\n * ```typescript\n * const thread: NewThread = {\n * title: \"Review pull request\"\n * };\n * ```\n */\nexport type NewThread = Partial<\n Omit<ThreadFields, \"priority\" | \"tags\" | \"id\" | \"accessContacts\">\n> &\n (\n | {\n /** Unique identifier for the thread, generated by Uuid.Generate(). */\n id: Uuid;\n }\n | {\n /* id is optional. An id will be generated and returned. */\n }\n ) &\n {\n /** Explicit priority - disables automatic priority matching. When omitted, the server classifies the thread using the user's priority rules. */\n priority?: Pick<Priority, \"id\">;\n } & {\n /**\n * All tags to set on the new thread.\n */\n tags?: NewTags;\n\n /**\n * The thread's sub-type/category. Sets the thread's icon.\n * If omitted, defaults to \"notes\" (private) or \"discussion\" (shared).\n */\n type?: ThreadType;\n\n /**\n * Contacts who can see a private thread.\n * Pass email-based NewContact objects; they are resolved to contact IDs by the API.\n * If omitted for a private thread, defaults to the connection owner.\n */\n accessContacts?: NewContact[];\n\n /**\n * Whether the thread should be marked as unread for users.\n * - undefined/omitted (default): Thread is unread for users, except auto-marked\n * as read for the author if they are the twist owner (user)\n * - true: Thread is explicitly unread for ALL users (use sparingly)\n * - false: Thread is marked as read for all users in the priority at creation time\n */\n unread?: boolean;\n\n /**\n * Whether the thread is archived.\n * - true: Archive the thread\n * - false: Unarchive the thread\n * - undefined (default): Preserve current archive state\n */\n archived?: boolean;\n\n /**\n * Optional preview content for the thread. Can be Markdown formatted.\n * The preview will be automatically generated from this content (truncated to 100 chars).\n */\n preview?: string | null;\n\n /**\n * Optional schedules to create alongside the thread.\n */\n schedules?: Array<Omit<NewSchedule, \"threadId\">>;\n\n /**\n * Optional schedule occurrence overrides.\n */\n scheduleOccurrences?: NewScheduleOccurrence[];\n };\n\nexport type ThreadFilter = {\n meta?: {\n [key: string]: JSONValue;\n };\n};\n\n/**\n * Fields supported by bulk updates via `match`. Only simple scalar fields\n * that can be applied uniformly across many threads are included.\n */\ntype ThreadBulkUpdateFields = Partial<\n Pick<ThreadFields, \"title\" | \"access\" | \"archived\">\n> & {\n /** Contacts who can see a private thread. Pass NewContact objects (email-based); resolved by the API. */\n accessContacts?: NewContact[];\n};\n\n/**\n * Fields supported by single-thread updates via `id` or `source`.\n * Includes all bulk fields plus tags and preview.\n */\ntype ThreadSingleUpdateFields = ThreadBulkUpdateFields & {\n /**\n * Tags to change on the thread. Use an empty array of NewActor to remove a tag.\n * Use twistTags to add/remove the twist from tags to avoid clearing other actors' tags.\n */\n tags?: NewTags;\n\n /**\n * Add or remove the twist's tags.\n * Maps tag ID to boolean: true = add tag, false = remove tag.\n * This is allowed on all threads the twist has access to.\n */\n twistTags?: Partial<Record<Tag, boolean>>;\n\n /**\n * Update the thread's sub-type/category.\n */\n type?: ThreadType;\n\n /**\n * Optional preview content for the thread. Can be Markdown formatted.\n * The preview will be automatically generated from this content (truncated to 100 chars).\n *\n * - string: Use this content for preview generation\n * - null: Explicitly disable preview (no preview will be shown)\n * - undefined (omitted): Preserve current preview value\n *\n * This field is write-only and won't be returned when reading threads.\n */\n preview?: string | null;\n\n /**\n * Move the thread to a different priority. Requires ThreadAccess.Full.\n * The target priority must be owned by the twist's user.\n */\n priority?: Pick<Priority, \"id\">;\n};\n\nexport type ThreadUpdate =\n | (({ id: Uuid } | { source: string }) & ThreadSingleUpdateFields)\n | ({\n /**\n * Update all threads matching the specified criteria. Only threads\n * that match all provided fields and were created by the twist will be updated.\n */\n match: ThreadFilter;\n } & ThreadBulkUpdateFields);\n\n/**\n * Represents a note within a thread.\n *\n * Notes contain the detailed content (note text, actions) associated with a thread.\n * They are always ordered by creation time within their parent thread.\n */\nexport type Note = ThreadCommon & {\n /** The author of this note */\n author: Actor;\n /**\n * Globally unique, stable identifier for the note within its thread + link.\n * Can be used to upsert without knowing the id.\n *\n * Note keys are scoped to a `(thread, link)` pair — two links on the same\n * thread (e.g. after a merge) can each carry a `\"description\"` note without\n * colliding. The runtime infers the link from the surrounding `saveLink`\n * call. For bare `saveNote` calls outside a `saveLink`, the runtime\n * resolves the link by looking up the connector's links on the thread\n * and errors if more than one matches.\n *\n * Use one of these patterns:\n * - Hardcoded semantic keys for fixed note types: \"description\", \"cancellation\"\n * - External service IDs for dynamic collections: `comment:${immutableId}`\n *\n * Examples:\n * - `\"description\"` (for a Jira issue's description note)\n * - `\"comment:12345\"` (for a specific comment by ID)\n * - `\"gmail:msg:18d4e5f2a3b1c9d7\"` (for a Gmail message within a thread)\n *\n * Ensure IDs are immutable - avoid human-readable slugs or titles.\n */\n key: string | null;\n /** The parent thread this note belongs to */\n thread: Thread;\n /** Primary content for the note (markdown) */\n content: string | null;\n /** Array of interactive actions attached to the note */\n actions: Array<Action> | null;\n /** The note this is a reply to, or null if not a reply */\n reNote: { id: Uuid } | null;\n /**\n * Contacts who can see this note, or null if the note inherits thread visibility.\n * When set (even to []), the note is private to the listed contacts plus the creator.\n */\n accessContacts: ActorId[] | null;\n /** Priority twist IDs (twists/connectors) mentioned for dispatch routing. Does not include user contacts. */\n mentions: ActorId[];\n};\n\n/**\n * Type for creating new notes.\n *\n * Requires the thread reference, with all other fields optional.\n * Can provide id, key, or neither for note identification:\n * - id: Provide a specific UUID for the note\n * - key: Provide an external identifier for upsert within the thread\n * - neither: A new note with auto-generated UUID will be created\n */\nexport type NewNote = Partial<\n Omit<\n Note,\n \"author\" | \"thread\" | \"tags\" | \"mentions\" | \"accessContacts\" | \"id\" | \"key\" | \"reNote\"\n >\n> &\n ({ id: Uuid } | { key: string } | {}) & {\n /** Reference to the parent thread (required) */\n thread:\n | Pick<Thread, \"id\">\n | {\n source: string;\n };\n\n /**\n * The person that created the item, or leave undefined to use the twist as author.\n */\n author?: NewActor;\n\n /**\n * Format of the note content. Determines how the note is processed:\n * - 'text': Plain text that will be converted to markdown (auto-links URLs, preserves line breaks)\n * - 'markdown': Already in markdown format (default, no conversion)\n * - 'html': HTML content that will be converted to markdown\n */\n contentType?: ContentType;\n\n /**\n * Tags to change on the thread. Use an empty array of NewActor to remove a tag.\n * Use twistTags to add/remove the twist from tags to avoid clearing other actors' tags.\n */\n tags?: NewTags;\n\n /**\n * Contacts who can see this note, or null/undefined to inherit thread visibility.\n * Accepts resolved ActorId UUIDs or email-based NewContact objects (resolved server-side).\n * Include all participants who should see the note (sender + recipients).\n * The note author is NOT implicitly included — add them explicitly.\n * When set (even to []), the note is private to the listed contacts plus the creator.\n */\n accessContacts?: (ActorId | NewContact)[] | null;\n\n /**\n * Twist/connector IDs to mention for dispatch routing.\n * Does not include user contacts — use accessContacts for visibility.\n */\n mentions?: NewActor[];\n\n /**\n * Whether the note should mark the parent thread as unread for users.\n * - undefined/omitted (default): Thread is unread for users, except auto-marked\n * as read for the author if they are the twist owner (user)\n * - true: Thread is explicitly unread for ALL users (use sparingly)\n * - false: Thread is marked as read for all users in the priority at note creation time\n *\n * For the default behavior, omit this field entirely.\n * Use false for initial sync to avoid marking historical items as unread.\n */\n unread?: boolean;\n\n /**\n * When true, the server will use AI to detect tasks in this note's content\n * and create separate Plot-authored reply notes for each detected task.\n * Use for messaging connectors (email, chat) where tasks are implicit\n * in conversation rather than explicitly structured.\n */\n checkForTasks?: boolean;\n\n /**\n * Reference to a parent note this note is a reply to.\n * - `{ id }`: reply by UUID\n * - `{ key }`: reply by key, resolved after creation (for batch ops)\n * - `null`: explicitly not a reply\n * - `undefined` (omitted): not a reply\n */\n reNote?: { id: Uuid } | { key: string } | null;\n };\n\n/**\n * Type for updating existing notes.\n * Must provide either id or key to identify the note to update.\n */\nexport type NoteUpdate = ({ id: Uuid; key?: string } | { key: string }) &\n Partial<\n Pick<Note, \"accessContacts\" | \"archived\" | \"content\" | \"actions\" | \"reNote\">\n > & {\n /**\n * Format of the note content. Determines how the note is processed:\n * - 'text': Plain text that will be converted to markdown (auto-links URLs, preserves line breaks)\n * - 'markdown': Already in markdown format (default, no conversion)\n * - 'html': HTML content that will be converted to markdown\n */\n contentType?: ContentType;\n\n /**\n * Tags to change on the note. Use an empty array of NewActor to remove a tag.\n * Use twistTags to add/remove the twist from tags to avoid clearing other actors' tags.\n */\n tags?: NewTags;\n\n /**\n * Add or remove the twist's tags.\n * Maps tag ID to boolean: true = add tag, false = remove tag.\n * This is allowed on all notes the twist has access to.\n */\n twistTags?: Partial<Record<Tag, boolean>>;\n\n /**\n * Twist/connector IDs to mention for dispatch routing.\n * Does not include user contacts — use accessContacts for visibility.\n */\n mentions?: NewActor[];\n };\n\n/**\n * Represents an actor in Plot - a user, contact, or twist.\n *\n * Actors can be associated with threads as authors, assignees, or mentions.\n * The email field is only included when ContactAccess.Read permission is granted.\n *\n * @example\n * ```typescript\n * const actor: Actor = {\n * id: \"f0ffd5f8-1635-4b13-9532-35f97446db90\" as ActorId,\n * type: ActorType.Contact,\n * email: \"john.doe@example.com\", // Only if ContactAccess.Read\n * name: \"John Doe\"\n * };\n * ```\n */\nexport type Actor = {\n /** Unique identifier for the actor */\n id: ActorId;\n /** Type of actor (User, Contact, or Twist) */\n type: ActorType;\n /**\n * Email address (only included with ContactAccess.Read permission).\n * - `undefined`: No permission to read email\n * - `null`: Permission granted but email not set\n * - `string`: Email address\n */\n email?: string | null;\n /**\n * Display name.\n * - `undefined`: Not included due to permissions\n * - `null`: Not set\n * - `string`: Display name\n */\n name?: string | null;\n};\n\n/**\n * A resolved contact with identity info. Used for access control lists\n * where only human contacts (not twists) are relevant.\n */\nexport type Contact = {\n /** Unique identifier for the contact */\n id: ActorId;\n /** Email address, or null if not set */\n email: string | null;\n /** Display name, or null if not set */\n name: string | null;\n};\n\n/**\n * An existing or new contact.\n */\nexport type NewActor =\n | {\n /** Unique identifier for the actor */\n id: ActorId;\n }\n | NewContact;\n\n/**\n * Enumeration of author types that can create threads.\n *\n * The author type affects how threads are displayed and processed\n * within the Plot system.\n */\nexport enum ActorType {\n /** Threads created by human users */\n User,\n /** Threads created by external contacts */\n Contact,\n /** Threads created by automated twists */\n Twist,\n}\n\n/**\n * Represents contact information for creating a new contact.\n *\n * Contacts are used throughout Plot for representing people associated\n * with activities, such as event attendees or task assignees.\n *\n * @example\n * ```typescript\n * const newContact: NewContact = {\n * email: \"john.doe@example.com\",\n * name: \"John Doe\",\n * avatar: \"https://avatar.example.com/john.jpg\"\n * };\n * ```\n */\n/**\n * Common fields shared by all NewContact variants.\n */\ntype NewContactBase = {\n /** Optional avatar image URL for the contact */\n avatar?: string;\n /**\n * External provider account source. Used for identity resolution\n * when email is unavailable and for privacy compliance reporting.\n */\n source?: { provider: AuthProvider; accountId: string };\n};\n\n/**\n * At least one of `email` or `name` must be provided so the contact can be\n * identified in the UI. Contacts with neither would display as \"Unknown\".\n */\nexport type NewContact = NewContactBase &\n ({ email: string; name?: string } | { email?: string; name: string });\n\nexport type ContentType = \"text\" | \"markdown\" | \"html\";\n\n/**\n * Represents an external entity linked to a thread.\n *\n * Links are created by sources to represent external entities (issues, emails, calendar events)\n * attached to a thread container. A thread can have multiple links (1:many).\n * Links store source-specific data like type, status, metadata, and embeddings.\n *\n * @example\n * ```typescript\n * // A link representing a Linear issue\n * const link: Link = {\n * threadId: \"...\" as Uuid,\n * source: \"linear:issue:549dd8bd-2bc9-43d1-95d5-4b4af0c5af1b\",\n * created: new Date(),\n * author: { id: \"...\" as ActorId, type: ActorType.Contact, name: \"Alice\" },\n * title: \"Fix login bug\",\n * type: \"issue\",\n * status: \"open\",\n * meta: { projectId: \"TEAM\", url: \"https://linear.app/team/TEAM-123\" },\n * assignee: null,\n * actions: null,\n * };\n * ```\n */\nexport type Link = {\n /** The thread this link belongs to */\n threadId: Uuid;\n /** External source identifier for dedup/upsert */\n source: string | null;\n /** When this link was originally created in its source system */\n created: Date;\n /** The actor credited with creating this link */\n author: Actor | null;\n /** Display title */\n title: string;\n /** Truncated preview */\n preview: string | null;\n /** The actor assigned to this link */\n assignee: Actor | null;\n /** Source-defined type string (e.g., issue, pull_request, email, event) */\n type: string | null;\n /** Source-defined status string (e.g., open, done, closed) */\n status: string | null;\n /** Interactive action buttons */\n actions: Array<Action> | null;\n /** Source metadata */\n meta: ThreadMeta | null;\n /** URL to open the original item in its source application (e.g., \"Open in Linear\") */\n sourceUrl: string | null;\n /** Channel ID that produced this link (matches source_channel.channel_id) */\n channelId: string | null;\n /**\n * Cross-connector thread bundling key.\n *\n * @deprecated Use `sources` instead. Reads return the first element of\n * `sources` for backward compatibility; new writes should populate `sources`.\n */\n relatedSource: string | null;\n /**\n * Canonical identifiers for this link. Two links whose `sources` arrays\n * overlap share the same thread (array overlap, `sources && new.sources`).\n *\n * Use this to bundle with another connector via a canonical alias. For\n * example, every calendar connector emits `icaluid:<iCalUID>` so any\n * meeting-notes connector can bundle by setting the same alias.\n */\n sources: string[];\n};\n\n/**\n * Type for creating new links.\n *\n * Links are created by sources to represent external entities.\n * Requires a source identifier for dedup/upsert.\n */\nexport type NewLink = Partial<\n Omit<Link, \"author\" | \"assignee\" | \"threadId\">\n> & {\n /**\n * Canonical ID for the item in an external system.\n * When set, uniquely identifies the link within a priority tree. This performs\n * an upsert.\n *\n * @deprecated Pass `sources: [...]` instead. Both fields can be set during\n * the transition; the runtime will normalize.\n */\n source?: string;\n /**\n * Canonical identifiers for this item. Any element shared with another\n * link's `sources` bundles the two links into the same thread. Used for\n * cross-connector bundling — e.g. a meeting-notes connector setting\n * `[\"granola:<id>\", \"icaluid:<uid>\"]` to attach onto a calendar event\n * thread that includes `icaluid:<uid>` in its own `sources`.\n */\n sources?: string[];\n /** The person that created the item. By default, it will be the twist itself. */\n author?: NewActor;\n /** The person assigned to the item. */\n assignee?: NewActor | null;\n /**\n * Thread access level: \"public\", \"members\" (default), or \"private\".\n * When \"private\", thread visibility is limited to the creator and contacts in accessContacts.\n */\n access?: ThreadAccessLevel;\n /**\n * Contacts who can see a private thread.\n * Pass email-based NewContact objects; they are resolved to contact IDs by the API.\n * If omitted for a private thread, defaults to the connection owner.\n */\n accessContacts?: NewContact[];\n /**\n * Whether the thread should be marked as unread for users.\n * - undefined/omitted (default): Thread is unread for users, except auto-marked\n * as read for the author if they are the twist owner (user)\n * - false: Thread is marked as read for all users in the priority at creation time\n */\n unread?: boolean;\n /**\n * Whether the thread is archived.\n * - true: Archive the thread\n * - false: Unarchive the thread\n * - undefined (default): Preserve current archive state\n */\n archived?: boolean;\n /**\n * Explicit priority (disables automatic priority matching).\n * Only used when the link creates a new thread. When omitted, the\n * server classifies the thread using the user's priority rules.\n */\n priority?: Pick<Priority, \"id\">;\n };\n\n/**\n * A new link with notes to save via integrations.saveLink().\n * Creates a thread+link pair, with notes attached to the thread.\n */\nexport type NewLinkWithNotes = NewLink & {\n /**\n * Title for the link and its thread container.\n * Must be the real entity title (e.g. issue title, message subject),\n * never a placeholder or ID. This value overwrites the existing title on upsert.\n * Omit to preserve the existing title (e.g. for cancelled events where the\n * title may not be available in the webhook payload).\n */\n title?: string;\n /** Notes to attach to the thread */\n notes?: Omit<NewNote, \"thread\">[];\n /** Schedules to create for the link */\n schedules?: Array<Omit<NewSchedule, \"threadId\">>;\n /** Schedule occurrence overrides */\n scheduleOccurrences?: NewScheduleOccurrence[];\n};\n\n/**\n * Type for updating existing links.\n *\n * Set `threadId` to move the link to a different thread.\n * Requires LinkAccess.Full.\n */\nexport type LinkUpdate = { id: Uuid } & {\n /** Move the link to a different thread owned by the twist's user. */\n threadId?: Uuid;\n};\n\n/**\n * A single operation within a plan submitted for user approval.\n *\n * Operations include display metadata (titles) so the app can render\n * a human-readable summary without additional lookups.\n */\nexport type PlanOperation =\n | {\n type: \"updateThread\";\n threadId: Uuid;\n /** Current thread title for display */\n threadTitle: string;\n changes: Partial<Pick<ThreadFields, \"archived\" | \"title\" | \"type\">> & {\n /** Move to this priority */\n priority?: { id: Uuid; title: string };\n };\n }\n | {\n type: \"updateLink\";\n linkId: Uuid;\n /** Current link title for display */\n linkTitle: string;\n changes: {\n /** Move to this thread */\n threadId?: Uuid;\n threadTitle?: string;\n };\n }\n | {\n type: \"createThread\";\n title: string;\n priorityId: Uuid;\n /** Priority title for display */\n priorityTitle: string;\n }\n | {\n type: \"createNote\";\n threadId: Uuid;\n /** Thread title for display */\n threadTitle: string;\n content: string;\n }\n | {\n type: \"updatePriority\";\n priorityId: Uuid;\n /** Current priority title for display */\n priorityTitle: string;\n changes: Partial<Pick<Priority, \"title\" | \"archived\">> & {\n /** Move under this parent */\n parent?: { id: Uuid; title: string };\n };\n };\n";
@@ -5,4 +5,4 @@
5
5
  * Generated from: prebuild.ts
6
6
  */
7
7
 
8
- export default "import { type Tag } from \"./tag\";\nimport {\n type Actor,\n type ActorId,\n type NewActor,\n type NewTags,\n type Tags,\n} from \"./plot\";\nimport { Uuid } from \"./utils/uuid\";\n\nexport { Uuid } from \"./utils/uuid\";\n\n/**\n * Represents a schedule entry for a thread.\n *\n * Schedules define when a thread occurs in time. A thread may have zero or more schedules:\n * - Shared schedules (userId is null): visible to all members of the thread's priority\n * - Per-user schedules (userId set): private ordering/scheduling for a specific user\n *\n * For recurring events in the SDK, start/end represent the first occurrence's\n * time. In the database, the `at`/`on` range is expanded to span from the first\n * occurrence start to the last occurrence end (or open-ended if no fixed end).\n * The `duration` column stores the per-occurrence duration, enabling range overlap\n * queries to correctly find all recurring events with occurrences in a given window.\n */\nexport type Schedule = {\n /** When this schedule was created */\n created: Date;\n /** Whether this schedule has been archived */\n archived: boolean;\n /** If set, this is a per-user schedule visible only to this user */\n userId: ActorId | null;\n /** Per-user ordering within a day (only set for per-user schedules) */\n order: number | null;\n /**\n * Start time of the schedule.\n * Date object for timed events, date string in \"YYYY-MM-DD\" format for all-day events.\n */\n start: Date | string | null;\n /**\n * End time of the schedule.\n * Date object for timed events, date string in \"YYYY-MM-DD\" format for all-day events.\n */\n end: Date | string | null;\n /** Recurrence rule in RFC 5545 RRULE format (e.g., \"FREQ=WEEKLY;BYDAY=MO,WE,FR\") */\n recurrenceRule: string | null;\n /** Duration of each occurrence in milliseconds (required for recurring schedules) */\n duration: number | null;\n /** Array of dates to exclude from the recurrence pattern */\n recurrenceExdates: Date[] | null;\n /**\n * For occurrence exceptions: the original date/time of this occurrence in the series.\n * Format: Date object or \"YYYY-MM-DD\" for all-day events.\n */\n occurrence: Date | string | null;\n /** Contacts invited to this schedule (attendees/participants) */\n contacts: ScheduleContact[];\n};\n\nexport type ScheduleContactStatus = \"attend\" | \"skip\";\nexport type ScheduleContactRole = \"organizer\" | \"required\" | \"optional\";\n\nexport type ScheduleContact = {\n contact: Actor;\n status: ScheduleContactStatus | null;\n role: ScheduleContactRole;\n archived: boolean;\n};\n\nexport type NewScheduleContact = {\n contact: NewActor;\n status?: ScheduleContactStatus | null;\n role?: ScheduleContactRole | null;\n archived?: boolean;\n};\n\n/**\n * Type for creating new schedules.\n *\n * Requires `threadId` and `start`. All other fields are optional.\n *\n * @example\n * ```typescript\n * // Simple timed event\n * const schedule: NewSchedule = {\n * threadId: threadId,\n * start: new Date(\"2025-03-15T10:00:00Z\"),\n * end: new Date(\"2025-03-15T11:00:00Z\")\n * };\n *\n * // All-day event\n * const allDay: NewSchedule = {\n * threadId: threadId,\n * start: \"2025-03-15\",\n * end: \"2025-03-16\"\n * };\n *\n * // Recurring weekly event\n * const recurring: NewSchedule = {\n * threadId: threadId,\n * start: new Date(\"2025-01-20T14:00:00Z\"),\n * end: new Date(\"2025-01-20T15:00:00Z\"),\n * recurrenceRule: \"FREQ=WEEKLY;BYDAY=MO\",\n * recurrenceUntil: new Date(\"2025-06-30\")\n * };\n * ```\n */\nexport type NewSchedule = {\n /** The thread this schedule belongs to */\n threadId: Uuid;\n /**\n * Start time. Date for timed events, \"YYYY-MM-DD\" for all-day.\n * Determines whether the schedule uses `at` (timed) or `on` (all-day) storage.\n */\n start: Date | string;\n /** End time. Date for timed events, \"YYYY-MM-DD\" for all-day. */\n end?: Date | string | null;\n /** Recurrence rule in RFC 5545 RRULE format */\n recurrenceRule?: string | null;\n /**\n * For recurring schedules, the last occurrence date (inclusive).\n * When both recurrenceCount and recurrenceUntil are provided, recurrenceCount takes precedence.\n */\n recurrenceUntil?: Date | string | null;\n /**\n * For recurring schedules, the number of occurrences to generate.\n * Takes precedence over recurrenceUntil if both are provided.\n */\n recurrenceCount?: number | null;\n /** Array of dates to exclude from the recurrence pattern */\n recurrenceExdates?: Date[] | null;\n /**\n * For occurrence exceptions: the original date/time of this occurrence.\n */\n occurrence?: Date | string | null;\n /** If set, this is a per-user schedule for the specified user */\n userId?: ActorId | null;\n /** Per-user ordering (only valid with userId) */\n order?: number | null;\n /** Whether to archive this schedule */\n archived?: boolean;\n /** Contacts to upsert on this schedule. Upserted by contact identity. */\n contacts?: NewScheduleContact[];\n};\n\n/**\n * Represents a specific instance of a recurring schedule.\n * All field values are computed by merging the recurring schedule's\n * defaults with any occurrence-specific overrides.\n */\nexport type ScheduleOccurrence = {\n /**\n * Original date/datetime of this occurrence.\n * Use start for the occurrence's current start time.\n */\n occurrence: Date | string;\n\n /** The recurring schedule of which this is an occurrence */\n schedule: Schedule;\n\n /** Effective start for this occurrence (series default + override) */\n start: Date | string;\n /** Effective end for this occurrence */\n end: Date | string | null;\n\n /** Tags for this occurrence */\n tags: Tags;\n\n /** True if the occurrence is archived */\n archived: boolean;\n};\n\n/**\n * Type for creating or updating schedule occurrences.\n */\nexport type NewScheduleOccurrence = Pick<\n ScheduleOccurrence,\n \"occurrence\" | \"start\"\n> &\n Partial<\n Omit<ScheduleOccurrence, \"occurrence\" | \"start\" | \"schedule\" | \"tags\">\n > & {\n /** Tags for this occurrence */\n tags?: NewTags;\n\n /** Add or remove the twist's tags on this occurrence */\n twistTags?: Partial<Record<Tag, boolean>>;\n\n /** Whether this occurrence should be marked as unread */\n unread?: boolean;\n\n /** Contacts to upsert on this occurrence's schedule */\n contacts?: NewScheduleContact[];\n };\n\n/**\n * Type for updating schedule occurrences inline.\n */\nexport type ScheduleOccurrenceUpdate = Pick<\n NewScheduleOccurrence,\n \"occurrence\"\n> &\n Partial<Omit<NewScheduleOccurrence, \"occurrence\" | \"schedule\">>;\n";
8
+ export default "import { type Tag } from \"./tag\";\nimport {\n type Actor,\n type ActorId,\n type NewActor,\n type NewTags,\n type Tags,\n} from \"./plot\";\nimport { Uuid } from \"./utils/uuid\";\n\nexport { Uuid } from \"./utils/uuid\";\n\n/**\n * Represents a schedule entry for a thread.\n *\n * Schedules define when a thread occurs in time. A thread may have zero or more schedules:\n * - Shared schedules (userId is null): visible to all members of the thread's priority\n * - Per-user schedules (userId set): private ordering/scheduling for a specific user\n *\n * For recurring events in the SDK, start/end represent the first occurrence's\n * time. In the database, the `at`/`on` range is expanded to span from the first\n * occurrence start to the last occurrence end (or open-ended if no fixed end).\n * The `duration` column stores the per-occurrence duration, enabling range overlap\n * queries to correctly find all recurring events with occurrences in a given window.\n */\nexport type Schedule = {\n /** When this schedule was created */\n created: Date;\n /** Whether this schedule has been archived */\n archived: boolean;\n /** If set, this is a per-user schedule visible only to this user */\n userId: ActorId | null;\n /** Per-user ordering within a day (only set for per-user schedules) */\n order: number | null;\n /**\n * Start time of the schedule.\n * Date object for timed events, date string in \"YYYY-MM-DD\" format for all-day events.\n */\n start: Date | string | null;\n /**\n * End time of the schedule.\n * Date object for timed events, date string in \"YYYY-MM-DD\" format for all-day events.\n */\n end: Date | string | null;\n /** Recurrence rule in RFC 5545 RRULE format (e.g., \"FREQ=WEEKLY;BYDAY=MO,WE,FR\") */\n recurrenceRule: string | null;\n /** Duration of each occurrence in milliseconds (required for recurring schedules) */\n duration: number | null;\n /** Array of dates to exclude from the recurrence pattern */\n recurrenceExdates: Date[] | null;\n /**\n * For occurrence exceptions: the original date/time of this occurrence in the series.\n * Format: Date object or \"YYYY-MM-DD\" for all-day events.\n */\n occurrence: Date | string | null;\n /** Contacts invited to this schedule (attendees/participants) */\n contacts: ScheduleContact[];\n};\n\nexport type ScheduleContactStatus = \"attend\" | \"skip\";\nexport type ScheduleContactRole = \"organizer\" | \"required\" | \"optional\";\n\nexport type ScheduleContact = {\n contact: Actor;\n status: ScheduleContactStatus | null;\n role: ScheduleContactRole;\n archived: boolean;\n};\n\nexport type NewScheduleContact = {\n contact: NewActor;\n status?: ScheduleContactStatus | null;\n role?: ScheduleContactRole | null;\n archived?: boolean;\n};\n\n/**\n * Type for creating new schedules.\n *\n * Requires `threadId` and `start`. All other fields are optional.\n *\n * @example\n * ```typescript\n * // Simple timed event\n * const schedule: NewSchedule = {\n * threadId: threadId,\n * start: new Date(\"2025-03-15T10:00:00Z\"),\n * end: new Date(\"2025-03-15T11:00:00Z\")\n * };\n *\n * // All-day event\n * const allDay: NewSchedule = {\n * threadId: threadId,\n * start: \"2025-03-15\",\n * end: \"2025-03-16\"\n * };\n *\n * // Recurring weekly event\n * const recurring: NewSchedule = {\n * threadId: threadId,\n * start: new Date(\"2025-01-20T14:00:00Z\"),\n * end: new Date(\"2025-01-20T15:00:00Z\"),\n * recurrenceRule: \"FREQ=WEEKLY;BYDAY=MO\",\n * recurrenceUntil: new Date(\"2025-06-30\")\n * };\n * ```\n */\nexport type NewSchedule = {\n /** The thread this schedule belongs to */\n threadId: Uuid;\n /**\n * Start time. Date for timed events, \"YYYY-MM-DD\" for all-day.\n * Determines whether the schedule uses `at` (timed) or `on` (all-day) storage.\n */\n start: Date | string;\n /** End time. Date for timed events, \"YYYY-MM-DD\" for all-day. */\n end?: Date | string | null;\n /** Recurrence rule in RFC 5545 RRULE format */\n recurrenceRule?: string | null;\n /**\n * For recurring schedules, the last occurrence date (inclusive).\n * When both recurrenceCount and recurrenceUntil are provided, recurrenceCount takes precedence.\n */\n recurrenceUntil?: Date | string | null;\n /**\n * For recurring schedules, the number of occurrences to generate.\n * Takes precedence over recurrenceUntil if both are provided.\n */\n recurrenceCount?: number | null;\n /** Array of dates to exclude from the recurrence pattern */\n recurrenceExdates?: Date[] | null;\n /**\n * For occurrence exceptions: the original date/time of this occurrence.\n */\n occurrence?: Date | string | null;\n /** If set, this is a per-user schedule for the specified user */\n userId?: ActorId | null;\n /** Per-user ordering (only valid with userId) */\n order?: number | null;\n /** Whether to archive this schedule */\n archived?: boolean;\n /** Contacts to upsert on this schedule. Upserted by contact identity. */\n contacts?: NewScheduleContact[];\n};\n\n/**\n * Represents a specific instance of a recurring schedule.\n * All field values are computed by merging the recurring schedule's\n * defaults with any occurrence-specific overrides.\n */\nexport type ScheduleOccurrence = {\n /**\n * Original date/datetime of this occurrence.\n * Use start for the occurrence's current start time.\n */\n occurrence: Date | string;\n\n /** The recurring schedule of which this is an occurrence */\n schedule: Schedule;\n\n /** Effective start for this occurrence (series default + override) */\n start: Date | string;\n /** Effective end for this occurrence */\n end: Date | string | null;\n\n /** Tags for this occurrence */\n tags: Tags;\n};\n\n/**\n * Type for creating or updating schedule occurrences.\n *\n * Use `cancelled: true` to skip a single date in a recurring series\n * (e.g. an upstream calendar reports one occurrence as cancelled).\n * Internally this is translated into an addition to the parent\n * schedule's `recurrenceExdates`; no occurrence schedule row is\n * persisted for the cancelled date. For seeding the full exception\n * list on the master at initial sync, use `NewSchedule.recurrenceExdates`\n * instead.\n *\n * Use a normal occurrence record (without `cancelled`) for genuine\n * overrides — time changes, RSVP differences, etc.\n */\nexport type NewScheduleOccurrence = Pick<\n ScheduleOccurrence,\n \"occurrence\" | \"start\"\n> &\n Partial<\n Omit<ScheduleOccurrence, \"occurrence\" | \"start\" | \"schedule\" | \"tags\">\n > & {\n /** Tags for this occurrence */\n tags?: NewTags;\n\n /** Add or remove the twist's tags on this occurrence */\n twistTags?: Partial<Record<Tag, boolean>>;\n\n /** Whether this occurrence should be marked as unread */\n unread?: boolean;\n\n /** Contacts to upsert on this occurrence's schedule */\n contacts?: NewScheduleContact[];\n\n /**\n * Mark this single date as cancelled. The runtime translates this\n * into an addition to the parent schedule's `recurrenceExdates` and\n * archives any pre-existing override row for the same date — no\n * occurrence schedule row is created.\n */\n cancelled?: boolean;\n };\n\n/**\n * Type for updating schedule occurrences inline.\n */\nexport type ScheduleOccurrenceUpdate = Pick<\n NewScheduleOccurrence,\n \"occurrence\"\n> &\n Partial<Omit<NewScheduleOccurrence, \"occurrence\" | \"schedule\">>;\n";
@@ -5,4 +5,4 @@
5
5
  * Generated from: prebuild.ts
6
6
  */
7
7
 
8
- export default "import {\n type Actor,\n type ActorId,\n type NewContact,\n type NewLinkWithNotes,\n ITool,\n Serializable,\n} from \"..\";\nimport { Tag } from \"../tag\";\nimport type { JSONValue } from \"../utils/types\";\nimport type { Uuid } from \"../utils/uuid\";\n\n/**\n * A resource that can be synced (e.g., a calendar, project, channel).\n * Returned by getChannels() and managed by users in the twist setup/edit modal.\n */\nexport type Channel = {\n /** External ID shared across users (e.g., Google calendar ID) */\n id: string;\n /** Display name shown in the UI */\n title: string;\n /** Optional nested channel resources (e.g., subfolders) */\n children?: Channel[];\n /** Per-channel link type configs. Overrides twist-level linkTypes when present. */\n linkTypes?: LinkTypeConfig[];\n};\n\n/**\n * Describes a link type that a connector creates.\n * Used for display in the UI (icons, labels).\n */\nexport type LinkTypeConfig = {\n /** Machine-readable type identifier (e.g., \"issue\", \"pull_request\") */\n type: string;\n /** Human-readable label (e.g., \"Issue\", \"Pull Request\") */\n label: string;\n /** URL to an icon for this link type (light mode). Prefer Iconify `logos/*` URLs. */\n logo?: string;\n /** URL to an icon for dark mode. Use when the default logo is invisible on dark backgrounds (e.g., Iconify `simple-icons/*` with `?color=`). */\n logoDark?: string;\n /** URL to a monochrome icon (uses `currentColor`). Prefer Iconify `simple-icons/*` URLs without a `?color=` param. */\n logoMono?: string;\n /** Possible status values for this type */\n statuses?: Array<{\n /** Machine-readable status (e.g., \"open\", \"done\") */\n status: string;\n /** Human-readable label (e.g., \"Open\", \"Done\") */\n label: string;\n /** Tag to propagate to thread when this status is active (e.g., Tag.Done) */\n tag?: Tag;\n /** Whether this status represents completion (done, closed, merged, cancelled, etc.) */\n done?: boolean;\n /**\n * Whether this status represents the connector's \"to-do\" / active state.\n * When a user adds a thread to Plot's agenda, done-status links flip to\n * the status marked `todo: true` (e.g., Gmail's \"starred\", Linear's\n * \"todo\") so the link widget and thread tags reflect the active state.\n * At most one status per type should set this.\n */\n todo?: boolean;\n /**\n * Default status applied when Plot asks the connector to create a new\n * item of this type via `Connector.onCreateLink`. Declaring at least one\n * status with `createDefault: true` is how a link type opts in to\n * Plot-initiated creation. At most one status per type should set this.\n */\n createDefault?: boolean;\n }>;\n /** Whether this link type supports displaying and changing the assignee */\n supportsAssignee?: boolean;\n /** Default thread creation mode for this link type: 'all' | 'actionable' | 'manual' */\n defaultCreateThreads?: string;\n};\n\n/**\n * Context passed to onChannelEnabled with plan-based sync hints.\n * Connectors can use these hints to limit initial sync scope.\n */\nexport type SyncContext = {\n /**\n * Earliest date to include in initial sync, based on the user's plan.\n *\n * Non-calendar connectors should use this as their date filter (timeMin,\n * created.gte, etc.) during initial sync. Calendar connectors should\n * ignore this for API queries (to avoid missing recurring events) — the\n * API layer filters non-recurring items automatically.\n *\n * Undefined when no limit applies.\n */\n syncHistoryMin?: Date;\n};\n\n/**\n * Built-in tool for managing OAuth authentication and channel resources.\n *\n * The Integrations tool:\n * 1. Manages channel resources (calendars, projects, etc.) per actor\n * 2. Returns tokens for the user who enabled sync on a channel\n * 3. Supports per-actor auth via actAs() for write-back operations\n * 4. Provides saveLink/saveContacts for Connectors to save data directly\n *\n * Connectors declare their provider, scopes, and channel lifecycle methods as\n * class properties and methods. The Integrations tool reads these automatically.\n * Auth and channel management is handled in the twist edit modal in Flutter.\n *\n * @example\n * ```typescript\n * class CalendarConnector extends Connector<CalendarConnector> {\n * readonly provider = AuthProvider.Google;\n * readonly scopes = [\"https://www.googleapis.com/auth/calendar\"];\n *\n * build(build: ToolBuilder) {\n * return {\n * integrations: build(Integrations),\n * };\n * }\n *\n * async getChannels(auth: Authorization, token: AuthToken): Promise<Channel[]> {\n * const calendars = await this.listCalendars(token);\n * return calendars.map(c => ({ id: c.id, title: c.name }));\n * }\n *\n * async onChannelEnabled(channel: Channel) {\n * // Start syncing\n * }\n *\n * async onChannelDisabled(channel: Channel) {\n * // Stop syncing\n * }\n * }\n * ```\n */\nexport abstract class Integrations extends ITool {\n /**\n * Merge scopes from multiple tools, deduplicating.\n *\n * @param scopeArrays - Arrays of scopes to merge\n * @returns Deduplicated array of scopes\n */\n static MergeScopes(...scopeArrays: string[][]): string[] {\n return Array.from(new Set(scopeArrays.flat()));\n }\n\n /**\n * Retrieves an access token for a channel resource.\n *\n * Returns the token of the user who enabled sync on the given channel.\n * If the channel is not enabled or the token is expired/invalid, returns null.\n *\n * @param channelId - The channel resource ID (e.g., calendar ID)\n * @returns Promise resolving to the access token or null\n */\n abstract get(channelId: string): Promise<AuthToken | null>;\n /**\n * Retrieves an access token for a channel resource.\n *\n * @param provider - The OAuth provider (deprecated, ignored for single-provider connectors)\n * @param channelId - The channel resource ID (e.g., calendar ID)\n * @returns Promise resolving to the access token or null\n * @deprecated Use get(channelId) instead. The provider is implicit from the connector.\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract get(provider: AuthProvider, channelId: string): Promise<AuthToken | null>;\n\n /**\n * Execute a callback as a specific actor, requesting auth if needed.\n *\n * If the actor has a valid token, calls the callback immediately with it.\n * If the actor has no token, creates a private auth note in the specified\n * activity prompting them to connect. Once they authorize, this callback fires.\n *\n * @param provider - The OAuth provider\n * @param actorId - The actor to act as\n * @param activityId - The activity to create an auth note in (if needed)\n * @param callback - Function to call with the token\n * @param extraArgs - Additional arguments to pass to the callback\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract actAs<\n TArgs extends Serializable[],\n TCallback extends (token: AuthToken, ...args: TArgs) => any\n >(\n provider: AuthProvider,\n actorId: ActorId,\n activityId: Uuid,\n callback: TCallback,\n ...extraArgs: TArgs\n ): Promise<void>;\n\n /**\n * Saves a link with notes to the connector's priority.\n *\n * Creates a thread+link pair. The thread is a lightweight container;\n * the link holds the external entity data (source, meta, type, status, etc.).\n *\n * This method is available only to Connectors (not regular Twists).\n *\n * @param link - The link with notes to save\n * @returns Promise resolving to the saved thread's UUID, or null if the\n * link was filtered out (e.g. older than the plan's sync history limit)\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract saveLink(link: NewLinkWithNotes): Promise<Uuid | null>;\n\n /**\n * Batch version of {@link saveLink} — saves many links in one call.\n *\n * Connectors syncing many items per page (e.g. calendar events, issues,\n * messages) should prefer this over looping `saveLink`. Each `saveLink`\n * crosses the runtime boundary and counts against the per-execution\n * request budget; `saveLinks` collapses N saves into a single crossing.\n *\n * Failures on individual links DO NOT abort the batch. A bad item lands\n * as `null` in its slot and the rest still save. This prevents one\n * malformed record from losing an entire page of sync progress.\n *\n * This method is available only to Connectors (not regular Twists).\n *\n * @param links - Array of links with notes to save\n * @returns Array of the same length and order as `links`. Each entry is\n * the saved thread's UUID, or `null` if the link was filtered out\n * (e.g. older than the plan's sync history limit) OR failed to save.\n * The two null causes are not distinguished; the save failure is\n * logged server-side.\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract saveLinks(links: NewLinkWithNotes[]): Promise<(Uuid | null)[]>;\n\n /**\n * Saves contacts to the connector's priority.\n *\n * @param contacts - Array of contacts to save\n * @returns Promise resolving to the saved actors\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract saveContacts(contacts: NewContact[]): Promise<Actor[]>;\n\n /**\n * Archives links matching the given filter that were created by this connector.\n *\n * For each archived link's thread, if no other non-archived links remain,\n * the thread is also archived.\n *\n * @param filter - Filter criteria for which links to archive\n * @returns Promise that resolves when archiving is complete\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract archiveLinks(filter: ArchiveLinkFilter): Promise<void>;\n\n /**\n * Sets or clears todo status on a thread owned by this connector.\n *\n * @param source - The link source URL identifying the thread\n * @param actorId - The user to set the todo for\n * @param todo - true to mark as todo, false to clear/complete\n * @param options - Additional options\n * @param options.date - The todo date (when todo=true). Defaults to today.\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract setThreadToDo(\n source: string,\n actorId: ActorId,\n todo: boolean,\n options?: { date?: Date | string }\n ): Promise<void>;\n\n}\n\n/**\n * Filter criteria for archiving links.\n * All fields are optional; only provided fields are used for matching.\n */\nexport type ArchiveLinkFilter = {\n /** Filter by channel ID */\n channelId?: string;\n /** Filter by link type (e.g., \"issue\", \"pull_request\") */\n type?: string;\n /** Filter by link status (e.g., \"open\", \"closed\") */\n status?: string;\n /** Filter by metadata fields (uses containment matching) */\n meta?: Record<string, JSONValue>;\n};\n\n/**\n * Enumeration of supported OAuth providers.\n *\n * Each provider has different OAuth endpoints, scopes, and token formats.\n * The Integrations tool handles the provider-specific implementation details.\n */\nexport enum AuthProvider {\n /** Google OAuth provider for Google Workspace services */\n Google = \"google\",\n /** Microsoft OAuth provider for Microsoft 365 services */\n Microsoft = \"microsoft\",\n /** Notion OAuth provider for Notion workspaces */\n Notion = \"notion\",\n /** Slack OAuth provider for Slack workspaces */\n Slack = \"slack\",\n /** Atlassian OAuth provider for Jira and Confluence */\n Atlassian = \"atlassian\",\n /** Linear OAuth provider for Linear workspaces */\n Linear = \"linear\",\n /** Monday.com OAuth provider */\n Monday = \"monday\",\n /** GitHub OAuth provider for GitHub repositories and organizations */\n GitHub = \"github\",\n /** Asana OAuth provider for Asana workspaces */\n Asana = \"asana\",\n /** HubSpot OAuth provider for HubSpot CRM */\n HubSpot = \"hubspot\",\n /** Todoist OAuth provider for Todoist task management */\n Todoist = \"todoist\",\n /** Airtable OAuth provider for Airtable bases */\n Airtable = \"airtable\",\n}\n\n/**\n * Represents a completed authorization from an OAuth flow.\n *\n * Contains the provider, granted scopes, and the actor (contact) that was authorized.\n * Tokens are looked up by (provider, actorId) rather than a random ID.\n */\nexport type Authorization = {\n /** The OAuth provider this authorization is for */\n provider: AuthProvider;\n /** Array of OAuth scopes this authorization covers */\n scopes: string[];\n /** The external account that was authorized (e.g., the Google account) */\n actor: Actor;\n};\n\n/**\n * Represents a stored OAuth authentication token.\n *\n * Contains the actual access token and the scopes it was granted,\n * which may be a subset of the originally requested scopes.\n */\nexport type AuthToken = {\n /** The OAuth access token */\n token: string;\n /** Array of granted OAuth scopes */\n scopes: string[];\n /**\n * Provider-specific metadata as key-value pairs.\n *\n * For Slack (AuthProvider.Slack):\n * - authed_user_id: The authenticated user's Slack ID\n * - bot_user_id: The bot user's Slack ID\n * - team_name: The Slack workspace/team name\n */\n provider?: Record<string, string>;\n};\n";
8
+ export default "import {\n type Actor,\n type ActorId,\n type NewContact,\n type NewLinkWithNotes,\n ITool,\n Serializable,\n} from \"..\";\nimport { Tag } from \"../tag\";\nimport type { JSONValue } from \"../utils/types\";\nimport type { Uuid } from \"../utils/uuid\";\n\n/**\n * A resource that can be synced (e.g., a calendar, project, channel).\n * Returned by getChannels() and managed by users in the twist setup/edit modal.\n */\nexport type Channel = {\n /** External ID shared across users (e.g., Google calendar ID) */\n id: string;\n /** Display name shown in the UI */\n title: string;\n /** Optional nested channel resources (e.g., subfolders) */\n children?: Channel[];\n /** Per-channel link type configs. Overrides twist-level linkTypes when present. */\n linkTypes?: LinkTypeConfig[];\n};\n\n/**\n * Describes a link type that a connector creates.\n * Used for display in the UI (icons, labels).\n */\nexport type LinkTypeConfig = {\n /** Machine-readable type identifier (e.g., \"issue\", \"pull_request\") */\n type: string;\n /** Human-readable label (e.g., \"Issue\", \"Pull Request\") */\n label: string;\n /** URL to an icon for this link type (light mode). Prefer Iconify `logos/*` URLs. */\n logo?: string;\n /** URL to an icon for dark mode. Use when the default logo is invisible on dark backgrounds (e.g., Iconify `simple-icons/*` with `?color=`). */\n logoDark?: string;\n /** URL to a monochrome icon (uses `currentColor`). Prefer Iconify `simple-icons/*` URLs without a `?color=` param. */\n logoMono?: string;\n /** Possible status values for this type */\n statuses?: Array<{\n /** Machine-readable status (e.g., \"open\", \"done\") */\n status: string;\n /** Human-readable label (e.g., \"Open\", \"Done\") */\n label: string;\n /** Tag to propagate to thread when this status is active (e.g., Tag.Done) */\n tag?: Tag;\n /** Whether this status represents completion (done, closed, merged, cancelled, etc.) */\n done?: boolean;\n /**\n * Whether this status represents the connector's \"to-do\" / active state.\n * When a user adds a thread to Plot's agenda, done-status links flip to\n * the status marked `todo: true` (e.g., Gmail's \"starred\", Linear's\n * \"todo\") so the link widget and thread tags reflect the active state.\n * At most one status per type should set this.\n */\n todo?: boolean;\n /**\n * Default status applied when Plot asks the connector to create a new\n * item of this type via `Connector.onCreateLink`. Declaring at least one\n * status with `createDefault: true` is how a link type opts in to\n * Plot-initiated creation. At most one status per type should set this.\n */\n createDefault?: boolean;\n }>;\n /** Whether this link type supports displaying and changing the assignee */\n supportsAssignee?: boolean;\n /** Default thread creation mode for this link type: 'all' | 'actionable' | 'manual' */\n defaultCreateThreads?: string;\n};\n\n/**\n * Context passed to onChannelEnabled with plan-based sync hints.\n * Connectors can use these hints to limit initial sync scope.\n */\nexport type SyncContext = {\n /**\n * Earliest date to include in initial sync, based on the user's plan.\n *\n * Non-calendar connectors should use this as their date filter (timeMin,\n * created.gte, etc.) during initial sync. Calendar connectors should\n * ignore this for API queries (to avoid missing recurring events) — the\n * API layer filters non-recurring items automatically.\n *\n * Undefined when no limit applies.\n */\n syncHistoryMin?: Date;\n\n /**\n * True when this is a recovery dispatch after the connection's auth was\n * restored (the user re-authorized a previously-broken connection).\n *\n * The framework calls `onChannelEnabled` again for every channel that was\n * already enabled at the time of re-auth so the connector can recover from\n * the auth gap. Connectors should:\n *\n * 1. Drop any persisted incremental sync cursors / sync tokens so the\n * next sync re-walks history (the cursor may be stale or invalid —\n * Google Calendar invalidates syncTokens after ~7 days).\n * 2. Re-register webhooks (any prior subscription may have been\n * invalidated during the auth outage).\n * 3. Treat this as a backfill that walks history but does NOT spam\n * notifications — set `unread: false` and `archived: false` on\n * items as you would during initial sync.\n *\n * Most connectors can take the same code path as a fresh\n * `onChannelEnabled` for `recovering: true` as long as that path\n * overwrites stored state rather than appending to it.\n */\n recovering?: boolean;\n};\n\n/**\n * Built-in tool for managing OAuth authentication and channel resources.\n *\n * The Integrations tool:\n * 1. Manages channel resources (calendars, projects, etc.) per actor\n * 2. Returns tokens for the user who enabled sync on a channel\n * 3. Supports per-actor auth via actAs() for write-back operations\n * 4. Provides saveLink/saveContacts for Connectors to save data directly\n *\n * Connectors declare their provider, scopes, and channel lifecycle methods as\n * class properties and methods. The Integrations tool reads these automatically.\n * Auth and channel management is handled in the twist edit modal in Flutter.\n *\n * @example\n * ```typescript\n * class CalendarConnector extends Connector<CalendarConnector> {\n * readonly provider = AuthProvider.Google;\n * readonly scopes = [\"https://www.googleapis.com/auth/calendar\"];\n *\n * build(build: ToolBuilder) {\n * return {\n * integrations: build(Integrations),\n * };\n * }\n *\n * async getChannels(auth: Authorization, token: AuthToken): Promise<Channel[]> {\n * const calendars = await this.listCalendars(token);\n * return calendars.map(c => ({ id: c.id, title: c.name }));\n * }\n *\n * async onChannelEnabled(channel: Channel) {\n * // Start syncing\n * }\n *\n * async onChannelDisabled(channel: Channel) {\n * // Stop syncing\n * }\n * }\n * ```\n */\nexport abstract class Integrations extends ITool {\n /**\n * Merge scopes from multiple tools, deduplicating.\n *\n * @param scopeArrays - Arrays of scopes to merge\n * @returns Deduplicated array of scopes\n */\n static MergeScopes(...scopeArrays: string[][]): string[] {\n return Array.from(new Set(scopeArrays.flat()));\n }\n\n /**\n * Retrieves an access token for a channel resource.\n *\n * Returns the token of the user who enabled sync on the given channel.\n * If the channel is not enabled or the token is expired/invalid, returns null.\n *\n * @param channelId - The channel resource ID (e.g., calendar ID)\n * @returns Promise resolving to the access token or null\n */\n abstract get(channelId: string): Promise<AuthToken | null>;\n /**\n * Retrieves an access token for a channel resource.\n *\n * @param provider - The OAuth provider (deprecated, ignored for single-provider connectors)\n * @param channelId - The channel resource ID (e.g., calendar ID)\n * @returns Promise resolving to the access token or null\n * @deprecated Use get(channelId) instead. The provider is implicit from the connector.\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract get(provider: AuthProvider, channelId: string): Promise<AuthToken | null>;\n\n /**\n * Execute a callback as a specific actor, requesting auth if needed.\n *\n * If the actor has a valid token, calls the callback immediately with it.\n * If the actor has no token, creates a private auth note in the specified\n * activity prompting them to connect. Once they authorize, this callback fires.\n *\n * @param provider - The OAuth provider\n * @param actorId - The actor to act as\n * @param activityId - The activity to create an auth note in (if needed)\n * @param callback - Function to call with the token\n * @param extraArgs - Additional arguments to pass to the callback\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract actAs<\n TArgs extends Serializable[],\n TCallback extends (token: AuthToken, ...args: TArgs) => any\n >(\n provider: AuthProvider,\n actorId: ActorId,\n activityId: Uuid,\n callback: TCallback,\n ...extraArgs: TArgs\n ): Promise<void>;\n\n /**\n * Saves a link with notes to the connector's priority.\n *\n * Creates a thread+link pair. The thread is a lightweight container;\n * the link holds the external entity data (source, meta, type, status, etc.).\n *\n * This method is available only to Connectors (not regular Twists).\n *\n * @param link - The link with notes to save\n * @returns Promise resolving to the saved thread's UUID, or null if the\n * link was filtered out (e.g. older than the plan's sync history limit)\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract saveLink(link: NewLinkWithNotes): Promise<Uuid | null>;\n\n /**\n * Batch version of {@link saveLink} — saves many links in one call.\n *\n * Connectors syncing many items per page (e.g. calendar events, issues,\n * messages) should prefer this over looping `saveLink`. Each `saveLink`\n * crosses the runtime boundary and counts against the per-execution\n * request budget; `saveLinks` collapses N saves into a single crossing.\n *\n * Failures on individual links DO NOT abort the batch. A bad item lands\n * as `null` in its slot and the rest still save. This prevents one\n * malformed record from losing an entire page of sync progress.\n *\n * This method is available only to Connectors (not regular Twists).\n *\n * @param links - Array of links with notes to save\n * @returns Array of the same length and order as `links`. Each entry is\n * the saved thread's UUID, or `null` if the link was filtered out\n * (e.g. older than the plan's sync history limit) OR failed to save.\n * The two null causes are not distinguished; the save failure is\n * logged server-side.\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract saveLinks(links: NewLinkWithNotes[]): Promise<(Uuid | null)[]>;\n\n /**\n * Saves contacts to the connector's priority.\n *\n * @param contacts - Array of contacts to save\n * @returns Promise resolving to the saved actors\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract saveContacts(contacts: NewContact[]): Promise<Actor[]>;\n\n /**\n * Archives links matching the given filter that were created by this connector.\n *\n * For each archived link's thread, if no other non-archived links remain,\n * the thread is also archived.\n *\n * @param filter - Filter criteria for which links to archive\n * @returns Promise that resolves when archiving is complete\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract archiveLinks(filter: ArchiveLinkFilter): Promise<void>;\n\n /**\n * Sets or clears todo status on a thread owned by this connector.\n *\n * @param source - The link source URL identifying the thread\n * @param actorId - The user to set the todo for\n * @param todo - true to mark as todo, false to clear/complete\n * @param options - Additional options\n * @param options.date - The todo date (when todo=true). Defaults to today.\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract setThreadToDo(\n source: string,\n actorId: ActorId,\n todo: boolean,\n options?: { date?: Date | string }\n ): Promise<void>;\n\n /**\n * Signal that initial bulk-sync (or recovery sync) for a channel is fully\n * complete. The Flutter app uses this to clear the \"syncing…\" indicator\n * on the connection.\n *\n * The framework automatically marks a channel as syncing when it dispatches\n * `onChannelEnabled` (whether initial-enable, auto-enable from\n * `setChannels`, or recovery after re-auth). Connectors do NOT need to\n * call anything to start tracking — only to signal completion.\n *\n * Call this exactly once when the initial backfill has finished (no more\n * pages, all phases exhausted). Do NOT call it on every incremental sync.\n *\n * If `onChannelEnabled` throws an unhandled exception, the framework\n * automatically clears the syncing state — connectors don't need a\n * `try/catch` to clear state on failure.\n *\n * No-op when no auth/user mapping exists for the channel (e.g. key-based\n * connectors that don't have a per-user OAuth association).\n *\n * @param channelId - The channel resource ID whose initial sync just finished\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract channelSyncCompleted(channelId: string): Promise<void>;\n\n /**\n * Flag a connection as needing re-authentication so the Flutter app\n * surfaces a re-auth prompt on the next sync.\n *\n * Call this when a connector's API call returns a permanent auth-style\n * error that the runtime can't observe through token refresh — e.g.\n * Slack `invalid_auth` / `token_revoked` / `not_authed`, or a 401 on a\n * provider that doesn't refresh. The runtime already flags reauth\n * automatically when an OAuth refresh permanently fails or when the\n * stored token is missing on a get(); only call this for cases the\n * runtime can't see.\n *\n * Idempotent: safe to call repeatedly; existing reauth flags are not\n * overwritten. No-op when the channel has no `enabledBy` actor (e.g.\n * key-based connectors).\n *\n * @param channelId - The channel resource ID whose token is bad\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract markNeedsReauth(channelId: string): Promise<void>;\n\n}\n\n/**\n * Filter criteria for archiving links.\n * All fields are optional; only provided fields are used for matching.\n */\nexport type ArchiveLinkFilter = {\n /** Filter by channel ID */\n channelId?: string;\n /** Filter by link type (e.g., \"issue\", \"pull_request\") */\n type?: string;\n /** Filter by link status (e.g., \"open\", \"closed\") */\n status?: string;\n /** Filter by metadata fields (uses containment matching) */\n meta?: Record<string, JSONValue>;\n};\n\n/**\n * Enumeration of supported OAuth providers.\n *\n * Each provider has different OAuth endpoints, scopes, and token formats.\n * The Integrations tool handles the provider-specific implementation details.\n */\nexport enum AuthProvider {\n /** Google OAuth provider for Google Workspace services */\n Google = \"google\",\n /** Microsoft OAuth provider for Microsoft 365 services */\n Microsoft = \"microsoft\",\n /** Notion OAuth provider for Notion workspaces */\n Notion = \"notion\",\n /** Slack OAuth provider for Slack workspaces */\n Slack = \"slack\",\n /** Atlassian OAuth provider for Jira and Confluence */\n Atlassian = \"atlassian\",\n /** Linear OAuth provider for Linear workspaces */\n Linear = \"linear\",\n /** Monday.com OAuth provider */\n Monday = \"monday\",\n /** GitHub OAuth provider for GitHub repositories and organizations */\n GitHub = \"github\",\n /** Asana OAuth provider for Asana workspaces */\n Asana = \"asana\",\n /** HubSpot OAuth provider for HubSpot CRM */\n HubSpot = \"hubspot\",\n /** Todoist OAuth provider for Todoist task management */\n Todoist = \"todoist\",\n /** Airtable OAuth provider for Airtable bases */\n Airtable = \"airtable\",\n}\n\n/**\n * Represents a completed authorization from an OAuth flow.\n *\n * Contains the provider, granted scopes, and the actor (contact) that was authorized.\n * Tokens are looked up by (provider, actorId) rather than a random ID.\n */\nexport type Authorization = {\n /** The OAuth provider this authorization is for */\n provider: AuthProvider;\n /** Array of OAuth scopes this authorization covers */\n scopes: string[];\n /** The external account that was authorized (e.g., the Google account) */\n actor: Actor;\n};\n\n/**\n * Represents a stored OAuth authentication token.\n *\n * Contains the actual access token and the scopes it was granted,\n * which may be a subset of the originally requested scopes.\n */\nexport type AuthToken = {\n /** The OAuth access token */\n token: string;\n /** Array of granted OAuth scopes */\n scopes: string[];\n /**\n * Provider-specific metadata as key-value pairs.\n *\n * For Slack (AuthProvider.Slack):\n * - authed_user_id: The authenticated user's Slack ID\n * - bot_user_id: The bot user's Slack ID\n * - team_name: The Slack workspace/team name\n */\n provider?: Record<string, string>;\n};\n";
@@ -5,4 +5,4 @@
5
5
  * Generated from: prebuild.ts
6
6
  */
7
7
 
8
- export default "import {\n type Action,\n type Thread,\n type ThreadUpdate,\n type Actor,\n type ActorId,\n ITool,\n type Link,\n type LinkUpdate,\n type NewThread,\n type NewThreadWithNotes,\n type NewNote,\n type NewPriority,\n type Note,\n type NoteUpdate,\n type PlanOperation,\n type Priority,\n type PriorityUpdate,\n Uuid,\n} from \"..\";\nimport {\n type Schedule,\n type NewSchedule,\n} from \"../schedule\";\nimport type { Callback } from \"./callbacks\";\n\nexport enum ThreadAccess {\n /**\n * Create new Note on a Thread where the twist was mentioned.\n * Add/remove tags on Thread or Note where the twist was mentioned.\n */\n Respond,\n /**\n * Create new Thread.\n * Create new Note in a Thread the twist created.\n * All Respond permissions.\n */\n Create,\n /**\n * List/query all Threads owned by the twist's user.\n * Update any Thread (title, tags, archived, type, priority) regardless of creator.\n * Create Notes on any Thread (not just own or mentioned).\n * All Create permissions.\n */\n Full,\n}\n\nexport enum PriorityAccess {\n /**\n * Create new Priorities under the twist owner's priority tree.\n * Update Priorities created by the twist.\n */\n Create,\n /**\n * Read all Priorities owned by the twist's user.\n * Create new Priorities under the twist owner's priority tree.\n * Update and archive any Priority owned by the twist's user.\n */\n Full,\n}\n\nexport enum ContactAccess {\n /** Read existing contact details. Without this, only the ID will be provided. */\n Read,\n}\n\nexport enum LinkAccess {\n /** Read links on any thread owned by the twist's user. */\n Read,\n /** Read + update links, including moving links between threads owned by the twist's user. */\n Full,\n}\n\n/**\n * Intent handler for thread mentions.\n * Defines how the twist should respond when mentioned in a thread.\n */\nexport type NoteIntentHandler = {\n /** Human-readable description of what this intent handles */\n description: string;\n /** Example phrases or activity content that would match this intent */\n examples: string[];\n /** The function to call when this intent is matched */\n handler: (note: Note) => Promise<void>;\n};\n\n/**\n * Filter for querying links from connected source channels.\n */\nexport type LinkFilter = {\n /** Only return links from these channel IDs. */\n channelIds?: string[];\n /** Only return links created/updated after this date. */\n since?: Date;\n /** Only return links of this type. */\n type?: string;\n /** Maximum number of links to return. */\n limit?: number;\n};\n\ntype SearchResultBase = {\n thread: { id: string; title: string | null };\n priority: { id: string; title: string | null };\n similarity: number;\n};\n\nexport type NoteSearchResult = SearchResultBase & {\n type: 'note';\n id: string;\n content: string | null;\n};\n\nexport type LinkSearchResult = SearchResultBase & {\n type: 'link';\n id: string;\n title: string | null;\n sourceUrl: string | null;\n content: string | null;\n};\n\nexport type SearchResult = NoteSearchResult | LinkSearchResult;\n\n/** Default number of search results returned */\nexport const SEARCH_DEFAULT_LIMIT = 10;\n/** Maximum number of search results allowed */\nexport const SEARCH_MAX_LIMIT = 30;\n\nexport type SearchOptions = {\n /** Max results to return (default: 10, max: 30) */\n limit?: number;\n /** Minimum similarity score 0-1 (default: 0.3) */\n threshold?: number;\n /**\n * Scope search to this priority + descendants. Must be owned by the twist\n * owner. When omitted, the server scopes the search to the owner's entire\n * priority tree.\n */\n priorityId?: string;\n};\n\n/**\n * Built-in tool for interacting with the core Plot data layer.\n *\n * The Plot tool provides twists with the ability to create and manage threads,\n * priorities, and contacts within the Plot system. This is the primary interface\n * for twists to persist data and interact with the Plot database.\n *\n * @example\n * ```typescript\n * class MyTwist extends Twist {\n * private plot: Plot;\n *\n * constructor(id: string, tools: ToolBuilder) {\n * super();\n * this.plot = tools.get(Plot);\n * }\n *\n * async activate() {\n * // Create a welcome thread\n * await this.plot.createThread({\n * title: \"Welcome to Plot!\",\n * actions: [{\n * title: \"Get Started\",\n * type: ActionType.external,\n * url: \"https://plot.day/docs\"\n * }]\n * });\n * }\n * }\n * ```\n */\nexport abstract class Plot extends ITool {\n /**\n * Configuration options for the Plot tool.\n *\n * **Important**: All permissions must be explicitly requested. There are no default permissions.\n *\n * @example\n * ```typescript\n * // Minimal configuration with required permissions\n * build(build: ToolBuilder) {\n * return {\n * plot: build(Plot, {\n * thread: {\n * access: ThreadAccess.Create\n * }\n * })\n * };\n * }\n *\n * // Full configuration with callbacks\n * build(build: ToolBuilder) {\n * return {\n * plot: build(Plot, {\n * thread: {\n * access: ThreadAccess.Create,\n * },\n * note: {\n * intents: [{\n * description: \"Schedule meetings\",\n * examples: [\"Schedule a meeting tomorrow\"],\n * handler: this.onSchedulingIntent\n * }],\n * },\n * link: true,\n * priority: {\n * access: PriorityAccess.Full\n * },\n * contact: {\n * access: ContactAccess.Read\n * }\n * })\n * };\n * }\n * ```\n */\n static readonly Options: {\n thread?: {\n /**\n * Capability to create Notes and modify tags.\n * Must be explicitly set to grant permissions.\n */\n access?: ThreadAccess;\n /** When true, auto-mention this twist on new notes in threads where it authored content. */\n defaultMention?: boolean;\n };\n note?: {\n /** When true, auto-mention this twist on new notes in threads where it was @-mentioned. */\n defaultMention?: boolean;\n /**\n * Respond to mentions in notes.\n *\n * When a note mentions this twist, the system will match the note\n * content against these intents and call the matching handler.\n *\n * @example\n * ```typescript\n * intents: [{\n * description: \"Schedule or reschedule calendar events\",\n * examples: [\"Schedule a meeting tomorrow at 2pm\", \"Move my 3pm meeting to 4pm\"],\n * handler: this.onSchedulingRequest\n * }, {\n * description: \"Find available meeting times\",\n * examples: [\"When am I free this week?\", \"Find time for a 1 hour meeting\"],\n * handler: this.onAvailabilityRequest\n * }]\n * ```\n */\n intents?: NoteIntentHandler[];\n };\n /** Enable link processing from connected source channels. */\n link?: true | {\n /** Access level for links. When omitted with `link: true`, only source channel links are accessible. */\n access?: LinkAccess;\n };\n priority?: {\n access?: PriorityAccess;\n };\n contact?: {\n access?: ContactAccess;\n };\n /** Enable semantic search across notes and links owned by the twist's user. */\n search?: true;\n /**\n * When true, admin write operations (on threads/notes/links/priorities not created by this twist)\n * require user approval via plan actions instead of executing immediately.\n * Read operations and operations on the twist's own content still work directly.\n */\n requireApproval?: boolean;\n };\n\n /**\n * Creates a new thread in the Plot system.\n *\n * The thread will be automatically assigned an ID and author information\n * based on the current execution context. All other fields from NewThread\n * will be preserved in the created thread.\n *\n * @param thread - The thread data to create\n * @returns Promise resolving to the created thread's ID\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract createThread(\n thread: NewThread | NewThreadWithNotes\n ): Promise<Uuid>;\n\n /**\n * Creates multiple threads in a single batch operation.\n *\n * This method efficiently creates multiple threads at once, which is\n * more performant than calling createThread() multiple times individually.\n * All threads are created with the same author and access control rules.\n *\n * @param threads - Array of thread data to create\n * @returns Promise resolving to array of created thread IDs\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract createThreads(\n threads: (NewThread | NewThreadWithNotes)[]\n ): Promise<Uuid[]>;\n\n /**\n * Updates an existing thread in the Plot system.\n *\n * **Important**: This method only updates existing threads. It will throw an error\n * if the thread does not exist. Use `createThread()` to create or update (upsert)\n * threads.\n *\n * Only the fields provided in the update object will be modified - all other fields\n * remain unchanged. This enables partial updates without needing to fetch and resend\n * the entire thread object.\n *\n * For tags, provide a Record<number, boolean> where true adds a tag and false removes it.\n * Tags not included in the update remain unchanged.\n *\n * When updating the parent, the thread's path will be automatically recalculated to\n * maintain the correct hierarchical structure.\n *\n * Scheduling is handled separately via `createSchedule()` / `updateSchedule()`.\n *\n * @param thread - The thread update containing the ID or source and fields to change\n * @returns Promise that resolves when the update is complete\n * @throws Error if the thread does not exist\n *\n * @example\n * ```typescript\n * // Mark a task as complete\n * await this.plot.updateThread({\n * id: \"task-123\",\n * done: new Date()\n * });\n *\n * // Add and remove tags\n * await this.plot.updateThread({\n * id: \"thread-789\",\n * tags: {\n * 1: true, // Add tag with ID 1\n * 2: false // Remove tag with ID 2\n * }\n * });\n * ```\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract updateThread(thread: ThreadUpdate): Promise<void>;\n\n /**\n * Retrieves all notes within a thread.\n *\n * Notes are detailed entries within a thread, ordered by creation time.\n * Each note can contain markdown content, actions, and other detailed information\n * related to the parent thread.\n *\n * @param thread - The thread whose notes to retrieve\n * @returns Promise resolving to array of notes in the thread\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract getNotes(thread: Thread): Promise<Note[]>;\n\n /**\n * Creates a new note in a thread.\n *\n * Notes provide detailed content within a thread, supporting markdown,\n * actions, and other rich content. The note will be automatically assigned\n * an ID and author information based on the current execution context.\n *\n * @param note - The note data to create\n * @returns Promise resolving to the created note's ID\n *\n * @example\n * ```typescript\n * // Create a note with content\n * await this.plot.createNote({\n * thread: { id: \"thread-123\" },\n * note: \"Discussion notes from the meeting...\",\n * contentType: \"markdown\"\n * });\n *\n * // Create a note with actions\n * await this.plot.createNote({\n * thread: { id: \"thread-456\" },\n * note: \"Meeting recording available\",\n * actions: [{\n * type: ActionType.external,\n * title: \"View Recording\",\n * url: \"https://example.com/recording\"\n * }]\n * });\n * ```\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract createNote(note: NewNote): Promise<Uuid>;\n\n /**\n * Creates multiple notes in a single batch operation.\n *\n * This method efficiently creates multiple notes at once, which is\n * more performant than calling createNote() multiple times individually.\n * All notes are created with the same author and access control rules.\n *\n * @param notes - Array of note data to create\n * @returns Promise resolving to array of created note IDs\n *\n * @example\n * ```typescript\n * // Create multiple notes in one batch\n * await this.plot.createNotes([\n * {\n * thread: { id: \"thread-123\" },\n * note: \"First message in thread\"\n * },\n * {\n * thread: { id: \"thread-123\" },\n * note: \"Second message in thread\"\n * }\n * ]);\n * ```\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract createNotes(notes: NewNote[]): Promise<Uuid[]>;\n\n /**\n * Updates an existing note in the Plot system.\n *\n * **Important**: This method only updates existing notes. It will throw an error\n * if the note does not exist. Use `createNote()` to create or update (upsert) notes.\n *\n * Only the fields provided in the update object will be modified - all other fields\n * remain unchanged. This enables partial updates without needing to fetch and resend\n * the entire note object.\n *\n * @param note - The note update containing the ID or key and fields to change\n * @returns Promise that resolves when the update is complete\n * @throws Error if the note does not exist\n *\n * @example\n * ```typescript\n * // Update note content\n * await this.plot.updateNote({\n * id: \"note-123\",\n * note: \"Updated content with more details\"\n * });\n *\n * // Add tags to a note\n * await this.plot.updateNote({\n * id: \"note-456\",\n * twistTags: {\n * [Tag.Important]: true\n * }\n * });\n * ```\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract updateNote(note: NoteUpdate): Promise<void>;\n\n /**\n * Retrieves a thread by ID or source.\n *\n * This method enables lookup of threads either by their unique ID or by their\n * source identifier (canonical URL from an external system). Archived threads\n * are included in the results.\n *\n * @param thread - Thread lookup by ID or source\n * @returns Promise resolving to the matching thread or null if not found\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract getThread(\n thread: { id: Uuid } | { source: string }\n ): Promise<Thread | null>;\n\n /**\n * Retrieves a note by ID or key.\n *\n * This method enables lookup of notes either by their unique ID or by their\n * key (unique identifier within the thread). Archived notes are included\n * in the results.\n *\n * @param note - Note lookup by ID or key\n * @returns Promise resolving to the matching note or null if not found\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract getNote(note: { id: Uuid } | { key: string }): Promise<Note | null>;\n\n /**\n * Creates a new priority in the Plot system.\n *\n * Priorities serve as organizational containers for threads and twists.\n * The created priority will be automatically assigned a unique ID.\n *\n * @param priority - The priority data to create\n * @returns Promise resolving to the complete created priority\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract createPriority(priority: NewPriority): Promise<Priority & { created: boolean }>;\n\n /**\n * Retrieves a priority by ID or key.\n *\n * Archived priorities are included in the results.\n *\n * @param priority - Priority lookup by ID or key\n * @returns Promise resolving to the matching priority or null if not found\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract getPriority(\n priority: { id: Uuid } | { key: string }\n ): Promise<Priority | null>;\n\n /**\n * Updates an existing priority in the Plot system.\n *\n * The priority is identified by either its ID or key.\n * Only the fields specified in the update will be changed.\n *\n * @param update - Priority update containing ID/key and fields to change\n * @returns Promise that resolves when the update is complete\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract updatePriority(update: PriorityUpdate): Promise<void>;\n\n /**\n * Retrieves actors by their IDs.\n *\n * Actors represent users, contacts, or twists in the Plot system.\n * This method requires ContactAccess.Read permission.\n *\n * @param ids - Array of actor IDs to retrieve\n * @returns Promise resolving to array of actors\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract getActors(ids: ActorId[]): Promise<Actor[]>;\n\n /**\n * Returns the full Actor for the user who installed this twist.\n * Useful for per-user operations like schedule creation, or when\n * the owner's name or email is needed.\n */\n abstract getOwner(): Promise<Actor>;\n\n /**\n * Returns the user ID (`twist_instance.owner_id`) that installed this\n * twist. This is the same value exposed on Twist via `this.userId`.\n */\n abstract getUserId(): Promise<string>;\n\n /**\n * Returns the owner user's root priority ID. Used as the implicit default\n * when an operation needs a priority but the caller didn't supply one —\n * for example, `plot.createPriority()` without a parent, or\n * `plot.getThreads()` without an explicit `priorityId`.\n *\n * On the server, priority resolution for newly created threads/links\n * happens automatically via `match_priority_for_user`; twists rarely need\n * to call this directly.\n */\n abstract getDefaultPriorityId(): Promise<string>;\n\n /**\n * Creates a new schedule for a thread.\n *\n * Schedules define when a thread occurs in time. A thread can have\n * multiple schedules (shared and per-user).\n *\n * @param schedule - The schedule data to create\n * @returns Promise resolving to the created schedule\n *\n * @example\n * ```typescript\n * // Schedule a timed event\n * const threadId = await this.plot.createThread({\n * title: \"Team standup\"\n * });\n * await this.plot.createSchedule({\n * threadId,\n * start: new Date(\"2025-01-15T10:00:00Z\"),\n * end: new Date(\"2025-01-15T10:30:00Z\"),\n * recurrenceRule: \"FREQ=DAILY;BYDAY=MO,TU,WE,TH,FR\"\n * });\n * ```\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract createSchedule(schedule: NewSchedule): Promise<Schedule>;\n\n /**\n * Retrieves all schedules for a thread.\n *\n * @param threadId - The thread whose schedules to retrieve\n * @returns Promise resolving to array of schedules for the thread\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract getSchedules(threadId: Uuid): Promise<Schedule[]>;\n\n /**\n * Retrieves links from connected source channels.\n *\n * Requires `link: true` in Plot options.\n *\n * @param filter - Optional filter criteria for links\n * @returns Promise resolving to array of links with their notes\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract getLinks(filter?: LinkFilter): Promise<Array<{ link: Link; notes: Note[] }>>;\n\n /**\n * Searches notes and links using semantic similarity.\n *\n * Requires `search: true` in Plot options.\n *\n * @param query - The search query text\n * @param options - Optional search configuration\n * @returns Promise resolving to array of search results ordered by similarity\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract search(query: string, options?: SearchOptions): Promise<SearchResult[]>;\n\n /**\n * Lists threads owned by the twist's user.\n *\n * Requires `ThreadAccess.Full`.\n *\n * @param options - Query options for filtering threads\n * @returns Promise resolving to array of threads\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract getThreads(options?: {\n /**\n * Priority to list threads from. Must be owned by the twist owner.\n * When omitted, defaults to the owner's root priority.\n */\n priorityId?: Uuid;\n /** Include threads from descendant priorities. Default: true. */\n includeDescendants?: boolean;\n /** Include archived threads. Default: false. */\n includeArchived?: boolean;\n /** Maximum number of threads to return. Default: 50, max: 200. */\n limit?: number;\n /** Number of threads to skip for pagination. Default: 0. */\n offset?: number;\n }): Promise<Thread[]>;\n\n /**\n * Lists priorities owned by the twist's user.\n *\n * Requires `PriorityAccess.Full`.\n *\n * @param options - Query options for filtering priorities\n * @returns Promise resolving to array of priorities\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract getPriorities(options?: {\n /**\n * Parent priority to list children of. Must be owned by the twist\n * owner. When omitted, defaults to the owner's root priority.\n */\n parentId?: Uuid;\n /** Include all descendants, not just direct children. Default: false. */\n includeDescendants?: boolean;\n /** Include archived priorities. Default: false. */\n includeArchived?: boolean;\n }): Promise<Priority[]>;\n\n /**\n * Updates a link.\n *\n * Requires `LinkAccess.Full`. Set `threadId` to move the link to a different thread.\n *\n * @param link - The link update containing the ID and fields to change\n * @returns Promise that resolves when the update is complete\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract updateLink(link: LinkUpdate): Promise<void>;\n\n /**\n * Creates a plan of operations for user approval.\n *\n * Returns an Action that can be attached to a note. The user can approve,\n * deny, or request changes. On approval, operations are executed by the API.\n *\n * Requires `requireApproval: true` in Plot options.\n *\n * @param options - Plan configuration\n * @returns An Action of type `plan` to attach to a note\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract createPlan(options: {\n /** Human-readable title summarizing the plan */\n title: string;\n /** Array of operations to execute on approval */\n operations: PlanOperation[];\n /** Callback invoked with (action, approved: boolean) when the user responds */\n callback: Callback;\n }): Action;\n}\n";
8
+ export default "import {\n type Action,\n type Thread,\n type ThreadUpdate,\n type Actor,\n type ActorId,\n ITool,\n type Link,\n type LinkUpdate,\n type NewThread,\n type NewThreadWithNotes,\n type NewNote,\n type NewPriority,\n type Note,\n type NoteUpdate,\n type PlanOperation,\n type Priority,\n type PriorityUpdate,\n Uuid,\n} from \"..\";\nimport {\n type Schedule,\n type NewSchedule,\n} from \"../schedule\";\nimport type { Callback } from \"./callbacks\";\n\nexport enum ThreadAccess {\n /**\n * Create new Note on a Thread where the twist was mentioned.\n * Add/remove tags on Thread or Note where the twist was mentioned.\n */\n Respond,\n /**\n * Create new Thread.\n * Create new Note in a Thread the twist created.\n * All Respond permissions.\n */\n Create,\n /**\n * List/query all Threads owned by the twist's user.\n * Update any Thread (title, tags, archived, type, priority) regardless of creator.\n * Create Notes on any Thread (not just own or mentioned).\n * All Create permissions.\n */\n Full,\n}\n\nexport enum PriorityAccess {\n /**\n * Create new Priorities under the twist owner's priority tree.\n * Update Priorities created by the twist.\n */\n Create,\n /**\n * Read all Priorities owned by the twist's user.\n * Create new Priorities under the twist owner's priority tree.\n * Update and archive any Priority owned by the twist's user.\n */\n Full,\n}\n\nexport enum ContactAccess {\n /** Read existing contact details. Without this, only the ID will be provided. */\n Read,\n}\n\nexport enum LinkAccess {\n /** Read links on any thread owned by the twist's user. */\n Read,\n /** Read + update links, including moving links between threads owned by the twist's user. */\n Full,\n}\n\n/**\n * Intent handler for thread mentions.\n * Defines how the twist should respond when mentioned in a thread.\n */\nexport type NoteIntentHandler = {\n /** Human-readable description of what this intent handles */\n description: string;\n /** Example phrases or activity content that would match this intent */\n examples: string[];\n /** The function to call when this intent is matched */\n handler: (note: Note) => Promise<void>;\n};\n\n/**\n * Filter for querying links from connected source channels.\n */\nexport type LinkFilter = {\n /** Only return links from these channel IDs. */\n channelIds?: string[];\n /** Only return links created/updated after this date. */\n since?: Date;\n /** Only return links of this type. */\n type?: string;\n /** Maximum number of links to return. */\n limit?: number;\n};\n\ntype SearchResultBase = {\n thread: { id: string; title: string | null };\n priority: { id: string; title: string | null };\n similarity: number;\n};\n\nexport type NoteSearchResult = SearchResultBase & {\n type: 'note';\n id: string;\n content: string | null;\n};\n\nexport type LinkSearchResult = SearchResultBase & {\n type: 'link';\n id: string;\n title: string | null;\n sourceUrl: string | null;\n content: string | null;\n};\n\nexport type SearchResult = NoteSearchResult | LinkSearchResult;\n\n/** Default number of search results returned */\nexport const SEARCH_DEFAULT_LIMIT = 10;\n/** Maximum number of search results allowed */\nexport const SEARCH_MAX_LIMIT = 30;\n\nexport type SearchOptions = {\n /** Max results to return (default: 10, max: 30) */\n limit?: number;\n /** Minimum similarity score 0-1 (default: 0.3) */\n threshold?: number;\n /**\n * Scope search to this priority + descendants. Must be owned by the twist\n * owner. When omitted, the server scopes the search to the owner's entire\n * priority tree.\n */\n priorityId?: string;\n};\n\n/**\n * Built-in tool for interacting with the core Plot data layer.\n *\n * The Plot tool provides twists with the ability to create and manage threads,\n * priorities, and contacts within the Plot system. This is the primary interface\n * for twists to persist data and interact with the Plot database.\n *\n * @example\n * ```typescript\n * class MyTwist extends Twist {\n * private plot: Plot;\n *\n * constructor(id: string, tools: ToolBuilder) {\n * super();\n * this.plot = tools.get(Plot);\n * }\n *\n * async activate() {\n * // Create a welcome thread\n * await this.plot.createThread({\n * title: \"Welcome to Plot!\",\n * actions: [{\n * title: \"Get Started\",\n * type: ActionType.external,\n * url: \"https://plot.day/docs\"\n * }]\n * });\n * }\n * }\n * ```\n */\nexport abstract class Plot extends ITool {\n /**\n * Configuration options for the Plot tool.\n *\n * **Important**: All permissions must be explicitly requested. There are no default permissions.\n *\n * @example\n * ```typescript\n * // Minimal configuration with required permissions\n * build(build: ToolBuilder) {\n * return {\n * plot: build(Plot, {\n * thread: {\n * access: ThreadAccess.Create\n * }\n * })\n * };\n * }\n *\n * // Full configuration with callbacks\n * build(build: ToolBuilder) {\n * return {\n * plot: build(Plot, {\n * thread: {\n * access: ThreadAccess.Create,\n * },\n * note: {\n * intents: [{\n * description: \"Schedule meetings\",\n * examples: [\"Schedule a meeting tomorrow\"],\n * handler: this.onSchedulingIntent\n * }],\n * },\n * link: true,\n * priority: {\n * access: PriorityAccess.Full\n * },\n * contact: {\n * access: ContactAccess.Read\n * }\n * })\n * };\n * }\n * ```\n */\n static readonly Options: {\n thread?: {\n /**\n * Capability to create Notes and modify tags.\n * Must be explicitly set to grant permissions.\n */\n access?: ThreadAccess;\n /** When true, auto-mention this twist on new notes in threads where it authored content. */\n defaultMention?: boolean;\n };\n note?: {\n /** When true, auto-mention this twist on new notes in threads where it was @-mentioned. */\n defaultMention?: boolean;\n /**\n * Respond to mentions in notes.\n *\n * When a note mentions this twist, the system will match the note\n * content against these intents and call the matching handler.\n *\n * @example\n * ```typescript\n * intents: [{\n * description: \"Schedule or reschedule calendar events\",\n * examples: [\"Schedule a meeting tomorrow at 2pm\", \"Move my 3pm meeting to 4pm\"],\n * handler: this.onSchedulingRequest\n * }, {\n * description: \"Find available meeting times\",\n * examples: [\"When am I free this week?\", \"Find time for a 1 hour meeting\"],\n * handler: this.onAvailabilityRequest\n * }]\n * ```\n */\n intents?: NoteIntentHandler[];\n };\n /** Enable link processing from connected source channels. */\n link?: true | {\n /** Access level for links. When omitted with `link: true`, only source channel links are accessible. */\n access?: LinkAccess;\n };\n priority?: {\n access?: PriorityAccess;\n };\n contact?: {\n access?: ContactAccess;\n };\n /** Enable semantic search across notes and links owned by the twist's user. */\n search?: true;\n /**\n * When true, admin write operations (on threads/notes/links/priorities not created by this twist)\n * require user approval via plan actions instead of executing immediately.\n * Read operations and operations on the twist's own content still work directly.\n */\n requireApproval?: boolean;\n };\n\n /**\n * Creates a new thread in the Plot system.\n *\n * The thread will be automatically assigned an ID and author information\n * based on the current execution context. All other fields from NewThread\n * will be preserved in the created thread.\n *\n * @param thread - The thread data to create\n * @returns Promise resolving to the created thread's ID\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract createThread(\n thread: NewThread | NewThreadWithNotes\n ): Promise<Uuid>;\n\n /**\n * Creates multiple threads in a single batch operation.\n *\n * This method efficiently creates multiple threads at once, which is\n * more performant than calling createThread() multiple times individually.\n * All threads are created with the same author and access control rules.\n *\n * @param threads - Array of thread data to create\n * @returns Promise resolving to array of created thread IDs\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract createThreads(\n threads: (NewThread | NewThreadWithNotes)[]\n ): Promise<Uuid[]>;\n\n /**\n * Updates an existing thread in the Plot system.\n *\n * **Important**: This method only updates existing threads. It will throw an error\n * if the thread does not exist. Use `createThread()` to create or update (upsert)\n * threads.\n *\n * Only the fields provided in the update object will be modified - all other fields\n * remain unchanged. This enables partial updates without needing to fetch and resend\n * the entire thread object.\n *\n * For tags, provide a Record<number, boolean> where true adds a tag and false removes it.\n * Tags not included in the update remain unchanged.\n *\n * When updating the parent, the thread's path will be automatically recalculated to\n * maintain the correct hierarchical structure.\n *\n * Scheduling is handled separately via `createSchedule()` / `updateSchedule()`.\n *\n * @param thread - The thread update containing the ID or source and fields to change\n * @returns Promise that resolves when the update is complete\n * @throws Error if the thread does not exist\n *\n * @example\n * ```typescript\n * // Mark a task as complete\n * await this.plot.updateThread({\n * id: \"task-123\",\n * done: new Date()\n * });\n *\n * // Add and remove tags\n * await this.plot.updateThread({\n * id: \"thread-789\",\n * tags: {\n * 1: true, // Add tag with ID 1\n * 2: false // Remove tag with ID 2\n * }\n * });\n * ```\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract updateThread(thread: ThreadUpdate): Promise<void>;\n\n /**\n * Retrieves all notes within a thread.\n *\n * Notes are detailed entries within a thread, ordered by creation time.\n * Each note can contain markdown content, actions, and other detailed information\n * related to the parent thread.\n *\n * @param thread - The thread whose notes to retrieve\n * @returns Promise resolving to array of notes in the thread\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract getNotes(thread: Thread): Promise<Note[]>;\n\n /**\n * Creates a new note in a thread.\n *\n * Notes provide detailed content within a thread, supporting markdown,\n * actions, and other rich content. The note will be automatically assigned\n * an ID and author information based on the current execution context.\n *\n * @param note - The note data to create\n * @returns Promise resolving to the created note's ID\n *\n * @example\n * ```typescript\n * // Create a note with content\n * await this.plot.createNote({\n * thread: { id: \"thread-123\" },\n * note: \"Discussion notes from the meeting...\",\n * contentType: \"markdown\"\n * });\n *\n * // Create a note with actions\n * await this.plot.createNote({\n * thread: { id: \"thread-456\" },\n * note: \"Meeting recording available\",\n * actions: [{\n * type: ActionType.external,\n * title: \"View Recording\",\n * url: \"https://example.com/recording\"\n * }]\n * });\n * ```\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract createNote(note: NewNote): Promise<Uuid>;\n\n /**\n * Creates multiple notes in a single batch operation.\n *\n * This method efficiently creates multiple notes at once, which is\n * more performant than calling createNote() multiple times individually.\n * All notes are created with the same author and access control rules.\n *\n * @param notes - Array of note data to create\n * @returns Promise resolving to array of created note IDs\n *\n * @example\n * ```typescript\n * // Create multiple notes in one batch\n * await this.plot.createNotes([\n * {\n * thread: { id: \"thread-123\" },\n * note: \"First message in thread\"\n * },\n * {\n * thread: { id: \"thread-123\" },\n * note: \"Second message in thread\"\n * }\n * ]);\n * ```\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract createNotes(notes: NewNote[]): Promise<Uuid[]>;\n\n /**\n * Updates an existing note in the Plot system.\n *\n * **Important**: This method only updates existing notes. It will throw an error\n * if the note does not exist. Use `createNote()` to create or update (upsert) notes.\n *\n * Only the fields provided in the update object will be modified - all other fields\n * remain unchanged. This enables partial updates without needing to fetch and resend\n * the entire note object.\n *\n * @param note - The note update containing the ID or key and fields to change\n * @returns Promise that resolves when the update is complete\n * @throws Error if the note does not exist\n *\n * @example\n * ```typescript\n * // Update note content\n * await this.plot.updateNote({\n * id: \"note-123\",\n * note: \"Updated content with more details\"\n * });\n *\n * // Add tags to a note\n * await this.plot.updateNote({\n * id: \"note-456\",\n * twistTags: {\n * [Tag.Important]: true\n * }\n * });\n * ```\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract updateNote(note: NoteUpdate): Promise<void>;\n\n /**\n * Retrieves a thread by ID or source.\n *\n * This method enables lookup of threads either by their unique ID or by their\n * source identifier (canonical URL from an external system). Archived threads\n * are included in the results.\n *\n * @param thread - Thread lookup by ID or source\n * @returns Promise resolving to the matching thread or null if not found\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract getThread(\n thread: { id: Uuid } | { source: string }\n ): Promise<Thread | null>;\n\n /**\n * Retrieves a note by ID or key.\n *\n * This method enables lookup of notes either by their unique ID or by their\n * key (unique identifier within the thread). Archived notes are included\n * in the results.\n *\n * @param note - Note lookup by ID or key\n * @returns Promise resolving to the matching note or null if not found\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract getNote(note: { id: Uuid } | { key: string }): Promise<Note | null>;\n\n /**\n * Creates a new priority in the Plot system.\n *\n * Priorities serve as organizational containers for threads and twists.\n * The created priority will be automatically assigned a unique ID.\n *\n * @param priority - The priority data to create\n * @returns Promise resolving to the complete created priority\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract createPriority(priority: NewPriority): Promise<Priority & { created: boolean }>;\n\n /**\n * Retrieves a priority by ID or key.\n *\n * Archived priorities are included in the results.\n *\n * @param priority - Priority lookup by ID or key\n * @returns Promise resolving to the matching priority or null if not found\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract getPriority(\n priority: { id: Uuid } | { key: string }\n ): Promise<Priority | null>;\n\n /**\n * Updates an existing priority in the Plot system.\n *\n * The priority is identified by either its ID or key.\n * Only the fields specified in the update will be changed.\n *\n * @param update - Priority update containing ID/key and fields to change\n * @returns Promise that resolves when the update is complete\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract updatePriority(update: PriorityUpdate): Promise<void>;\n\n /**\n * Retrieves actors by their IDs.\n *\n * Actors represent users, contacts, or twists in the Plot system.\n * This method requires ContactAccess.Read permission.\n *\n * @param ids - Array of actor IDs to retrieve\n * @returns Promise resolving to array of actors\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract getActors(ids: ActorId[]): Promise<Actor[]>;\n\n /**\n * Returns the full Actor for the user who installed this twist.\n * Useful for per-user operations like schedule creation, or when\n * the owner's name or email is needed.\n */\n abstract getOwner(): Promise<Actor>;\n\n /**\n * Returns the user ID (`twist_instance.owner_id`) that installed this\n * twist. This is the same value exposed on Twist via `this.userId`.\n */\n abstract getUserId(): Promise<string>;\n\n /**\n * Creates a new schedule for a thread.\n *\n * Schedules define when a thread occurs in time. A thread can have\n * multiple schedules (shared and per-user).\n *\n * @param schedule - The schedule data to create\n * @returns Promise resolving to the created schedule\n *\n * @example\n * ```typescript\n * // Schedule a timed event\n * const threadId = await this.plot.createThread({\n * title: \"Team standup\"\n * });\n * await this.plot.createSchedule({\n * threadId,\n * start: new Date(\"2025-01-15T10:00:00Z\"),\n * end: new Date(\"2025-01-15T10:30:00Z\"),\n * recurrenceRule: \"FREQ=DAILY;BYDAY=MO,TU,WE,TH,FR\"\n * });\n * ```\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract createSchedule(schedule: NewSchedule): Promise<Schedule>;\n\n /**\n * Retrieves all schedules for a thread.\n *\n * @param threadId - The thread whose schedules to retrieve\n * @returns Promise resolving to array of schedules for the thread\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract getSchedules(threadId: Uuid): Promise<Schedule[]>;\n\n /**\n * Retrieves links from connected source channels.\n *\n * Requires `link: true` in Plot options.\n *\n * @param filter - Optional filter criteria for links\n * @returns Promise resolving to array of links with their notes\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract getLinks(filter?: LinkFilter): Promise<Array<{ link: Link; notes: Note[] }>>;\n\n /**\n * Searches notes and links using semantic similarity.\n *\n * Requires `search: true` in Plot options.\n *\n * @param query - The search query text\n * @param options - Optional search configuration\n * @returns Promise resolving to array of search results ordered by similarity\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract search(query: string, options?: SearchOptions): Promise<SearchResult[]>;\n\n /**\n * Lists threads owned by the twist's user.\n *\n * Requires `ThreadAccess.Full`.\n *\n * @param options - Query options for filtering threads\n * @returns Promise resolving to array of threads\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract getThreads(options?: {\n /**\n * Priority to list threads from. Must be owned by the twist owner.\n * When omitted, defaults to the owner's root priority.\n */\n priorityId?: Uuid;\n /** Include threads from descendant priorities. Default: true. */\n includeDescendants?: boolean;\n /** Include archived threads. Default: false. */\n includeArchived?: boolean;\n /** Maximum number of threads to return. Default: 50, max: 200. */\n limit?: number;\n /** Number of threads to skip for pagination. Default: 0. */\n offset?: number;\n }): Promise<Thread[]>;\n\n /**\n * Lists priorities owned by the twist's user.\n *\n * Requires `PriorityAccess.Full`.\n *\n * @param options - Query options for filtering priorities\n * @returns Promise resolving to array of priorities\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract getPriorities(options?: {\n /**\n * Parent priority to list children of. Must be owned by the twist\n * owner. When omitted, defaults to the owner's root priority.\n */\n parentId?: Uuid;\n /** Include all descendants, not just direct children. Default: false. */\n includeDescendants?: boolean;\n /** Include archived priorities. Default: false. */\n includeArchived?: boolean;\n }): Promise<Priority[]>;\n\n /**\n * Updates a link.\n *\n * Requires `LinkAccess.Full`. Set `threadId` to move the link to a different thread.\n *\n * @param link - The link update containing the ID and fields to change\n * @returns Promise that resolves when the update is complete\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract updateLink(link: LinkUpdate): Promise<void>;\n\n /**\n * Creates a plan of operations for user approval.\n *\n * Returns an Action that can be attached to a note. The user can approve,\n * deny, or request changes. On approval, operations are executed by the API.\n *\n * Requires `requireApproval: true` in Plot options.\n *\n * @param options - Plan configuration\n * @returns An Action of type `plan` to attach to a note\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract createPlan(options: {\n /** Human-readable title summarizing the plan */\n title: string;\n /** Array of operations to execute on approval */\n operations: PlanOperation[];\n /** Callback invoked with (action, approved: boolean) when the user responds */\n callback: Callback;\n }): Action;\n}\n";
@@ -5,4 +5,4 @@
5
5
  * Generated from: prebuild.ts
6
6
  */
7
7
 
8
- export default "import { ITool, type Serializable } from \"..\";\n\n/**\n * Built-in tool for persistent key-value storage.\n *\n * The Store tool provides twists and tools with a simple, persistent storage\n * mechanism that survives worker restarts and invocations. Each twist/tool\n * instance gets its own isolated storage namespace.\n *\n * **Note:** Store methods are also available directly on Twist and Tool classes\n * via `this.get()`, `this.set()`, `this.clear()`, and `this.clearAll()`.\n * This is the recommended approach for most use cases.\n *\n * **Storage Characteristics:**\n * - Persistent across worker restarts\n * - Isolated per twist/tool instance\n * - Supports SuperJSON-serializable data (see below)\n * - Async operations for scalability\n *\n * **Supported Data Types (via SuperJSON):**\n * - Primitives: string, number, boolean, null, undefined\n * - Complex types: Date, RegExp, Map, Set, Error, URL, BigInt\n * - Collections: Arrays and objects (recursively)\n *\n * **NOT Supported (will throw validation errors):**\n * - Functions (use callback tokens instead - see Callbacks tool)\n * - Symbols\n * - Circular references\n * - Custom class instances\n *\n * **Use Cases:**\n * - Storing authentication tokens\n * - Caching configuration data\n * - Maintaining sync state\n * - Persisting user preferences\n * - Tracking processing checkpoints\n *\n * @example\n * ```typescript\n * class CalendarTool extends Tool {\n * async saveAuthToken(provider: string, token: string) {\n * // Using built-in set method (recommended)\n * await this.set(`auth_token_${provider}`, token);\n * }\n *\n * async getAuthToken(provider: string): Promise<string | null> {\n * // Using built-in get method (recommended)\n * return await this.get<string>(`auth_token_${provider}`);\n * }\n *\n * async clearAllTokens() {\n * // Using built-in clearAll method (recommended)\n * await this.clearAll();\n * }\n * }\n * ```\n */\nexport abstract class Store extends ITool {\n /**\n * Retrieves a value from storage by key.\n *\n * Returns the stored value deserialized to the specified type,\n * or null if the key doesn't exist or the value is null.\n *\n * Values are automatically deserialized using SuperJSON, which\n * properly restores Date objects, Maps, Sets, and other complex types.\n *\n * @template T - The expected type of the stored value (must be Serializable)\n * @param key - The storage key to retrieve\n * @returns Promise resolving to the stored value or null\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract get<T extends Serializable>(key: string): Promise<T | null>;\n\n /**\n * Stores a value in persistent storage.\n *\n * The value will be serialized using SuperJSON and stored persistently.\n * Any existing value at the same key will be overwritten.\n *\n * SuperJSON automatically handles Date objects, Maps, Sets, undefined values,\n * and other complex types that standard JSON doesn't support.\n *\n * @template T - The type of value being stored (must be Serializable)\n * @param key - The storage key to use\n * @param value - The value to store (must be SuperJSON-serializable)\n * @returns Promise that resolves when the value is stored\n *\n * @example\n * ```typescript\n * // Date objects are preserved\n * await this.set('sync_state', {\n * lastSync: new Date(),\n * minDate: new Date(2024, 0, 1)\n * });\n *\n * // undefined is now supported\n * await this.set('data', { name: 'test', optional: undefined }); // ✅ Works\n *\n * // Arrays with undefined are supported\n * await this.set('items', [1, undefined, 3]); // ✅ Works\n * await this.set('items', [1, null, 3]); // ✅ Also works\n *\n * // Maps and Sets are supported\n * await this.set('mapping', new Map([['key', 'value']])); // ✅ Works\n * await this.set('tags', new Set(['tag1', 'tag2'])); // ✅ Works\n *\n * // Functions are NOT supported - use callback tokens instead\n * const token = await this.callback(this.myFunction);\n * await this.set('callback_ref', token); // ✅ Use callback token\n * ```\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract set<T extends Serializable>(key: string, value: T): Promise<void>;\n\n /**\n * Lists all storage keys matching a prefix.\n *\n * Returns an array of key strings that start with the given prefix.\n * Useful for finding all keys in a namespace (e.g., all sync locks).\n *\n * @param prefix - The prefix to match keys against\n * @returns Promise resolving to an array of matching key strings\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract list(prefix: string): Promise<string[]>;\n\n /**\n * Removes a specific key from storage.\n *\n * After this operation, get() calls for this key will return null.\n * No error is thrown if the key doesn't exist.\n *\n * @param key - The storage key to remove\n * @returns Promise that resolves when the key is removed\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract clear(key: string): Promise<void>;\n\n /**\n * Removes all keys from this storage instance.\n *\n * This operation clears all data stored by this twist/tool instance\n * but does not affect storage for other twists or tools.\n *\n * @returns Promise that resolves when all keys are removed\n */\n abstract clearAll(): Promise<void>;\n}\n";
8
+ export default "import { ITool, type Serializable } from \"..\";\n\n/**\n * Built-in tool for persistent key-value storage.\n *\n * The Store tool provides twists and tools with a simple, persistent storage\n * mechanism that survives worker restarts and invocations. Each twist/tool\n * instance gets its own isolated storage namespace.\n *\n * **Note:** Store methods are also available directly on Twist and Tool classes\n * via `this.get()`, `this.set()`, `this.clear()`, and `this.clearAll()`.\n * This is the recommended approach for most use cases.\n *\n * **Storage Characteristics:**\n * - Persistent across worker restarts\n * - Isolated per twist/tool instance\n * - Supports SuperJSON-serializable data (see below)\n * - Async operations for scalability\n *\n * **Supported Data Types (via SuperJSON):**\n * - Primitives: string, number, boolean, null, undefined\n * - Complex types: Date, RegExp, Map, Set, Error, URL, BigInt\n * - Collections: Arrays and objects (recursively)\n *\n * **NOT Supported (will throw validation errors):**\n * - Functions (use callback tokens instead - see Callbacks tool)\n * - Symbols\n * - Circular references\n * - Custom class instances\n *\n * **Use Cases:**\n * - Storing authentication tokens\n * - Caching configuration data\n * - Maintaining sync state\n * - Persisting user preferences\n * - Tracking processing checkpoints\n *\n * @example\n * ```typescript\n * class CalendarTool extends Tool {\n * async saveAuthToken(provider: string, token: string) {\n * // Using built-in set method (recommended)\n * await this.set(`auth_token_${provider}`, token);\n * }\n *\n * async getAuthToken(provider: string): Promise<string | null> {\n * // Using built-in get method (recommended)\n * return await this.get<string>(`auth_token_${provider}`);\n * }\n *\n * async clearAllTokens() {\n * // Using built-in clearAll method (recommended)\n * await this.clearAll();\n * }\n * }\n * ```\n */\nexport abstract class Store extends ITool {\n /**\n * Retrieves a value from storage by key.\n *\n * Returns the stored value deserialized to the specified type,\n * or null if the key doesn't exist or the value is null.\n *\n * Values are automatically deserialized using SuperJSON, which\n * properly restores Date objects, Maps, Sets, and other complex types.\n *\n * @template T - The expected type of the stored value (must be Serializable)\n * @param key - The storage key to retrieve\n * @returns Promise resolving to the stored value or null\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract get<T extends Serializable>(key: string): Promise<T | null>;\n\n /**\n * Stores a value in persistent storage.\n *\n * The value will be serialized using SuperJSON and stored persistently.\n * Any existing value at the same key will be overwritten.\n *\n * SuperJSON automatically handles Date objects, Maps, Sets, undefined values,\n * and other complex types that standard JSON doesn't support.\n *\n * @template T - The type of value being stored (must be Serializable)\n * @param key - The storage key to use\n * @param value - The value to store (must be SuperJSON-serializable)\n * @returns Promise that resolves when the value is stored\n *\n * @example\n * ```typescript\n * // Date objects are preserved\n * await this.set('sync_state', {\n * lastSync: new Date(),\n * minDate: new Date(2024, 0, 1)\n * });\n *\n * // undefined is now supported\n * await this.set('data', { name: 'test', optional: undefined }); // ✅ Works\n *\n * // Arrays with undefined are supported\n * await this.set('items', [1, undefined, 3]); // ✅ Works\n * await this.set('items', [1, null, 3]); // ✅ Also works\n *\n * // Maps and Sets are supported\n * await this.set('mapping', new Map([['key', 'value']])); // ✅ Works\n * await this.set('tags', new Set(['tag1', 'tag2'])); // ✅ Works\n *\n * // Functions are NOT supported - use callback tokens instead\n * const token = await this.callback(this.myFunction);\n * await this.set('callback_ref', token); // ✅ Use callback token\n * ```\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract set<T extends Serializable>(key: string, value: T): Promise<void>;\n\n /**\n * Lists all storage keys matching a prefix.\n *\n * Returns an array of key strings that start with the given prefix.\n * Useful for finding all keys in a namespace (e.g., all sync locks).\n *\n * @param prefix - The prefix to match keys against\n * @returns Promise resolving to an array of matching key strings\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract list(prefix: string): Promise<string[]>;\n\n /**\n * Removes a specific key from storage.\n *\n * After this operation, get() calls for this key will return null.\n * No error is thrown if the key doesn't exist.\n *\n * @param key - The storage key to remove\n * @returns Promise that resolves when the key is removed\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract clear(key: string): Promise<void>;\n\n /**\n * Removes all keys from this storage instance.\n *\n * This operation clears all data stored by this twist/tool instance\n * but does not affect storage for other twists or tools.\n *\n * @returns Promise that resolves when all keys are removed\n */\n abstract clearAll(): Promise<void>;\n\n /**\n * Acquire a self-expiring lock. Returns true if the caller now holds the\n * lock, false if another holder has a non-expired lease.\n *\n * Use this for any operation where you previously hand-rolled a boolean\n * \"in progress\" flag with manual cleanup on every error path. The lock\n * auto-releases after `ttlMs`, so a crashed/timed-out holder cannot wedge\n * the system permanently — pick a `ttlMs` comfortably longer than the\n * worst-case duration of the protected work.\n *\n * Acquisition is atomic across concurrent callers (the underlying\n * Durable Object serializes operations). Lock keys live in a reserved\n * namespace and never appear in `get` / `list` results.\n *\n * @example\n * ```typescript\n * if (!(await this.tools.store.acquireLock(`sync_${id}`, 30 * 60_000))) {\n * return; // another sync is already running\n * }\n * try {\n * await this.runSync(id);\n * } finally {\n * await this.tools.store.releaseLock(`sync_${id}`);\n * }\n * ```\n *\n * @param key Lock identifier (any string).\n * @param ttlMs Lease duration in milliseconds. After this time the lock\n * is considered expired and a new caller can acquire it even if\n * `releaseLock` was never called.\n * @returns Promise resolving to true if acquired, false if held.\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract acquireLock(key: string, ttlMs: number): Promise<boolean>;\n\n /**\n * Release a lock acquired via {@link acquireLock}. Safe to call even if\n * the caller never acquired the lock or the lease has already expired.\n *\n * @param key The same key that was passed to `acquireLock`.\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n abstract releaseLock(key: string): Promise<void>;\n}\n";
package/src/plot.ts CHANGED
@@ -600,9 +600,16 @@ export type Note = ThreadCommon & {
600
600
  /** The author of this note */
601
601
  author: Actor;
602
602
  /**
603
- * Globally unique, stable identifier for the note within its thread.
603
+ * Globally unique, stable identifier for the note within its thread + link.
604
604
  * Can be used to upsert without knowing the id.
605
605
  *
606
+ * Note keys are scoped to a `(thread, link)` pair — two links on the same
607
+ * thread (e.g. after a merge) can each carry a `"description"` note without
608
+ * colliding. The runtime infers the link from the surrounding `saveLink`
609
+ * call. For bare `saveNote` calls outside a `saveLink`, the runtime
610
+ * resolves the link by looking up the connector's links on the thread
611
+ * and errors if more than one matches.
612
+ *
606
613
  * Use one of these patterns:
607
614
  * - Hardcoded semantic keys for fixed note types: "description", "cancellation"
608
615
  * - External service IDs for dynamic collections: `comment:${immutableId}`
@@ -920,11 +927,20 @@ export type Link = {
920
927
  channelId: string | null;
921
928
  /**
922
929
  * Cross-connector thread bundling key.
923
- * When set, this link shares a thread with any link whose `source` matches
924
- * this value (or whose `relatedSource` matches this link's `source`).
925
- * Works regardless of creation order.
930
+ *
931
+ * @deprecated Use `sources` instead. Reads return the first element of
932
+ * `sources` for backward compatibility; new writes should populate `sources`.
926
933
  */
927
934
  relatedSource: string | null;
935
+ /**
936
+ * Canonical identifiers for this link. Two links whose `sources` arrays
937
+ * overlap share the same thread (array overlap, `sources && new.sources`).
938
+ *
939
+ * Use this to bundle with another connector via a canonical alias. For
940
+ * example, every calendar connector emits `icaluid:<iCalUID>` so any
941
+ * meeting-notes connector can bundle by setting the same alias.
942
+ */
943
+ sources: string[];
928
944
  };
929
945
 
930
946
  /**
@@ -933,18 +949,26 @@ export type Link = {
933
949
  * Links are created by sources to represent external entities.
934
950
  * Requires a source identifier for dedup/upsert.
935
951
  */
936
- export type NewLink = (
937
- | {
938
- /**
939
- * Canonical ID for the item in an external system.
940
- * When set, uniquely identifies the link within a priority tree. This performs
941
- * an upsert.
942
- */
943
- source: string;
944
- }
945
- | {}
946
- ) &
947
- Partial<Omit<Link, "source" | "author" | "assignee" | "threadId">> & {
952
+ export type NewLink = Partial<
953
+ Omit<Link, "author" | "assignee" | "threadId">
954
+ > & {
955
+ /**
956
+ * Canonical ID for the item in an external system.
957
+ * When set, uniquely identifies the link within a priority tree. This performs
958
+ * an upsert.
959
+ *
960
+ * @deprecated Pass `sources: [...]` instead. Both fields can be set during
961
+ * the transition; the runtime will normalize.
962
+ */
963
+ source?: string;
964
+ /**
965
+ * Canonical identifiers for this item. Any element shared with another
966
+ * link's `sources` bundles the two links into the same thread. Used for
967
+ * cross-connector bundling — e.g. a meeting-notes connector setting
968
+ * `["granola:<id>", "icaluid:<uid>"]` to attach onto a calendar event
969
+ * thread that includes `icaluid:<uid>` in its own `sources`.
970
+ */
971
+ sources?: string[];
948
972
  /** The person that created the item. By default, it will be the twist itself. */
949
973
  author?: NewActor;
950
974
  /** The person assigned to the item. */
package/src/schedule.ts CHANGED
@@ -165,13 +165,21 @@ export type ScheduleOccurrence = {
165
165
 
166
166
  /** Tags for this occurrence */
167
167
  tags: Tags;
168
-
169
- /** True if the occurrence is archived */
170
- archived: boolean;
171
168
  };
172
169
 
173
170
  /**
174
171
  * Type for creating or updating schedule occurrences.
172
+ *
173
+ * Use `cancelled: true` to skip a single date in a recurring series
174
+ * (e.g. an upstream calendar reports one occurrence as cancelled).
175
+ * Internally this is translated into an addition to the parent
176
+ * schedule's `recurrenceExdates`; no occurrence schedule row is
177
+ * persisted for the cancelled date. For seeding the full exception
178
+ * list on the master at initial sync, use `NewSchedule.recurrenceExdates`
179
+ * instead.
180
+ *
181
+ * Use a normal occurrence record (without `cancelled`) for genuine
182
+ * overrides — time changes, RSVP differences, etc.
175
183
  */
176
184
  export type NewScheduleOccurrence = Pick<
177
185
  ScheduleOccurrence,
@@ -191,6 +199,14 @@ export type NewScheduleOccurrence = Pick<
191
199
 
192
200
  /** Contacts to upsert on this occurrence's schedule */
193
201
  contacts?: NewScheduleContact[];
202
+
203
+ /**
204
+ * Mark this single date as cancelled. The runtime translates this
205
+ * into an addition to the parent schedule's `recurrenceExdates` and
206
+ * archives any pre-existing override row for the same date — no
207
+ * occurrence schedule row is created.
208
+ */
209
+ cancelled?: boolean;
194
210
  };
195
211
 
196
212
  /**