@zigil/agent-react 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.
@@ -0,0 +1,722 @@
1
+ // src/context-draft.ts
2
+ import { useSyncExternalStore } from "react";
3
+
4
+ // src/attention.tsx
5
+ import {
6
+ createContext,
7
+ createElement,
8
+ useContext
9
+ } from "react";
10
+ var MAX_SERIALIZED_BYTES = 4 * 1024;
11
+ var MAX_REQUIRED_FIELD_LENGTH = 128;
12
+ var MAX_SELECTIONS = 12;
13
+ var MAX_HISTORY = 40;
14
+ var MAX_FOCUSED_HISTORY = 8;
15
+ var MAX_HOVER_HISTORY = 2;
16
+ var ATTENTION_ACTIVITY_ACTIONS = [
17
+ "focus",
18
+ "select",
19
+ "navigate",
20
+ "edit",
21
+ "execute",
22
+ "approve",
23
+ "dismiss",
24
+ "hover"
25
+ ];
26
+ var AttentionContextReact = createContext(null);
27
+ var AttentionContextSchema = standardSchema(
28
+ (value) => {
29
+ const issues = validateAttentionContext(value);
30
+ return issues.length > 0 ? issues : value;
31
+ }
32
+ );
33
+ function serializeAttention(context, privacy = "focused") {
34
+ const normalized = normalizeAttention(context, privacy);
35
+ assertClosedAttention(normalized);
36
+ let serialized = JSON.stringify(normalized);
37
+ if (byteLength(serialized) <= MAX_SERIALIZED_BYTES) return serialized;
38
+ warnOversizedAttention();
39
+ while (normalized.history?.some((event) => event.action === "hover")) {
40
+ const hoverIndex = normalized.history.findIndex(
41
+ (event) => event.action === "hover"
42
+ );
43
+ normalized.history.splice(hoverIndex, 1);
44
+ if (normalized.history.length === 0) delete normalized.history;
45
+ serialized = JSON.stringify(normalized);
46
+ if (byteLength(serialized) <= MAX_SERIALIZED_BYTES) return serialized;
47
+ }
48
+ while ((normalized.history?.length ?? 0) > 0) {
49
+ normalized.history?.shift();
50
+ if (normalized.history?.length === 0) delete normalized.history;
51
+ serialized = JSON.stringify(normalized);
52
+ if (byteLength(serialized) <= MAX_SERIALIZED_BYTES) return serialized;
53
+ }
54
+ delete normalized.hover;
55
+ serialized = JSON.stringify(normalized);
56
+ if (byteLength(serialized) <= MAX_SERIALIZED_BYTES) return serialized;
57
+ removeOptionalSelectionFields(normalized);
58
+ serialized = JSON.stringify(normalized);
59
+ if (byteLength(serialized) <= MAX_SERIALIZED_BYTES) return serialized;
60
+ const required = truncateRequiredFields(normalized);
61
+ serialized = JSON.stringify(required);
62
+ while (byteLength(serialized) > MAX_SERIALIZED_BYTES && (required.selections?.length ?? 0) > 0) {
63
+ required.selections?.pop();
64
+ if (required.selections?.length === 0) delete required.selections;
65
+ serialized = JSON.stringify(required);
66
+ }
67
+ return serialized;
68
+ }
69
+ function validateAttentionContext(value, path = []) {
70
+ if (!isRecord(value)) return [issue("Expected attention context", ...path)];
71
+ const issues = [];
72
+ if (!hasOnlyKeys(value, [
73
+ "application",
74
+ "route",
75
+ "workspace",
76
+ "selection",
77
+ "selections",
78
+ "hover",
79
+ "history"
80
+ ]))
81
+ issues.push(issue("Unexpected attention field", ...path));
82
+ if (typeof value.application !== "string")
83
+ issues.push(issue("Expected a string", ...path, "application"));
84
+ if (typeof value.route !== "string")
85
+ issues.push(issue("Expected a string", ...path, "route"));
86
+ if (value.workspace !== void 0)
87
+ issues.push(...validateWorkspace(value.workspace, [...path, "workspace"]));
88
+ if (value.selection !== void 0)
89
+ issues.push(...validateSelection(value.selection, [...path, "selection"]));
90
+ if (value.hover !== void 0)
91
+ issues.push(...validateSelection(value.hover, [...path, "hover"]));
92
+ if (value.selections !== void 0) {
93
+ if (!Array.isArray(value.selections))
94
+ issues.push(issue("Expected selections", ...path, "selections"));
95
+ else
96
+ value.selections.forEach(
97
+ (selection, index) => issues.push(
98
+ ...validateSelection(selection, [...path, "selections", index])
99
+ )
100
+ );
101
+ }
102
+ if (value.history !== void 0) {
103
+ if (!Array.isArray(value.history))
104
+ issues.push(issue("Expected activity history", ...path, "history"));
105
+ else
106
+ value.history.forEach(
107
+ (event, index) => issues.push(...validateActivity(event, [...path, "history", index]))
108
+ );
109
+ }
110
+ return issues;
111
+ }
112
+ function normalizeAttention(context, privacy) {
113
+ const workspace = context.workspace ? {
114
+ kind: context.workspace.kind,
115
+ id: context.workspace.id,
116
+ ...context.workspace.revision === void 0 ? {} : { revision: context.workspace.revision },
117
+ ...privacy === "minimal" || context.workspace.label === void 0 ? {} : { label: context.workspace.label }
118
+ } : void 0;
119
+ const primary = context.selection ? normalizeSelection(context.selection, privacy !== "minimal") : void 0;
120
+ if (privacy === "minimal") {
121
+ return {
122
+ application: context.application,
123
+ route: context.route,
124
+ ...workspace ? { workspace } : {},
125
+ ...primary ? { selection: primary } : {}
126
+ };
127
+ }
128
+ const selections = normalizeSelections(context.selections, primary);
129
+ const history = normalizeHistory(
130
+ context.history,
131
+ privacy === "focused" ? MAX_FOCUSED_HISTORY : MAX_HISTORY,
132
+ privacy === "expanded"
133
+ );
134
+ return {
135
+ application: context.application,
136
+ route: context.route,
137
+ ...workspace ? { workspace } : {},
138
+ ...primary ? { selection: primary } : {},
139
+ ...selections.length > 0 ? { selections } : {},
140
+ ...privacy === "expanded" && context.hover ? { hover: normalizeSelection(context.hover, true) } : {},
141
+ ...history.length > 0 ? { history } : {}
142
+ };
143
+ }
144
+ function normalizeSelections(selections, primary) {
145
+ const ordered = primary ? [
146
+ primary,
147
+ ...(selections ?? []).map((item) => normalizeSelection(item, true))
148
+ ] : (selections ?? []).map((item) => normalizeSelection(item, true));
149
+ const seen = /* @__PURE__ */ new Set();
150
+ return ordered.filter((item) => {
151
+ const key = selectionKey(item);
152
+ if (seen.has(key)) return false;
153
+ seen.add(key);
154
+ return true;
155
+ }).slice(0, MAX_SELECTIONS);
156
+ }
157
+ function normalizeHistory(history, limit, includeHover) {
158
+ const semantic = [];
159
+ const hovers = [];
160
+ for (const event of history ?? []) {
161
+ const normalized = normalizeEvent(event);
162
+ if (normalized.action !== "hover") semantic.push(normalized);
163
+ else if (includeHover) {
164
+ const previous = hovers.at(-1);
165
+ if (previous && selectionKey(previous.target) === selectionKey(normalized.target))
166
+ hovers[hovers.length - 1] = normalized;
167
+ else hovers.push(normalized);
168
+ }
169
+ }
170
+ const retainedSemantic = semantic.slice(-limit);
171
+ const remaining = Math.max(0, limit - retainedSemantic.length);
172
+ const hoverLimit = Math.min(MAX_HOVER_HISTORY, remaining);
173
+ const retainedHover = hoverLimit > 0 ? hovers.slice(-hoverLimit) : [];
174
+ return [...retainedSemantic, ...retainedHover].sort(
175
+ (left, right) => left.timestamp - right.timestamp
176
+ );
177
+ }
178
+ function normalizeEvent(event) {
179
+ return {
180
+ action: event.action,
181
+ target: normalizeSelection(event.target, true),
182
+ timestamp: event.timestamp,
183
+ ...event.summary === void 0 ? {} : { summary: event.summary },
184
+ ...event.detail === void 0 ? {} : { detail: sortRecord(event.detail) }
185
+ };
186
+ }
187
+ function normalizeSelection(selection, includeOptional) {
188
+ return {
189
+ kind: selection.kind,
190
+ id: selection.id,
191
+ ...includeOptional && selection.label !== void 0 ? { label: selection.label } : {},
192
+ ...includeOptional && selection.detail !== void 0 ? { detail: sortRecord(selection.detail) } : {}
193
+ };
194
+ }
195
+ function removeOptionalSelectionFields(context) {
196
+ const selections = [
197
+ context.selection,
198
+ ...context.selections ?? [],
199
+ context.hover
200
+ ].filter((selection) => Boolean(selection));
201
+ for (const selection of selections.reverse()) {
202
+ for (const key of Object.keys(selection.detail ?? {}).reverse()) {
203
+ if (!selection.detail) break;
204
+ delete selection.detail[key];
205
+ if (Object.keys(selection.detail).length === 0) delete selection.detail;
206
+ }
207
+ delete selection.label;
208
+ }
209
+ if (context.workspace) delete context.workspace.label;
210
+ }
211
+ function truncateRequiredFields(context) {
212
+ return {
213
+ application: truncateText(context.application),
214
+ route: truncateText(context.route),
215
+ ...context.workspace ? {
216
+ workspace: {
217
+ kind: truncateText(context.workspace.kind),
218
+ id: truncateText(context.workspace.id),
219
+ ...context.workspace.revision === void 0 ? {} : { revision: context.workspace.revision }
220
+ }
221
+ } : {},
222
+ ...context.selection ? {
223
+ selection: {
224
+ kind: truncateText(context.selection.kind),
225
+ id: truncateText(context.selection.id)
226
+ }
227
+ } : {},
228
+ ...context.selections ? {
229
+ selections: context.selections.slice(0, MAX_SELECTIONS).map((item) => ({
230
+ kind: truncateText(item.kind),
231
+ id: truncateText(item.id)
232
+ }))
233
+ } : {}
234
+ };
235
+ }
236
+ function validateWorkspace(value, path) {
237
+ if (!isRecord(value)) return [issue("Expected a workspace", ...path)];
238
+ const issues = [];
239
+ if (!hasOnlyKeys(value, ["kind", "id", "label", "revision"]))
240
+ issues.push(issue("Unexpected workspace field", ...path));
241
+ for (const key of ["kind", "id"])
242
+ if (typeof value[key] !== "string")
243
+ issues.push(issue("Expected a string", ...path, key));
244
+ if (value.label !== void 0 && typeof value.label !== "string")
245
+ issues.push(issue("Expected a string", ...path, "label"));
246
+ if (value.revision !== void 0 && (typeof value.revision !== "number" || !Number.isFinite(value.revision)))
247
+ issues.push(issue("Expected a finite revision", ...path, "revision"));
248
+ return issues;
249
+ }
250
+ function validateSelection(value, path) {
251
+ if (!isRecord(value)) return [issue("Expected a selection", ...path)];
252
+ const issues = [];
253
+ if (!hasOnlyKeys(value, ["kind", "id", "label", "detail"]))
254
+ issues.push(issue("Unexpected selection field", ...path));
255
+ for (const key of ["kind", "id"])
256
+ if (typeof value[key] !== "string")
257
+ issues.push(issue("Expected a string", ...path, key));
258
+ if (value.label !== void 0 && typeof value.label !== "string")
259
+ issues.push(issue("Expected a string", ...path, "label"));
260
+ issues.push(...validateStringRecord(value.detail, [...path, "detail"]));
261
+ return issues;
262
+ }
263
+ function validateActivity(value, path) {
264
+ if (!isRecord(value)) return [issue("Expected activity", ...path)];
265
+ const issues = [];
266
+ if (!hasOnlyKeys(value, ["action", "target", "timestamp", "summary", "detail"]))
267
+ issues.push(issue("Unexpected activity field", ...path));
268
+ if (typeof value.action !== "string" || !ATTENTION_ACTIVITY_ACTIONS.includes(
269
+ value.action
270
+ ))
271
+ issues.push(issue("Expected an action", ...path, "action"));
272
+ if (typeof value.timestamp !== "number" || !Number.isFinite(value.timestamp))
273
+ issues.push(issue("Expected a finite timestamp", ...path, "timestamp"));
274
+ if (value.summary !== void 0 && typeof value.summary !== "string")
275
+ issues.push(issue("Expected a string", ...path, "summary"));
276
+ issues.push(...validateSelection(value.target, [...path, "target"]));
277
+ issues.push(...validateStringRecord(value.detail, [...path, "detail"]));
278
+ return issues;
279
+ }
280
+ function validateStringRecord(value, path) {
281
+ if (value === void 0) return [];
282
+ if (!isRecord(value) || !Object.values(value).every((item) => typeof item === "string"))
283
+ return [issue("Expected string details", ...path)];
284
+ return [];
285
+ }
286
+ function assertClosedAttention(value) {
287
+ const issues = validateAttentionContext(value);
288
+ if (issues.length > 0)
289
+ throw new Error(`Invalid attention serialization: ${issues[0]?.message}`);
290
+ }
291
+ function standardSchema(validate) {
292
+ return {
293
+ "~standard": {
294
+ version: 1,
295
+ vendor: "sigil-agent",
296
+ validate(value) {
297
+ const result = validate(value);
298
+ return Array.isArray(result) ? { issues: result } : { value: result };
299
+ }
300
+ }
301
+ };
302
+ }
303
+ function issue(message, ...path) {
304
+ return { message, path };
305
+ }
306
+ function isRecord(value) {
307
+ return typeof value === "object" && value !== null && !Array.isArray(value);
308
+ }
309
+ function hasOnlyKeys(value, allowed) {
310
+ const keys = new Set(allowed);
311
+ return Object.keys(value).every((key) => keys.has(key));
312
+ }
313
+ function sortRecord(record) {
314
+ return Object.fromEntries(
315
+ Object.entries(record).sort(([left], [right]) => left.localeCompare(right))
316
+ );
317
+ }
318
+ function selectionKey(selection) {
319
+ return `${selection.kind}\0${selection.id}`;
320
+ }
321
+ function truncateText(value) {
322
+ return value.length <= MAX_REQUIRED_FIELD_LENGTH ? value : `${value.slice(0, MAX_REQUIRED_FIELD_LENGTH - 1)}\u2026`;
323
+ }
324
+ function byteLength(value) {
325
+ return new TextEncoder().encode(value).byteLength;
326
+ }
327
+ function warnOversizedAttention() {
328
+ const isDevelopment = import.meta.env?.DEV === true;
329
+ if (isDevelopment)
330
+ console.warn(
331
+ "Agent attention exceeded 4 KB; optional context was removed before sending."
332
+ );
333
+ }
334
+
335
+ // src/context-draft.ts
336
+ var listeners = /* @__PURE__ */ new Set();
337
+ var drafts = /* @__PURE__ */ new Map();
338
+ var activeDraftScope = "default";
339
+ var MAX_ATTACHMENTS = 6;
340
+ var MAX_ATTACHMENT_TEXT_LENGTH = 192;
341
+ var MAX_CONTEXT_DRAFT_BYTES = 8 * 1024;
342
+ var ContextAttachmentSchema = standardSchema2(
343
+ (value) => {
344
+ const issues = validateAttachment(value);
345
+ return issues.length > 0 ? issues : value;
346
+ }
347
+ );
348
+ var ContextDraftPayloadSchema = standardSchema2(
349
+ (value) => {
350
+ const issues = validateDraftPayload(value);
351
+ return issues.length > 0 ? issues : value;
352
+ }
353
+ );
354
+ function attentionSelectionKey(selection) {
355
+ return `selection:${selection.kind}:${selection.id}`;
356
+ }
357
+ function attentionHistoryKey(event) {
358
+ return `history:${event.timestamp}:${event.action}:${event.target.kind}:${event.target.id}`;
359
+ }
360
+ function getContextDraftScope() {
361
+ return activeDraftScope;
362
+ }
363
+ function getAttentionExclusions() {
364
+ return getDraftState().exclusions;
365
+ }
366
+ function getTurnContextAttachments() {
367
+ return getDraftState().attachments;
368
+ }
369
+ function setContextDraftScope(scopeId) {
370
+ const normalized = scopeId.trim();
371
+ if (!normalized) throw new Error("Context draft scope must be non-empty");
372
+ if (activeDraftScope === normalized) return;
373
+ activeDraftScope = normalized;
374
+ getDraftState();
375
+ emitChange();
376
+ }
377
+ function addContextAttachment(attachment) {
378
+ const validated = validateAttachment(attachment);
379
+ if (validated.length > 0)
380
+ throw new Error(`Invalid context attachment: ${validated[0]?.message}`);
381
+ const normalized = normalizeAttachment(attachment, "expanded");
382
+ const state = getDraftState();
383
+ state.attachments = [
384
+ ...state.attachments.filter((item) => item.id !== normalized.id),
385
+ normalized
386
+ ].slice(-MAX_ATTACHMENTS);
387
+ emitChange();
388
+ }
389
+ function addTurnContextAttachment(selection, retention = "turn") {
390
+ addContextAttachment({
391
+ id: attentionSelectionKey(selection),
392
+ source: "application-selection",
393
+ inclusion: "user-added",
394
+ resource: { kind: selection.kind, id: selection.id },
395
+ label: truncateAttachmentText(selection.label ?? selection.id),
396
+ ...selection.detail ? {
397
+ summary: truncateAttachmentText(
398
+ Object.entries(selection.detail).sort(([left], [right]) => left.localeCompare(right)).map(([key, value]) => `${key}: ${value}`).join(" \xB7 ")
399
+ )
400
+ } : {},
401
+ retention
402
+ });
403
+ }
404
+ function removeTurnContextAttachment(id) {
405
+ const state = getDraftState();
406
+ const next = state.attachments.filter((item) => item.id !== id);
407
+ if (next.length === state.attachments.length) return;
408
+ state.attachments = next;
409
+ emitChange();
410
+ }
411
+ function moveTurnContextAttachment(id, direction) {
412
+ const state = getDraftState();
413
+ const index = state.attachments.findIndex((item) => item.id === id);
414
+ const target = index + direction;
415
+ if (index < 0 || target < 0 || target >= state.attachments.length) return;
416
+ const next = [...state.attachments];
417
+ [next[index], next[target]] = [next[target], next[index]];
418
+ state.attachments = next;
419
+ emitChange();
420
+ }
421
+ function setTurnContextAttachmentRetention(id, retention) {
422
+ let changed = false;
423
+ const state = getDraftState();
424
+ state.attachments = state.attachments.map((item) => {
425
+ if (item.id !== id || item.retention === retention) return item;
426
+ changed = true;
427
+ return { ...item, retention };
428
+ });
429
+ if (changed) emitChange();
430
+ }
431
+ function clearTurnContextAttachments() {
432
+ const state = getDraftState();
433
+ const next = state.attachments.filter((item) => item.retention === "session");
434
+ if (next.length === state.attachments.length) return;
435
+ state.attachments = next;
436
+ emitChange();
437
+ }
438
+ function clearContextDraft() {
439
+ const state = getDraftState();
440
+ if (state.exclusions.length === 0 && state.attachments.length === 0) return;
441
+ state.exclusions = [];
442
+ state.attachments = [];
443
+ emitChange();
444
+ }
445
+ function resetContextDraftForTests() {
446
+ drafts.clear();
447
+ activeDraftScope = "default";
448
+ emitChange();
449
+ }
450
+ function setAttentionItemExcluded(key, excluded) {
451
+ const state = getDraftState();
452
+ const next = new Set(state.exclusions);
453
+ if (excluded) next.add(key);
454
+ else next.delete(key);
455
+ state.exclusions = [...next].sort();
456
+ emitChange();
457
+ }
458
+ function clearAttentionExclusions() {
459
+ const state = getDraftState();
460
+ if (state.exclusions.length === 0) return;
461
+ state.exclusions = [];
462
+ emitChange();
463
+ }
464
+ function useAttentionExclusions() {
465
+ return useSyncExternalStore(
466
+ subscribeContextDraft,
467
+ getAttentionExclusions,
468
+ getAttentionExclusions
469
+ );
470
+ }
471
+ function useTurnContextAttachments() {
472
+ return useSyncExternalStore(
473
+ subscribeContextDraft,
474
+ getTurnContextAttachments,
475
+ getTurnContextAttachments
476
+ );
477
+ }
478
+ function serializeAttentionDraft(context, privacy, excludedKeys = getAttentionExclusions(), contextAttachments = getTurnContextAttachments()) {
479
+ const attention = context ? JSON.parse(
480
+ serializeAttention(
481
+ applyAttentionExclusions(context, excludedKeys),
482
+ privacy
483
+ )
484
+ ) : {};
485
+ const normalizedAttachments = contextAttachments.slice(0, MAX_ATTACHMENTS).map((attachment) => normalizeAttachment(attachment, privacy));
486
+ const serialized = fitContextPayload(attention, normalizedAttachments);
487
+ const parsed = JSON.parse(serialized);
488
+ const issues = validateDraftPayload(parsed);
489
+ if (issues.length > 0)
490
+ throw new Error(
491
+ `Invalid context draft serialization: ${issues[0]?.message}`
492
+ );
493
+ return serialized;
494
+ }
495
+ function createAttentionContextPreview(context, privacy, excludedKeys = getAttentionExclusions(), contextAttachments = getTurnContextAttachments()) {
496
+ const serialized = serializeAttentionDraft(
497
+ context,
498
+ privacy,
499
+ excludedKeys,
500
+ contextAttachments
501
+ );
502
+ const normalized = JSON.parse(serialized);
503
+ const selections = normalized.selections?.length ? normalized.selections : normalized.selection ? [normalized.selection] : [];
504
+ const history = normalized.history ?? [];
505
+ const normalizedAttachments = normalized.attachments ?? [];
506
+ const truncatedAttachmentCount = Math.max(
507
+ 0,
508
+ contextAttachments.length - normalizedAttachments.length
509
+ );
510
+ const byteLength3 = new TextEncoder().encode(serialized).byteLength;
511
+ const parts = [
512
+ `${selections.length} selection${selections.length === 1 ? "" : "s"}`,
513
+ `${normalizedAttachments.length} attachment${normalizedAttachments.length === 1 ? "" : "s"}`,
514
+ `${history.length} recent action${history.length === 1 ? "" : "s"}`
515
+ ];
516
+ return {
517
+ attachmentCount: normalizedAttachments.length,
518
+ attachments: normalizedAttachments,
519
+ byteLength: byteLength3,
520
+ estimatedTokens: Math.ceil(byteLength3 / 4),
521
+ formatted: JSON.stringify(normalized, null, 2),
522
+ history,
523
+ selectionCount: selections.length,
524
+ selections,
525
+ serialized,
526
+ summary: parts.join(" \xB7 "),
527
+ truncatedAttachmentCount
528
+ };
529
+ }
530
+ function applyAttentionExclusions(context, excludedKeys) {
531
+ if (excludedKeys.length === 0) return context;
532
+ const excluded = new Set(excludedKeys);
533
+ const selectionCandidates = deduplicateSelections([
534
+ ...context.selection ? [context.selection] : [],
535
+ ...context.selections ?? []
536
+ ]);
537
+ const selections = selectionCandidates.filter(
538
+ (selection) => !excluded.has(attentionSelectionKey(selection))
539
+ );
540
+ const excludedTargets = new Set(
541
+ selectionCandidates.filter((selection) => excluded.has(attentionSelectionKey(selection))).map((selection) => `${selection.kind}\0${selection.id}`)
542
+ );
543
+ const history = (context.history ?? []).filter(
544
+ (event) => !excluded.has(attentionHistoryKey(event)) && !excludedTargets.has(`${event.target.kind}\0${event.target.id}`)
545
+ );
546
+ const hover = context.hover && !excluded.has(attentionSelectionKey(context.hover)) && !excludedTargets.has(`${context.hover.kind}\0${context.hover.id}`) ? context.hover : void 0;
547
+ return {
548
+ ...context,
549
+ selection: selections[0],
550
+ selections: selections.length > 0 ? selections : void 0,
551
+ history: history.length > 0 ? history : void 0,
552
+ hover
553
+ };
554
+ }
555
+ function deduplicateSelections(selections) {
556
+ const seen = /* @__PURE__ */ new Set();
557
+ return selections.filter((selection) => {
558
+ const key = `${selection.kind}\0${selection.id}`;
559
+ if (seen.has(key)) return false;
560
+ seen.add(key);
561
+ return true;
562
+ });
563
+ }
564
+ function subscribeContextDraft(listener) {
565
+ listeners.add(listener);
566
+ return () => listeners.delete(listener);
567
+ }
568
+ function emitChange() {
569
+ listeners.forEach((listener) => listener());
570
+ }
571
+ function getDraftState() {
572
+ let state = drafts.get(activeDraftScope);
573
+ if (!state) {
574
+ state = { attachments: [], exclusions: [] };
575
+ drafts.set(activeDraftScope, state);
576
+ }
577
+ return state;
578
+ }
579
+ function normalizeAttachment(attachment, privacy) {
580
+ return {
581
+ id: truncateAttachmentText(attachment.id),
582
+ source: attachment.source,
583
+ inclusion: attachment.inclusion,
584
+ resource: {
585
+ kind: truncateAttachmentText(attachment.resource.kind),
586
+ id: truncateAttachmentText(attachment.resource.id)
587
+ },
588
+ label: privacy === "minimal" ? truncateAttachmentText(attachment.resource.id) : truncateAttachmentText(attachment.label),
589
+ ...privacy === "minimal" || attachment.summary === void 0 ? {} : { summary: truncateAttachmentText(attachment.summary) },
590
+ retention: attachment.retention
591
+ };
592
+ }
593
+ function fitContextPayload(attention, contextAttachments) {
594
+ const included = contextAttachments.map((attachment) => ({ ...attachment }));
595
+ let serialized = serializeContextPayload(attention, included);
596
+ if (byteLength2(serialized) <= MAX_CONTEXT_DRAFT_BYTES) return serialized;
597
+ for (let index = included.length - 1; index >= 0; index -= 1) {
598
+ if (included[index]?.summary === void 0) continue;
599
+ delete included[index].summary;
600
+ serialized = serializeContextPayload(attention, included);
601
+ if (byteLength2(serialized) <= MAX_CONTEXT_DRAFT_BYTES) return serialized;
602
+ }
603
+ while (included.length > 0) {
604
+ included.pop();
605
+ serialized = serializeContextPayload(attention, included);
606
+ if (byteLength2(serialized) <= MAX_CONTEXT_DRAFT_BYTES) return serialized;
607
+ }
608
+ return serialized;
609
+ }
610
+ function serializeContextPayload(attention, included) {
611
+ return JSON.stringify({
612
+ ...attention,
613
+ ...included.length > 0 ? { attachments: included } : {}
614
+ });
615
+ }
616
+ function validateDraftPayload(value) {
617
+ if (!isRecord2(value)) return [issue2("Expected a context draft payload")];
618
+ const { attachments, ...attention } = value;
619
+ const issues = [];
620
+ if (Object.keys(attention).length > 0)
621
+ issues.push(...validateAttentionContext(attention));
622
+ if (attachments !== void 0) {
623
+ if (!Array.isArray(attachments))
624
+ issues.push(issue2("Expected attachments", "attachments"));
625
+ else
626
+ attachments.forEach(
627
+ (attachment, index) => issues.push(...validateAttachment(attachment, ["attachments", index]))
628
+ );
629
+ }
630
+ return issues;
631
+ }
632
+ function validateAttachment(value, path = []) {
633
+ if (!isRecord2(value)) return [issue2("Expected an attachment", ...path)];
634
+ const issues = [];
635
+ if (!hasOnlyKeys2(value, [
636
+ "id",
637
+ "source",
638
+ "inclusion",
639
+ "resource",
640
+ "label",
641
+ "summary",
642
+ "retention"
643
+ ]))
644
+ issues.push(issue2("Unexpected attachment field", ...path));
645
+ for (const key of ["id", "label"])
646
+ if (typeof value[key] !== "string")
647
+ issues.push(issue2("Expected a string", ...path, key));
648
+ if (value.summary !== void 0 && typeof value.summary !== "string")
649
+ issues.push(issue2("Expected a string", ...path, "summary"));
650
+ if (value.source !== "application-selection" && value.source !== "semantic-fork" && value.source !== "agent-requested")
651
+ issues.push(issue2("Expected a supported source", ...path, "source"));
652
+ if (value.inclusion !== "user-added" && value.inclusion !== "automatic" && value.inclusion !== "agent-requested")
653
+ issues.push(issue2("Expected a supported inclusion", ...path, "inclusion"));
654
+ if (value.retention !== "turn" && value.retention !== "session")
655
+ issues.push(
656
+ issue2("Expected turn or session retention", ...path, "retention")
657
+ );
658
+ if (!isRecord2(value.resource))
659
+ issues.push(issue2("Expected a resource", ...path, "resource"));
660
+ else {
661
+ if (!hasOnlyKeys2(value.resource, ["kind", "id"]))
662
+ issues.push(issue2("Unexpected resource field", ...path, "resource"));
663
+ for (const key of ["kind", "id"])
664
+ if (typeof value.resource[key] !== "string")
665
+ issues.push(issue2("Expected a string", ...path, "resource", key));
666
+ }
667
+ return issues;
668
+ }
669
+ function standardSchema2(validate) {
670
+ return {
671
+ "~standard": {
672
+ version: 1,
673
+ vendor: "sigil-agent",
674
+ validate(value) {
675
+ const result = validate(value);
676
+ return Array.isArray(result) ? { issues: result } : { value: result };
677
+ }
678
+ }
679
+ };
680
+ }
681
+ function issue2(message, ...path) {
682
+ return { message, path };
683
+ }
684
+ function isRecord2(value) {
685
+ return typeof value === "object" && value !== null && !Array.isArray(value);
686
+ }
687
+ function hasOnlyKeys2(value, allowed) {
688
+ const keys = new Set(allowed);
689
+ return Object.keys(value).every((key) => keys.has(key));
690
+ }
691
+ function truncateAttachmentText(value) {
692
+ return value.length <= MAX_ATTACHMENT_TEXT_LENGTH ? value : `${value.slice(0, MAX_ATTACHMENT_TEXT_LENGTH - 1)}\u2026`;
693
+ }
694
+ function byteLength2(value) {
695
+ return new TextEncoder().encode(value).byteLength;
696
+ }
697
+ export {
698
+ ContextAttachmentSchema,
699
+ ContextDraftPayloadSchema,
700
+ addContextAttachment,
701
+ addTurnContextAttachment,
702
+ applyAttentionExclusions,
703
+ attentionHistoryKey,
704
+ attentionSelectionKey,
705
+ clearAttentionExclusions,
706
+ clearContextDraft,
707
+ clearTurnContextAttachments,
708
+ createAttentionContextPreview,
709
+ getAttentionExclusions,
710
+ getContextDraftScope,
711
+ getTurnContextAttachments,
712
+ moveTurnContextAttachment,
713
+ removeTurnContextAttachment,
714
+ resetContextDraftForTests,
715
+ serializeAttentionDraft,
716
+ setAttentionItemExcluded,
717
+ setContextDraftScope,
718
+ setTurnContextAttachmentRetention,
719
+ useAttentionExclusions,
720
+ useTurnContextAttachments
721
+ };
722
+ //# sourceMappingURL=context-draft.js.map