@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.
- package/LICENSE +202 -0
- package/dist/attention-telemetry.d.ts +61 -0
- package/dist/attention-telemetry.js +206 -0
- package/dist/attention-telemetry.js.map +1 -0
- package/dist/attention.d.ts +45 -0
- package/dist/attention.js +358 -0
- package/dist/attention.js.map +1 -0
- package/dist/context-draft.d.ts +68 -0
- package/dist/context-draft.js +722 -0
- package/dist/context-draft.js.map +1 -0
- package/dist/context-privacy.d.ts +13 -0
- package/dist/context-privacy.js +82 -0
- package/dist/context-privacy.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +1078 -0
- package/dist/index.js.map +1 -0
- package/dist/session.d.ts +11 -0
- package/dist/session.js +26 -0
- package/dist/session.js.map +1 -0
- package/dist/thread-controls.d.ts +11 -0
- package/dist/thread-controls.js +27 -0
- package/dist/thread-controls.js.map +1 -0
- package/package.json +60 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1078 @@
|
|
|
1
|
+
// src/attention.tsx
|
|
2
|
+
import {
|
|
3
|
+
createContext,
|
|
4
|
+
createElement,
|
|
5
|
+
useContext
|
|
6
|
+
} from "react";
|
|
7
|
+
var MAX_SERIALIZED_BYTES = 4 * 1024;
|
|
8
|
+
var MAX_REQUIRED_FIELD_LENGTH = 128;
|
|
9
|
+
var MAX_SELECTIONS = 12;
|
|
10
|
+
var MAX_HISTORY = 40;
|
|
11
|
+
var MAX_FOCUSED_HISTORY = 8;
|
|
12
|
+
var MAX_HOVER_HISTORY = 2;
|
|
13
|
+
var ATTENTION_ACTIVITY_ACTIONS = [
|
|
14
|
+
"focus",
|
|
15
|
+
"select",
|
|
16
|
+
"navigate",
|
|
17
|
+
"edit",
|
|
18
|
+
"execute",
|
|
19
|
+
"approve",
|
|
20
|
+
"dismiss",
|
|
21
|
+
"hover"
|
|
22
|
+
];
|
|
23
|
+
var AttentionContextReact = createContext(null);
|
|
24
|
+
function AttentionProvider({
|
|
25
|
+
children,
|
|
26
|
+
context
|
|
27
|
+
}) {
|
|
28
|
+
return createElement(
|
|
29
|
+
AttentionContextReact.Provider,
|
|
30
|
+
{ value: context },
|
|
31
|
+
children
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
function useAttention() {
|
|
35
|
+
return useContext(AttentionContextReact);
|
|
36
|
+
}
|
|
37
|
+
function formatAttentionLabel(attention) {
|
|
38
|
+
const primary = attention?.selection?.label ?? attention?.selection?.id ?? attention?.workspace?.label ?? "this workspace";
|
|
39
|
+
const extraSelections = Math.max(0, (attention?.selections?.length ?? 0) - 1);
|
|
40
|
+
return extraSelections > 0 ? `${primary} +${extraSelections}` : primary;
|
|
41
|
+
}
|
|
42
|
+
var AttentionContextSchema = standardSchema(
|
|
43
|
+
(value) => {
|
|
44
|
+
const issues = validateAttentionContext(value);
|
|
45
|
+
return issues.length > 0 ? issues : value;
|
|
46
|
+
}
|
|
47
|
+
);
|
|
48
|
+
function serializeAttention(context, privacy = "focused") {
|
|
49
|
+
const normalized = normalizeAttention(context, privacy);
|
|
50
|
+
assertClosedAttention(normalized);
|
|
51
|
+
let serialized = JSON.stringify(normalized);
|
|
52
|
+
if (byteLength(serialized) <= MAX_SERIALIZED_BYTES) return serialized;
|
|
53
|
+
warnOversizedAttention();
|
|
54
|
+
while (normalized.history?.some((event) => event.action === "hover")) {
|
|
55
|
+
const hoverIndex = normalized.history.findIndex(
|
|
56
|
+
(event) => event.action === "hover"
|
|
57
|
+
);
|
|
58
|
+
normalized.history.splice(hoverIndex, 1);
|
|
59
|
+
if (normalized.history.length === 0) delete normalized.history;
|
|
60
|
+
serialized = JSON.stringify(normalized);
|
|
61
|
+
if (byteLength(serialized) <= MAX_SERIALIZED_BYTES) return serialized;
|
|
62
|
+
}
|
|
63
|
+
while ((normalized.history?.length ?? 0) > 0) {
|
|
64
|
+
normalized.history?.shift();
|
|
65
|
+
if (normalized.history?.length === 0) delete normalized.history;
|
|
66
|
+
serialized = JSON.stringify(normalized);
|
|
67
|
+
if (byteLength(serialized) <= MAX_SERIALIZED_BYTES) return serialized;
|
|
68
|
+
}
|
|
69
|
+
delete normalized.hover;
|
|
70
|
+
serialized = JSON.stringify(normalized);
|
|
71
|
+
if (byteLength(serialized) <= MAX_SERIALIZED_BYTES) return serialized;
|
|
72
|
+
removeOptionalSelectionFields(normalized);
|
|
73
|
+
serialized = JSON.stringify(normalized);
|
|
74
|
+
if (byteLength(serialized) <= MAX_SERIALIZED_BYTES) return serialized;
|
|
75
|
+
const required = truncateRequiredFields(normalized);
|
|
76
|
+
serialized = JSON.stringify(required);
|
|
77
|
+
while (byteLength(serialized) > MAX_SERIALIZED_BYTES && (required.selections?.length ?? 0) > 0) {
|
|
78
|
+
required.selections?.pop();
|
|
79
|
+
if (required.selections?.length === 0) delete required.selections;
|
|
80
|
+
serialized = JSON.stringify(required);
|
|
81
|
+
}
|
|
82
|
+
return serialized;
|
|
83
|
+
}
|
|
84
|
+
function validateAttentionContext(value, path = []) {
|
|
85
|
+
if (!isRecord(value)) return [issue("Expected attention context", ...path)];
|
|
86
|
+
const issues = [];
|
|
87
|
+
if (!hasOnlyKeys(value, [
|
|
88
|
+
"application",
|
|
89
|
+
"route",
|
|
90
|
+
"workspace",
|
|
91
|
+
"selection",
|
|
92
|
+
"selections",
|
|
93
|
+
"hover",
|
|
94
|
+
"history"
|
|
95
|
+
]))
|
|
96
|
+
issues.push(issue("Unexpected attention field", ...path));
|
|
97
|
+
if (typeof value.application !== "string")
|
|
98
|
+
issues.push(issue("Expected a string", ...path, "application"));
|
|
99
|
+
if (typeof value.route !== "string")
|
|
100
|
+
issues.push(issue("Expected a string", ...path, "route"));
|
|
101
|
+
if (value.workspace !== void 0)
|
|
102
|
+
issues.push(...validateWorkspace(value.workspace, [...path, "workspace"]));
|
|
103
|
+
if (value.selection !== void 0)
|
|
104
|
+
issues.push(...validateSelection(value.selection, [...path, "selection"]));
|
|
105
|
+
if (value.hover !== void 0)
|
|
106
|
+
issues.push(...validateSelection(value.hover, [...path, "hover"]));
|
|
107
|
+
if (value.selections !== void 0) {
|
|
108
|
+
if (!Array.isArray(value.selections))
|
|
109
|
+
issues.push(issue("Expected selections", ...path, "selections"));
|
|
110
|
+
else
|
|
111
|
+
value.selections.forEach(
|
|
112
|
+
(selection, index) => issues.push(
|
|
113
|
+
...validateSelection(selection, [...path, "selections", index])
|
|
114
|
+
)
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
if (value.history !== void 0) {
|
|
118
|
+
if (!Array.isArray(value.history))
|
|
119
|
+
issues.push(issue("Expected activity history", ...path, "history"));
|
|
120
|
+
else
|
|
121
|
+
value.history.forEach(
|
|
122
|
+
(event, index) => issues.push(...validateActivity(event, [...path, "history", index]))
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
return issues;
|
|
126
|
+
}
|
|
127
|
+
function normalizeAttention(context, privacy) {
|
|
128
|
+
const workspace = context.workspace ? {
|
|
129
|
+
kind: context.workspace.kind,
|
|
130
|
+
id: context.workspace.id,
|
|
131
|
+
...context.workspace.revision === void 0 ? {} : { revision: context.workspace.revision },
|
|
132
|
+
...privacy === "minimal" || context.workspace.label === void 0 ? {} : { label: context.workspace.label }
|
|
133
|
+
} : void 0;
|
|
134
|
+
const primary = context.selection ? normalizeSelection(context.selection, privacy !== "minimal") : void 0;
|
|
135
|
+
if (privacy === "minimal") {
|
|
136
|
+
return {
|
|
137
|
+
application: context.application,
|
|
138
|
+
route: context.route,
|
|
139
|
+
...workspace ? { workspace } : {},
|
|
140
|
+
...primary ? { selection: primary } : {}
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
const selections = normalizeSelections(context.selections, primary);
|
|
144
|
+
const history = normalizeHistory(
|
|
145
|
+
context.history,
|
|
146
|
+
privacy === "focused" ? MAX_FOCUSED_HISTORY : MAX_HISTORY,
|
|
147
|
+
privacy === "expanded"
|
|
148
|
+
);
|
|
149
|
+
return {
|
|
150
|
+
application: context.application,
|
|
151
|
+
route: context.route,
|
|
152
|
+
...workspace ? { workspace } : {},
|
|
153
|
+
...primary ? { selection: primary } : {},
|
|
154
|
+
...selections.length > 0 ? { selections } : {},
|
|
155
|
+
...privacy === "expanded" && context.hover ? { hover: normalizeSelection(context.hover, true) } : {},
|
|
156
|
+
...history.length > 0 ? { history } : {}
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
function normalizeSelections(selections, primary) {
|
|
160
|
+
const ordered = primary ? [
|
|
161
|
+
primary,
|
|
162
|
+
...(selections ?? []).map((item) => normalizeSelection(item, true))
|
|
163
|
+
] : (selections ?? []).map((item) => normalizeSelection(item, true));
|
|
164
|
+
const seen = /* @__PURE__ */ new Set();
|
|
165
|
+
return ordered.filter((item) => {
|
|
166
|
+
const key = selectionKey(item);
|
|
167
|
+
if (seen.has(key)) return false;
|
|
168
|
+
seen.add(key);
|
|
169
|
+
return true;
|
|
170
|
+
}).slice(0, MAX_SELECTIONS);
|
|
171
|
+
}
|
|
172
|
+
function normalizeHistory(history, limit, includeHover) {
|
|
173
|
+
const semantic = [];
|
|
174
|
+
const hovers = [];
|
|
175
|
+
for (const event of history ?? []) {
|
|
176
|
+
const normalized = normalizeEvent(event);
|
|
177
|
+
if (normalized.action !== "hover") semantic.push(normalized);
|
|
178
|
+
else if (includeHover) {
|
|
179
|
+
const previous = hovers.at(-1);
|
|
180
|
+
if (previous && selectionKey(previous.target) === selectionKey(normalized.target))
|
|
181
|
+
hovers[hovers.length - 1] = normalized;
|
|
182
|
+
else hovers.push(normalized);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
const retainedSemantic = semantic.slice(-limit);
|
|
186
|
+
const remaining = Math.max(0, limit - retainedSemantic.length);
|
|
187
|
+
const hoverLimit = Math.min(MAX_HOVER_HISTORY, remaining);
|
|
188
|
+
const retainedHover = hoverLimit > 0 ? hovers.slice(-hoverLimit) : [];
|
|
189
|
+
return [...retainedSemantic, ...retainedHover].sort(
|
|
190
|
+
(left, right) => left.timestamp - right.timestamp
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
function normalizeEvent(event) {
|
|
194
|
+
return {
|
|
195
|
+
action: event.action,
|
|
196
|
+
target: normalizeSelection(event.target, true),
|
|
197
|
+
timestamp: event.timestamp,
|
|
198
|
+
...event.summary === void 0 ? {} : { summary: event.summary },
|
|
199
|
+
...event.detail === void 0 ? {} : { detail: sortRecord(event.detail) }
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
function normalizeSelection(selection, includeOptional) {
|
|
203
|
+
return {
|
|
204
|
+
kind: selection.kind,
|
|
205
|
+
id: selection.id,
|
|
206
|
+
...includeOptional && selection.label !== void 0 ? { label: selection.label } : {},
|
|
207
|
+
...includeOptional && selection.detail !== void 0 ? { detail: sortRecord(selection.detail) } : {}
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
function removeOptionalSelectionFields(context) {
|
|
211
|
+
const selections = [
|
|
212
|
+
context.selection,
|
|
213
|
+
...context.selections ?? [],
|
|
214
|
+
context.hover
|
|
215
|
+
].filter((selection) => Boolean(selection));
|
|
216
|
+
for (const selection of selections.reverse()) {
|
|
217
|
+
for (const key of Object.keys(selection.detail ?? {}).reverse()) {
|
|
218
|
+
if (!selection.detail) break;
|
|
219
|
+
delete selection.detail[key];
|
|
220
|
+
if (Object.keys(selection.detail).length === 0) delete selection.detail;
|
|
221
|
+
}
|
|
222
|
+
delete selection.label;
|
|
223
|
+
}
|
|
224
|
+
if (context.workspace) delete context.workspace.label;
|
|
225
|
+
}
|
|
226
|
+
function truncateRequiredFields(context) {
|
|
227
|
+
return {
|
|
228
|
+
application: truncateText(context.application),
|
|
229
|
+
route: truncateText(context.route),
|
|
230
|
+
...context.workspace ? {
|
|
231
|
+
workspace: {
|
|
232
|
+
kind: truncateText(context.workspace.kind),
|
|
233
|
+
id: truncateText(context.workspace.id),
|
|
234
|
+
...context.workspace.revision === void 0 ? {} : { revision: context.workspace.revision }
|
|
235
|
+
}
|
|
236
|
+
} : {},
|
|
237
|
+
...context.selection ? {
|
|
238
|
+
selection: {
|
|
239
|
+
kind: truncateText(context.selection.kind),
|
|
240
|
+
id: truncateText(context.selection.id)
|
|
241
|
+
}
|
|
242
|
+
} : {},
|
|
243
|
+
...context.selections ? {
|
|
244
|
+
selections: context.selections.slice(0, MAX_SELECTIONS).map((item) => ({
|
|
245
|
+
kind: truncateText(item.kind),
|
|
246
|
+
id: truncateText(item.id)
|
|
247
|
+
}))
|
|
248
|
+
} : {}
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
function validateWorkspace(value, path) {
|
|
252
|
+
if (!isRecord(value)) return [issue("Expected a workspace", ...path)];
|
|
253
|
+
const issues = [];
|
|
254
|
+
if (!hasOnlyKeys(value, ["kind", "id", "label", "revision"]))
|
|
255
|
+
issues.push(issue("Unexpected workspace field", ...path));
|
|
256
|
+
for (const key of ["kind", "id"])
|
|
257
|
+
if (typeof value[key] !== "string")
|
|
258
|
+
issues.push(issue("Expected a string", ...path, key));
|
|
259
|
+
if (value.label !== void 0 && typeof value.label !== "string")
|
|
260
|
+
issues.push(issue("Expected a string", ...path, "label"));
|
|
261
|
+
if (value.revision !== void 0 && (typeof value.revision !== "number" || !Number.isFinite(value.revision)))
|
|
262
|
+
issues.push(issue("Expected a finite revision", ...path, "revision"));
|
|
263
|
+
return issues;
|
|
264
|
+
}
|
|
265
|
+
function validateSelection(value, path) {
|
|
266
|
+
if (!isRecord(value)) return [issue("Expected a selection", ...path)];
|
|
267
|
+
const issues = [];
|
|
268
|
+
if (!hasOnlyKeys(value, ["kind", "id", "label", "detail"]))
|
|
269
|
+
issues.push(issue("Unexpected selection field", ...path));
|
|
270
|
+
for (const key of ["kind", "id"])
|
|
271
|
+
if (typeof value[key] !== "string")
|
|
272
|
+
issues.push(issue("Expected a string", ...path, key));
|
|
273
|
+
if (value.label !== void 0 && typeof value.label !== "string")
|
|
274
|
+
issues.push(issue("Expected a string", ...path, "label"));
|
|
275
|
+
issues.push(...validateStringRecord(value.detail, [...path, "detail"]));
|
|
276
|
+
return issues;
|
|
277
|
+
}
|
|
278
|
+
function validateActivity(value, path) {
|
|
279
|
+
if (!isRecord(value)) return [issue("Expected activity", ...path)];
|
|
280
|
+
const issues = [];
|
|
281
|
+
if (!hasOnlyKeys(value, ["action", "target", "timestamp", "summary", "detail"]))
|
|
282
|
+
issues.push(issue("Unexpected activity field", ...path));
|
|
283
|
+
if (typeof value.action !== "string" || !ATTENTION_ACTIVITY_ACTIONS.includes(
|
|
284
|
+
value.action
|
|
285
|
+
))
|
|
286
|
+
issues.push(issue("Expected an action", ...path, "action"));
|
|
287
|
+
if (typeof value.timestamp !== "number" || !Number.isFinite(value.timestamp))
|
|
288
|
+
issues.push(issue("Expected a finite timestamp", ...path, "timestamp"));
|
|
289
|
+
if (value.summary !== void 0 && typeof value.summary !== "string")
|
|
290
|
+
issues.push(issue("Expected a string", ...path, "summary"));
|
|
291
|
+
issues.push(...validateSelection(value.target, [...path, "target"]));
|
|
292
|
+
issues.push(...validateStringRecord(value.detail, [...path, "detail"]));
|
|
293
|
+
return issues;
|
|
294
|
+
}
|
|
295
|
+
function validateStringRecord(value, path) {
|
|
296
|
+
if (value === void 0) return [];
|
|
297
|
+
if (!isRecord(value) || !Object.values(value).every((item) => typeof item === "string"))
|
|
298
|
+
return [issue("Expected string details", ...path)];
|
|
299
|
+
return [];
|
|
300
|
+
}
|
|
301
|
+
function assertClosedAttention(value) {
|
|
302
|
+
const issues = validateAttentionContext(value);
|
|
303
|
+
if (issues.length > 0)
|
|
304
|
+
throw new Error(`Invalid attention serialization: ${issues[0]?.message}`);
|
|
305
|
+
}
|
|
306
|
+
function standardSchema(validate) {
|
|
307
|
+
return {
|
|
308
|
+
"~standard": {
|
|
309
|
+
version: 1,
|
|
310
|
+
vendor: "sigil-agent",
|
|
311
|
+
validate(value) {
|
|
312
|
+
const result = validate(value);
|
|
313
|
+
return Array.isArray(result) ? { issues: result } : { value: result };
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
function issue(message, ...path) {
|
|
319
|
+
return { message, path };
|
|
320
|
+
}
|
|
321
|
+
function isRecord(value) {
|
|
322
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
323
|
+
}
|
|
324
|
+
function hasOnlyKeys(value, allowed) {
|
|
325
|
+
const keys = new Set(allowed);
|
|
326
|
+
return Object.keys(value).every((key) => keys.has(key));
|
|
327
|
+
}
|
|
328
|
+
function sortRecord(record) {
|
|
329
|
+
return Object.fromEntries(
|
|
330
|
+
Object.entries(record).sort(([left], [right]) => left.localeCompare(right))
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
function selectionKey(selection) {
|
|
334
|
+
return `${selection.kind}\0${selection.id}`;
|
|
335
|
+
}
|
|
336
|
+
function truncateText(value) {
|
|
337
|
+
return value.length <= MAX_REQUIRED_FIELD_LENGTH ? value : `${value.slice(0, MAX_REQUIRED_FIELD_LENGTH - 1)}\u2026`;
|
|
338
|
+
}
|
|
339
|
+
function byteLength(value) {
|
|
340
|
+
return new TextEncoder().encode(value).byteLength;
|
|
341
|
+
}
|
|
342
|
+
function warnOversizedAttention() {
|
|
343
|
+
const isDevelopment = import.meta.env?.DEV === true;
|
|
344
|
+
if (isDevelopment)
|
|
345
|
+
console.warn(
|
|
346
|
+
"Agent attention exceeded 4 KB; optional context was removed before sending."
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// src/attention-telemetry.ts
|
|
351
|
+
import { useCallback, useEffect, useReducer, useRef } from "react";
|
|
352
|
+
var DEFAULT_HOVER_DELAY_MS = 500;
|
|
353
|
+
var DEFAULT_HISTORY_LIMIT = 40;
|
|
354
|
+
function createAttentionTelemetryState(initialSelections = []) {
|
|
355
|
+
return {
|
|
356
|
+
selections: deduplicateTargets(initialSelections),
|
|
357
|
+
history: []
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
function reduceAttentionTelemetry(state, action) {
|
|
361
|
+
switch (action.type) {
|
|
362
|
+
case "select": {
|
|
363
|
+
const exists = state.selections.some(
|
|
364
|
+
(item) => sameAttentionTarget(item, action.target)
|
|
365
|
+
);
|
|
366
|
+
const selections = action.mode === "replace" ? [action.target] : exists ? state.selections.filter(
|
|
367
|
+
(item) => !sameAttentionTarget(item, action.target)
|
|
368
|
+
) : [action.target, ...state.selections];
|
|
369
|
+
return {
|
|
370
|
+
...state,
|
|
371
|
+
selections,
|
|
372
|
+
history: appendBoundedActivity(
|
|
373
|
+
state.history,
|
|
374
|
+
action.event,
|
|
375
|
+
action.historyLimit
|
|
376
|
+
)
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
case "record":
|
|
380
|
+
return {
|
|
381
|
+
...state,
|
|
382
|
+
history: appendBoundedActivity(
|
|
383
|
+
state.history,
|
|
384
|
+
action.event,
|
|
385
|
+
action.historyLimit
|
|
386
|
+
)
|
|
387
|
+
};
|
|
388
|
+
case "hover":
|
|
389
|
+
return {
|
|
390
|
+
...state,
|
|
391
|
+
hover: action.target,
|
|
392
|
+
history: appendBoundedActivity(
|
|
393
|
+
state.history,
|
|
394
|
+
action.event,
|
|
395
|
+
action.historyLimit
|
|
396
|
+
)
|
|
397
|
+
};
|
|
398
|
+
case "end-hover": {
|
|
399
|
+
const { hover: _hover, ...withoutHover } = state;
|
|
400
|
+
return withoutHover;
|
|
401
|
+
}
|
|
402
|
+
case "clear-selections":
|
|
403
|
+
return { ...state, selections: [] };
|
|
404
|
+
case "clear-history":
|
|
405
|
+
return { ...state, history: [] };
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
function useAttentionTelemetry({
|
|
409
|
+
initialSelections = [],
|
|
410
|
+
hoverDelayMs = DEFAULT_HOVER_DELAY_MS,
|
|
411
|
+
historyLimit = DEFAULT_HISTORY_LIMIT
|
|
412
|
+
} = {}) {
|
|
413
|
+
const [state, dispatch] = useReducer(
|
|
414
|
+
reduceAttentionTelemetry,
|
|
415
|
+
initialSelections,
|
|
416
|
+
createAttentionTelemetryState
|
|
417
|
+
);
|
|
418
|
+
const hoverTimer = useRef(
|
|
419
|
+
void 0
|
|
420
|
+
);
|
|
421
|
+
const hoverTarget = useRef(void 0);
|
|
422
|
+
useEffect(
|
|
423
|
+
() => () => {
|
|
424
|
+
if (hoverTimer.current) clearTimeout(hoverTimer.current);
|
|
425
|
+
},
|
|
426
|
+
[]
|
|
427
|
+
);
|
|
428
|
+
const recordActivity = useCallback(
|
|
429
|
+
(action, target, options = {}) => {
|
|
430
|
+
dispatch({
|
|
431
|
+
type: "record",
|
|
432
|
+
event: createActivity(action, target, options),
|
|
433
|
+
historyLimit
|
|
434
|
+
});
|
|
435
|
+
},
|
|
436
|
+
[historyLimit]
|
|
437
|
+
);
|
|
438
|
+
const select = useCallback(
|
|
439
|
+
(target) => {
|
|
440
|
+
dispatch({
|
|
441
|
+
type: "select",
|
|
442
|
+
target,
|
|
443
|
+
mode: "replace",
|
|
444
|
+
event: createActivity("select", target),
|
|
445
|
+
historyLimit
|
|
446
|
+
});
|
|
447
|
+
},
|
|
448
|
+
[historyLimit]
|
|
449
|
+
);
|
|
450
|
+
const toggleSelection = useCallback(
|
|
451
|
+
(target) => {
|
|
452
|
+
dispatch({
|
|
453
|
+
type: "select",
|
|
454
|
+
target,
|
|
455
|
+
mode: "toggle",
|
|
456
|
+
event: createActivity("select", target),
|
|
457
|
+
historyLimit
|
|
458
|
+
});
|
|
459
|
+
},
|
|
460
|
+
[historyLimit]
|
|
461
|
+
);
|
|
462
|
+
const clearSelections = useCallback(
|
|
463
|
+
() => dispatch({ type: "clear-selections" }),
|
|
464
|
+
[]
|
|
465
|
+
);
|
|
466
|
+
const beginHover = useCallback(
|
|
467
|
+
(target) => {
|
|
468
|
+
if (hoverTimer.current) clearTimeout(hoverTimer.current);
|
|
469
|
+
hoverTarget.current = target;
|
|
470
|
+
hoverTimer.current = setTimeout(() => {
|
|
471
|
+
if (!hoverTarget.current || !sameAttentionTarget(hoverTarget.current, target))
|
|
472
|
+
return;
|
|
473
|
+
dispatch({
|
|
474
|
+
type: "hover",
|
|
475
|
+
target,
|
|
476
|
+
event: createActivity("hover", target),
|
|
477
|
+
historyLimit
|
|
478
|
+
});
|
|
479
|
+
}, hoverDelayMs);
|
|
480
|
+
},
|
|
481
|
+
[historyLimit, hoverDelayMs]
|
|
482
|
+
);
|
|
483
|
+
const endHover = useCallback((target) => {
|
|
484
|
+
if (target && hoverTarget.current && !sameAttentionTarget(target, hoverTarget.current))
|
|
485
|
+
return;
|
|
486
|
+
if (hoverTimer.current) clearTimeout(hoverTimer.current);
|
|
487
|
+
hoverTimer.current = void 0;
|
|
488
|
+
hoverTarget.current = void 0;
|
|
489
|
+
dispatch({ type: "end-hover" });
|
|
490
|
+
}, []);
|
|
491
|
+
const clearHistory = useCallback(
|
|
492
|
+
() => dispatch({ type: "clear-history" }),
|
|
493
|
+
[]
|
|
494
|
+
);
|
|
495
|
+
return {
|
|
496
|
+
selection: state.selections[0],
|
|
497
|
+
selections: state.selections,
|
|
498
|
+
hover: state.hover,
|
|
499
|
+
history: state.history,
|
|
500
|
+
select,
|
|
501
|
+
toggleSelection,
|
|
502
|
+
clearSelections,
|
|
503
|
+
beginHover,
|
|
504
|
+
endHover,
|
|
505
|
+
recordActivity,
|
|
506
|
+
clearHistory
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
function appendBoundedActivity(history, event, limit = DEFAULT_HISTORY_LIMIT) {
|
|
510
|
+
const withoutDuplicateHover = event.action === "hover" ? history.filter(
|
|
511
|
+
(item) => item.action !== "hover" || !sameAttentionTarget(item.target, event.target)
|
|
512
|
+
) : [...history];
|
|
513
|
+
const next = [...withoutDuplicateHover, event];
|
|
514
|
+
const hoverIndexes = next.map((item, index) => item.action === "hover" ? index : -1).filter((index) => index >= 0);
|
|
515
|
+
for (const index of hoverIndexes.slice(0, -2).reverse())
|
|
516
|
+
next.splice(index, 1);
|
|
517
|
+
while (next.length > Math.max(0, limit)) {
|
|
518
|
+
const firstHover = next.findIndex((item) => item.action === "hover");
|
|
519
|
+
next.splice(firstHover >= 0 ? firstHover : 0, 1);
|
|
520
|
+
}
|
|
521
|
+
return next;
|
|
522
|
+
}
|
|
523
|
+
function sameAttentionTarget(left, right) {
|
|
524
|
+
return left.kind === right.kind && left.id === right.id;
|
|
525
|
+
}
|
|
526
|
+
function createActivity(action, target, {
|
|
527
|
+
timestamp = Date.now(),
|
|
528
|
+
summary,
|
|
529
|
+
detail
|
|
530
|
+
} = {}) {
|
|
531
|
+
return {
|
|
532
|
+
action,
|
|
533
|
+
target,
|
|
534
|
+
timestamp,
|
|
535
|
+
...summary === void 0 ? {} : { summary },
|
|
536
|
+
...detail === void 0 ? {} : { detail }
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
function deduplicateTargets(selections) {
|
|
540
|
+
const seen = /* @__PURE__ */ new Set();
|
|
541
|
+
return selections.filter((selection) => {
|
|
542
|
+
const key = `${selection.kind}\0${selection.id}`;
|
|
543
|
+
if (seen.has(key)) return false;
|
|
544
|
+
seen.add(key);
|
|
545
|
+
return true;
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// src/context-draft.ts
|
|
550
|
+
import { useSyncExternalStore } from "react";
|
|
551
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
552
|
+
var drafts = /* @__PURE__ */ new Map();
|
|
553
|
+
var activeDraftScope = "default";
|
|
554
|
+
var MAX_ATTACHMENTS = 6;
|
|
555
|
+
var MAX_ATTACHMENT_TEXT_LENGTH = 192;
|
|
556
|
+
var MAX_CONTEXT_DRAFT_BYTES = 8 * 1024;
|
|
557
|
+
var ContextAttachmentSchema = standardSchema2(
|
|
558
|
+
(value) => {
|
|
559
|
+
const issues = validateAttachment(value);
|
|
560
|
+
return issues.length > 0 ? issues : value;
|
|
561
|
+
}
|
|
562
|
+
);
|
|
563
|
+
var ContextDraftPayloadSchema = standardSchema2(
|
|
564
|
+
(value) => {
|
|
565
|
+
const issues = validateDraftPayload(value);
|
|
566
|
+
return issues.length > 0 ? issues : value;
|
|
567
|
+
}
|
|
568
|
+
);
|
|
569
|
+
function attentionSelectionKey(selection) {
|
|
570
|
+
return `selection:${selection.kind}:${selection.id}`;
|
|
571
|
+
}
|
|
572
|
+
function attentionHistoryKey(event) {
|
|
573
|
+
return `history:${event.timestamp}:${event.action}:${event.target.kind}:${event.target.id}`;
|
|
574
|
+
}
|
|
575
|
+
function getContextDraftScope() {
|
|
576
|
+
return activeDraftScope;
|
|
577
|
+
}
|
|
578
|
+
function getAttentionExclusions() {
|
|
579
|
+
return getDraftState().exclusions;
|
|
580
|
+
}
|
|
581
|
+
function getTurnContextAttachments() {
|
|
582
|
+
return getDraftState().attachments;
|
|
583
|
+
}
|
|
584
|
+
function setContextDraftScope(scopeId) {
|
|
585
|
+
const normalized = scopeId.trim();
|
|
586
|
+
if (!normalized) throw new Error("Context draft scope must be non-empty");
|
|
587
|
+
if (activeDraftScope === normalized) return;
|
|
588
|
+
activeDraftScope = normalized;
|
|
589
|
+
getDraftState();
|
|
590
|
+
emitChange();
|
|
591
|
+
}
|
|
592
|
+
function addContextAttachment(attachment) {
|
|
593
|
+
const validated = validateAttachment(attachment);
|
|
594
|
+
if (validated.length > 0)
|
|
595
|
+
throw new Error(`Invalid context attachment: ${validated[0]?.message}`);
|
|
596
|
+
const normalized = normalizeAttachment(attachment, "expanded");
|
|
597
|
+
const state = getDraftState();
|
|
598
|
+
state.attachments = [
|
|
599
|
+
...state.attachments.filter((item) => item.id !== normalized.id),
|
|
600
|
+
normalized
|
|
601
|
+
].slice(-MAX_ATTACHMENTS);
|
|
602
|
+
emitChange();
|
|
603
|
+
}
|
|
604
|
+
function addTurnContextAttachment(selection, retention = "turn") {
|
|
605
|
+
addContextAttachment({
|
|
606
|
+
id: attentionSelectionKey(selection),
|
|
607
|
+
source: "application-selection",
|
|
608
|
+
inclusion: "user-added",
|
|
609
|
+
resource: { kind: selection.kind, id: selection.id },
|
|
610
|
+
label: truncateAttachmentText(selection.label ?? selection.id),
|
|
611
|
+
...selection.detail ? {
|
|
612
|
+
summary: truncateAttachmentText(
|
|
613
|
+
Object.entries(selection.detail).sort(([left], [right]) => left.localeCompare(right)).map(([key, value]) => `${key}: ${value}`).join(" \xB7 ")
|
|
614
|
+
)
|
|
615
|
+
} : {},
|
|
616
|
+
retention
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
function removeTurnContextAttachment(id) {
|
|
620
|
+
const state = getDraftState();
|
|
621
|
+
const next = state.attachments.filter((item) => item.id !== id);
|
|
622
|
+
if (next.length === state.attachments.length) return;
|
|
623
|
+
state.attachments = next;
|
|
624
|
+
emitChange();
|
|
625
|
+
}
|
|
626
|
+
function moveTurnContextAttachment(id, direction) {
|
|
627
|
+
const state = getDraftState();
|
|
628
|
+
const index = state.attachments.findIndex((item) => item.id === id);
|
|
629
|
+
const target = index + direction;
|
|
630
|
+
if (index < 0 || target < 0 || target >= state.attachments.length) return;
|
|
631
|
+
const next = [...state.attachments];
|
|
632
|
+
[next[index], next[target]] = [next[target], next[index]];
|
|
633
|
+
state.attachments = next;
|
|
634
|
+
emitChange();
|
|
635
|
+
}
|
|
636
|
+
function setTurnContextAttachmentRetention(id, retention) {
|
|
637
|
+
let changed = false;
|
|
638
|
+
const state = getDraftState();
|
|
639
|
+
state.attachments = state.attachments.map((item) => {
|
|
640
|
+
if (item.id !== id || item.retention === retention) return item;
|
|
641
|
+
changed = true;
|
|
642
|
+
return { ...item, retention };
|
|
643
|
+
});
|
|
644
|
+
if (changed) emitChange();
|
|
645
|
+
}
|
|
646
|
+
function clearTurnContextAttachments() {
|
|
647
|
+
const state = getDraftState();
|
|
648
|
+
const next = state.attachments.filter((item) => item.retention === "session");
|
|
649
|
+
if (next.length === state.attachments.length) return;
|
|
650
|
+
state.attachments = next;
|
|
651
|
+
emitChange();
|
|
652
|
+
}
|
|
653
|
+
function clearContextDraft() {
|
|
654
|
+
const state = getDraftState();
|
|
655
|
+
if (state.exclusions.length === 0 && state.attachments.length === 0) return;
|
|
656
|
+
state.exclusions = [];
|
|
657
|
+
state.attachments = [];
|
|
658
|
+
emitChange();
|
|
659
|
+
}
|
|
660
|
+
function resetContextDraftForTests() {
|
|
661
|
+
drafts.clear();
|
|
662
|
+
activeDraftScope = "default";
|
|
663
|
+
emitChange();
|
|
664
|
+
}
|
|
665
|
+
function setAttentionItemExcluded(key, excluded) {
|
|
666
|
+
const state = getDraftState();
|
|
667
|
+
const next = new Set(state.exclusions);
|
|
668
|
+
if (excluded) next.add(key);
|
|
669
|
+
else next.delete(key);
|
|
670
|
+
state.exclusions = [...next].sort();
|
|
671
|
+
emitChange();
|
|
672
|
+
}
|
|
673
|
+
function clearAttentionExclusions() {
|
|
674
|
+
const state = getDraftState();
|
|
675
|
+
if (state.exclusions.length === 0) return;
|
|
676
|
+
state.exclusions = [];
|
|
677
|
+
emitChange();
|
|
678
|
+
}
|
|
679
|
+
function useAttentionExclusions() {
|
|
680
|
+
return useSyncExternalStore(
|
|
681
|
+
subscribeContextDraft,
|
|
682
|
+
getAttentionExclusions,
|
|
683
|
+
getAttentionExclusions
|
|
684
|
+
);
|
|
685
|
+
}
|
|
686
|
+
function useTurnContextAttachments() {
|
|
687
|
+
return useSyncExternalStore(
|
|
688
|
+
subscribeContextDraft,
|
|
689
|
+
getTurnContextAttachments,
|
|
690
|
+
getTurnContextAttachments
|
|
691
|
+
);
|
|
692
|
+
}
|
|
693
|
+
function serializeAttentionDraft(context, privacy, excludedKeys = getAttentionExclusions(), contextAttachments = getTurnContextAttachments()) {
|
|
694
|
+
const attention = context ? JSON.parse(
|
|
695
|
+
serializeAttention(
|
|
696
|
+
applyAttentionExclusions(context, excludedKeys),
|
|
697
|
+
privacy
|
|
698
|
+
)
|
|
699
|
+
) : {};
|
|
700
|
+
const normalizedAttachments = contextAttachments.slice(0, MAX_ATTACHMENTS).map((attachment) => normalizeAttachment(attachment, privacy));
|
|
701
|
+
const serialized = fitContextPayload(attention, normalizedAttachments);
|
|
702
|
+
const parsed = JSON.parse(serialized);
|
|
703
|
+
const issues = validateDraftPayload(parsed);
|
|
704
|
+
if (issues.length > 0)
|
|
705
|
+
throw new Error(
|
|
706
|
+
`Invalid context draft serialization: ${issues[0]?.message}`
|
|
707
|
+
);
|
|
708
|
+
return serialized;
|
|
709
|
+
}
|
|
710
|
+
function createAttentionContextPreview(context, privacy, excludedKeys = getAttentionExclusions(), contextAttachments = getTurnContextAttachments()) {
|
|
711
|
+
const serialized = serializeAttentionDraft(
|
|
712
|
+
context,
|
|
713
|
+
privacy,
|
|
714
|
+
excludedKeys,
|
|
715
|
+
contextAttachments
|
|
716
|
+
);
|
|
717
|
+
const normalized = JSON.parse(serialized);
|
|
718
|
+
const selections = normalized.selections?.length ? normalized.selections : normalized.selection ? [normalized.selection] : [];
|
|
719
|
+
const history = normalized.history ?? [];
|
|
720
|
+
const normalizedAttachments = normalized.attachments ?? [];
|
|
721
|
+
const truncatedAttachmentCount = Math.max(
|
|
722
|
+
0,
|
|
723
|
+
contextAttachments.length - normalizedAttachments.length
|
|
724
|
+
);
|
|
725
|
+
const byteLength3 = new TextEncoder().encode(serialized).byteLength;
|
|
726
|
+
const parts = [
|
|
727
|
+
`${selections.length} selection${selections.length === 1 ? "" : "s"}`,
|
|
728
|
+
`${normalizedAttachments.length} attachment${normalizedAttachments.length === 1 ? "" : "s"}`,
|
|
729
|
+
`${history.length} recent action${history.length === 1 ? "" : "s"}`
|
|
730
|
+
];
|
|
731
|
+
return {
|
|
732
|
+
attachmentCount: normalizedAttachments.length,
|
|
733
|
+
attachments: normalizedAttachments,
|
|
734
|
+
byteLength: byteLength3,
|
|
735
|
+
estimatedTokens: Math.ceil(byteLength3 / 4),
|
|
736
|
+
formatted: JSON.stringify(normalized, null, 2),
|
|
737
|
+
history,
|
|
738
|
+
selectionCount: selections.length,
|
|
739
|
+
selections,
|
|
740
|
+
serialized,
|
|
741
|
+
summary: parts.join(" \xB7 "),
|
|
742
|
+
truncatedAttachmentCount
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
function applyAttentionExclusions(context, excludedKeys) {
|
|
746
|
+
if (excludedKeys.length === 0) return context;
|
|
747
|
+
const excluded = new Set(excludedKeys);
|
|
748
|
+
const selectionCandidates = deduplicateSelections([
|
|
749
|
+
...context.selection ? [context.selection] : [],
|
|
750
|
+
...context.selections ?? []
|
|
751
|
+
]);
|
|
752
|
+
const selections = selectionCandidates.filter(
|
|
753
|
+
(selection) => !excluded.has(attentionSelectionKey(selection))
|
|
754
|
+
);
|
|
755
|
+
const excludedTargets = new Set(
|
|
756
|
+
selectionCandidates.filter((selection) => excluded.has(attentionSelectionKey(selection))).map((selection) => `${selection.kind}\0${selection.id}`)
|
|
757
|
+
);
|
|
758
|
+
const history = (context.history ?? []).filter(
|
|
759
|
+
(event) => !excluded.has(attentionHistoryKey(event)) && !excludedTargets.has(`${event.target.kind}\0${event.target.id}`)
|
|
760
|
+
);
|
|
761
|
+
const hover = context.hover && !excluded.has(attentionSelectionKey(context.hover)) && !excludedTargets.has(`${context.hover.kind}\0${context.hover.id}`) ? context.hover : void 0;
|
|
762
|
+
return {
|
|
763
|
+
...context,
|
|
764
|
+
selection: selections[0],
|
|
765
|
+
selections: selections.length > 0 ? selections : void 0,
|
|
766
|
+
history: history.length > 0 ? history : void 0,
|
|
767
|
+
hover
|
|
768
|
+
};
|
|
769
|
+
}
|
|
770
|
+
function deduplicateSelections(selections) {
|
|
771
|
+
const seen = /* @__PURE__ */ new Set();
|
|
772
|
+
return selections.filter((selection) => {
|
|
773
|
+
const key = `${selection.kind}\0${selection.id}`;
|
|
774
|
+
if (seen.has(key)) return false;
|
|
775
|
+
seen.add(key);
|
|
776
|
+
return true;
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
function subscribeContextDraft(listener) {
|
|
780
|
+
listeners.add(listener);
|
|
781
|
+
return () => listeners.delete(listener);
|
|
782
|
+
}
|
|
783
|
+
function emitChange() {
|
|
784
|
+
listeners.forEach((listener) => listener());
|
|
785
|
+
}
|
|
786
|
+
function getDraftState() {
|
|
787
|
+
let state = drafts.get(activeDraftScope);
|
|
788
|
+
if (!state) {
|
|
789
|
+
state = { attachments: [], exclusions: [] };
|
|
790
|
+
drafts.set(activeDraftScope, state);
|
|
791
|
+
}
|
|
792
|
+
return state;
|
|
793
|
+
}
|
|
794
|
+
function normalizeAttachment(attachment, privacy) {
|
|
795
|
+
return {
|
|
796
|
+
id: truncateAttachmentText(attachment.id),
|
|
797
|
+
source: attachment.source,
|
|
798
|
+
inclusion: attachment.inclusion,
|
|
799
|
+
resource: {
|
|
800
|
+
kind: truncateAttachmentText(attachment.resource.kind),
|
|
801
|
+
id: truncateAttachmentText(attachment.resource.id)
|
|
802
|
+
},
|
|
803
|
+
label: privacy === "minimal" ? truncateAttachmentText(attachment.resource.id) : truncateAttachmentText(attachment.label),
|
|
804
|
+
...privacy === "minimal" || attachment.summary === void 0 ? {} : { summary: truncateAttachmentText(attachment.summary) },
|
|
805
|
+
retention: attachment.retention
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
function fitContextPayload(attention, contextAttachments) {
|
|
809
|
+
const included = contextAttachments.map((attachment) => ({ ...attachment }));
|
|
810
|
+
let serialized = serializeContextPayload(attention, included);
|
|
811
|
+
if (byteLength2(serialized) <= MAX_CONTEXT_DRAFT_BYTES) return serialized;
|
|
812
|
+
for (let index = included.length - 1; index >= 0; index -= 1) {
|
|
813
|
+
if (included[index]?.summary === void 0) continue;
|
|
814
|
+
delete included[index].summary;
|
|
815
|
+
serialized = serializeContextPayload(attention, included);
|
|
816
|
+
if (byteLength2(serialized) <= MAX_CONTEXT_DRAFT_BYTES) return serialized;
|
|
817
|
+
}
|
|
818
|
+
while (included.length > 0) {
|
|
819
|
+
included.pop();
|
|
820
|
+
serialized = serializeContextPayload(attention, included);
|
|
821
|
+
if (byteLength2(serialized) <= MAX_CONTEXT_DRAFT_BYTES) return serialized;
|
|
822
|
+
}
|
|
823
|
+
return serialized;
|
|
824
|
+
}
|
|
825
|
+
function serializeContextPayload(attention, included) {
|
|
826
|
+
return JSON.stringify({
|
|
827
|
+
...attention,
|
|
828
|
+
...included.length > 0 ? { attachments: included } : {}
|
|
829
|
+
});
|
|
830
|
+
}
|
|
831
|
+
function validateDraftPayload(value) {
|
|
832
|
+
if (!isRecord2(value)) return [issue2("Expected a context draft payload")];
|
|
833
|
+
const { attachments, ...attention } = value;
|
|
834
|
+
const issues = [];
|
|
835
|
+
if (Object.keys(attention).length > 0)
|
|
836
|
+
issues.push(...validateAttentionContext(attention));
|
|
837
|
+
if (attachments !== void 0) {
|
|
838
|
+
if (!Array.isArray(attachments))
|
|
839
|
+
issues.push(issue2("Expected attachments", "attachments"));
|
|
840
|
+
else
|
|
841
|
+
attachments.forEach(
|
|
842
|
+
(attachment, index) => issues.push(...validateAttachment(attachment, ["attachments", index]))
|
|
843
|
+
);
|
|
844
|
+
}
|
|
845
|
+
return issues;
|
|
846
|
+
}
|
|
847
|
+
function validateAttachment(value, path = []) {
|
|
848
|
+
if (!isRecord2(value)) return [issue2("Expected an attachment", ...path)];
|
|
849
|
+
const issues = [];
|
|
850
|
+
if (!hasOnlyKeys2(value, [
|
|
851
|
+
"id",
|
|
852
|
+
"source",
|
|
853
|
+
"inclusion",
|
|
854
|
+
"resource",
|
|
855
|
+
"label",
|
|
856
|
+
"summary",
|
|
857
|
+
"retention"
|
|
858
|
+
]))
|
|
859
|
+
issues.push(issue2("Unexpected attachment field", ...path));
|
|
860
|
+
for (const key of ["id", "label"])
|
|
861
|
+
if (typeof value[key] !== "string")
|
|
862
|
+
issues.push(issue2("Expected a string", ...path, key));
|
|
863
|
+
if (value.summary !== void 0 && typeof value.summary !== "string")
|
|
864
|
+
issues.push(issue2("Expected a string", ...path, "summary"));
|
|
865
|
+
if (value.source !== "application-selection" && value.source !== "semantic-fork" && value.source !== "agent-requested")
|
|
866
|
+
issues.push(issue2("Expected a supported source", ...path, "source"));
|
|
867
|
+
if (value.inclusion !== "user-added" && value.inclusion !== "automatic" && value.inclusion !== "agent-requested")
|
|
868
|
+
issues.push(issue2("Expected a supported inclusion", ...path, "inclusion"));
|
|
869
|
+
if (value.retention !== "turn" && value.retention !== "session")
|
|
870
|
+
issues.push(
|
|
871
|
+
issue2("Expected turn or session retention", ...path, "retention")
|
|
872
|
+
);
|
|
873
|
+
if (!isRecord2(value.resource))
|
|
874
|
+
issues.push(issue2("Expected a resource", ...path, "resource"));
|
|
875
|
+
else {
|
|
876
|
+
if (!hasOnlyKeys2(value.resource, ["kind", "id"]))
|
|
877
|
+
issues.push(issue2("Unexpected resource field", ...path, "resource"));
|
|
878
|
+
for (const key of ["kind", "id"])
|
|
879
|
+
if (typeof value.resource[key] !== "string")
|
|
880
|
+
issues.push(issue2("Expected a string", ...path, "resource", key));
|
|
881
|
+
}
|
|
882
|
+
return issues;
|
|
883
|
+
}
|
|
884
|
+
function standardSchema2(validate) {
|
|
885
|
+
return {
|
|
886
|
+
"~standard": {
|
|
887
|
+
version: 1,
|
|
888
|
+
vendor: "sigil-agent",
|
|
889
|
+
validate(value) {
|
|
890
|
+
const result = validate(value);
|
|
891
|
+
return Array.isArray(result) ? { issues: result } : { value: result };
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
};
|
|
895
|
+
}
|
|
896
|
+
function issue2(message, ...path) {
|
|
897
|
+
return { message, path };
|
|
898
|
+
}
|
|
899
|
+
function isRecord2(value) {
|
|
900
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
901
|
+
}
|
|
902
|
+
function hasOnlyKeys2(value, allowed) {
|
|
903
|
+
const keys = new Set(allowed);
|
|
904
|
+
return Object.keys(value).every((key) => keys.has(key));
|
|
905
|
+
}
|
|
906
|
+
function truncateAttachmentText(value) {
|
|
907
|
+
return value.length <= MAX_ATTACHMENT_TEXT_LENGTH ? value : `${value.slice(0, MAX_ATTACHMENT_TEXT_LENGTH - 1)}\u2026`;
|
|
908
|
+
}
|
|
909
|
+
function byteLength2(value) {
|
|
910
|
+
return new TextEncoder().encode(value).byteLength;
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
// src/context-privacy.ts
|
|
914
|
+
import { useSyncExternalStore as useSyncExternalStore2 } from "react";
|
|
915
|
+
var ATTENTION_PRIVACY_STORAGE_KEY = "sigil-agent:attention-privacy";
|
|
916
|
+
var listeners2 = /* @__PURE__ */ new Set();
|
|
917
|
+
var cachedLevel;
|
|
918
|
+
var listeningForStorage = false;
|
|
919
|
+
var AttentionPrivacyLevelSchema = {
|
|
920
|
+
"~standard": {
|
|
921
|
+
version: 1,
|
|
922
|
+
vendor: "sigil-agent",
|
|
923
|
+
validate(value) {
|
|
924
|
+
return isAttentionPrivacyLevel(value) ? { value } : { issues: [{ message: "Expected minimal, focused, or expanded" }] };
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
};
|
|
928
|
+
function getAttentionPrivacyLevel() {
|
|
929
|
+
if (typeof window === "undefined") return "focused";
|
|
930
|
+
if (cachedLevel === void 0)
|
|
931
|
+
cachedLevel = parseAttentionPrivacyLevel(
|
|
932
|
+
window.localStorage.getItem(ATTENTION_PRIVACY_STORAGE_KEY)
|
|
933
|
+
);
|
|
934
|
+
return cachedLevel;
|
|
935
|
+
}
|
|
936
|
+
function setAttentionPrivacyLevel(level) {
|
|
937
|
+
cachedLevel = level;
|
|
938
|
+
if (typeof window !== "undefined")
|
|
939
|
+
window.localStorage.setItem(ATTENTION_PRIVACY_STORAGE_KEY, level);
|
|
940
|
+
emitChange2();
|
|
941
|
+
}
|
|
942
|
+
function useAttentionPrivacyLevel() {
|
|
943
|
+
return useSyncExternalStore2(
|
|
944
|
+
subscribeAttentionPrivacyLevel,
|
|
945
|
+
getAttentionPrivacyLevel,
|
|
946
|
+
() => "focused"
|
|
947
|
+
);
|
|
948
|
+
}
|
|
949
|
+
function subscribeAttentionPrivacyLevel(listener) {
|
|
950
|
+
listeners2.add(listener);
|
|
951
|
+
if (typeof window !== "undefined" && !listeningForStorage) {
|
|
952
|
+
window.addEventListener("storage", handleStorage);
|
|
953
|
+
listeningForStorage = true;
|
|
954
|
+
}
|
|
955
|
+
return () => {
|
|
956
|
+
listeners2.delete(listener);
|
|
957
|
+
if (typeof window !== "undefined" && listeningForStorage && listeners2.size === 0) {
|
|
958
|
+
window.removeEventListener("storage", handleStorage);
|
|
959
|
+
listeningForStorage = false;
|
|
960
|
+
}
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
function parseAttentionPrivacyLevel(value) {
|
|
964
|
+
return value === "minimal" || value === "expanded" ? value : "focused";
|
|
965
|
+
}
|
|
966
|
+
function resetAttentionPrivacyForTests() {
|
|
967
|
+
cachedLevel = void 0;
|
|
968
|
+
listeners2.clear();
|
|
969
|
+
if (typeof window !== "undefined" && listeningForStorage)
|
|
970
|
+
window.removeEventListener("storage", handleStorage);
|
|
971
|
+
listeningForStorage = false;
|
|
972
|
+
}
|
|
973
|
+
function handleStorage(event) {
|
|
974
|
+
if (event.key !== ATTENTION_PRIVACY_STORAGE_KEY) return;
|
|
975
|
+
cachedLevel = parseAttentionPrivacyLevel(event.newValue);
|
|
976
|
+
emitChange2();
|
|
977
|
+
}
|
|
978
|
+
function isAttentionPrivacyLevel(value) {
|
|
979
|
+
return value === "minimal" || value === "focused" || value === "expanded";
|
|
980
|
+
}
|
|
981
|
+
function emitChange2() {
|
|
982
|
+
listeners2.forEach((listener) => listener());
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
// src/session.tsx
|
|
986
|
+
import { createContext as createContext2, useContext as useContext2 } from "react";
|
|
987
|
+
import { jsx } from "react/jsx-runtime";
|
|
988
|
+
var AgentRuntimeSessionContext = createContext2(
|
|
989
|
+
null
|
|
990
|
+
);
|
|
991
|
+
function AgentRuntimeSessionProvider({
|
|
992
|
+
children,
|
|
993
|
+
session
|
|
994
|
+
}) {
|
|
995
|
+
return /* @__PURE__ */ jsx(AgentRuntimeSessionContext.Provider, { value: session, children });
|
|
996
|
+
}
|
|
997
|
+
function useAgentRuntimeSession() {
|
|
998
|
+
const session = useContext2(AgentRuntimeSessionContext);
|
|
999
|
+
if (!session) {
|
|
1000
|
+
throw new Error(
|
|
1001
|
+
"useAgentRuntimeSession must be used within <AgentRuntimeSessionProvider>."
|
|
1002
|
+
);
|
|
1003
|
+
}
|
|
1004
|
+
return session;
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
// src/thread-controls.tsx
|
|
1008
|
+
import {
|
|
1009
|
+
createContext as createContext3,
|
|
1010
|
+
createElement as createElement2,
|
|
1011
|
+
useContext as useContext3
|
|
1012
|
+
} from "react";
|
|
1013
|
+
var AgentThreadControlsContext = createContext3(
|
|
1014
|
+
null
|
|
1015
|
+
);
|
|
1016
|
+
function AgentThreadControlsProvider({
|
|
1017
|
+
children,
|
|
1018
|
+
value
|
|
1019
|
+
}) {
|
|
1020
|
+
return createElement2(
|
|
1021
|
+
AgentThreadControlsContext.Provider,
|
|
1022
|
+
{ value },
|
|
1023
|
+
children
|
|
1024
|
+
);
|
|
1025
|
+
}
|
|
1026
|
+
function useAgentThreadControls() {
|
|
1027
|
+
return useContext3(AgentThreadControlsContext);
|
|
1028
|
+
}
|
|
1029
|
+
export {
|
|
1030
|
+
ATTENTION_ACTIVITY_ACTIONS,
|
|
1031
|
+
ATTENTION_PRIVACY_STORAGE_KEY,
|
|
1032
|
+
AgentRuntimeSessionProvider,
|
|
1033
|
+
AgentThreadControlsProvider,
|
|
1034
|
+
AttentionContextSchema,
|
|
1035
|
+
AttentionPrivacyLevelSchema,
|
|
1036
|
+
AttentionProvider,
|
|
1037
|
+
ContextAttachmentSchema,
|
|
1038
|
+
ContextDraftPayloadSchema,
|
|
1039
|
+
addContextAttachment,
|
|
1040
|
+
addTurnContextAttachment,
|
|
1041
|
+
appendBoundedActivity,
|
|
1042
|
+
applyAttentionExclusions,
|
|
1043
|
+
attentionHistoryKey,
|
|
1044
|
+
attentionSelectionKey,
|
|
1045
|
+
clearAttentionExclusions,
|
|
1046
|
+
clearContextDraft,
|
|
1047
|
+
clearTurnContextAttachments,
|
|
1048
|
+
createAttentionContextPreview,
|
|
1049
|
+
createAttentionTelemetryState,
|
|
1050
|
+
formatAttentionLabel,
|
|
1051
|
+
getAttentionExclusions,
|
|
1052
|
+
getAttentionPrivacyLevel,
|
|
1053
|
+
getContextDraftScope,
|
|
1054
|
+
getTurnContextAttachments,
|
|
1055
|
+
moveTurnContextAttachment,
|
|
1056
|
+
parseAttentionPrivacyLevel,
|
|
1057
|
+
reduceAttentionTelemetry,
|
|
1058
|
+
removeTurnContextAttachment,
|
|
1059
|
+
resetAttentionPrivacyForTests,
|
|
1060
|
+
resetContextDraftForTests,
|
|
1061
|
+
sameAttentionTarget,
|
|
1062
|
+
serializeAttention,
|
|
1063
|
+
serializeAttentionDraft,
|
|
1064
|
+
setAttentionItemExcluded,
|
|
1065
|
+
setAttentionPrivacyLevel,
|
|
1066
|
+
setContextDraftScope,
|
|
1067
|
+
setTurnContextAttachmentRetention,
|
|
1068
|
+
subscribeAttentionPrivacyLevel,
|
|
1069
|
+
useAgentRuntimeSession,
|
|
1070
|
+
useAgentThreadControls,
|
|
1071
|
+
useAttention,
|
|
1072
|
+
useAttentionExclusions,
|
|
1073
|
+
useAttentionPrivacyLevel,
|
|
1074
|
+
useAttentionTelemetry,
|
|
1075
|
+
useTurnContextAttachments,
|
|
1076
|
+
validateAttentionContext
|
|
1077
|
+
};
|
|
1078
|
+
//# sourceMappingURL=index.js.map
|