@agent-relay/events 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,108 @@
1
+ ![Agent Relay](../../readme-banner.png)
2
+
3
+ <div align="center">
4
+
5
+ # @agent-relay/events
6
+
7
+ Normalized event stream for proactive agents — Layer 2 of the Agent Relay runtime.
8
+
9
+ [![npm](https://img.shields.io/npm/v/@agent-relay/events)](https://www.npmjs.com/package/@agent-relay/events)
10
+ [![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](../../LICENSE)
11
+
12
+ **Website:** [agentrelay.com](https://agentrelay.com) · **Docs:** [agentrelay.com/docs](https://agentrelay.com/docs)
13
+
14
+ </div>
15
+
16
+ ## What it does
17
+
18
+ `@agent-relay/events` turns the three proactive primitives (cron, watch, inbox) into one normalized event stream. Subscribe with a single `onEvent` handler; receive lightweight envelopes that you can lazily `expand()` to full payloads.
19
+
20
+ It is the transport-agnostic core: no workspace concept, no scheduling, no cloud assumptions — pure event stream plus retry policy plus OpenTelemetry tracing. Compose it directly for hosted webhooks, or build a higher-level runtime on top (see [`@agent-relay/agent`](../agent)).
21
+
22
+ ## Install
23
+
24
+ ```bash
25
+ npm install @agent-relay/events
26
+ ```
27
+
28
+ ## Quick start
29
+
30
+ ```ts
31
+ import { events } from '@agent-relay/events';
32
+
33
+ const handle = events({
34
+ workspace: 'support',
35
+ apiKey: process.env.RELAY_API_KEY!,
36
+ onEvent: async (event) => {
37
+ console.log(event.type, event.resource.path);
38
+
39
+ const full = await event.expand('full');
40
+ // ... act on full.data
41
+ },
42
+ });
43
+
44
+ // ... later
45
+ await handle.stop();
46
+ ```
47
+
48
+ ## Event envelope shape
49
+
50
+ Every event arrives as a normalized `AgentEvent`:
51
+
52
+ ```ts
53
+ type AgentEvent = {
54
+ id: string; // stable event id
55
+ workspace: string;
56
+ type: 'cron.tick' | 'startup' | 'relayfile.changed' | 'relaycast.message' | ...;
57
+ occurredAt: string; // ISO timestamp
58
+ attempt: number; // delivery attempt count
59
+ resource: { path: string; kind: string; id: string; provider: string };
60
+ summary: EventSummary; // <1KB, PII-stripped
61
+ expand: (level: 'summary' | 'full' | 'diff' | 'thread') => Promise<Expansion>;
62
+ digest?: string;
63
+ };
64
+ ```
65
+
66
+ The envelope is the **shape your handler receives** — not the full provider payload. Call `event.expand('full')` to materialize the resource lazily.
67
+
68
+ ## Expansion levels
69
+
70
+ | Level | What you get | Cost |
71
+ | --------- | ------------------------------------------------------- | ----------------- |
72
+ | `summary` | Pre-included lightweight summary (already on the event) | free |
73
+ | `full` | Full canonical resource via gateway / relayfile VFS | one read |
74
+ | `diff` | Changed fields vs prior state | one read |
75
+ | `thread` | Paginated comments / replies | one or more reads |
76
+
77
+ ## Retry + NoRetry sentinel
78
+
79
+ ```ts
80
+ import { events, NoRetry } from '@agent-relay/events';
81
+
82
+ events({
83
+ workspace: 'support',
84
+ apiKey: process.env.RELAY_API_KEY!,
85
+ onEvent: async (event) => {
86
+ try {
87
+ // ... your work
88
+ } catch (err) {
89
+ if (isPermanent(err)) throw new NoRetry(err); // skip retry queue
90
+ throw err; // 5-attempt exp backoff
91
+ }
92
+ },
93
+ });
94
+ ```
95
+
96
+ ## OpenTelemetry
97
+
98
+ Every dispatched event is wrapped in a `SpanKind.CONSUMER` span tagged with the event id, type, workspace, and attempt. Configure your exporter (e.g. `OTEL_EXPORTER_OTLP_ENDPOINT`) — `@agent-relay/events` plugs into the global tracer provider via `@opentelemetry/api`.
99
+
100
+ ## Related
101
+
102
+ - [`@agent-relay/agent`](../agent) — Layer 3: workspace + ctx + `agent({...})` API on top of this stream
103
+ - [`@agent-relay/sdk`](../sdk) — broker control + workflow orchestration
104
+ - [Proactive Agent Runtime spec](https://github.com/AgentWorkforce/cloud/blob/main/docs/proactive-runtime/spec.md) — the design this implements
105
+
106
+ ## License
107
+
108
+ Apache-2.0 — see [LICENSE](../../LICENSE).
@@ -0,0 +1,124 @@
1
+ import type { AgentEvent, AgentEventMap, BaseAgentEvent, CronTickEvent, EventResource, EventSummary, EventType, Expansion, ThreadExpansionOptions, RelaycastMessageEvent, RelayfileChangeEvent, StartupReason, TransportErrorEvent } from './types.js';
2
+ /**
3
+ * Serializable event record accepted by envelope constructors and transport code.
4
+ */
5
+ export interface AgentEventRecord<TType extends EventType = EventType, TResource extends Partial<EventResource> | undefined = Partial<EventResource> | undefined> {
6
+ /** Optional stable event identifier. */
7
+ id?: string;
8
+ /** Workspace associated with the event. */
9
+ workspace: string;
10
+ /** Normalized event type. */
11
+ type: TType;
12
+ /** Optional occurrence timestamp override. */
13
+ occurredAt?: string;
14
+ /** Optional delivery attempt override. */
15
+ attempt?: number;
16
+ /** Optional explicit resource metadata. */
17
+ resource?: TResource;
18
+ /** Optional lightweight summary override. */
19
+ summary?: EventSummary;
20
+ /** Optional digest of the current resource state. */
21
+ digest?: string;
22
+ /** Cron schedule identifier or expression for `cron.tick`. */
23
+ schedule?: string;
24
+ /** Scheduled fire time for `cron.tick`. */
25
+ scheduledFor?: string;
26
+ /** Startup reason for `startup`. */
27
+ reason?: StartupReason;
28
+ /** Changed path for `relayfile.changed`. */
29
+ path?: string;
30
+ /** Matched watch glob for `relayfile.changed`. */
31
+ watch?: string;
32
+ /** Normalized action for `relayfile.changed`. */
33
+ action?: 'created' | 'updated' | 'deleted';
34
+ /** Optional relayfile agent id for agent-authored changes. */
35
+ agentId?: string;
36
+ /** Channel for `relaycast.message`. */
37
+ channel?: string;
38
+ /** Message id for `relaycast.message`. */
39
+ messageId?: string;
40
+ /** Thread id for `relaycast.message`. */
41
+ threadId?: string;
42
+ /** Transport error detail for `transport.error`. */
43
+ detail?: string;
44
+ }
45
+ /**
46
+ * Additional options used while constructing a normalized event envelope.
47
+ */
48
+ export interface CreateAgentEventOptions {
49
+ /** Shared expansion cache keyed by `(event.id, level)`. */
50
+ expansionCache?: Map<string, Promise<Expansion>>;
51
+ /** Loader used by `expand("full")`. */
52
+ loadFull?: () => Promise<Expansion<'full'>>;
53
+ /** Loader used by `expand("diff")`. */
54
+ loadDiff?: () => Promise<Expansion<'diff'>>;
55
+ /** Loader used by `expand("thread")`. */
56
+ loadThread?: (options?: ThreadExpansionOptions) => Promise<Expansion<'thread'>>;
57
+ }
58
+ type ResolvedRecordResource<TResource extends Partial<EventResource> | undefined> = EventResource & (TResource extends Partial<EventResource> ? TResource : Record<string, never>);
59
+ type CreatedAgentEvent<TType extends EventType, TResource extends Partial<EventResource> | undefined> = TType extends keyof AgentEventMap ? AgentEventMap[TType] : BaseAgentEvent<TType, ResolvedRecordResource<TResource>>;
60
+ /**
61
+ * Creates a fully typed normalized event envelope.
62
+ */
63
+ export declare function createAgentEvent<TType extends EventType, TResource extends Partial<EventResource> | undefined = Partial<EventResource> | undefined>(record: AgentEventRecord<TType, TResource>, options?: CreateAgentEventOptions): CreatedAgentEvent<TType, TResource>;
64
+ /**
65
+ * Creates a cron tick event using the M1 synthetic resource conventions.
66
+ */
67
+ export declare function createCronTickEvent(input: {
68
+ workspace: string;
69
+ schedule: string;
70
+ scheduledFor?: string;
71
+ id?: string;
72
+ attempt?: number;
73
+ occurredAt?: string;
74
+ digest?: string;
75
+ resourceId?: string;
76
+ summary?: EventSummary;
77
+ }): CronTickEvent;
78
+ /**
79
+ * Creates a startup event envelope.
80
+ */
81
+ export declare function createStartupEvent(input: {
82
+ workspace: string;
83
+ reason?: StartupReason;
84
+ id?: string;
85
+ attempt?: number;
86
+ occurredAt?: string;
87
+ digest?: string;
88
+ summary?: EventSummary;
89
+ }): AgentEvent<'startup'>;
90
+ /**
91
+ * Creates a synthetic transport error event.
92
+ */
93
+ export declare function createTransportErrorEvent(input: {
94
+ workspace: string;
95
+ detail: string;
96
+ id?: string;
97
+ occurredAt?: string;
98
+ }): TransportErrorEvent;
99
+ /**
100
+ * Converts a typed event back into the serializable record form.
101
+ */
102
+ export declare function toAgentEventRecord(event: AgentEvent): AgentEventRecord;
103
+ /**
104
+ * Type guard for `cron.tick`.
105
+ */
106
+ export declare function isCronTickEvent(event: AgentEvent): event is CronTickEvent;
107
+ /**
108
+ * Type guard for `startup`.
109
+ */
110
+ export declare function isStartupEvent(event: AgentEvent): event is AgentEvent<'startup'>;
111
+ /**
112
+ * Type guard for `relayfile.changed`.
113
+ */
114
+ export declare function isRelayfileChangeEvent(event: AgentEvent): event is RelayfileChangeEvent;
115
+ /**
116
+ * Type guard for `relaycast.message`.
117
+ */
118
+ export declare function isRelaycastMessageEvent(event: AgentEvent): event is RelaycastMessageEvent;
119
+ /**
120
+ * Type guard for `transport.error`.
121
+ */
122
+ export declare function isTransportErrorEvent(event: AgentEvent): event is TransportErrorEvent;
123
+ export {};
124
+ //# sourceMappingURL=envelope.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"envelope.d.ts","sourceRoot":"","sources":["../src/envelope.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,447 @@
1
+ import { createExpander } from './expand.js';
2
+ /**
3
+ * Creates a fully typed normalized event envelope.
4
+ */
5
+ export function createAgentEvent(record, options = {}) {
6
+ const id = record.id ?? createId(record.type);
7
+ const occurredAt = record.occurredAt ?? new Date().toISOString();
8
+ const resource = resolveResource(record, id);
9
+ const summary = sanitizeSummary(record.summary ?? defaultSummary(record, resource));
10
+ const base = {
11
+ id,
12
+ workspace: record.workspace,
13
+ type: record.type,
14
+ occurredAt,
15
+ attempt: record.attempt ?? 1,
16
+ resource,
17
+ summary,
18
+ expand: createExpander({
19
+ eventId: id,
20
+ path: resource.path,
21
+ summary,
22
+ cache: options.expansionCache,
23
+ loadFull: options.loadFull,
24
+ loadDiff: options.loadDiff,
25
+ loadThread: options.loadThread,
26
+ }),
27
+ digest: record.digest,
28
+ };
29
+ switch (record.type) {
30
+ case 'startup':
31
+ return {
32
+ ...base,
33
+ reason: record.reason ?? 'manual',
34
+ };
35
+ case 'cron.tick':
36
+ return {
37
+ ...base,
38
+ schedule: record.schedule ?? 'manual',
39
+ scheduledFor: record.scheduledFor ?? occurredAt,
40
+ };
41
+ case 'relaycast.message':
42
+ return {
43
+ ...base,
44
+ channel: record.channel ?? 'unknown',
45
+ messageId: record.messageId ?? resource.id,
46
+ threadId: record.threadId,
47
+ };
48
+ case 'transport.error':
49
+ return {
50
+ ...base,
51
+ detail: record.detail ?? 'transport.error',
52
+ };
53
+ case 'relayfile.changed':
54
+ return {
55
+ ...base,
56
+ path: resource.path,
57
+ watch: record.watch,
58
+ action: record.action,
59
+ agentId: record.agentId,
60
+ current: buildRelayfileCurrent(summary),
61
+ };
62
+ default:
63
+ return base;
64
+ }
65
+ }
66
+ /**
67
+ * Creates a cron tick event using the M1 synthetic resource conventions.
68
+ */
69
+ export function createCronTickEvent(input) {
70
+ const resourceId = input.resourceId ?? sanitizeScheduleId(input.schedule);
71
+ return createAgentEvent({
72
+ workspace: input.workspace,
73
+ type: 'cron.tick',
74
+ id: input.id,
75
+ occurredAt: input.occurredAt,
76
+ attempt: input.attempt,
77
+ digest: input.digest,
78
+ schedule: input.schedule,
79
+ scheduledFor: input.scheduledFor,
80
+ summary: input.summary,
81
+ resource: {
82
+ path: `/_cron/${resourceId}`,
83
+ kind: 'cron.tick',
84
+ id: resourceId,
85
+ provider: 'internal',
86
+ },
87
+ });
88
+ }
89
+ /**
90
+ * Creates a startup event envelope.
91
+ */
92
+ export function createStartupEvent(input) {
93
+ return createAgentEvent({
94
+ workspace: input.workspace,
95
+ type: 'startup',
96
+ id: input.id,
97
+ occurredAt: input.occurredAt,
98
+ attempt: input.attempt,
99
+ digest: input.digest,
100
+ reason: input.reason,
101
+ summary: input.summary,
102
+ resource: {
103
+ path: '/_system/startup',
104
+ kind: 'startup',
105
+ id: input.id ?? 'startup',
106
+ provider: 'internal',
107
+ },
108
+ });
109
+ }
110
+ /**
111
+ * Creates a synthetic transport error event.
112
+ */
113
+ export function createTransportErrorEvent(input) {
114
+ return createAgentEvent({
115
+ workspace: input.workspace,
116
+ type: 'transport.error',
117
+ id: input.id,
118
+ occurredAt: input.occurredAt,
119
+ detail: input.detail,
120
+ resource: {
121
+ path: '/_system/transport',
122
+ kind: 'transport.error',
123
+ id: input.id ?? 'transport.error',
124
+ provider: 'internal',
125
+ },
126
+ summary: {
127
+ title: 'transport error',
128
+ status: input.detail,
129
+ tags: ['transport'],
130
+ },
131
+ });
132
+ }
133
+ /**
134
+ * Converts a typed event back into the serializable record form.
135
+ */
136
+ export function toAgentEventRecord(event) {
137
+ const record = {
138
+ id: event.id,
139
+ workspace: event.workspace,
140
+ type: event.type,
141
+ occurredAt: event.occurredAt,
142
+ attempt: event.attempt,
143
+ resource: { ...event.resource },
144
+ summary: cloneSummary(event.summary),
145
+ digest: event.digest,
146
+ };
147
+ if (isCronTickEvent(event)) {
148
+ record.schedule = event.schedule;
149
+ record.scheduledFor = event.scheduledFor;
150
+ }
151
+ if (isStartupEvent(event)) {
152
+ record.reason = event.reason;
153
+ }
154
+ if (isRelayfileChangeEvent(event)) {
155
+ record.watch = event.watch;
156
+ record.action = event.action;
157
+ record.agentId = event.agentId;
158
+ }
159
+ if (isRelaycastMessageEvent(event)) {
160
+ record.channel = event.channel;
161
+ record.messageId = event.messageId;
162
+ record.threadId = event.threadId;
163
+ }
164
+ if (isTransportErrorEvent(event)) {
165
+ record.detail = event.detail;
166
+ }
167
+ return record;
168
+ }
169
+ /**
170
+ * Type guard for `cron.tick`.
171
+ */
172
+ export function isCronTickEvent(event) {
173
+ return event.type === 'cron.tick';
174
+ }
175
+ /**
176
+ * Type guard for `startup`.
177
+ */
178
+ export function isStartupEvent(event) {
179
+ return event.type === 'startup';
180
+ }
181
+ /**
182
+ * Type guard for `relayfile.changed`.
183
+ */
184
+ export function isRelayfileChangeEvent(event) {
185
+ return event.type === 'relayfile.changed';
186
+ }
187
+ /**
188
+ * Type guard for `relaycast.message`.
189
+ */
190
+ export function isRelaycastMessageEvent(event) {
191
+ return event.type === 'relaycast.message';
192
+ }
193
+ /**
194
+ * Type guard for `transport.error`.
195
+ */
196
+ export function isTransportErrorEvent(event) {
197
+ return event.type === 'transport.error';
198
+ }
199
+ function resolveResource(record, fallbackId) {
200
+ if (record.resource?.path && record.resource.id && record.resource.kind && record.resource.provider) {
201
+ return {
202
+ path: record.resource.path,
203
+ id: record.resource.id,
204
+ kind: record.resource.kind,
205
+ provider: record.resource.provider,
206
+ };
207
+ }
208
+ switch (record.type) {
209
+ case 'cron.tick': {
210
+ const scheduleId = sanitizeScheduleId(record.schedule ?? fallbackId);
211
+ return {
212
+ path: record.resource?.path ?? `/_cron/${scheduleId}`,
213
+ kind: record.resource?.kind ?? 'cron.tick',
214
+ id: record.resource?.id ?? scheduleId,
215
+ provider: record.resource?.provider ?? 'internal',
216
+ };
217
+ }
218
+ case 'startup':
219
+ return {
220
+ path: record.resource?.path ?? '/_system/startup',
221
+ kind: record.resource?.kind ?? 'startup',
222
+ id: record.resource?.id ?? fallbackId,
223
+ provider: record.resource?.provider ?? 'internal',
224
+ };
225
+ case 'relayfile.changed': {
226
+ const path = record.resource?.path ?? record.path ?? `/_relayfile/${fallbackId}`;
227
+ const provider = record.resource?.provider ?? inferProviderFromPath(path);
228
+ return {
229
+ path,
230
+ kind: record.resource?.kind ?? inferRelayfileResourceKind(path, provider),
231
+ id: record.resource?.id ?? inferRelayfileResourceId(path, fallbackId),
232
+ provider,
233
+ };
234
+ }
235
+ case 'relaycast.message':
236
+ return {
237
+ path: record.resource?.path ??
238
+ `/_relaycast/${record.channel ?? 'unknown'}/${record.messageId ?? fallbackId}`,
239
+ kind: record.resource?.kind ?? 'relaycast.message',
240
+ id: record.resource?.id ?? record.messageId ?? fallbackId,
241
+ provider: record.resource?.provider ?? 'relaycast',
242
+ };
243
+ case 'transport.error':
244
+ return {
245
+ path: record.resource?.path ?? '/_system/transport',
246
+ kind: record.resource?.kind ?? 'transport.error',
247
+ id: record.resource?.id ?? fallbackId,
248
+ provider: record.resource?.provider ?? 'internal',
249
+ };
250
+ default:
251
+ return {
252
+ path: record.resource?.path ?? `/_events/${record.type}/${fallbackId}`,
253
+ kind: record.resource?.kind ?? record.type,
254
+ id: record.resource?.id ?? fallbackId,
255
+ provider: record.resource?.provider ?? inferProvider(record.type),
256
+ };
257
+ }
258
+ }
259
+ function defaultSummary(record, resource) {
260
+ switch (record.type) {
261
+ case 'cron.tick':
262
+ return {
263
+ title: 'cron tick',
264
+ status: record.schedule ?? 'manual',
265
+ tags: ['cron'],
266
+ };
267
+ case 'startup':
268
+ return {
269
+ title: 'startup',
270
+ status: record.reason ?? 'manual',
271
+ tags: ['runtime'],
272
+ };
273
+ case 'transport.error':
274
+ return {
275
+ title: 'transport error',
276
+ status: record.detail ?? 'transport.error',
277
+ tags: ['transport'],
278
+ };
279
+ case 'relayfile.changed': {
280
+ const path = resource?.path ?? record.resource?.path ?? record.path;
281
+ const provider = resource?.provider ?? record.resource?.provider ?? (path ? inferProviderFromPath(path) : 'relayfile');
282
+ return {
283
+ ...(path ? { title: describePath(path) } : { title: 'relayfile.changed' }),
284
+ ...(record.action ? { status: record.action } : {}),
285
+ ...(provider && provider !== 'relayfile' ? { tags: [provider] } : {}),
286
+ };
287
+ }
288
+ default:
289
+ return {
290
+ title: record.type,
291
+ };
292
+ }
293
+ }
294
+ function inferProvider(type) {
295
+ const [provider] = type.split('.', 1);
296
+ return provider || 'internal';
297
+ }
298
+ function inferProviderFromPath(path) {
299
+ const segments = path.split('/').filter(Boolean);
300
+ const first = segments[0]?.toLowerCase();
301
+ return !first || first.startsWith('_') ? 'relayfile' : first;
302
+ }
303
+ function inferRelayfileResourceKind(path, provider) {
304
+ if (provider === 'relayfile') {
305
+ return 'relayfile.file';
306
+ }
307
+ const segments = path.split('/').filter(Boolean);
308
+ const collection = segments.length > 1 ? segments.at(-2) : undefined;
309
+ const normalized = normalizeResourceCollection(collection);
310
+ return normalized ? `${provider}.${normalized}` : `${provider}.resource`;
311
+ }
312
+ function inferRelayfileResourceId(path, fallbackId) {
313
+ const segments = path.split('/').filter(Boolean);
314
+ const leaf = segments.at(-1);
315
+ if (!leaf) {
316
+ return fallbackId;
317
+ }
318
+ const trimmed = leaf.replace(/\.(json|md|txt|yaml|yml)$/i, '').trim();
319
+ return trimmed || fallbackId;
320
+ }
321
+ function normalizeResourceCollection(segment) {
322
+ const normalized = segment?.trim().replace(/^_+/, '').toLowerCase();
323
+ if (!normalized) {
324
+ return undefined;
325
+ }
326
+ const aliases = {
327
+ notifications: 'notification',
328
+ prs: 'pull_request',
329
+ pulls: 'pull_request',
330
+ merge_requests: 'merge_request',
331
+ 'merge-requests': 'merge_request',
332
+ records: 'record',
333
+ issues: 'issue',
334
+ comments: 'comment',
335
+ tickets: 'ticket',
336
+ pages: 'page',
337
+ tasks: 'task',
338
+ projects: 'project',
339
+ messages: 'message',
340
+ threads: 'thread',
341
+ files: 'file',
342
+ tables: 'table',
343
+ bases: 'base',
344
+ };
345
+ if (aliases[normalized]) {
346
+ return aliases[normalized];
347
+ }
348
+ if (normalized.endsWith('ies')) {
349
+ return `${normalized.slice(0, -3)}y`;
350
+ }
351
+ if (normalized.endsWith('s') && normalized.length > 1) {
352
+ return normalized.slice(0, -1);
353
+ }
354
+ return normalized;
355
+ }
356
+ function sanitizeScheduleId(schedule) {
357
+ return schedule.replace(/[^a-zA-Z0-9._-]+/g, '-');
358
+ }
359
+ function createId(type) {
360
+ return `${type}:${Date.now()}:${Math.random().toString(36).slice(2, 10)}`;
361
+ }
362
+ function buildRelayfileCurrent(summary) {
363
+ const current = {
364
+ ...(summary.title ? { title: summary.title } : {}),
365
+ ...(summary.status ? { status: summary.status } : {}),
366
+ ...(summary.priority ? { priority: summary.priority } : {}),
367
+ ...(summary.labels?.length ? { labels: [...summary.labels] } : {}),
368
+ };
369
+ return Object.keys(current).length > 0 ? current : undefined;
370
+ }
371
+ function cloneSummary(summary) {
372
+ return {
373
+ ...summary,
374
+ labels: summary.labels ? [...summary.labels] : undefined,
375
+ fieldsChanged: summary.fieldsChanged ? [...summary.fieldsChanged] : undefined,
376
+ tags: summary.tags ? [...summary.tags] : undefined,
377
+ actor: summary.actor ? { ...summary.actor } : undefined,
378
+ };
379
+ }
380
+ const MAX_LABELS = 8;
381
+ const MAX_FIELDS_CHANGED = 16;
382
+ const MAX_TAGS = 8;
383
+ const MAX_TITLE_LENGTH = 120;
384
+ const MAX_STATUS_LENGTH = 96;
385
+ const MAX_PRIORITY_LENGTH = 48;
386
+ const MAX_TOKEN_LENGTH = 96;
387
+ function sanitizeSummary(summary) {
388
+ return {
389
+ ...(sanitizeText(summary.title, MAX_TITLE_LENGTH)
390
+ ? { title: sanitizeText(summary.title, MAX_TITLE_LENGTH) }
391
+ : {}),
392
+ ...(sanitizeText(summary.status, MAX_STATUS_LENGTH)
393
+ ? { status: sanitizeText(summary.status, MAX_STATUS_LENGTH) }
394
+ : {}),
395
+ ...(sanitizeText(summary.priority, MAX_PRIORITY_LENGTH)
396
+ ? { priority: sanitizeText(summary.priority, MAX_PRIORITY_LENGTH) }
397
+ : {}),
398
+ ...(sanitizeTokenList(summary.labels, MAX_LABELS)
399
+ ? { labels: sanitizeTokenList(summary.labels, MAX_LABELS) }
400
+ : {}),
401
+ ...(sanitizeTokenList(summary.fieldsChanged, MAX_FIELDS_CHANGED)
402
+ ? { fieldsChanged: sanitizeTokenList(summary.fieldsChanged, MAX_FIELDS_CHANGED) }
403
+ : {}),
404
+ ...(sanitizeTokenList(summary.tags, MAX_TAGS) ? { tags: sanitizeTokenList(summary.tags, MAX_TAGS) } : {}),
405
+ ...(summary.actor?.id?.trim()
406
+ ? {
407
+ actor: {
408
+ id: summary.actor.id.trim(),
409
+ ...(sanitizeText(summary.actor.displayName, MAX_TOKEN_LENGTH)
410
+ ? { displayName: sanitizeText(summary.actor.displayName, MAX_TOKEN_LENGTH) }
411
+ : {}),
412
+ },
413
+ }
414
+ : {}),
415
+ };
416
+ }
417
+ function sanitizeTokenList(values, max) {
418
+ if (!values?.length) {
419
+ return undefined;
420
+ }
421
+ const output = [];
422
+ for (const value of values) {
423
+ const normalized = sanitizeText(value, MAX_TOKEN_LENGTH);
424
+ if (!normalized || output.includes(normalized)) {
425
+ continue;
426
+ }
427
+ output.push(normalized);
428
+ if (output.length >= max) {
429
+ break;
430
+ }
431
+ }
432
+ return output.length > 0 ? output : undefined;
433
+ }
434
+ function sanitizeText(value, maxLength) {
435
+ const normalized = value?.replace(/\s+/g, ' ').trim();
436
+ if (!normalized) {
437
+ return undefined;
438
+ }
439
+ return normalized.length <= maxLength
440
+ ? normalized
441
+ : `${normalized.slice(0, Math.max(0, maxLength - 3)).trimEnd()}...`;
442
+ }
443
+ function describePath(path) {
444
+ const segments = path.split('/').filter(Boolean);
445
+ return segments.at(-1) || path || 'relayfile change';
446
+ }
447
+ //# sourceMappingURL=envelope.js.map