From e5bb04902be5eaee838877d4ecdd1f5a59b48b6c Mon Sep 17 00:00:00 2001 From: Kris Braun Date: Sun, 22 Feb 2026 10:53:54 -0500 Subject: [PATCH 1/3] =?UTF-8?q?Rename=20ActivityLink=20=E2=86=92=20Link=20?= =?UTF-8?q?and=20move=20source=20links=20to=20activity=20level?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename ActivityLinkType enum → LinkType and ActivityLink type → Link - Add deprecated aliases for backwards compatibility - Add links field to ActivityFields and update fields types - Update all tools to use Link/LinkType imports - Move external and conferencing links from notes to activity-level links in google-calendar, outlook-calendar, linear, github, github-issues, asana, and jira tools - Keep auth/file links on notes; callback links can be on either Co-Authored-By: Claude Opus 4.6 --- tools/AGENTS.md | 4 +- tools/asana/src/asana.ts | 15 ++++--- tools/github-issues/src/github-issues.ts | 20 ++++----- tools/github/src/github.ts | 16 +++---- tools/google-calendar/src/google-calendar.ts | 20 ++++----- tools/google-drive/src/google-drive.ts | 8 ++-- tools/jira/src/jira.ts | 15 ++++--- tools/linear/src/linear.ts | 20 ++++----- .../outlook-calendar/src/outlook-calendar.ts | 18 ++++---- twister/src/plot.ts | 43 +++++++++++-------- twister/src/tools/callbacks.ts | 2 +- twister/src/tools/plot.ts | 4 +- twister/src/twist.ts | 10 ++--- twists/chat/src/index.ts | 10 ++--- 14 files changed, 108 insertions(+), 97 deletions(-) diff --git a/tools/AGENTS.md b/tools/AGENTS.md index ed02f72..83050f5 100644 --- a/tools/AGENTS.md +++ b/tools/AGENTS.md @@ -93,7 +93,7 @@ export { default, ToolName } from "./tool-name"; ```typescript import { ActivityType, - ActivityLinkType, + LinkType, type NewActivity, type NewActivityWithNotes, type NewNote, @@ -346,7 +346,7 @@ export class MyTool extends Tool implements ProjectTool { key: "description", // Enables note upsert content: item.description || null, links: item.url ? [{ - type: ActivityLinkType.external, + type: LinkType.external, title: "Open in Service", url: item.url, }] : null, diff --git a/tools/asana/src/asana.ts b/tools/asana/src/asana.ts index 118c03f..4843fae 100644 --- a/tools/asana/src/asana.ts +++ b/tools/asana/src/asana.ts @@ -3,8 +3,8 @@ import * as asana from "asana"; import { type Activity, type ActivityFilter, - type ActivityLink, - ActivityLinkType, + type Link, + LinkType, ActivityMeta, ActivityType, type NewActivity, @@ -424,20 +424,20 @@ export class Asana extends Tool implements ProjectTool { // Construct Asana task URL for link const taskUrl = `https://app.asana.com/0/${projectId}/${task.gid}`; - // Create initial note with description and link to Asana task - const links: ActivityLink[] = []; - links.push({ - type: ActivityLinkType.external, + // Build activity-level links + const activityLinks: Link[] = []; + activityLinks.push({ + type: LinkType.external, title: `Open in Asana`, url: taskUrl, }); + // Create initial note with description (links moved to activity level) notes.push({ activity: { source: activitySource }, key: "description", content: description, created: task.created_at ? new Date(task.created_at) : undefined, - links: links.length > 0 ? links : null, }); return { @@ -451,6 +451,7 @@ export class Asana extends Tool implements ProjectTool { syncProvider: "asana", syncableId: projectId, }, + links: activityLinks.length > 0 ? activityLinks : undefined, author: authorContact, assignee: assigneeContact ?? null, // Explicitly set to null for unassigned tasks done: diff --git a/tools/github-issues/src/github-issues.ts b/tools/github-issues/src/github-issues.ts index 7f17a05..4df4a36 100644 --- a/tools/github-issues/src/github-issues.ts +++ b/tools/github-issues/src/github-issues.ts @@ -1,8 +1,8 @@ import { Octokit } from "@octokit/rest"; import { - type ActivityLink, - ActivityLinkType, + type Link, + LinkType, type ActivityMeta, ActivityType, type NewActivity, @@ -453,24 +453,23 @@ export class GitHubIssues extends Tool implements ProjectTool { const description = issue.body || ""; const hasDescription = description.trim().length > 0; - // Build notes array (inline notes don't require the `activity` field) - const notes: any[] = []; - - // Description note with link to GitHub issue - const links: ActivityLink[] = []; + // Build activity-level links + const activityLinks: Link[] = []; if (issue.html_url) { - links.push({ - type: ActivityLinkType.external, + activityLinks.push({ + type: LinkType.external, title: "Open in GitHub", url: issue.html_url, }); } + // Build notes array (inline notes don't require the `activity` field) + const notes: any[] = []; + notes.push({ key: "description", content: hasDescription ? description : null, created: issue.created_at, - links: links.length > 0 ? links : null, author: authorContact, }); @@ -534,6 +533,7 @@ export class GitHubIssues extends Tool implements ProjectTool { githubRepoFullName: repoFullName, projectId: repoId, }, + links: activityLinks.length > 0 ? activityLinks : undefined, notes, preview: hasDescription ? description : null, ...(initialSync ? { unread: false } : {}), diff --git a/tools/github/src/github.ts b/tools/github/src/github.ts index a18f957..096073b 100644 --- a/tools/github/src/github.ts +++ b/tools/github/src/github.ts @@ -1,7 +1,7 @@ import { type Activity, - type ActivityLink, - ActivityLinkType, + type Link, + LinkType, type ActivityMeta, ActivityType, type NewActivity, @@ -784,23 +784,22 @@ export class GitHub extends Tool implements SourceControlTool { ? this.userToContact(pr.assignee) : null; - const notes: any[] = []; - - // Description note with link to GitHub PR - const links: ActivityLink[] = [ + // Build activity-level links + const activityLinks: Link[] = [ { - type: ActivityLinkType.external, + type: LinkType.external, title: `Open in GitHub`, url: pr.html_url, }, ]; + const notes: any[] = []; + const hasDescription = pr.body && pr.body.trim().length > 0; notes.push({ key: "description", content: hasDescription ? pr.body : null, created: new Date(pr.created_at), - links, author: authorContact, }); @@ -873,6 +872,7 @@ export class GitHub extends Tool implements SourceControlTool { prNumber: pr.number, prNodeId: pr.id, }, + links: activityLinks, notes, preview: hasDescription ? pr.body : null, ...(initialSync ? { unread: false } : {}), diff --git a/tools/google-calendar/src/google-calendar.ts b/tools/google-calendar/src/google-calendar.ts index b31d4a6..b323937 100644 --- a/tools/google-calendar/src/google-calendar.ts +++ b/tools/google-calendar/src/google-calendar.ts @@ -1,8 +1,8 @@ import GoogleContacts from "@plotday/tool-google-contacts"; import { type Activity, - ActivityLinkType, - type ActivityLink, + LinkType, + type Link, type ActivityOccurrence, ActivityType, type ActorId, @@ -832,7 +832,7 @@ export class GoogleCalendar } // Build links array for videoconferencing and calendar links - const links: ActivityLink[] = []; + const links: Link[] = []; const seenUrls = new Set(); // Extract all conferencing links (Zoom, Teams, Webex, etc.) @@ -841,7 +841,7 @@ export class GoogleCalendar if (!seenUrls.has(link.url)) { seenUrls.add(link.url); links.push({ - type: ActivityLinkType.conferencing, + type: LinkType.conferencing, url: link.url, provider: link.provider, }); @@ -852,7 +852,7 @@ export class GoogleCalendar if (event.hangoutLink && !seenUrls.has(event.hangoutLink)) { seenUrls.add(event.hangoutLink); links.push({ - type: ActivityLinkType.conferencing, + type: LinkType.conferencing, url: event.hangoutLink, provider: ConferencingProvider.googleMeet, }); @@ -861,7 +861,7 @@ export class GoogleCalendar // Add calendar link if (event.htmlLink) { links.push({ - type: ActivityLinkType.external, + type: LinkType.external, title: "View in Calendar", url: event.htmlLink, }); @@ -882,14 +882,13 @@ export class GoogleCalendar // Canonical source for this event (required for upsert) const canonicalUrl = `google-calendar:${event.id}`; - // Create note with description and/or links + // Create note with description (links moved to activity level) const notes: NewNote[] = []; - if (hasDescription || hasLinks) { + if (hasDescription) { notes.push({ activity: { source: canonicalUrl }, key: "description", - content: hasDescription ? description : null, - links: hasLinks ? links : null, + content: description, contentType: description && containsHtml(description) ? "html" : "text", created: event.created ? new Date(event.created) : new Date(), @@ -909,6 +908,7 @@ export class GoogleCalendar recurrenceExdates: activityData.recurrenceExdates || null, meta: activityData.meta ?? null, tags: tags || undefined, + links: hasLinks ? links : undefined, notes, preview: hasDescription ? description : null, ...(initialSync ? { unread: false } : {}), // false for initial sync, omit for incremental updates diff --git a/tools/google-drive/src/google-drive.ts b/tools/google-drive/src/google-drive.ts index 0cf4bff..08288a3 100644 --- a/tools/google-drive/src/google-drive.ts +++ b/tools/google-drive/src/google-drive.ts @@ -2,8 +2,8 @@ import GoogleContacts from "@plotday/tool-google-contacts"; import { type ActivityFilter, ActivityKind, - type ActivityLink, - ActivityLinkType, + type Link, + LinkType, ActivityType, type NewActivityWithNotes, type NewContact, @@ -778,10 +778,10 @@ export class GoogleDrive extends Tool implements DocumentTool { } // Build external link - const links: ActivityLink[] = []; + const links: Link[] = []; if (file.webViewLink) { links.push({ - type: ActivityLinkType.external, + type: LinkType.external, title: "View in Drive", url: file.webViewLink, }); diff --git a/tools/jira/src/jira.ts b/tools/jira/src/jira.ts index f5cac7c..ef3e992 100644 --- a/tools/jira/src/jira.ts +++ b/tools/jira/src/jira.ts @@ -2,8 +2,8 @@ import { Version3Client } from "jira.js"; import { type Activity, - type ActivityLink, - ActivityLinkType, + type Link, + LinkType, ActivityType, type NewActivity, type NewActivityWithNotes, @@ -430,21 +430,21 @@ export class Jira extends Tool implements ProjectTool { ? `jira:${cloudId}:issue:${issue.id}` : undefined; - // Create initial note with description and link to Jira issue - const links: ActivityLink[] = []; + // Build activity-level links + const activityLinks: Link[] = []; if (issueUrl) { - links.push({ - type: ActivityLinkType.external, + activityLinks.push({ + type: LinkType.external, title: `Open in Jira`, url: issueUrl, }); } + // Create initial note with description (links moved to activity level) notes.push({ key: "description", content: description, created: fields.created ? new Date(fields.created) : undefined, - links: links.length > 0 ? links : null, author: authorContact, }); @@ -486,6 +486,7 @@ export class Jira extends Tool implements ProjectTool { author: authorContact, assignee: assigneeContact ?? null, // Explicitly set to null for unassigned issues done: fields.resolutiondate ? new Date(fields.resolutiondate) : null, + links: activityLinks.length > 0 ? activityLinks : undefined, notes, preview: description || null, }; diff --git a/tools/linear/src/linear.ts b/tools/linear/src/linear.ts index 70ee612..010eeb1 100644 --- a/tools/linear/src/linear.ts +++ b/tools/linear/src/linear.ts @@ -7,8 +7,8 @@ import type { import { LinearWebhookClient } from "@linear/sdk/webhooks"; import { - type ActivityLink, - ActivityLinkType, + type Link, + LinkType, ActivityMeta, ActivityType, type NewActivity, @@ -420,24 +420,23 @@ export class Linear extends Tool implements ProjectTool { const description = issue.description || ""; const hasDescription = description.trim().length > 0; - // Build notes array: description note with link + comment notes - const notes: any[] = []; - - // Create description note with link to Linear issue - const links: ActivityLink[] = []; + // Build activity-level links + const activityLinks: Link[] = []; if (issue.url) { - links.push({ - type: ActivityLinkType.external, + activityLinks.push({ + type: LinkType.external, title: `Open in Linear`, url: issue.url, }); } + // Build notes array: description note + comment notes + const notes: any[] = []; + notes.push({ key: "description", content: hasDescription ? description : null, created: issue.createdAt, - links: links.length > 0 ? links : null, author: authorContact, }); @@ -483,6 +482,7 @@ export class Linear extends Tool implements ProjectTool { linearId: issue.id, projectId, }, + links: activityLinks.length > 0 ? activityLinks : undefined, notes, preview: hasDescription ? description : null, ...(initialSync ? { unread: false } : {}), // false for initial sync, omit for incremental updates diff --git a/tools/outlook-calendar/src/outlook-calendar.ts b/tools/outlook-calendar/src/outlook-calendar.ts index 3f52574..378f15c 100644 --- a/tools/outlook-calendar/src/outlook-calendar.ts +++ b/tools/outlook-calendar/src/outlook-calendar.ts @@ -1,7 +1,7 @@ import { type Activity, - type ActivityLink, - ActivityLinkType, + type Link, + LinkType, type ActivityOccurrence, ActivityType, type ActorId, @@ -609,12 +609,12 @@ export class OutlookCalendar } // Build links array for videoconferencing and calendar links - const links: ActivityLink[] = []; + const links: Link[] = []; // Add conferencing link if available if (outlookEvent.onlineMeeting?.joinUrl) { links.push({ - type: ActivityLinkType.conferencing, + type: LinkType.conferencing, url: outlookEvent.onlineMeeting.joinUrl, provider: detectConferencingProvider( outlookEvent.onlineMeeting.joinUrl @@ -625,27 +625,26 @@ export class OutlookCalendar // Add calendar link if (outlookEvent.webLink) { links.push({ - type: ActivityLinkType.external, + type: LinkType.external, title: "View in Calendar", url: outlookEvent.webLink, }); } - // Create note with description and/or links + // Create note with description (links moved to activity level) const notes: NewNote[] = []; const hasDescription = outlookEvent.body?.content && outlookEvent.body.content.trim().length > 0; const hasLinks = links.length > 0; - if (hasDescription || hasLinks) { + if (hasDescription) { notes.push({ activity: { source: `outlook-calendar:${outlookEvent.id}`, }, key: "description", - content: hasDescription ? outlookEvent.body!.content! : null, - links: hasLinks ? links : null, + content: outlookEvent.body!.content!, contentType: (outlookEvent.body?.contentType === "html" ? "html" : "text") as ContentType, @@ -662,6 +661,7 @@ export class OutlookCalendar syncableId: calendarId, }, tags: tags && Object.keys(tags).length > 0 ? tags : activity.tags, + links: hasLinks ? links : undefined, notes, preview: hasDescription ? outlookEvent.body!.content! : null, ...(initialSync ? { unread: false } : {}), // false for initial sync, omit for incremental updates diff --git a/twister/src/plot.ts b/twister/src/plot.ts index 40d2f14..21c53d7 100644 --- a/twister/src/plot.ts +++ b/twister/src/plot.ts @@ -206,7 +206,7 @@ export enum ActivityKind { * Different link types have different behaviors when clicked by users * and may require different rendering approaches. */ -export enum ActivityLinkType { +export enum LinkType { /** External web links that open in browser */ external = "external", /** Authentication flows for connecting services */ @@ -247,22 +247,22 @@ export enum ConferencingProvider { * @example * ```typescript * // External link - opens URL in browser - * const externalLink: ActivityLink = { - * type: ActivityLinkType.external, + * const externalLink: Link = { + * type: LinkType.external, * title: "Open in Google Calendar", * url: "https://calendar.google.com/event/123", * }; * * // Conferencing link - opens video conference with provider info - * const conferencingLink: ActivityLink = { - * type: ActivityLinkType.conferencing, + * const conferencingLink: Link = { + * type: LinkType.conferencing, * url: "https://meet.google.com/abc-defg-hij", * provider: ConferencingProvider.googleMeet, * }; * * // Integrations link - initiates OAuth flow - * const authLink: ActivityLink = { - * type: ActivityLinkType.auth, + * const authLink: Link = { + * type: LinkType.auth, * title: "Continue with Google", * provider: AuthProvider.Google, * scopes: ["https://www.googleapis.com/auth/calendar.readonly"], @@ -270,17 +270,17 @@ export enum ConferencingProvider { * }; * * // Callback link - triggers a twist method - * const callbackLink: ActivityLink = { - * type: ActivityLinkType.callback, + * const callbackLink: Link = { + * type: LinkType.callback, * title: "📅 Primary Calendar", * token: "callback-token-here" * }; * ``` */ -export type ActivityLink = +export type Link = | { /** External web link that opens in browser */ - type: ActivityLinkType.external; + type: LinkType.external; /** Display text for the link button */ title: string; /** URL to open when clicked */ @@ -288,7 +288,7 @@ export type ActivityLink = } | { /** Video conferencing link with provider-specific handling */ - type: ActivityLinkType.conferencing; + type: LinkType.conferencing; /** URL to join the conference */ url: string; /** Conferencing provider for UI customization */ @@ -296,7 +296,7 @@ export type ActivityLink = } | { /** Authentication link that initiates an OAuth flow */ - type: ActivityLinkType.auth; + type: LinkType.auth; /** Display text for the auth button */ title: string; /** OAuth provider (e.g., "google", "microsoft") */ @@ -308,7 +308,7 @@ export type ActivityLink = } | { /** Callback link that triggers a twist method when clicked */ - type: ActivityLinkType.callback; + type: LinkType.callback; /** Display text for the callback button */ title: string; /** Token identifying the callback to execute */ @@ -316,7 +316,7 @@ export type ActivityLink = } | { /** File attachment link stored in R2 */ - type: ActivityLinkType.file; + type: LinkType.file; /** Unique identifier for the stored file */ fileId: string; /** Original filename */ @@ -543,6 +543,8 @@ type ActivityFields = ActivityCommon & { meta: ActivityMeta | null; /** Sort order for the activity (fractional positioning) */ order: number; + /** Array of interactive links attached to the activity (external, conferencing, callback) */ + links: Array | null; }; export type Activity = ActivityFields & @@ -929,7 +931,7 @@ export type ActivityFilter = { * that can be applied uniformly across many activities are included. */ type ActivityBulkUpdateFields = Partial< - Pick + Pick > & { /** Update the type of all matching activities. */ type?: ActivityType; @@ -1065,7 +1067,7 @@ export type Note = ActivityCommon & { /** Primary content for the note (markdown) */ content: string | null; /** Array of interactive links attached to the note */ - links: Array | null; + links: Array | null; /** The note this is a reply to, or null if not a reply */ reNote: { id: Uuid } | null; }; @@ -1267,3 +1269,10 @@ export type NewContact = { }; export type ContentType = "text" | "markdown" | "html"; + +/** @deprecated Use LinkType instead */ +export const ActivityLinkType = LinkType; +/** @deprecated Use LinkType instead */ +export type ActivityLinkType = LinkType; +/** @deprecated Use Link instead */ +export type ActivityLink = Link; diff --git a/twister/src/tools/callbacks.ts b/twister/src/tools/callbacks.ts index 01b61ef..821fd02 100644 --- a/twister/src/tools/callbacks.ts +++ b/twister/src/tools/callbacks.ts @@ -32,7 +32,7 @@ export type Callback = string & { readonly __brand: "Callback" }; * **When to use callbacks:** * - Webhook handlers that need persistent function references * - Scheduled operations that run after worker timeouts - * - User interaction links (ActivityLinkType.callback) + * - User interaction links (LinkType.callback) * - Cross-tool communication that survives restarts * * **Type Safety:** diff --git a/twister/src/tools/plot.ts b/twister/src/tools/plot.ts index d17d032..52b72db 100644 --- a/twister/src/tools/plot.ts +++ b/twister/src/tools/plot.ts @@ -90,7 +90,7 @@ export type NoteIntentHandler = { * title: "Welcome to Plot!", * links: [{ * title: "Get Started", - * type: ActivityLinkType.external, + * type: LinkType.external, * url: "https://plot.day/docs" * }] * }); @@ -337,7 +337,7 @@ export abstract class Plot extends ITool { * activity: { id: "activity-456" }, * note: "Meeting recording available", * links: [{ - * type: ActivityLinkType.external, + * type: LinkType.external, * title: "View Recording", * url: "https://example.com/recording" * }] diff --git a/twister/src/twist.ts b/twister/src/twist.ts index 9318c51..993f21a 100644 --- a/twister/src/twist.ts +++ b/twister/src/twist.ts @@ -1,4 +1,4 @@ -import { ActivityLink, type Actor, type Priority, Uuid } from "./plot"; +import { type Link, type Actor, type Priority, Uuid } from "./plot"; import { type ITool } from "./tool"; import type { Callback } from "./tools/callbacks"; import type { Serializable } from "./utils/serializable"; @@ -92,7 +92,7 @@ export abstract class Twist { } /** - * Like callback(), but for an ActivityLink, which receives the link as the first argument. + * Like callback(), but for a Link, which receives the link as the first argument. * * @param fn - The method to callback * @param extraArgs - Additional arguments to pass after the link @@ -101,8 +101,8 @@ export abstract class Twist { * @example * ```typescript * const callback = await this.linkCallback(this.doSomething, 123); - * const link: ActivityLink = { - * type: ActivityLinkType.Callback, + * const link: Link = { + * type: LinkType.Callback, * title: "Do Something", * callback, * }; @@ -110,7 +110,7 @@ export abstract class Twist { */ protected async linkCallback< TArgs extends Serializable[], - Fn extends (link: ActivityLink, ...extraArgs: TArgs) => any + Fn extends (link: Link, ...extraArgs: TArgs) => any >(fn: Fn, ...extraArgs: TArgs): Promise { return this.tools.callbacks.create(fn, ...extraArgs); } diff --git a/twists/chat/src/index.ts b/twists/chat/src/index.ts index d455761..b272c09 100644 --- a/twists/chat/src/index.ts +++ b/twists/chat/src/index.ts @@ -1,8 +1,8 @@ import { Type } from "typebox"; import { - type ActivityLink, - ActivityLinkType, + type Link, + LinkType, ActorType, type Note, Tag, @@ -132,10 +132,10 @@ You can provide either or both inline and standalone links. Only use standalone outputSchema: schema, }); - // Convert AI links to ActivityLink format - const activityLinks: ActivityLink[] | null = + // Convert AI links to Link format + const activityLinks: Link[] | null = response.output!.links?.map((link) => ({ - type: ActivityLinkType.external, + type: LinkType.external, title: link.title, url: link.url, })) || null; From 22a71369ab686ded5c9c16362cf23d02f0ed11e4 Mon Sep 17 00:00:00 2001 From: Kris Braun Date: Sun, 22 Feb 2026 16:47:54 -0500 Subject: [PATCH 2/3] Move View in Drive link from note to activity level Co-Authored-By: Claude Opus 4.6 --- tools/google-drive/src/google-drive.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tools/google-drive/src/google-drive.ts b/tools/google-drive/src/google-drive.ts index 08288a3..1999a8a 100644 --- a/tools/google-drive/src/google-drive.ts +++ b/tools/google-drive/src/google-drive.ts @@ -787,17 +787,13 @@ export class GoogleDrive extends Tool implements DocumentTool { }); } - // Add links to the summary note if present - if (links.length > 0 && notes.length > 0) { - notes[0].links = links; - } - const activity: NewActivityWithNotes = { source: canonicalSource, type: ActivityType.Note, kind: ActivityKind.document, title: file.name, author, + links: links.length > 0 ? links : null, meta: { fileId: file.id, folderId, From 76cac5a562b4bb9dcb1dfe5571d6e8ae325316ef Mon Sep 17 00:00:00 2001 From: Kris Braun Date: Sun, 22 Feb 2026 17:27:09 -0500 Subject: [PATCH 3/3] Add changeset --- .changeset/full-ends-rescue.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .changeset/full-ends-rescue.md diff --git a/.changeset/full-ends-rescue.md b/.changeset/full-ends-rescue.md new file mode 100644 index 0000000..c87ee28 --- /dev/null +++ b/.changeset/full-ends-rescue.md @@ -0,0 +1,13 @@ +--- +"@plotday/tool-outlook-calendar": minor +"@plotday/tool-google-calendar": minor +"@plotday/tool-github-issues": minor +"@plotday/tool-google-drive": minor +"@plotday/tool-github": minor +"@plotday/tool-linear": minor +"@plotday/tool-asana": minor +"@plotday/tool-jira": minor +"@plotday/twister": minor +--- + +Added: Activity.links, better for activity-scoped links such as the link to the original item