@athenaintel/react 0.40.2 → 0.40.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/dist/agent-runtime/src/subscription-wake-event.cjs +87 -2
- package/dist/agent-runtime/src/subscription-wake-event.cjs.map +1 -1
- package/dist/agent-runtime/src/subscription-wake-event.js +87 -2
- package/dist/agent-runtime/src/subscription-wake-event.js.map +1 -1
- package/dist/react/package.json.cjs +1 -1
- package/dist/react/package.json.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -807,6 +807,12 @@ Both use the same bearer / API key as session creation. The full recipe
|
|
|
807
807
|
template skill `voice-session-guide.md`. `createAthenaVoiceSession` is also
|
|
808
808
|
exported if you hold the bridge state yourself.
|
|
809
809
|
|
|
810
|
+
## What's new in 0.40.3
|
|
811
|
+
|
|
812
|
+
**A wake from a GitHub watch says what happened to the pull request.** When a `watch` on a pull request or an issue wakes the conversation, its card read "Someone triggered “Github check run completed” on GitHub - owner/repo", which named neither the pull request nor the result. It now says what happened, from the subject agora stamps with the event (the number and the state word, never text): "A check failed on #47506", "Checks passed on #47506", "#123 was merged", "Changes were requested on #123", "New comment on #123". The repository stands in when the event names no pull request, and the line under it still counts a batch ("4 events"). Every other event reads as before.
|
|
813
|
+
|
|
814
|
+
Nothing to change in your app.
|
|
815
|
+
|
|
810
816
|
## What's new in 0.40.2
|
|
811
817
|
|
|
812
818
|
**An answer that is still arriving is never called stalled.** The grouped turn header said "Nothing new for 31s" in amber whenever a turn's answer, or the agent's thinking, had streamed for more than thirty seconds, because its stall clock restarted only when a new part or tool step arrived. It now restarts whenever anything streams, an answer or thinking growing included, so the stall tone keeps its meaning: nothing has streamed for thirty seconds while the run waits on the model. A running tool still never gets the stall tone (0.40.1).
|
|
@@ -9,6 +9,7 @@ const COMMENT_REPLIED = "asset.comment.replied";
|
|
|
9
9
|
const COMMENT_RESOLVED = "asset.comment.resolved";
|
|
10
10
|
const PRESENCE_JOINED = "asset.presence.joined";
|
|
11
11
|
const SLACK_PREFIX = "user_slack.";
|
|
12
|
+
const GITHUB_PREFIX = "github.";
|
|
12
13
|
const COMMENT_EVENTS = /* @__PURE__ */ new Set([
|
|
13
14
|
COMMENT_CREATED,
|
|
14
15
|
COMMENT_REPLIED,
|
|
@@ -76,6 +77,16 @@ function readChanges(value) {
|
|
|
76
77
|
return [{ place: subscriptionPlaceWords(entry.anchor), value: scalar }];
|
|
77
78
|
});
|
|
78
79
|
}
|
|
80
|
+
function subscriptionWakeGithub(connector) {
|
|
81
|
+
var _a;
|
|
82
|
+
if (!isRecord(connector) || connector.provider !== "github") return null;
|
|
83
|
+
const number = numberOf(connector.number);
|
|
84
|
+
return {
|
|
85
|
+
repo: stringOf(connector.repo),
|
|
86
|
+
number: number !== null && number > 0 ? number : null,
|
|
87
|
+
state: ((_a = stringOf(connector.state)) == null ? void 0 : _a.toLowerCase()) ?? null
|
|
88
|
+
};
|
|
89
|
+
}
|
|
79
90
|
function readSubscriptionWakeEvent(custom) {
|
|
80
91
|
const envelope = custom == null ? void 0 : custom[SUBSCRIPTION_EVENT_KEY];
|
|
81
92
|
if (!isRecord(envelope)) return void 0;
|
|
@@ -103,7 +114,8 @@ function readSubscriptionWakeEvent(custom) {
|
|
|
103
114
|
scheduleSetAt: schedule ? stringOf(schedule.set_at) : null,
|
|
104
115
|
note: stringOf(envelope.note),
|
|
105
116
|
runsLeft: numberOf(envelope.runs_left),
|
|
106
|
-
deliveredAt: stringOf(envelope.delivered_at)
|
|
117
|
+
deliveredAt: stringOf(envelope.delivered_at),
|
|
118
|
+
github: subscriptionWakeGithub(envelope.connector)
|
|
107
119
|
};
|
|
108
120
|
}
|
|
109
121
|
function isSubscriptionWakeMessage(custom) {
|
|
@@ -196,6 +208,77 @@ function eventClauses(event) {
|
|
|
196
208
|
return true;
|
|
197
209
|
});
|
|
198
210
|
}
|
|
211
|
+
const CHECK_OUTCOMES = {
|
|
212
|
+
success: { one: "passed", many: "passed" },
|
|
213
|
+
failure: { one: "failed", many: "failed" },
|
|
214
|
+
cancelled: { one: "was cancelled", many: "were cancelled" },
|
|
215
|
+
timed_out: { one: "timed out", many: "timed out" },
|
|
216
|
+
skipped: { one: "was skipped", many: "were skipped" },
|
|
217
|
+
action_required: { one: "needs action", many: "need action" },
|
|
218
|
+
stale: { one: "went stale", many: "went stale" }
|
|
219
|
+
};
|
|
220
|
+
function githubSubscriptionWords({
|
|
221
|
+
eventType,
|
|
222
|
+
github
|
|
223
|
+
}) {
|
|
224
|
+
if (!eventType.startsWith(GITHUB_PREFIX)) return null;
|
|
225
|
+
const target = (github == null ? void 0 : github.number) != null ? `#${github.number}` : (github == null ? void 0 : github.repo) ?? "GitHub";
|
|
226
|
+
const state = (github == null ? void 0 : github.state) ?? null;
|
|
227
|
+
switch (eventType) {
|
|
228
|
+
case "github.check_run.completed":
|
|
229
|
+
case "github.check_suite.completed": {
|
|
230
|
+
const suite = eventType === "github.check_suite.completed";
|
|
231
|
+
const subject = suite ? "Checks" : "A check";
|
|
232
|
+
const outcome = state ? CHECK_OUTCOMES[state] : void 0;
|
|
233
|
+
if (!outcome) return `${subject} finished on ${target}`;
|
|
234
|
+
return `${subject} ${suite ? outcome.many : outcome.one} on ${target}`;
|
|
235
|
+
}
|
|
236
|
+
case "github.pr.opened":
|
|
237
|
+
return `${target} was opened`;
|
|
238
|
+
case "github.pr.reopened":
|
|
239
|
+
return `${target} was reopened`;
|
|
240
|
+
case "github.pr.ready_for_review":
|
|
241
|
+
return `${target} is ready for review`;
|
|
242
|
+
case "github.pr.updated":
|
|
243
|
+
return `${target} was updated`;
|
|
244
|
+
case "github.pr.merged":
|
|
245
|
+
return `${target} was merged`;
|
|
246
|
+
case "github.pr.closed":
|
|
247
|
+
return `${target} was closed without merging`;
|
|
248
|
+
case "github.pr.review.submitted":
|
|
249
|
+
if (state === "approved") return `${target} was approved`;
|
|
250
|
+
if (state === "changes_requested") return `Changes were requested on ${target}`;
|
|
251
|
+
if (state === "dismissed") return `A review on ${target} was dismissed`;
|
|
252
|
+
return `${target} was reviewed`;
|
|
253
|
+
case "github.pr.review_comment.created":
|
|
254
|
+
return `New review comment on ${target}`;
|
|
255
|
+
case "github.issue_comment.created":
|
|
256
|
+
return `New comment on ${target}`;
|
|
257
|
+
case "github.issue.created":
|
|
258
|
+
return `Issue ${target} was opened`;
|
|
259
|
+
case "github.issue.closed":
|
|
260
|
+
return `Issue ${target} was closed`;
|
|
261
|
+
case "github.issue.labeled":
|
|
262
|
+
return `Issue ${target} was labeled`;
|
|
263
|
+
case "github.issue.assigned":
|
|
264
|
+
return `Issue ${target} was assigned`;
|
|
265
|
+
case "github.push":
|
|
266
|
+
return `New commits were pushed to ${(github == null ? void 0 : github.repo) ?? target}`;
|
|
267
|
+
case "github.installation.changed":
|
|
268
|
+
return `The GitHub app's access to ${(github == null ? void 0 : github.repo) ?? "a repository"} changed`;
|
|
269
|
+
default:
|
|
270
|
+
return `${eventLabel(eventType.slice(GITHUB_PREFIX.length))} on ${target}`;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
function githubSubscriptionHeadline({
|
|
274
|
+
eventTypes,
|
|
275
|
+
github
|
|
276
|
+
}) {
|
|
277
|
+
if (eventTypes.length === 0 || !eventTypes.every((type) => type.startsWith(GITHUB_PREFIX))) {
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
return githubSubscriptionWords({ eventType: eventTypes[0], github });
|
|
281
|
+
}
|
|
199
282
|
function setOnWords({ setAt, now }) {
|
|
200
283
|
const date = new Date(setAt);
|
|
201
284
|
if (Number.isNaN(date.getTime())) return null;
|
|
@@ -218,7 +301,7 @@ function subscriptionWakeHeadline({
|
|
|
218
301
|
const when = event.scheduleSetAt ? setOnWords({ setAt: event.scheduleSetAt, now }) : null;
|
|
219
302
|
return when ? `Scheduled check-in you set ${when}` : "Scheduled check-in";
|
|
220
303
|
}
|
|
221
|
-
return `${event.actor} ${joinClauses(eventClauses(event))}`;
|
|
304
|
+
return githubSubscriptionHeadline({ eventTypes: event.eventTypes, github: event.github }) ?? `${event.actor} ${joinClauses(eventClauses(event))}`;
|
|
222
305
|
}
|
|
223
306
|
function runsLeftWords(runsLeft) {
|
|
224
307
|
if (runsLeft <= 0) return "No runs left";
|
|
@@ -244,9 +327,11 @@ function subscriptionWakeDetail(event) {
|
|
|
244
327
|
return parts.length > 0 ? parts.join(" · ") : null;
|
|
245
328
|
}
|
|
246
329
|
exports.SUBSCRIPTION_EVENT_KEY = SUBSCRIPTION_EVENT_KEY;
|
|
330
|
+
exports.githubSubscriptionHeadline = githubSubscriptionHeadline;
|
|
247
331
|
exports.isSubscriptionWakeMessage = isSubscriptionWakeMessage;
|
|
248
332
|
exports.readSubscriptionWakeEvent = readSubscriptionWakeEvent;
|
|
249
333
|
exports.subscriptionPlaceWords = subscriptionPlaceWords;
|
|
250
334
|
exports.subscriptionWakeDetail = subscriptionWakeDetail;
|
|
335
|
+
exports.subscriptionWakeGithub = subscriptionWakeGithub;
|
|
251
336
|
exports.subscriptionWakeHeadline = subscriptionWakeHeadline;
|
|
252
337
|
//# sourceMappingURL=subscription-wake-event.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"subscription-wake-event.cjs","sources":["../../../../agent-runtime/src/subscription-wake-event.ts"],"sourcesContent":["/**\n * The asset-subscription delivery that woke a conversation, read from the user\n * turn the delivery started.\n *\n * When an asset subscription fires, agora starts a turn on its conversation\n * with the delivery text the model reads (`render_delivery`), and stamps the\n * facts a person needs beside it under `athena_subscription_event` in the\n * message metadata — server-authoritative like `author`, never sent to the\n * model. The shipped converter exposes that metadata as the thread message's\n * `metadata.custom`, so a surface renders the turn as a compact marker\n * (\"Brendon Geils set B4 to Target\") instead of a user bubble quoting the\n * delivery text. Olympus draws the same facts as its event card with citation\n * chips; this is the text every other surface shows. The envelope is for\n * RENDERING only: a surface that ignores it degrades to a plain bubble, never\n * to a lost instruction.\n */\n\n/** `metadata.custom` key agora stamps on a subscription's delivery turn. */\nexport const SUBSCRIPTION_EVENT_KEY = 'athena_subscription_event';\n\n/** A changed cell or range, and its value now when agora read it. */\nexport interface SubscriptionWakeChange {\n /** Where: \"B4\", \"Sheet1!B2:B11\", \"page 5\"; `null` for a place the marker has no words for. */\n place: string | null;\n /** The value now; `null` when agora read none (a busy run's tray, a failed or capped read). */\n value: string | null;\n}\n\nexport interface SubscriptionWakeEvent {\n subscriptionId: string;\n /** `event`: something happened on an asset. `schedule`: a check-in the person set. */\n kind: 'event' | 'schedule';\n assetTitle: string | null;\n eventTypes: string[];\n eventCount: number | null;\n /** Who did it, in words: their name, \"You\", \"An agent\", \"Athena\", \"Someone\", with others counted. */\n actor: string;\n changes: SubscriptionWakeChange[];\n /** Changed places past the ones `changes` lists (agora caps the list): counted, never named. */\n moreChanges: number;\n /** The ranges the subscription names (\"B2:B11\"); empty for the whole asset. */\n subscribed: string[];\n /** Where a comment event's thread sits, when its anchor names a place. */\n commentPlace: string | null;\n /** Whether a comment-resolved event resolved (true) or reopened (false); `null` when unsaid. */\n commentResolved: boolean | null;\n /** When the person set a scheduled check-in. */\n scheduleSetAt: string | null;\n /** A scheduled check-in's own words. */\n note: string | null;\n runsLeft: number | null;\n deliveredAt: string | null;\n}\n\n/** Changed places and subscribed ranges named in one line; the rest are counted. */\nconst MAX_SHOWN_PLACES = 3;\n/** A changed value longer than this is cut in the marker. */\nconst MAX_VALUE_CHARS = 40;\n\nconst CONTENT_CHANGED = 'asset.content.changed';\nconst COMMENT_CREATED = 'asset.comment.created';\nconst COMMENT_REPLIED = 'asset.comment.replied';\nconst COMMENT_RESOLVED = 'asset.comment.resolved';\nconst PRESENCE_JOINED = 'asset.presence.joined';\nconst SLACK_PREFIX = 'user_slack.';\nconst COMMENT_EVENTS: ReadonlySet<string> = new Set([\n COMMENT_CREATED,\n COMMENT_REPLIED,\n COMMENT_RESOLVED,\n]);\n\nconst SLACK_WORDS: Readonly<Record<string, string>> = {\n 'user_slack.dm.received': 'sent you a Slack direct message',\n 'user_slack.mention.received': 'mentioned you in Slack',\n 'user_slack.thread_reply.received': 'replied in your Slack thread',\n 'user_slack.channel_message.posted': 'posted in a Slack channel',\n 'user_slack.reaction.added': 'reacted to a Slack message',\n 'user_slack.message.saved': 'saved a Slack message',\n 'user_slack.pin.added': 'pinned a Slack message',\n};\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/** A string with something in it, else `null`. */\nfunction stringOf(value: unknown): string | null {\n return typeof value === 'string' && value.trim().length > 0 ? value : null;\n}\n\n/** A finite number, else `null`. */\nfunction numberOf(value: unknown): number | null {\n return typeof value === 'number' && Number.isFinite(value) ? value : null;\n}\n\n/** A sheet name as an A1 qualifier: quoted when it is not a bare word. */\nfunction sheetQualifier(sheet: string): string {\n return /^[A-Za-z_][A-Za-z0-9_.]*$/.test(sheet) ? sheet : `'${sheet.replaceAll(\"'\", \"''\")}'`;\n}\n\n/**\n * Where a reference-standard anchor in agora's wire form points, in words:\n * a range or cell (with its sheet when the anchor names one), a page, a\n * slide; `null` for any other place.\n */\nexport function subscriptionPlaceWords(anchor: unknown): string | null {\n if (!isRecord(anchor)) return null;\n const sheet = stringOf(anchor.sheet) ?? stringOf(anchor.sheet_name);\n const range = stringOf(anchor.range) ?? stringOf(anchor.cell);\n if (range) return sheet ? `${sheetQualifier(sheet)}!${range}` : range;\n const page = numberOf(anchor.page);\n if (page !== null) return `page ${page}`;\n const slide = numberOf(anchor.slide_index);\n if (slide !== null) return `slide ${slide}`;\n return null;\n}\n\nfunction actorWords(envelope: Record<string, unknown>): string {\n const actor = isRecord(envelope.actor) ? envelope.actor : {};\n const name = stringOf(actor.name);\n let who: string;\n switch (actor.kind) {\n case 'self':\n who = 'You';\n break;\n case 'agent':\n who = name ?? 'An agent';\n break;\n case 'system':\n who = name ?? 'Athena';\n break;\n default:\n who = name ?? 'Someone';\n }\n const more = numberOf(envelope.more_actors) ?? 0;\n if (more <= 0) return who;\n return `${who} and ${more} ${more === 1 ? 'other' : 'others'}`;\n}\n\nfunction readChanges(value: unknown): SubscriptionWakeChange[] {\n if (!Array.isArray(value)) return [];\n return value.flatMap((entry) => {\n if (!isRecord(entry)) return [];\n const raw = entry.value;\n const scalar =\n typeof raw === 'string' || typeof raw === 'number' || typeof raw === 'boolean'\n ? String(raw)\n : null;\n return [{ place: subscriptionPlaceWords(entry.anchor), value: scalar }];\n });\n}\n\n/**\n * The delivery behind a thread message's `metadata.custom`, or `undefined`\n * when the message is not a subscription's delivery (or carries no\n * subscription id). Every other field is read leniently: a field of the wrong\n * shape reads as absent.\n */\nexport function readSubscriptionWakeEvent(\n custom: Record<string, unknown> | null | undefined\n): SubscriptionWakeEvent | undefined {\n const envelope = custom?.[SUBSCRIPTION_EVENT_KEY];\n if (!isRecord(envelope)) return undefined;\n const subscriptionId = stringOf(envelope.subscription_id);\n if (subscriptionId === null) return undefined;\n const comment = isRecord(envelope.comment) ? envelope.comment : null;\n const schedule = isRecord(envelope.schedule) ? envelope.schedule : null;\n const eventTypes = Array.isArray(envelope.event_types)\n ? envelope.event_types.filter((type): type is string => typeof type === 'string')\n : [];\n const subscribed = Array.isArray(envelope.subscribed)\n ? envelope.subscribed.flatMap((anchor) => {\n const words = subscriptionPlaceWords(anchor);\n return words ? [words] : [];\n })\n : [];\n return {\n subscriptionId,\n kind: envelope.kind === 'schedule' ? 'schedule' : 'event',\n assetTitle: stringOf(envelope.asset_title),\n eventTypes,\n eventCount: numberOf(envelope.event_count),\n actor: actorWords(envelope),\n changes: readChanges(envelope.changes),\n moreChanges: Math.max(0, numberOf(envelope.more_changes) ?? 0),\n subscribed,\n commentPlace: comment ? subscriptionPlaceWords(comment.anchor) : null,\n commentResolved: typeof comment?.resolved === 'boolean' ? comment.resolved : null,\n scheduleSetAt: schedule ? stringOf(schedule.set_at) : null,\n note: stringOf(envelope.note),\n runsLeft: numberOf(envelope.runs_left),\n deliveredAt: stringOf(envelope.delivered_at),\n };\n}\n\n/**\n * Whether a thread message is a subscription's delivery. Returns a primitive,\n * so it is safe as a `useAuiState` selector.\n */\nexport function isSubscriptionWakeMessage(\n custom: Record<string, unknown> | null | undefined\n): boolean {\n return readSubscriptionWakeEvent(custom) !== undefined;\n}\n\n/**\n * A value as the marker shows it: whitespace folded, cut at\n * {@link MAX_VALUE_CHARS}. A value of only whitespace is not empty (an empty\n * one is \"cleared\"), so it reads as a quoted space rather than as nothing.\n */\nfunction valueWords(value: string): string {\n const folded = value.replace(/\\s+/g, ' ').trim();\n if (folded === '') return '“ ”';\n return folded.length > MAX_VALUE_CHARS\n ? `${folded.slice(0, MAX_VALUE_CHARS - 1).trimEnd()}…`\n : folded;\n}\n\n/** \"a, b and c\" — items joined the way a sentence lists them, `last` before the last. */\nfunction joinList({ items, last }: { items: string[]; last: string }): string {\n if (items.length <= 1) return items.join('');\n return `${items.slice(0, -1).join(', ')}${last}${items.at(-1)}`;\n}\n\n/** One clause of the marker's line: its leading words, then the places it lists. */\ninterface Clause {\n lead: string;\n items: string[];\n}\n\n/**\n * The clauses after who did it, as one predicate: \"set B4 to Target and\n * commented on B7\". The clauses are listed \"a, b and c\". A lone clause lists\n * its own items the same way (\"changed B4 and B7\"); with several, each lists\n * its items with commas only, so the clauses' \"and\" is the only one.\n */\nfunction joinClauses(clauses: Clause[]): string {\n const itemLast = clauses.length === 1 ? ' and ' : ', ';\n return joinList({\n items: clauses.map(({ lead, items }) => `${lead}${joinList({ items, last: itemLast })}`),\n last: ' and ',\n });\n}\n\n/**\n * The changed places as clauses: \"set B4 to Target\", \"cleared B9\", \"changed\n * B7\" (no value read). At most three places are named; the others — and the\n * ones agora counted past what it sent — are one more clause, \"2 more\".\n */\nfunction changeClauses({\n changes,\n moreChanges,\n}: {\n changes: SubscriptionWakeChange[];\n moreChanges: number;\n}): Clause[] {\n const located = changes.filter(\n (change): change is { place: string; value: string | null } => change.place !== null\n );\n const shown = located.slice(0, MAX_SHOWN_PLACES);\n if (shown.length === 0) return [];\n const hidden = located.length - shown.length + moreChanges;\n const set = shown.filter((change) => change.value !== null && change.value !== '');\n const cleared = shown.filter((change) => change.value === '');\n const changed = shown.filter((change) => change.value === null);\n const clauses: Clause[] = [];\n if (set.length > 0) {\n clauses.push({\n lead: 'set ',\n items: set.map((change) => `${change.place} to ${valueWords(change.value ?? '')}`),\n });\n }\n if (cleared.length > 0) {\n clauses.push({ lead: 'cleared ', items: cleared.map((change) => change.place) });\n }\n if (changed.length > 0) {\n clauses.push({ lead: 'changed ', items: changed.map((change) => change.place) });\n }\n if (hidden > 0) clauses.push({ lead: `${hidden} more`, items: [] });\n return clauses;\n}\n\nfunction commentLead({ type, resolved }: { type: string; resolved: boolean | null }): string {\n if (type === COMMENT_CREATED) return 'commented on ';\n if (type === COMMENT_REPLIED) return 'replied to a comment on ';\n const verb = resolved === true ? 'resolved' : resolved === false ? 'reopened' : 'updated';\n return `${verb} a comment on `;\n}\n\n/** An event type without words of its own, in words: `asset.version.created` → \"Version created\". */\nfunction eventLabel(type: string): string {\n const words = type\n .replace(/^asset\\./, '')\n .replace(/[.\\s_-]+/g, ' ')\n .trim()\n .toLowerCase();\n return words ? `${words.charAt(0).toUpperCase()}${words.slice(1)}` : '—';\n}\n\n/**\n * One clause per kind of event the delivery carries, in the order agora lists\n * them (first seen first), so a batch that both changed cells and brought a\n * comment says both. A content change is its changed places, or \"edited\n * <asset>\" when none were located; changes count as one even when the types\n * leave the content change out. The envelope's comment is the batch's first\n * comment event's, so only the first comment clause names its place.\n */\nfunction eventClauses(event: SubscriptionWakeEvent): Clause[] {\n const asset = event.assetTitle ?? 'the asset';\n const changes = changeClauses({ changes: event.changes, moreChanges: event.moreChanges });\n const listed = event.eventTypes;\n const types = [\n ...new Set(\n listed.length === 0 || (changes.length > 0 && !listed.includes(CONTENT_CHANGED))\n ? [CONTENT_CHANGED, ...listed]\n : listed\n ),\n ];\n const firstComment = types.find((type) => COMMENT_EVENTS.has(type));\n const clauses = types.flatMap((type): Clause[] => {\n if (type === CONTENT_CHANGED) {\n return changes.length > 0 ? changes : [{ lead: 'edited ', items: [asset] }];\n }\n if (COMMENT_EVENTS.has(type)) {\n const where = type === firstComment && event.commentPlace ? event.commentPlace : asset;\n return [{ lead: commentLead({ type, resolved: event.commentResolved }), items: [where] }];\n }\n if (type === PRESENCE_JOINED) return [{ lead: 'opened ', items: [asset] }];\n if (type.startsWith(SLACK_PREFIX)) {\n return [{ lead: SLACK_WORDS[type] ?? 'had Slack activity', items: [] }];\n }\n return [{ lead: `triggered “${eventLabel(type)}” on `, items: [asset] }];\n });\n // Two Slack events of kinds without words of their own say the same thing once.\n const said = new Set<string>();\n return clauses.filter((clause) => {\n if (clause.items.length > 0) return true;\n if (said.has(clause.lead)) return false;\n said.add(clause.lead);\n return true;\n });\n}\n\n/** The day a check-in was set, as a person says it: \"today\", \"yesterday\", \"on Oct 8\". */\nfunction setOnWords({ setAt, now }: { setAt: string; now: Date }): string | null {\n const date = new Date(setAt);\n if (Number.isNaN(date.getTime())) return null;\n const midnight = (day: Date) => new Date(day.getFullYear(), day.getMonth(), day.getDate()).getTime();\n const days = Math.round((midnight(now) - midnight(date)) / (24 * 60 * 60 * 1000));\n if (days === 0) return 'today';\n if (days === 1) return 'yesterday';\n const sameYear = date.getFullYear() === now.getFullYear();\n return `on ${date.toLocaleDateString('en-US', {\n month: 'short',\n day: 'numeric',\n ...(sameYear ? {} : { year: 'numeric' as const }),\n })}`;\n}\n\n/**\n * The marker's line: who did what, where — \"Brendon Geils set B4 to Target\",\n * \"Brendon Geils changed B4\" when agora read no value, \"Ana Lopez commented\n * on B7\", \"Brendon Geils set B4 to Target and commented on B7\" for a batch\n * that carried both — or, for a schedule, \"Scheduled check-in you set on\n * Oct 8\" (`now` decides \"today\" and \"yesterday\").\n */\nexport function subscriptionWakeHeadline({\n event,\n now = new Date(),\n}: {\n event: SubscriptionWakeEvent;\n now?: Date;\n}): string {\n if (event.kind === 'schedule') {\n const when = event.scheduleSetAt ? setOnWords({ setAt: event.scheduleSetAt, now }) : null;\n return when ? `Scheduled check-in you set ${when}` : 'Scheduled check-in';\n }\n return `${event.actor} ${joinClauses(eventClauses(event))}`;\n}\n\n/** \"99 runs left\", \"1 run left\", \"No runs left\". */\nfunction runsLeftWords(runsLeft: number): string {\n if (runsLeft <= 0) return 'No runs left';\n return `${runsLeft} ${runsLeft === 1 ? 'run' : 'runs'} left`;\n}\n\n/**\n * The line under it: \"Series A Investors · subscribed to B2:B11 · 99 runs\n * left · 3 events\" for an asset event; a check-in's note for a schedule.\n * `null` when there is nothing to add.\n */\nexport function subscriptionWakeDetail(event: SubscriptionWakeEvent): string | null {\n if (event.kind === 'schedule') {\n const parts = [\n event.note,\n event.runsLeft !== null && event.runsLeft > 0 ? runsLeftWords(event.runsLeft) : null,\n ].filter((part): part is string => part !== null);\n return parts.length > 0 ? parts.join(' · ') : null;\n }\n const shown = event.subscribed.slice(0, MAX_SHOWN_PLACES);\n const hidden = event.subscribed.length - shown.length;\n const subscribed =\n shown.length > 0\n ? `subscribed to ${shown.join(', ')}${hidden > 0 ? ` and ${hidden} more` : ''}`\n : null;\n const parts = [\n event.assetTitle,\n subscribed,\n event.runsLeft !== null ? runsLeftWords(event.runsLeft) : null,\n event.eventCount !== null && event.eventCount > 1 ? `${event.eventCount} events` : null,\n ].filter((part): part is string => part !== null);\n return parts.length > 0 ? parts.join(' · ') : null;\n}\n"],"names":["parts"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"subscription-wake-event.cjs","sources":["../../../../agent-runtime/src/subscription-wake-event.ts"],"sourcesContent":["/**\n * The asset-subscription delivery that woke a conversation, read from the user\n * turn the delivery started.\n *\n * When an asset subscription fires, agora starts a turn on its conversation\n * with the delivery text the model reads (`render_delivery`), and stamps the\n * facts a person needs beside it under `athena_subscription_event` in the\n * message metadata — server-authoritative like `author`, never sent to the\n * model. The shipped converter exposes that metadata as the thread message's\n * `metadata.custom`, so a surface renders the turn as a compact marker\n * (\"Brendon Geils set B4 to Target\") instead of a user bubble quoting the\n * delivery text. Olympus draws the same facts as its event card with citation\n * chips; this is the text every other surface shows. The envelope is for\n * RENDERING only: a surface that ignores it degrades to a plain bubble, never\n * to a lost instruction.\n */\n\n/** `metadata.custom` key agora stamps on a subscription's delivery turn. */\nexport const SUBSCRIPTION_EVENT_KEY = 'athena_subscription_event';\n\n/** A changed cell or range, and its value now when agora read it. */\nexport interface SubscriptionWakeChange {\n /** Where: \"B4\", \"Sheet1!B2:B11\", \"page 5\"; `null` for a place the marker has no words for. */\n place: string | null;\n /** The value now; `null` when agora read none (a busy run's tray, a failed or capped read). */\n value: string | null;\n}\n\nexport interface SubscriptionWakeEvent {\n subscriptionId: string;\n /** `event`: something happened on an asset. `schedule`: a check-in the person set. */\n kind: 'event' | 'schedule';\n assetTitle: string | null;\n eventTypes: string[];\n eventCount: number | null;\n /** Who did it, in words: their name, \"You\", \"An agent\", \"Athena\", \"Someone\", with others counted. */\n actor: string;\n changes: SubscriptionWakeChange[];\n /** Changed places past the ones `changes` lists (agora caps the list): counted, never named. */\n moreChanges: number;\n /** The ranges the subscription names (\"B2:B11\"); empty for the whole asset. */\n subscribed: string[];\n /** Where a comment event's thread sits, when its anchor names a place. */\n commentPlace: string | null;\n /** Whether a comment-resolved event resolved (true) or reopened (false); `null` when unsaid. */\n commentResolved: boolean | null;\n /** When the person set a scheduled check-in. */\n scheduleSetAt: string | null;\n /** A scheduled check-in's own words. */\n note: string | null;\n runsLeft: number | null;\n deliveredAt: string | null;\n /** A GitHub event's subject (the envelope's `connector`); `null` for any other event. */\n github: SubscriptionWakeGithub | null;\n}\n\n/** A GitHub event's subject as agora's chip carries it: ids and a state word, never text. */\nexport interface SubscriptionWakeGithub {\n /** `owner/repo`. */\n repo: string | null;\n /** The pull request or issue; `null` when the event names none (a check on no pull request). */\n number: number | null;\n /** `merged` or `closed`, a review's state, or a check's conclusion, lowercased. */\n state: string | null;\n}\n\n/** Changed places and subscribed ranges named in one line; the rest are counted. */\nconst MAX_SHOWN_PLACES = 3;\n/** A changed value longer than this is cut in the marker. */\nconst MAX_VALUE_CHARS = 40;\n\nconst CONTENT_CHANGED = 'asset.content.changed';\nconst COMMENT_CREATED = 'asset.comment.created';\nconst COMMENT_REPLIED = 'asset.comment.replied';\nconst COMMENT_RESOLVED = 'asset.comment.resolved';\nconst PRESENCE_JOINED = 'asset.presence.joined';\nconst SLACK_PREFIX = 'user_slack.';\nconst GITHUB_PREFIX = 'github.';\nconst COMMENT_EVENTS: ReadonlySet<string> = new Set([\n COMMENT_CREATED,\n COMMENT_REPLIED,\n COMMENT_RESOLVED,\n]);\n\nconst SLACK_WORDS: Readonly<Record<string, string>> = {\n 'user_slack.dm.received': 'sent you a Slack direct message',\n 'user_slack.mention.received': 'mentioned you in Slack',\n 'user_slack.thread_reply.received': 'replied in your Slack thread',\n 'user_slack.channel_message.posted': 'posted in a Slack channel',\n 'user_slack.reaction.added': 'reacted to a Slack message',\n 'user_slack.message.saved': 'saved a Slack message',\n 'user_slack.pin.added': 'pinned a Slack message',\n};\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/** A string with something in it, else `null`. */\nfunction stringOf(value: unknown): string | null {\n return typeof value === 'string' && value.trim().length > 0 ? value : null;\n}\n\n/** A finite number, else `null`. */\nfunction numberOf(value: unknown): number | null {\n return typeof value === 'number' && Number.isFinite(value) ? value : null;\n}\n\n/** A sheet name as an A1 qualifier: quoted when it is not a bare word. */\nfunction sheetQualifier(sheet: string): string {\n return /^[A-Za-z_][A-Za-z0-9_.]*$/.test(sheet) ? sheet : `'${sheet.replaceAll(\"'\", \"''\")}'`;\n}\n\n/**\n * Where a reference-standard anchor in agora's wire form points, in words:\n * a range or cell (with its sheet when the anchor names one), a page, a\n * slide; `null` for any other place.\n */\nexport function subscriptionPlaceWords(anchor: unknown): string | null {\n if (!isRecord(anchor)) return null;\n const sheet = stringOf(anchor.sheet) ?? stringOf(anchor.sheet_name);\n const range = stringOf(anchor.range) ?? stringOf(anchor.cell);\n if (range) return sheet ? `${sheetQualifier(sheet)}!${range}` : range;\n const page = numberOf(anchor.page);\n if (page !== null) return `page ${page}`;\n const slide = numberOf(anchor.slide_index);\n if (slide !== null) return `slide ${slide}`;\n return null;\n}\n\nfunction actorWords(envelope: Record<string, unknown>): string {\n const actor = isRecord(envelope.actor) ? envelope.actor : {};\n const name = stringOf(actor.name);\n let who: string;\n switch (actor.kind) {\n case 'self':\n who = 'You';\n break;\n case 'agent':\n who = name ?? 'An agent';\n break;\n case 'system':\n who = name ?? 'Athena';\n break;\n default:\n who = name ?? 'Someone';\n }\n const more = numberOf(envelope.more_actors) ?? 0;\n if (more <= 0) return who;\n return `${who} and ${more} ${more === 1 ? 'other' : 'others'}`;\n}\n\nfunction readChanges(value: unknown): SubscriptionWakeChange[] {\n if (!Array.isArray(value)) return [];\n return value.flatMap((entry) => {\n if (!isRecord(entry)) return [];\n const raw = entry.value;\n const scalar =\n typeof raw === 'string' || typeof raw === 'number' || typeof raw === 'boolean'\n ? String(raw)\n : null;\n return [{ place: subscriptionPlaceWords(entry.anchor), value: scalar }];\n });\n}\n\n/**\n * The GitHub subject of an envelope's `connector`, or `null` when the\n * connector is absent or another provider's (Slack).\n */\nexport function subscriptionWakeGithub(connector: unknown): SubscriptionWakeGithub | null {\n if (!isRecord(connector) || connector.provider !== 'github') return null;\n const number = numberOf(connector.number);\n return {\n repo: stringOf(connector.repo),\n number: number !== null && number > 0 ? number : null,\n state: stringOf(connector.state)?.toLowerCase() ?? null,\n };\n}\n\n/**\n * The delivery behind a thread message's `metadata.custom`, or `undefined`\n * when the message is not a subscription's delivery (or carries no\n * subscription id). Every other field is read leniently: a field of the wrong\n * shape reads as absent.\n */\nexport function readSubscriptionWakeEvent(\n custom: Record<string, unknown> | null | undefined\n): SubscriptionWakeEvent | undefined {\n const envelope = custom?.[SUBSCRIPTION_EVENT_KEY];\n if (!isRecord(envelope)) return undefined;\n const subscriptionId = stringOf(envelope.subscription_id);\n if (subscriptionId === null) return undefined;\n const comment = isRecord(envelope.comment) ? envelope.comment : null;\n const schedule = isRecord(envelope.schedule) ? envelope.schedule : null;\n const eventTypes = Array.isArray(envelope.event_types)\n ? envelope.event_types.filter((type): type is string => typeof type === 'string')\n : [];\n const subscribed = Array.isArray(envelope.subscribed)\n ? envelope.subscribed.flatMap((anchor) => {\n const words = subscriptionPlaceWords(anchor);\n return words ? [words] : [];\n })\n : [];\n return {\n subscriptionId,\n kind: envelope.kind === 'schedule' ? 'schedule' : 'event',\n assetTitle: stringOf(envelope.asset_title),\n eventTypes,\n eventCount: numberOf(envelope.event_count),\n actor: actorWords(envelope),\n changes: readChanges(envelope.changes),\n moreChanges: Math.max(0, numberOf(envelope.more_changes) ?? 0),\n subscribed,\n commentPlace: comment ? subscriptionPlaceWords(comment.anchor) : null,\n commentResolved: typeof comment?.resolved === 'boolean' ? comment.resolved : null,\n scheduleSetAt: schedule ? stringOf(schedule.set_at) : null,\n note: stringOf(envelope.note),\n runsLeft: numberOf(envelope.runs_left),\n deliveredAt: stringOf(envelope.delivered_at),\n github: subscriptionWakeGithub(envelope.connector),\n };\n}\n\n/**\n * Whether a thread message is a subscription's delivery. Returns a primitive,\n * so it is safe as a `useAuiState` selector.\n */\nexport function isSubscriptionWakeMessage(\n custom: Record<string, unknown> | null | undefined\n): boolean {\n return readSubscriptionWakeEvent(custom) !== undefined;\n}\n\n/**\n * A value as the marker shows it: whitespace folded, cut at\n * {@link MAX_VALUE_CHARS}. A value of only whitespace is not empty (an empty\n * one is \"cleared\"), so it reads as a quoted space rather than as nothing.\n */\nfunction valueWords(value: string): string {\n const folded = value.replace(/\\s+/g, ' ').trim();\n if (folded === '') return '“ ”';\n return folded.length > MAX_VALUE_CHARS\n ? `${folded.slice(0, MAX_VALUE_CHARS - 1).trimEnd()}…`\n : folded;\n}\n\n/** \"a, b and c\" — items joined the way a sentence lists them, `last` before the last. */\nfunction joinList({ items, last }: { items: string[]; last: string }): string {\n if (items.length <= 1) return items.join('');\n return `${items.slice(0, -1).join(', ')}${last}${items.at(-1)}`;\n}\n\n/** One clause of the marker's line: its leading words, then the places it lists. */\ninterface Clause {\n lead: string;\n items: string[];\n}\n\n/**\n * The clauses after who did it, as one predicate: \"set B4 to Target and\n * commented on B7\". The clauses are listed \"a, b and c\". A lone clause lists\n * its own items the same way (\"changed B4 and B7\"); with several, each lists\n * its items with commas only, so the clauses' \"and\" is the only one.\n */\nfunction joinClauses(clauses: Clause[]): string {\n const itemLast = clauses.length === 1 ? ' and ' : ', ';\n return joinList({\n items: clauses.map(({ lead, items }) => `${lead}${joinList({ items, last: itemLast })}`),\n last: ' and ',\n });\n}\n\n/**\n * The changed places as clauses: \"set B4 to Target\", \"cleared B9\", \"changed\n * B7\" (no value read). At most three places are named; the others — and the\n * ones agora counted past what it sent — are one more clause, \"2 more\".\n */\nfunction changeClauses({\n changes,\n moreChanges,\n}: {\n changes: SubscriptionWakeChange[];\n moreChanges: number;\n}): Clause[] {\n const located = changes.filter(\n (change): change is { place: string; value: string | null } => change.place !== null\n );\n const shown = located.slice(0, MAX_SHOWN_PLACES);\n if (shown.length === 0) return [];\n const hidden = located.length - shown.length + moreChanges;\n const set = shown.filter((change) => change.value !== null && change.value !== '');\n const cleared = shown.filter((change) => change.value === '');\n const changed = shown.filter((change) => change.value === null);\n const clauses: Clause[] = [];\n if (set.length > 0) {\n clauses.push({\n lead: 'set ',\n items: set.map((change) => `${change.place} to ${valueWords(change.value ?? '')}`),\n });\n }\n if (cleared.length > 0) {\n clauses.push({ lead: 'cleared ', items: cleared.map((change) => change.place) });\n }\n if (changed.length > 0) {\n clauses.push({ lead: 'changed ', items: changed.map((change) => change.place) });\n }\n if (hidden > 0) clauses.push({ lead: `${hidden} more`, items: [] });\n return clauses;\n}\n\nfunction commentLead({ type, resolved }: { type: string; resolved: boolean | null }): string {\n if (type === COMMENT_CREATED) return 'commented on ';\n if (type === COMMENT_REPLIED) return 'replied to a comment on ';\n const verb = resolved === true ? 'resolved' : resolved === false ? 'reopened' : 'updated';\n return `${verb} a comment on `;\n}\n\n/** An event type without words of its own, in words: `asset.version.created` → \"Version created\". */\nfunction eventLabel(type: string): string {\n const words = type\n .replace(/^asset\\./, '')\n .replace(/[.\\s_-]+/g, ' ')\n .trim()\n .toLowerCase();\n return words ? `${words.charAt(0).toUpperCase()}${words.slice(1)}` : '—';\n}\n\n/**\n * One clause per kind of event the delivery carries, in the order agora lists\n * them (first seen first), so a batch that both changed cells and brought a\n * comment says both. A content change is its changed places, or \"edited\n * <asset>\" when none were located; changes count as one even when the types\n * leave the content change out. The envelope's comment is the batch's first\n * comment event's, so only the first comment clause names its place.\n */\nfunction eventClauses(event: SubscriptionWakeEvent): Clause[] {\n const asset = event.assetTitle ?? 'the asset';\n const changes = changeClauses({ changes: event.changes, moreChanges: event.moreChanges });\n const listed = event.eventTypes;\n const types = [\n ...new Set(\n listed.length === 0 || (changes.length > 0 && !listed.includes(CONTENT_CHANGED))\n ? [CONTENT_CHANGED, ...listed]\n : listed\n ),\n ];\n const firstComment = types.find((type) => COMMENT_EVENTS.has(type));\n const clauses = types.flatMap((type): Clause[] => {\n if (type === CONTENT_CHANGED) {\n return changes.length > 0 ? changes : [{ lead: 'edited ', items: [asset] }];\n }\n if (COMMENT_EVENTS.has(type)) {\n const where = type === firstComment && event.commentPlace ? event.commentPlace : asset;\n return [{ lead: commentLead({ type, resolved: event.commentResolved }), items: [where] }];\n }\n if (type === PRESENCE_JOINED) return [{ lead: 'opened ', items: [asset] }];\n if (type.startsWith(SLACK_PREFIX)) {\n return [{ lead: SLACK_WORDS[type] ?? 'had Slack activity', items: [] }];\n }\n return [{ lead: `triggered “${eventLabel(type)}” on `, items: [asset] }];\n });\n // Two Slack events of kinds without words of their own say the same thing once.\n const said = new Set<string>();\n return clauses.filter((clause) => {\n if (clause.items.length > 0) return true;\n if (said.has(clause.lead)) return false;\n said.add(clause.lead);\n return true;\n });\n}\n\n/** What a check's conclusion did, said of one check and of a suite of them. */\nconst CHECK_OUTCOMES: Readonly<Record<string, { one: string; many: string }>> = {\n success: { one: 'passed', many: 'passed' },\n failure: { one: 'failed', many: 'failed' },\n cancelled: { one: 'was cancelled', many: 'were cancelled' },\n timed_out: { one: 'timed out', many: 'timed out' },\n skipped: { one: 'was skipped', many: 'were skipped' },\n action_required: { one: 'needs action', many: 'need action' },\n stale: { one: 'went stale', many: 'went stale' },\n};\n\n/**\n * A GitHub event in words, from the chip's subject: \"A check failed on\n * #47506\", \"Checks passed on #47506\", \"#123 was merged\", \"Changes were\n * requested on #123\". The pull request or issue is named by its number; the\n * repository stands in when the event names none. No one is named: GitHub's\n * actor is not an Athena person, and the chip carries no text. `null` for an\n * event type that is not GitHub's.\n */\nfunction githubSubscriptionWords({\n eventType,\n github,\n}: {\n eventType: string;\n github: SubscriptionWakeGithub | null;\n}): string | null {\n if (!eventType.startsWith(GITHUB_PREFIX)) return null;\n const target = github?.number != null ? `#${github.number}` : (github?.repo ?? 'GitHub');\n const state = github?.state ?? null;\n switch (eventType) {\n case 'github.check_run.completed':\n case 'github.check_suite.completed': {\n const suite = eventType === 'github.check_suite.completed';\n const subject = suite ? 'Checks' : 'A check';\n const outcome = state ? CHECK_OUTCOMES[state] : undefined;\n if (!outcome) return `${subject} finished on ${target}`;\n return `${subject} ${suite ? outcome.many : outcome.one} on ${target}`;\n }\n case 'github.pr.opened':\n return `${target} was opened`;\n case 'github.pr.reopened':\n return `${target} was reopened`;\n case 'github.pr.ready_for_review':\n return `${target} is ready for review`;\n case 'github.pr.updated':\n return `${target} was updated`;\n case 'github.pr.merged':\n return `${target} was merged`;\n case 'github.pr.closed':\n return `${target} was closed without merging`;\n case 'github.pr.review.submitted':\n if (state === 'approved') return `${target} was approved`;\n if (state === 'changes_requested') return `Changes were requested on ${target}`;\n if (state === 'dismissed') return `A review on ${target} was dismissed`;\n return `${target} was reviewed`;\n case 'github.pr.review_comment.created':\n return `New review comment on ${target}`;\n case 'github.issue_comment.created':\n return `New comment on ${target}`;\n case 'github.issue.created':\n return `Issue ${target} was opened`;\n case 'github.issue.closed':\n return `Issue ${target} was closed`;\n case 'github.issue.labeled':\n return `Issue ${target} was labeled`;\n case 'github.issue.assigned':\n return `Issue ${target} was assigned`;\n case 'github.push':\n return `New commits were pushed to ${github?.repo ?? target}`;\n case 'github.installation.changed':\n return `The GitHub app's access to ${github?.repo ?? 'a repository'} changed`;\n default:\n // A GitHub event without words of its own is named by its type, as any other event is.\n return `${eventLabel(eventType.slice(GITHUB_PREFIX.length))} on ${target}`;\n }\n}\n\n/**\n * The words for a delivery whose events are all GitHub's, from its first\n * event (the one the chip describes; the detail line counts the rest), or\n * `null` for any other delivery. Olympus's event card says the same.\n */\nexport function githubSubscriptionHeadline({\n eventTypes,\n github,\n}: {\n eventTypes: readonly string[];\n github: SubscriptionWakeGithub | null;\n}): string | null {\n if (eventTypes.length === 0 || !eventTypes.every((type) => type.startsWith(GITHUB_PREFIX))) {\n return null;\n }\n return githubSubscriptionWords({ eventType: eventTypes[0], github });\n}\n\n/** The day a check-in was set, as a person says it: \"today\", \"yesterday\", \"on Oct 8\". */\nfunction setOnWords({ setAt, now }: { setAt: string; now: Date }): string | null {\n const date = new Date(setAt);\n if (Number.isNaN(date.getTime())) return null;\n const midnight = (day: Date) => new Date(day.getFullYear(), day.getMonth(), day.getDate()).getTime();\n const days = Math.round((midnight(now) - midnight(date)) / (24 * 60 * 60 * 1000));\n if (days === 0) return 'today';\n if (days === 1) return 'yesterday';\n const sameYear = date.getFullYear() === now.getFullYear();\n return `on ${date.toLocaleDateString('en-US', {\n month: 'short',\n day: 'numeric',\n ...(sameYear ? {} : { year: 'numeric' as const }),\n })}`;\n}\n\n/**\n * The marker's line: who did what, where — \"Brendon Geils set B4 to Target\",\n * \"Brendon Geils changed B4\" when agora read no value, \"Ana Lopez commented\n * on B7\", \"Brendon Geils set B4 to Target and commented on B7\" for a batch\n * that carried both — for GitHub, what happened to the pull request (\"A\n * check failed on #47506\") — or, for a schedule, \"Scheduled check-in you set\n * on Oct 8\" (`now` decides \"today\" and \"yesterday\").\n */\nexport function subscriptionWakeHeadline({\n event,\n now = new Date(),\n}: {\n event: SubscriptionWakeEvent;\n now?: Date;\n}): string {\n if (event.kind === 'schedule') {\n const when = event.scheduleSetAt ? setOnWords({ setAt: event.scheduleSetAt, now }) : null;\n return when ? `Scheduled check-in you set ${when}` : 'Scheduled check-in';\n }\n return (\n githubSubscriptionHeadline({ eventTypes: event.eventTypes, github: event.github }) ??\n `${event.actor} ${joinClauses(eventClauses(event))}`\n );\n}\n\n/** \"99 runs left\", \"1 run left\", \"No runs left\". */\nfunction runsLeftWords(runsLeft: number): string {\n if (runsLeft <= 0) return 'No runs left';\n return `${runsLeft} ${runsLeft === 1 ? 'run' : 'runs'} left`;\n}\n\n/**\n * The line under it: \"Series A Investors · subscribed to B2:B11 · 99 runs\n * left · 3 events\" for an asset event; a check-in's note for a schedule.\n * `null` when there is nothing to add.\n */\nexport function subscriptionWakeDetail(event: SubscriptionWakeEvent): string | null {\n if (event.kind === 'schedule') {\n const parts = [\n event.note,\n event.runsLeft !== null && event.runsLeft > 0 ? runsLeftWords(event.runsLeft) : null,\n ].filter((part): part is string => part !== null);\n return parts.length > 0 ? parts.join(' · ') : null;\n }\n const shown = event.subscribed.slice(0, MAX_SHOWN_PLACES);\n const hidden = event.subscribed.length - shown.length;\n const subscribed =\n shown.length > 0\n ? `subscribed to ${shown.join(', ')}${hidden > 0 ? ` and ${hidden} more` : ''}`\n : null;\n const parts = [\n event.assetTitle,\n subscribed,\n event.runsLeft !== null ? runsLeftWords(event.runsLeft) : null,\n event.eventCount !== null && event.eventCount > 1 ? `${event.eventCount} events` : null,\n ].filter((part): part is string => part !== null);\n return parts.length > 0 ? parts.join(' · ') : null;\n}\n"],"names":["parts"],"mappings":"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"}
|
|
@@ -7,6 +7,7 @@ const COMMENT_REPLIED = "asset.comment.replied";
|
|
|
7
7
|
const COMMENT_RESOLVED = "asset.comment.resolved";
|
|
8
8
|
const PRESENCE_JOINED = "asset.presence.joined";
|
|
9
9
|
const SLACK_PREFIX = "user_slack.";
|
|
10
|
+
const GITHUB_PREFIX = "github.";
|
|
10
11
|
const COMMENT_EVENTS = /* @__PURE__ */ new Set([
|
|
11
12
|
COMMENT_CREATED,
|
|
12
13
|
COMMENT_REPLIED,
|
|
@@ -74,6 +75,16 @@ function readChanges(value) {
|
|
|
74
75
|
return [{ place: subscriptionPlaceWords(entry.anchor), value: scalar }];
|
|
75
76
|
});
|
|
76
77
|
}
|
|
78
|
+
function subscriptionWakeGithub(connector) {
|
|
79
|
+
var _a;
|
|
80
|
+
if (!isRecord(connector) || connector.provider !== "github") return null;
|
|
81
|
+
const number = numberOf(connector.number);
|
|
82
|
+
return {
|
|
83
|
+
repo: stringOf(connector.repo),
|
|
84
|
+
number: number !== null && number > 0 ? number : null,
|
|
85
|
+
state: ((_a = stringOf(connector.state)) == null ? void 0 : _a.toLowerCase()) ?? null
|
|
86
|
+
};
|
|
87
|
+
}
|
|
77
88
|
function readSubscriptionWakeEvent(custom) {
|
|
78
89
|
const envelope = custom == null ? void 0 : custom[SUBSCRIPTION_EVENT_KEY];
|
|
79
90
|
if (!isRecord(envelope)) return void 0;
|
|
@@ -101,7 +112,8 @@ function readSubscriptionWakeEvent(custom) {
|
|
|
101
112
|
scheduleSetAt: schedule ? stringOf(schedule.set_at) : null,
|
|
102
113
|
note: stringOf(envelope.note),
|
|
103
114
|
runsLeft: numberOf(envelope.runs_left),
|
|
104
|
-
deliveredAt: stringOf(envelope.delivered_at)
|
|
115
|
+
deliveredAt: stringOf(envelope.delivered_at),
|
|
116
|
+
github: subscriptionWakeGithub(envelope.connector)
|
|
105
117
|
};
|
|
106
118
|
}
|
|
107
119
|
function isSubscriptionWakeMessage(custom) {
|
|
@@ -194,6 +206,77 @@ function eventClauses(event) {
|
|
|
194
206
|
return true;
|
|
195
207
|
});
|
|
196
208
|
}
|
|
209
|
+
const CHECK_OUTCOMES = {
|
|
210
|
+
success: { one: "passed", many: "passed" },
|
|
211
|
+
failure: { one: "failed", many: "failed" },
|
|
212
|
+
cancelled: { one: "was cancelled", many: "were cancelled" },
|
|
213
|
+
timed_out: { one: "timed out", many: "timed out" },
|
|
214
|
+
skipped: { one: "was skipped", many: "were skipped" },
|
|
215
|
+
action_required: { one: "needs action", many: "need action" },
|
|
216
|
+
stale: { one: "went stale", many: "went stale" }
|
|
217
|
+
};
|
|
218
|
+
function githubSubscriptionWords({
|
|
219
|
+
eventType,
|
|
220
|
+
github
|
|
221
|
+
}) {
|
|
222
|
+
if (!eventType.startsWith(GITHUB_PREFIX)) return null;
|
|
223
|
+
const target = (github == null ? void 0 : github.number) != null ? `#${github.number}` : (github == null ? void 0 : github.repo) ?? "GitHub";
|
|
224
|
+
const state = (github == null ? void 0 : github.state) ?? null;
|
|
225
|
+
switch (eventType) {
|
|
226
|
+
case "github.check_run.completed":
|
|
227
|
+
case "github.check_suite.completed": {
|
|
228
|
+
const suite = eventType === "github.check_suite.completed";
|
|
229
|
+
const subject = suite ? "Checks" : "A check";
|
|
230
|
+
const outcome = state ? CHECK_OUTCOMES[state] : void 0;
|
|
231
|
+
if (!outcome) return `${subject} finished on ${target}`;
|
|
232
|
+
return `${subject} ${suite ? outcome.many : outcome.one} on ${target}`;
|
|
233
|
+
}
|
|
234
|
+
case "github.pr.opened":
|
|
235
|
+
return `${target} was opened`;
|
|
236
|
+
case "github.pr.reopened":
|
|
237
|
+
return `${target} was reopened`;
|
|
238
|
+
case "github.pr.ready_for_review":
|
|
239
|
+
return `${target} is ready for review`;
|
|
240
|
+
case "github.pr.updated":
|
|
241
|
+
return `${target} was updated`;
|
|
242
|
+
case "github.pr.merged":
|
|
243
|
+
return `${target} was merged`;
|
|
244
|
+
case "github.pr.closed":
|
|
245
|
+
return `${target} was closed without merging`;
|
|
246
|
+
case "github.pr.review.submitted":
|
|
247
|
+
if (state === "approved") return `${target} was approved`;
|
|
248
|
+
if (state === "changes_requested") return `Changes were requested on ${target}`;
|
|
249
|
+
if (state === "dismissed") return `A review on ${target} was dismissed`;
|
|
250
|
+
return `${target} was reviewed`;
|
|
251
|
+
case "github.pr.review_comment.created":
|
|
252
|
+
return `New review comment on ${target}`;
|
|
253
|
+
case "github.issue_comment.created":
|
|
254
|
+
return `New comment on ${target}`;
|
|
255
|
+
case "github.issue.created":
|
|
256
|
+
return `Issue ${target} was opened`;
|
|
257
|
+
case "github.issue.closed":
|
|
258
|
+
return `Issue ${target} was closed`;
|
|
259
|
+
case "github.issue.labeled":
|
|
260
|
+
return `Issue ${target} was labeled`;
|
|
261
|
+
case "github.issue.assigned":
|
|
262
|
+
return `Issue ${target} was assigned`;
|
|
263
|
+
case "github.push":
|
|
264
|
+
return `New commits were pushed to ${(github == null ? void 0 : github.repo) ?? target}`;
|
|
265
|
+
case "github.installation.changed":
|
|
266
|
+
return `The GitHub app's access to ${(github == null ? void 0 : github.repo) ?? "a repository"} changed`;
|
|
267
|
+
default:
|
|
268
|
+
return `${eventLabel(eventType.slice(GITHUB_PREFIX.length))} on ${target}`;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
function githubSubscriptionHeadline({
|
|
272
|
+
eventTypes,
|
|
273
|
+
github
|
|
274
|
+
}) {
|
|
275
|
+
if (eventTypes.length === 0 || !eventTypes.every((type) => type.startsWith(GITHUB_PREFIX))) {
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
return githubSubscriptionWords({ eventType: eventTypes[0], github });
|
|
279
|
+
}
|
|
197
280
|
function setOnWords({ setAt, now }) {
|
|
198
281
|
const date = new Date(setAt);
|
|
199
282
|
if (Number.isNaN(date.getTime())) return null;
|
|
@@ -216,7 +299,7 @@ function subscriptionWakeHeadline({
|
|
|
216
299
|
const when = event.scheduleSetAt ? setOnWords({ setAt: event.scheduleSetAt, now }) : null;
|
|
217
300
|
return when ? `Scheduled check-in you set ${when}` : "Scheduled check-in";
|
|
218
301
|
}
|
|
219
|
-
return `${event.actor} ${joinClauses(eventClauses(event))}`;
|
|
302
|
+
return githubSubscriptionHeadline({ eventTypes: event.eventTypes, github: event.github }) ?? `${event.actor} ${joinClauses(eventClauses(event))}`;
|
|
220
303
|
}
|
|
221
304
|
function runsLeftWords(runsLeft) {
|
|
222
305
|
if (runsLeft <= 0) return "No runs left";
|
|
@@ -243,10 +326,12 @@ function subscriptionWakeDetail(event) {
|
|
|
243
326
|
}
|
|
244
327
|
export {
|
|
245
328
|
SUBSCRIPTION_EVENT_KEY,
|
|
329
|
+
githubSubscriptionHeadline,
|
|
246
330
|
isSubscriptionWakeMessage,
|
|
247
331
|
readSubscriptionWakeEvent,
|
|
248
332
|
subscriptionPlaceWords,
|
|
249
333
|
subscriptionWakeDetail,
|
|
334
|
+
subscriptionWakeGithub,
|
|
250
335
|
subscriptionWakeHeadline
|
|
251
336
|
};
|
|
252
337
|
//# sourceMappingURL=subscription-wake-event.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"subscription-wake-event.js","sources":["../../../../agent-runtime/src/subscription-wake-event.ts"],"sourcesContent":["/**\n * The asset-subscription delivery that woke a conversation, read from the user\n * turn the delivery started.\n *\n * When an asset subscription fires, agora starts a turn on its conversation\n * with the delivery text the model reads (`render_delivery`), and stamps the\n * facts a person needs beside it under `athena_subscription_event` in the\n * message metadata — server-authoritative like `author`, never sent to the\n * model. The shipped converter exposes that metadata as the thread message's\n * `metadata.custom`, so a surface renders the turn as a compact marker\n * (\"Brendon Geils set B4 to Target\") instead of a user bubble quoting the\n * delivery text. Olympus draws the same facts as its event card with citation\n * chips; this is the text every other surface shows. The envelope is for\n * RENDERING only: a surface that ignores it degrades to a plain bubble, never\n * to a lost instruction.\n */\n\n/** `metadata.custom` key agora stamps on a subscription's delivery turn. */\nexport const SUBSCRIPTION_EVENT_KEY = 'athena_subscription_event';\n\n/** A changed cell or range, and its value now when agora read it. */\nexport interface SubscriptionWakeChange {\n /** Where: \"B4\", \"Sheet1!B2:B11\", \"page 5\"; `null` for a place the marker has no words for. */\n place: string | null;\n /** The value now; `null` when agora read none (a busy run's tray, a failed or capped read). */\n value: string | null;\n}\n\nexport interface SubscriptionWakeEvent {\n subscriptionId: string;\n /** `event`: something happened on an asset. `schedule`: a check-in the person set. */\n kind: 'event' | 'schedule';\n assetTitle: string | null;\n eventTypes: string[];\n eventCount: number | null;\n /** Who did it, in words: their name, \"You\", \"An agent\", \"Athena\", \"Someone\", with others counted. */\n actor: string;\n changes: SubscriptionWakeChange[];\n /** Changed places past the ones `changes` lists (agora caps the list): counted, never named. */\n moreChanges: number;\n /** The ranges the subscription names (\"B2:B11\"); empty for the whole asset. */\n subscribed: string[];\n /** Where a comment event's thread sits, when its anchor names a place. */\n commentPlace: string | null;\n /** Whether a comment-resolved event resolved (true) or reopened (false); `null` when unsaid. */\n commentResolved: boolean | null;\n /** When the person set a scheduled check-in. */\n scheduleSetAt: string | null;\n /** A scheduled check-in's own words. */\n note: string | null;\n runsLeft: number | null;\n deliveredAt: string | null;\n}\n\n/** Changed places and subscribed ranges named in one line; the rest are counted. */\nconst MAX_SHOWN_PLACES = 3;\n/** A changed value longer than this is cut in the marker. */\nconst MAX_VALUE_CHARS = 40;\n\nconst CONTENT_CHANGED = 'asset.content.changed';\nconst COMMENT_CREATED = 'asset.comment.created';\nconst COMMENT_REPLIED = 'asset.comment.replied';\nconst COMMENT_RESOLVED = 'asset.comment.resolved';\nconst PRESENCE_JOINED = 'asset.presence.joined';\nconst SLACK_PREFIX = 'user_slack.';\nconst COMMENT_EVENTS: ReadonlySet<string> = new Set([\n COMMENT_CREATED,\n COMMENT_REPLIED,\n COMMENT_RESOLVED,\n]);\n\nconst SLACK_WORDS: Readonly<Record<string, string>> = {\n 'user_slack.dm.received': 'sent you a Slack direct message',\n 'user_slack.mention.received': 'mentioned you in Slack',\n 'user_slack.thread_reply.received': 'replied in your Slack thread',\n 'user_slack.channel_message.posted': 'posted in a Slack channel',\n 'user_slack.reaction.added': 'reacted to a Slack message',\n 'user_slack.message.saved': 'saved a Slack message',\n 'user_slack.pin.added': 'pinned a Slack message',\n};\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/** A string with something in it, else `null`. */\nfunction stringOf(value: unknown): string | null {\n return typeof value === 'string' && value.trim().length > 0 ? value : null;\n}\n\n/** A finite number, else `null`. */\nfunction numberOf(value: unknown): number | null {\n return typeof value === 'number' && Number.isFinite(value) ? value : null;\n}\n\n/** A sheet name as an A1 qualifier: quoted when it is not a bare word. */\nfunction sheetQualifier(sheet: string): string {\n return /^[A-Za-z_][A-Za-z0-9_.]*$/.test(sheet) ? sheet : `'${sheet.replaceAll(\"'\", \"''\")}'`;\n}\n\n/**\n * Where a reference-standard anchor in agora's wire form points, in words:\n * a range or cell (with its sheet when the anchor names one), a page, a\n * slide; `null` for any other place.\n */\nexport function subscriptionPlaceWords(anchor: unknown): string | null {\n if (!isRecord(anchor)) return null;\n const sheet = stringOf(anchor.sheet) ?? stringOf(anchor.sheet_name);\n const range = stringOf(anchor.range) ?? stringOf(anchor.cell);\n if (range) return sheet ? `${sheetQualifier(sheet)}!${range}` : range;\n const page = numberOf(anchor.page);\n if (page !== null) return `page ${page}`;\n const slide = numberOf(anchor.slide_index);\n if (slide !== null) return `slide ${slide}`;\n return null;\n}\n\nfunction actorWords(envelope: Record<string, unknown>): string {\n const actor = isRecord(envelope.actor) ? envelope.actor : {};\n const name = stringOf(actor.name);\n let who: string;\n switch (actor.kind) {\n case 'self':\n who = 'You';\n break;\n case 'agent':\n who = name ?? 'An agent';\n break;\n case 'system':\n who = name ?? 'Athena';\n break;\n default:\n who = name ?? 'Someone';\n }\n const more = numberOf(envelope.more_actors) ?? 0;\n if (more <= 0) return who;\n return `${who} and ${more} ${more === 1 ? 'other' : 'others'}`;\n}\n\nfunction readChanges(value: unknown): SubscriptionWakeChange[] {\n if (!Array.isArray(value)) return [];\n return value.flatMap((entry) => {\n if (!isRecord(entry)) return [];\n const raw = entry.value;\n const scalar =\n typeof raw === 'string' || typeof raw === 'number' || typeof raw === 'boolean'\n ? String(raw)\n : null;\n return [{ place: subscriptionPlaceWords(entry.anchor), value: scalar }];\n });\n}\n\n/**\n * The delivery behind a thread message's `metadata.custom`, or `undefined`\n * when the message is not a subscription's delivery (or carries no\n * subscription id). Every other field is read leniently: a field of the wrong\n * shape reads as absent.\n */\nexport function readSubscriptionWakeEvent(\n custom: Record<string, unknown> | null | undefined\n): SubscriptionWakeEvent | undefined {\n const envelope = custom?.[SUBSCRIPTION_EVENT_KEY];\n if (!isRecord(envelope)) return undefined;\n const subscriptionId = stringOf(envelope.subscription_id);\n if (subscriptionId === null) return undefined;\n const comment = isRecord(envelope.comment) ? envelope.comment : null;\n const schedule = isRecord(envelope.schedule) ? envelope.schedule : null;\n const eventTypes = Array.isArray(envelope.event_types)\n ? envelope.event_types.filter((type): type is string => typeof type === 'string')\n : [];\n const subscribed = Array.isArray(envelope.subscribed)\n ? envelope.subscribed.flatMap((anchor) => {\n const words = subscriptionPlaceWords(anchor);\n return words ? [words] : [];\n })\n : [];\n return {\n subscriptionId,\n kind: envelope.kind === 'schedule' ? 'schedule' : 'event',\n assetTitle: stringOf(envelope.asset_title),\n eventTypes,\n eventCount: numberOf(envelope.event_count),\n actor: actorWords(envelope),\n changes: readChanges(envelope.changes),\n moreChanges: Math.max(0, numberOf(envelope.more_changes) ?? 0),\n subscribed,\n commentPlace: comment ? subscriptionPlaceWords(comment.anchor) : null,\n commentResolved: typeof comment?.resolved === 'boolean' ? comment.resolved : null,\n scheduleSetAt: schedule ? stringOf(schedule.set_at) : null,\n note: stringOf(envelope.note),\n runsLeft: numberOf(envelope.runs_left),\n deliveredAt: stringOf(envelope.delivered_at),\n };\n}\n\n/**\n * Whether a thread message is a subscription's delivery. Returns a primitive,\n * so it is safe as a `useAuiState` selector.\n */\nexport function isSubscriptionWakeMessage(\n custom: Record<string, unknown> | null | undefined\n): boolean {\n return readSubscriptionWakeEvent(custom) !== undefined;\n}\n\n/**\n * A value as the marker shows it: whitespace folded, cut at\n * {@link MAX_VALUE_CHARS}. A value of only whitespace is not empty (an empty\n * one is \"cleared\"), so it reads as a quoted space rather than as nothing.\n */\nfunction valueWords(value: string): string {\n const folded = value.replace(/\\s+/g, ' ').trim();\n if (folded === '') return '“ ”';\n return folded.length > MAX_VALUE_CHARS\n ? `${folded.slice(0, MAX_VALUE_CHARS - 1).trimEnd()}…`\n : folded;\n}\n\n/** \"a, b and c\" — items joined the way a sentence lists them, `last` before the last. */\nfunction joinList({ items, last }: { items: string[]; last: string }): string {\n if (items.length <= 1) return items.join('');\n return `${items.slice(0, -1).join(', ')}${last}${items.at(-1)}`;\n}\n\n/** One clause of the marker's line: its leading words, then the places it lists. */\ninterface Clause {\n lead: string;\n items: string[];\n}\n\n/**\n * The clauses after who did it, as one predicate: \"set B4 to Target and\n * commented on B7\". The clauses are listed \"a, b and c\". A lone clause lists\n * its own items the same way (\"changed B4 and B7\"); with several, each lists\n * its items with commas only, so the clauses' \"and\" is the only one.\n */\nfunction joinClauses(clauses: Clause[]): string {\n const itemLast = clauses.length === 1 ? ' and ' : ', ';\n return joinList({\n items: clauses.map(({ lead, items }) => `${lead}${joinList({ items, last: itemLast })}`),\n last: ' and ',\n });\n}\n\n/**\n * The changed places as clauses: \"set B4 to Target\", \"cleared B9\", \"changed\n * B7\" (no value read). At most three places are named; the others — and the\n * ones agora counted past what it sent — are one more clause, \"2 more\".\n */\nfunction changeClauses({\n changes,\n moreChanges,\n}: {\n changes: SubscriptionWakeChange[];\n moreChanges: number;\n}): Clause[] {\n const located = changes.filter(\n (change): change is { place: string; value: string | null } => change.place !== null\n );\n const shown = located.slice(0, MAX_SHOWN_PLACES);\n if (shown.length === 0) return [];\n const hidden = located.length - shown.length + moreChanges;\n const set = shown.filter((change) => change.value !== null && change.value !== '');\n const cleared = shown.filter((change) => change.value === '');\n const changed = shown.filter((change) => change.value === null);\n const clauses: Clause[] = [];\n if (set.length > 0) {\n clauses.push({\n lead: 'set ',\n items: set.map((change) => `${change.place} to ${valueWords(change.value ?? '')}`),\n });\n }\n if (cleared.length > 0) {\n clauses.push({ lead: 'cleared ', items: cleared.map((change) => change.place) });\n }\n if (changed.length > 0) {\n clauses.push({ lead: 'changed ', items: changed.map((change) => change.place) });\n }\n if (hidden > 0) clauses.push({ lead: `${hidden} more`, items: [] });\n return clauses;\n}\n\nfunction commentLead({ type, resolved }: { type: string; resolved: boolean | null }): string {\n if (type === COMMENT_CREATED) return 'commented on ';\n if (type === COMMENT_REPLIED) return 'replied to a comment on ';\n const verb = resolved === true ? 'resolved' : resolved === false ? 'reopened' : 'updated';\n return `${verb} a comment on `;\n}\n\n/** An event type without words of its own, in words: `asset.version.created` → \"Version created\". */\nfunction eventLabel(type: string): string {\n const words = type\n .replace(/^asset\\./, '')\n .replace(/[.\\s_-]+/g, ' ')\n .trim()\n .toLowerCase();\n return words ? `${words.charAt(0).toUpperCase()}${words.slice(1)}` : '—';\n}\n\n/**\n * One clause per kind of event the delivery carries, in the order agora lists\n * them (first seen first), so a batch that both changed cells and brought a\n * comment says both. A content change is its changed places, or \"edited\n * <asset>\" when none were located; changes count as one even when the types\n * leave the content change out. The envelope's comment is the batch's first\n * comment event's, so only the first comment clause names its place.\n */\nfunction eventClauses(event: SubscriptionWakeEvent): Clause[] {\n const asset = event.assetTitle ?? 'the asset';\n const changes = changeClauses({ changes: event.changes, moreChanges: event.moreChanges });\n const listed = event.eventTypes;\n const types = [\n ...new Set(\n listed.length === 0 || (changes.length > 0 && !listed.includes(CONTENT_CHANGED))\n ? [CONTENT_CHANGED, ...listed]\n : listed\n ),\n ];\n const firstComment = types.find((type) => COMMENT_EVENTS.has(type));\n const clauses = types.flatMap((type): Clause[] => {\n if (type === CONTENT_CHANGED) {\n return changes.length > 0 ? changes : [{ lead: 'edited ', items: [asset] }];\n }\n if (COMMENT_EVENTS.has(type)) {\n const where = type === firstComment && event.commentPlace ? event.commentPlace : asset;\n return [{ lead: commentLead({ type, resolved: event.commentResolved }), items: [where] }];\n }\n if (type === PRESENCE_JOINED) return [{ lead: 'opened ', items: [asset] }];\n if (type.startsWith(SLACK_PREFIX)) {\n return [{ lead: SLACK_WORDS[type] ?? 'had Slack activity', items: [] }];\n }\n return [{ lead: `triggered “${eventLabel(type)}” on `, items: [asset] }];\n });\n // Two Slack events of kinds without words of their own say the same thing once.\n const said = new Set<string>();\n return clauses.filter((clause) => {\n if (clause.items.length > 0) return true;\n if (said.has(clause.lead)) return false;\n said.add(clause.lead);\n return true;\n });\n}\n\n/** The day a check-in was set, as a person says it: \"today\", \"yesterday\", \"on Oct 8\". */\nfunction setOnWords({ setAt, now }: { setAt: string; now: Date }): string | null {\n const date = new Date(setAt);\n if (Number.isNaN(date.getTime())) return null;\n const midnight = (day: Date) => new Date(day.getFullYear(), day.getMonth(), day.getDate()).getTime();\n const days = Math.round((midnight(now) - midnight(date)) / (24 * 60 * 60 * 1000));\n if (days === 0) return 'today';\n if (days === 1) return 'yesterday';\n const sameYear = date.getFullYear() === now.getFullYear();\n return `on ${date.toLocaleDateString('en-US', {\n month: 'short',\n day: 'numeric',\n ...(sameYear ? {} : { year: 'numeric' as const }),\n })}`;\n}\n\n/**\n * The marker's line: who did what, where — \"Brendon Geils set B4 to Target\",\n * \"Brendon Geils changed B4\" when agora read no value, \"Ana Lopez commented\n * on B7\", \"Brendon Geils set B4 to Target and commented on B7\" for a batch\n * that carried both — or, for a schedule, \"Scheduled check-in you set on\n * Oct 8\" (`now` decides \"today\" and \"yesterday\").\n */\nexport function subscriptionWakeHeadline({\n event,\n now = new Date(),\n}: {\n event: SubscriptionWakeEvent;\n now?: Date;\n}): string {\n if (event.kind === 'schedule') {\n const when = event.scheduleSetAt ? setOnWords({ setAt: event.scheduleSetAt, now }) : null;\n return when ? `Scheduled check-in you set ${when}` : 'Scheduled check-in';\n }\n return `${event.actor} ${joinClauses(eventClauses(event))}`;\n}\n\n/** \"99 runs left\", \"1 run left\", \"No runs left\". */\nfunction runsLeftWords(runsLeft: number): string {\n if (runsLeft <= 0) return 'No runs left';\n return `${runsLeft} ${runsLeft === 1 ? 'run' : 'runs'} left`;\n}\n\n/**\n * The line under it: \"Series A Investors · subscribed to B2:B11 · 99 runs\n * left · 3 events\" for an asset event; a check-in's note for a schedule.\n * `null` when there is nothing to add.\n */\nexport function subscriptionWakeDetail(event: SubscriptionWakeEvent): string | null {\n if (event.kind === 'schedule') {\n const parts = [\n event.note,\n event.runsLeft !== null && event.runsLeft > 0 ? runsLeftWords(event.runsLeft) : null,\n ].filter((part): part is string => part !== null);\n return parts.length > 0 ? parts.join(' · ') : null;\n }\n const shown = event.subscribed.slice(0, MAX_SHOWN_PLACES);\n const hidden = event.subscribed.length - shown.length;\n const subscribed =\n shown.length > 0\n ? `subscribed to ${shown.join(', ')}${hidden > 0 ? ` and ${hidden} more` : ''}`\n : null;\n const parts = [\n event.assetTitle,\n subscribed,\n event.runsLeft !== null ? runsLeftWords(event.runsLeft) : null,\n event.eventCount !== null && event.eventCount > 1 ? `${event.eventCount} events` : null,\n ].filter((part): part is string => part !== null);\n return parts.length > 0 ? parts.join(' · ') : null;\n}\n"],"names":["parts"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"subscription-wake-event.js","sources":["../../../../agent-runtime/src/subscription-wake-event.ts"],"sourcesContent":["/**\n * The asset-subscription delivery that woke a conversation, read from the user\n * turn the delivery started.\n *\n * When an asset subscription fires, agora starts a turn on its conversation\n * with the delivery text the model reads (`render_delivery`), and stamps the\n * facts a person needs beside it under `athena_subscription_event` in the\n * message metadata — server-authoritative like `author`, never sent to the\n * model. The shipped converter exposes that metadata as the thread message's\n * `metadata.custom`, so a surface renders the turn as a compact marker\n * (\"Brendon Geils set B4 to Target\") instead of a user bubble quoting the\n * delivery text. Olympus draws the same facts as its event card with citation\n * chips; this is the text every other surface shows. The envelope is for\n * RENDERING only: a surface that ignores it degrades to a plain bubble, never\n * to a lost instruction.\n */\n\n/** `metadata.custom` key agora stamps on a subscription's delivery turn. */\nexport const SUBSCRIPTION_EVENT_KEY = 'athena_subscription_event';\n\n/** A changed cell or range, and its value now when agora read it. */\nexport interface SubscriptionWakeChange {\n /** Where: \"B4\", \"Sheet1!B2:B11\", \"page 5\"; `null` for a place the marker has no words for. */\n place: string | null;\n /** The value now; `null` when agora read none (a busy run's tray, a failed or capped read). */\n value: string | null;\n}\n\nexport interface SubscriptionWakeEvent {\n subscriptionId: string;\n /** `event`: something happened on an asset. `schedule`: a check-in the person set. */\n kind: 'event' | 'schedule';\n assetTitle: string | null;\n eventTypes: string[];\n eventCount: number | null;\n /** Who did it, in words: their name, \"You\", \"An agent\", \"Athena\", \"Someone\", with others counted. */\n actor: string;\n changes: SubscriptionWakeChange[];\n /** Changed places past the ones `changes` lists (agora caps the list): counted, never named. */\n moreChanges: number;\n /** The ranges the subscription names (\"B2:B11\"); empty for the whole asset. */\n subscribed: string[];\n /** Where a comment event's thread sits, when its anchor names a place. */\n commentPlace: string | null;\n /** Whether a comment-resolved event resolved (true) or reopened (false); `null` when unsaid. */\n commentResolved: boolean | null;\n /** When the person set a scheduled check-in. */\n scheduleSetAt: string | null;\n /** A scheduled check-in's own words. */\n note: string | null;\n runsLeft: number | null;\n deliveredAt: string | null;\n /** A GitHub event's subject (the envelope's `connector`); `null` for any other event. */\n github: SubscriptionWakeGithub | null;\n}\n\n/** A GitHub event's subject as agora's chip carries it: ids and a state word, never text. */\nexport interface SubscriptionWakeGithub {\n /** `owner/repo`. */\n repo: string | null;\n /** The pull request or issue; `null` when the event names none (a check on no pull request). */\n number: number | null;\n /** `merged` or `closed`, a review's state, or a check's conclusion, lowercased. */\n state: string | null;\n}\n\n/** Changed places and subscribed ranges named in one line; the rest are counted. */\nconst MAX_SHOWN_PLACES = 3;\n/** A changed value longer than this is cut in the marker. */\nconst MAX_VALUE_CHARS = 40;\n\nconst CONTENT_CHANGED = 'asset.content.changed';\nconst COMMENT_CREATED = 'asset.comment.created';\nconst COMMENT_REPLIED = 'asset.comment.replied';\nconst COMMENT_RESOLVED = 'asset.comment.resolved';\nconst PRESENCE_JOINED = 'asset.presence.joined';\nconst SLACK_PREFIX = 'user_slack.';\nconst GITHUB_PREFIX = 'github.';\nconst COMMENT_EVENTS: ReadonlySet<string> = new Set([\n COMMENT_CREATED,\n COMMENT_REPLIED,\n COMMENT_RESOLVED,\n]);\n\nconst SLACK_WORDS: Readonly<Record<string, string>> = {\n 'user_slack.dm.received': 'sent you a Slack direct message',\n 'user_slack.mention.received': 'mentioned you in Slack',\n 'user_slack.thread_reply.received': 'replied in your Slack thread',\n 'user_slack.channel_message.posted': 'posted in a Slack channel',\n 'user_slack.reaction.added': 'reacted to a Slack message',\n 'user_slack.message.saved': 'saved a Slack message',\n 'user_slack.pin.added': 'pinned a Slack message',\n};\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/** A string with something in it, else `null`. */\nfunction stringOf(value: unknown): string | null {\n return typeof value === 'string' && value.trim().length > 0 ? value : null;\n}\n\n/** A finite number, else `null`. */\nfunction numberOf(value: unknown): number | null {\n return typeof value === 'number' && Number.isFinite(value) ? value : null;\n}\n\n/** A sheet name as an A1 qualifier: quoted when it is not a bare word. */\nfunction sheetQualifier(sheet: string): string {\n return /^[A-Za-z_][A-Za-z0-9_.]*$/.test(sheet) ? sheet : `'${sheet.replaceAll(\"'\", \"''\")}'`;\n}\n\n/**\n * Where a reference-standard anchor in agora's wire form points, in words:\n * a range or cell (with its sheet when the anchor names one), a page, a\n * slide; `null` for any other place.\n */\nexport function subscriptionPlaceWords(anchor: unknown): string | null {\n if (!isRecord(anchor)) return null;\n const sheet = stringOf(anchor.sheet) ?? stringOf(anchor.sheet_name);\n const range = stringOf(anchor.range) ?? stringOf(anchor.cell);\n if (range) return sheet ? `${sheetQualifier(sheet)}!${range}` : range;\n const page = numberOf(anchor.page);\n if (page !== null) return `page ${page}`;\n const slide = numberOf(anchor.slide_index);\n if (slide !== null) return `slide ${slide}`;\n return null;\n}\n\nfunction actorWords(envelope: Record<string, unknown>): string {\n const actor = isRecord(envelope.actor) ? envelope.actor : {};\n const name = stringOf(actor.name);\n let who: string;\n switch (actor.kind) {\n case 'self':\n who = 'You';\n break;\n case 'agent':\n who = name ?? 'An agent';\n break;\n case 'system':\n who = name ?? 'Athena';\n break;\n default:\n who = name ?? 'Someone';\n }\n const more = numberOf(envelope.more_actors) ?? 0;\n if (more <= 0) return who;\n return `${who} and ${more} ${more === 1 ? 'other' : 'others'}`;\n}\n\nfunction readChanges(value: unknown): SubscriptionWakeChange[] {\n if (!Array.isArray(value)) return [];\n return value.flatMap((entry) => {\n if (!isRecord(entry)) return [];\n const raw = entry.value;\n const scalar =\n typeof raw === 'string' || typeof raw === 'number' || typeof raw === 'boolean'\n ? String(raw)\n : null;\n return [{ place: subscriptionPlaceWords(entry.anchor), value: scalar }];\n });\n}\n\n/**\n * The GitHub subject of an envelope's `connector`, or `null` when the\n * connector is absent or another provider's (Slack).\n */\nexport function subscriptionWakeGithub(connector: unknown): SubscriptionWakeGithub | null {\n if (!isRecord(connector) || connector.provider !== 'github') return null;\n const number = numberOf(connector.number);\n return {\n repo: stringOf(connector.repo),\n number: number !== null && number > 0 ? number : null,\n state: stringOf(connector.state)?.toLowerCase() ?? null,\n };\n}\n\n/**\n * The delivery behind a thread message's `metadata.custom`, or `undefined`\n * when the message is not a subscription's delivery (or carries no\n * subscription id). Every other field is read leniently: a field of the wrong\n * shape reads as absent.\n */\nexport function readSubscriptionWakeEvent(\n custom: Record<string, unknown> | null | undefined\n): SubscriptionWakeEvent | undefined {\n const envelope = custom?.[SUBSCRIPTION_EVENT_KEY];\n if (!isRecord(envelope)) return undefined;\n const subscriptionId = stringOf(envelope.subscription_id);\n if (subscriptionId === null) return undefined;\n const comment = isRecord(envelope.comment) ? envelope.comment : null;\n const schedule = isRecord(envelope.schedule) ? envelope.schedule : null;\n const eventTypes = Array.isArray(envelope.event_types)\n ? envelope.event_types.filter((type): type is string => typeof type === 'string')\n : [];\n const subscribed = Array.isArray(envelope.subscribed)\n ? envelope.subscribed.flatMap((anchor) => {\n const words = subscriptionPlaceWords(anchor);\n return words ? [words] : [];\n })\n : [];\n return {\n subscriptionId,\n kind: envelope.kind === 'schedule' ? 'schedule' : 'event',\n assetTitle: stringOf(envelope.asset_title),\n eventTypes,\n eventCount: numberOf(envelope.event_count),\n actor: actorWords(envelope),\n changes: readChanges(envelope.changes),\n moreChanges: Math.max(0, numberOf(envelope.more_changes) ?? 0),\n subscribed,\n commentPlace: comment ? subscriptionPlaceWords(comment.anchor) : null,\n commentResolved: typeof comment?.resolved === 'boolean' ? comment.resolved : null,\n scheduleSetAt: schedule ? stringOf(schedule.set_at) : null,\n note: stringOf(envelope.note),\n runsLeft: numberOf(envelope.runs_left),\n deliveredAt: stringOf(envelope.delivered_at),\n github: subscriptionWakeGithub(envelope.connector),\n };\n}\n\n/**\n * Whether a thread message is a subscription's delivery. Returns a primitive,\n * so it is safe as a `useAuiState` selector.\n */\nexport function isSubscriptionWakeMessage(\n custom: Record<string, unknown> | null | undefined\n): boolean {\n return readSubscriptionWakeEvent(custom) !== undefined;\n}\n\n/**\n * A value as the marker shows it: whitespace folded, cut at\n * {@link MAX_VALUE_CHARS}. A value of only whitespace is not empty (an empty\n * one is \"cleared\"), so it reads as a quoted space rather than as nothing.\n */\nfunction valueWords(value: string): string {\n const folded = value.replace(/\\s+/g, ' ').trim();\n if (folded === '') return '“ ”';\n return folded.length > MAX_VALUE_CHARS\n ? `${folded.slice(0, MAX_VALUE_CHARS - 1).trimEnd()}…`\n : folded;\n}\n\n/** \"a, b and c\" — items joined the way a sentence lists them, `last` before the last. */\nfunction joinList({ items, last }: { items: string[]; last: string }): string {\n if (items.length <= 1) return items.join('');\n return `${items.slice(0, -1).join(', ')}${last}${items.at(-1)}`;\n}\n\n/** One clause of the marker's line: its leading words, then the places it lists. */\ninterface Clause {\n lead: string;\n items: string[];\n}\n\n/**\n * The clauses after who did it, as one predicate: \"set B4 to Target and\n * commented on B7\". The clauses are listed \"a, b and c\". A lone clause lists\n * its own items the same way (\"changed B4 and B7\"); with several, each lists\n * its items with commas only, so the clauses' \"and\" is the only one.\n */\nfunction joinClauses(clauses: Clause[]): string {\n const itemLast = clauses.length === 1 ? ' and ' : ', ';\n return joinList({\n items: clauses.map(({ lead, items }) => `${lead}${joinList({ items, last: itemLast })}`),\n last: ' and ',\n });\n}\n\n/**\n * The changed places as clauses: \"set B4 to Target\", \"cleared B9\", \"changed\n * B7\" (no value read). At most three places are named; the others — and the\n * ones agora counted past what it sent — are one more clause, \"2 more\".\n */\nfunction changeClauses({\n changes,\n moreChanges,\n}: {\n changes: SubscriptionWakeChange[];\n moreChanges: number;\n}): Clause[] {\n const located = changes.filter(\n (change): change is { place: string; value: string | null } => change.place !== null\n );\n const shown = located.slice(0, MAX_SHOWN_PLACES);\n if (shown.length === 0) return [];\n const hidden = located.length - shown.length + moreChanges;\n const set = shown.filter((change) => change.value !== null && change.value !== '');\n const cleared = shown.filter((change) => change.value === '');\n const changed = shown.filter((change) => change.value === null);\n const clauses: Clause[] = [];\n if (set.length > 0) {\n clauses.push({\n lead: 'set ',\n items: set.map((change) => `${change.place} to ${valueWords(change.value ?? '')}`),\n });\n }\n if (cleared.length > 0) {\n clauses.push({ lead: 'cleared ', items: cleared.map((change) => change.place) });\n }\n if (changed.length > 0) {\n clauses.push({ lead: 'changed ', items: changed.map((change) => change.place) });\n }\n if (hidden > 0) clauses.push({ lead: `${hidden} more`, items: [] });\n return clauses;\n}\n\nfunction commentLead({ type, resolved }: { type: string; resolved: boolean | null }): string {\n if (type === COMMENT_CREATED) return 'commented on ';\n if (type === COMMENT_REPLIED) return 'replied to a comment on ';\n const verb = resolved === true ? 'resolved' : resolved === false ? 'reopened' : 'updated';\n return `${verb} a comment on `;\n}\n\n/** An event type without words of its own, in words: `asset.version.created` → \"Version created\". */\nfunction eventLabel(type: string): string {\n const words = type\n .replace(/^asset\\./, '')\n .replace(/[.\\s_-]+/g, ' ')\n .trim()\n .toLowerCase();\n return words ? `${words.charAt(0).toUpperCase()}${words.slice(1)}` : '—';\n}\n\n/**\n * One clause per kind of event the delivery carries, in the order agora lists\n * them (first seen first), so a batch that both changed cells and brought a\n * comment says both. A content change is its changed places, or \"edited\n * <asset>\" when none were located; changes count as one even when the types\n * leave the content change out. The envelope's comment is the batch's first\n * comment event's, so only the first comment clause names its place.\n */\nfunction eventClauses(event: SubscriptionWakeEvent): Clause[] {\n const asset = event.assetTitle ?? 'the asset';\n const changes = changeClauses({ changes: event.changes, moreChanges: event.moreChanges });\n const listed = event.eventTypes;\n const types = [\n ...new Set(\n listed.length === 0 || (changes.length > 0 && !listed.includes(CONTENT_CHANGED))\n ? [CONTENT_CHANGED, ...listed]\n : listed\n ),\n ];\n const firstComment = types.find((type) => COMMENT_EVENTS.has(type));\n const clauses = types.flatMap((type): Clause[] => {\n if (type === CONTENT_CHANGED) {\n return changes.length > 0 ? changes : [{ lead: 'edited ', items: [asset] }];\n }\n if (COMMENT_EVENTS.has(type)) {\n const where = type === firstComment && event.commentPlace ? event.commentPlace : asset;\n return [{ lead: commentLead({ type, resolved: event.commentResolved }), items: [where] }];\n }\n if (type === PRESENCE_JOINED) return [{ lead: 'opened ', items: [asset] }];\n if (type.startsWith(SLACK_PREFIX)) {\n return [{ lead: SLACK_WORDS[type] ?? 'had Slack activity', items: [] }];\n }\n return [{ lead: `triggered “${eventLabel(type)}” on `, items: [asset] }];\n });\n // Two Slack events of kinds without words of their own say the same thing once.\n const said = new Set<string>();\n return clauses.filter((clause) => {\n if (clause.items.length > 0) return true;\n if (said.has(clause.lead)) return false;\n said.add(clause.lead);\n return true;\n });\n}\n\n/** What a check's conclusion did, said of one check and of a suite of them. */\nconst CHECK_OUTCOMES: Readonly<Record<string, { one: string; many: string }>> = {\n success: { one: 'passed', many: 'passed' },\n failure: { one: 'failed', many: 'failed' },\n cancelled: { one: 'was cancelled', many: 'were cancelled' },\n timed_out: { one: 'timed out', many: 'timed out' },\n skipped: { one: 'was skipped', many: 'were skipped' },\n action_required: { one: 'needs action', many: 'need action' },\n stale: { one: 'went stale', many: 'went stale' },\n};\n\n/**\n * A GitHub event in words, from the chip's subject: \"A check failed on\n * #47506\", \"Checks passed on #47506\", \"#123 was merged\", \"Changes were\n * requested on #123\". The pull request or issue is named by its number; the\n * repository stands in when the event names none. No one is named: GitHub's\n * actor is not an Athena person, and the chip carries no text. `null` for an\n * event type that is not GitHub's.\n */\nfunction githubSubscriptionWords({\n eventType,\n github,\n}: {\n eventType: string;\n github: SubscriptionWakeGithub | null;\n}): string | null {\n if (!eventType.startsWith(GITHUB_PREFIX)) return null;\n const target = github?.number != null ? `#${github.number}` : (github?.repo ?? 'GitHub');\n const state = github?.state ?? null;\n switch (eventType) {\n case 'github.check_run.completed':\n case 'github.check_suite.completed': {\n const suite = eventType === 'github.check_suite.completed';\n const subject = suite ? 'Checks' : 'A check';\n const outcome = state ? CHECK_OUTCOMES[state] : undefined;\n if (!outcome) return `${subject} finished on ${target}`;\n return `${subject} ${suite ? outcome.many : outcome.one} on ${target}`;\n }\n case 'github.pr.opened':\n return `${target} was opened`;\n case 'github.pr.reopened':\n return `${target} was reopened`;\n case 'github.pr.ready_for_review':\n return `${target} is ready for review`;\n case 'github.pr.updated':\n return `${target} was updated`;\n case 'github.pr.merged':\n return `${target} was merged`;\n case 'github.pr.closed':\n return `${target} was closed without merging`;\n case 'github.pr.review.submitted':\n if (state === 'approved') return `${target} was approved`;\n if (state === 'changes_requested') return `Changes were requested on ${target}`;\n if (state === 'dismissed') return `A review on ${target} was dismissed`;\n return `${target} was reviewed`;\n case 'github.pr.review_comment.created':\n return `New review comment on ${target}`;\n case 'github.issue_comment.created':\n return `New comment on ${target}`;\n case 'github.issue.created':\n return `Issue ${target} was opened`;\n case 'github.issue.closed':\n return `Issue ${target} was closed`;\n case 'github.issue.labeled':\n return `Issue ${target} was labeled`;\n case 'github.issue.assigned':\n return `Issue ${target} was assigned`;\n case 'github.push':\n return `New commits were pushed to ${github?.repo ?? target}`;\n case 'github.installation.changed':\n return `The GitHub app's access to ${github?.repo ?? 'a repository'} changed`;\n default:\n // A GitHub event without words of its own is named by its type, as any other event is.\n return `${eventLabel(eventType.slice(GITHUB_PREFIX.length))} on ${target}`;\n }\n}\n\n/**\n * The words for a delivery whose events are all GitHub's, from its first\n * event (the one the chip describes; the detail line counts the rest), or\n * `null` for any other delivery. Olympus's event card says the same.\n */\nexport function githubSubscriptionHeadline({\n eventTypes,\n github,\n}: {\n eventTypes: readonly string[];\n github: SubscriptionWakeGithub | null;\n}): string | null {\n if (eventTypes.length === 0 || !eventTypes.every((type) => type.startsWith(GITHUB_PREFIX))) {\n return null;\n }\n return githubSubscriptionWords({ eventType: eventTypes[0], github });\n}\n\n/** The day a check-in was set, as a person says it: \"today\", \"yesterday\", \"on Oct 8\". */\nfunction setOnWords({ setAt, now }: { setAt: string; now: Date }): string | null {\n const date = new Date(setAt);\n if (Number.isNaN(date.getTime())) return null;\n const midnight = (day: Date) => new Date(day.getFullYear(), day.getMonth(), day.getDate()).getTime();\n const days = Math.round((midnight(now) - midnight(date)) / (24 * 60 * 60 * 1000));\n if (days === 0) return 'today';\n if (days === 1) return 'yesterday';\n const sameYear = date.getFullYear() === now.getFullYear();\n return `on ${date.toLocaleDateString('en-US', {\n month: 'short',\n day: 'numeric',\n ...(sameYear ? {} : { year: 'numeric' as const }),\n })}`;\n}\n\n/**\n * The marker's line: who did what, where — \"Brendon Geils set B4 to Target\",\n * \"Brendon Geils changed B4\" when agora read no value, \"Ana Lopez commented\n * on B7\", \"Brendon Geils set B4 to Target and commented on B7\" for a batch\n * that carried both — for GitHub, what happened to the pull request (\"A\n * check failed on #47506\") — or, for a schedule, \"Scheduled check-in you set\n * on Oct 8\" (`now` decides \"today\" and \"yesterday\").\n */\nexport function subscriptionWakeHeadline({\n event,\n now = new Date(),\n}: {\n event: SubscriptionWakeEvent;\n now?: Date;\n}): string {\n if (event.kind === 'schedule') {\n const when = event.scheduleSetAt ? setOnWords({ setAt: event.scheduleSetAt, now }) : null;\n return when ? `Scheduled check-in you set ${when}` : 'Scheduled check-in';\n }\n return (\n githubSubscriptionHeadline({ eventTypes: event.eventTypes, github: event.github }) ??\n `${event.actor} ${joinClauses(eventClauses(event))}`\n );\n}\n\n/** \"99 runs left\", \"1 run left\", \"No runs left\". */\nfunction runsLeftWords(runsLeft: number): string {\n if (runsLeft <= 0) return 'No runs left';\n return `${runsLeft} ${runsLeft === 1 ? 'run' : 'runs'} left`;\n}\n\n/**\n * The line under it: \"Series A Investors · subscribed to B2:B11 · 99 runs\n * left · 3 events\" for an asset event; a check-in's note for a schedule.\n * `null` when there is nothing to add.\n */\nexport function subscriptionWakeDetail(event: SubscriptionWakeEvent): string | null {\n if (event.kind === 'schedule') {\n const parts = [\n event.note,\n event.runsLeft !== null && event.runsLeft > 0 ? runsLeftWords(event.runsLeft) : null,\n ].filter((part): part is string => part !== null);\n return parts.length > 0 ? parts.join(' · ') : null;\n }\n const shown = event.subscribed.slice(0, MAX_SHOWN_PLACES);\n const hidden = event.subscribed.length - shown.length;\n const subscribed =\n shown.length > 0\n ? `subscribed to ${shown.join(', ')}${hidden > 0 ? ` and ${hidden} more` : ''}`\n : null;\n const parts = [\n event.assetTitle,\n subscribed,\n event.runsLeft !== null ? runsLeftWords(event.runsLeft) : null,\n event.eventCount !== null && event.eventCount > 1 ? `${event.eventCount} events` : null,\n ].filter((part): part is string => part !== null);\n return parts.length > 0 ? parts.join(' · ') : null;\n}\n"],"names":["parts"],"mappings":"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"}
|