lumiverse-spindle-types 0.5.0 → 0.5.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lumiverse-spindle-types",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "types": "./src/index.ts",
5
5
  "keywords": [
6
6
  "lumiverse",
package/src/api.ts CHANGED
@@ -1,5 +1,14 @@
1
1
  import type { SpindleManifest } from "./manifest";
2
2
  import type { CouncilMemberContext } from "./council";
3
+ import type {
4
+ ChatLinkAttachDTO,
5
+ CortexQueryDTO,
6
+ MemoryCortexConfigDTO,
7
+ MemoryEntityStatusUpdateDTO,
8
+ MemoryEntityUpsertDTO,
9
+ MemoryRelationUpsertDTO,
10
+ VaultCreateDTO,
11
+ } from "./memories";
3
12
 
4
13
  // ─── DTO types for messages ──────────────────────────────────────────────
5
14
 
@@ -1257,6 +1266,99 @@ export interface PermissionChangedDetail {
1257
1266
  allGranted: string[];
1258
1267
  }
1259
1268
 
1269
+ // ─── Web Search DTOs ────────────────────────────────────────────────────
1270
+
1271
+ /** Identifies which provider backs the user's configured web search engine. */
1272
+ export type WebSearchProviderDTO = "searxng";
1273
+
1274
+ /**
1275
+ * Safe view of a user's web search configuration. The raw API key is never
1276
+ * exposed — only `hasApiKey` indicates whether one is on file.
1277
+ */
1278
+ export interface WebSearchSettingsDTO {
1279
+ enabled: boolean;
1280
+ provider: WebSearchProviderDTO;
1281
+ apiUrl: string;
1282
+ requestTimeoutMs: number;
1283
+ defaultResultCount: number;
1284
+ maxResultCount: number;
1285
+ maxPagesToScrape: number;
1286
+ maxCharsPerPage: number;
1287
+ language: string;
1288
+ safeSearch: 0 | 1 | 2;
1289
+ engines: string[];
1290
+ hasApiKey: boolean;
1291
+ }
1292
+
1293
+ /** A single search result row, normalized across providers. */
1294
+ export interface WebSearchResultDTO {
1295
+ title: string;
1296
+ url: string;
1297
+ snippet: string;
1298
+ /** Provider-reported engine identifier when available (e.g. `"google"`, `"bing"`). */
1299
+ engine?: string;
1300
+ /** Provider-reported relevance score when available. */
1301
+ score?: number;
1302
+ }
1303
+
1304
+ /**
1305
+ * A search result enriched with scraped page content. Only present when the
1306
+ * query was run with `scrape: true` (the default).
1307
+ */
1308
+ export interface WebSearchDocumentDTO {
1309
+ title: string;
1310
+ url: string;
1311
+ snippet: string;
1312
+ /** How the page content was extracted (e.g. `"html"`, `"pdf"`). */
1313
+ sourceType?: string;
1314
+ /** Extracted page text, clipped to `WebSearchSettingsDTO.maxCharsPerPage`. */
1315
+ content?: string;
1316
+ /** Length of the source page content before clipping. */
1317
+ contentLength?: number;
1318
+ /** Populated when scraping failed for this result; `content` is then absent. */
1319
+ error?: string;
1320
+ }
1321
+
1322
+ /** Options forwarded to `spindle.webSearch.query()`. */
1323
+ export interface WebSearchRequestDTO {
1324
+ /** Free-text search query. Trimmed by the host; empty values are rejected. */
1325
+ query: string;
1326
+ /**
1327
+ * Desired number of results. Clamped to `WebSearchSettingsDTO.maxResultCount`
1328
+ * on the host; omit to use the user's `defaultResultCount`.
1329
+ */
1330
+ count?: number;
1331
+ /**
1332
+ * When `true` (default), the host scrapes the first
1333
+ * `WebSearchSettingsDTO.maxPagesToScrape` results, fills in
1334
+ * `documents[].content`, and assembles the `context` block. Set to `false`
1335
+ * to skip scraping entirely — only `results` are returned, and
1336
+ * `documents` / `context` are omitted from the response. Useful when the
1337
+ * extension only needs titles, URLs, and snippets.
1338
+ */
1339
+ scrape?: boolean;
1340
+ /** For operator-scoped extensions; ignored on user-scoped extensions. */
1341
+ userId?: string;
1342
+ }
1343
+
1344
+ /**
1345
+ * Result of a successful `spindle.webSearch.query()` call. `documents` and
1346
+ * `context` are omitted when the request was issued with `scrape: false`.
1347
+ */
1348
+ export interface WebSearchResponseDTO {
1349
+ /** The (trimmed) query that was executed. */
1350
+ query: string;
1351
+ /** Raw normalized results from the search provider. */
1352
+ results: WebSearchResultDTO[];
1353
+ /** Per-result scraped page content. Absent when `scrape: false`. */
1354
+ documents?: WebSearchDocumentDTO[];
1355
+ /**
1356
+ * Pre-assembled, prompt-ready context block summarizing the query plus the
1357
+ * scraped documents. Absent when `scrape: false`.
1358
+ */
1359
+ context?: string;
1360
+ }
1361
+
1260
1362
  // ─── Theme DTOs ──────────────────────────────────────────────────────────
1261
1363
 
1262
1364
  /**
@@ -2281,6 +2383,50 @@ export type WorkerToHost =
2281
2383
  | { type: "generate_dry_run"; requestId: string; input: DryRunRequestDTO; userId?: string }
2282
2384
  // ─── Chat Memories (gated: "chats") ───────────────────────────────
2283
2385
  | { type: "chats_get_memories"; requestId: string; chatId: string; topK?: number; userId?: string }
2386
+ // ─── Memory Cortex & Long-Term Chat Memory (gated: "memories") ────
2387
+ | { type: "memories_config_get"; requestId: string; userId?: string }
2388
+ | { type: "memories_config_put"; requestId: string; patch: Partial<MemoryCortexConfigDTO>; userId?: string }
2389
+ | { type: "memories_query_cortex"; requestId: string; query: CortexQueryDTO }
2390
+ | { type: "memories_query_linked"; requestId: string; chatId: string; queryText?: string; userId?: string }
2391
+ | { type: "memories_get_cached"; requestId: string; chatId: string }
2392
+ | { type: "memories_get_cached_linked"; requestId: string; chatId: string }
2393
+ | { type: "memories_invalidate_cache"; requestId: string; chatId: string }
2394
+ | { type: "memories_invalidate_linked_cache"; requestId: string; chatId: string }
2395
+ | { type: "memories_entities_list"; requestId: string; chatId: string; activeOnly?: boolean; limit?: number; userId?: string }
2396
+ | { type: "memories_entities_get"; requestId: string; entityId: string; userId?: string }
2397
+ | { type: "memories_entities_find_by_name"; requestId: string; chatId: string; name: string; userId?: string }
2398
+ | { type: "memories_entities_upsert"; requestId: string; chatId: string; entity: MemoryEntityUpsertDTO; chunkId?: string | null; createdAt?: number; userId?: string }
2399
+ | { type: "memories_entities_update_status"; requestId: string; entityId: string; patch: MemoryEntityStatusUpdateDTO; userId?: string }
2400
+ | { type: "memories_entities_add_facts"; requestId: string; entityId: string; facts: string[]; userId?: string }
2401
+ | { type: "memories_entities_get_facts"; requestId: string; entityId: string; userId?: string }
2402
+ | { type: "memories_entities_update_emotional_valence"; requestId: string; entityId: string; valence: Record<string, number>; userId?: string }
2403
+ | { type: "memories_relations_list"; requestId: string; chatId: string; userId?: string }
2404
+ | { type: "memories_relations_list_all"; requestId: string; chatId: string; userId?: string }
2405
+ | { type: "memories_relations_for_entity"; requestId: string; chatId: string; entityId: string; userId?: string }
2406
+ | { type: "memories_relations_for_entities"; requestId: string; chatId: string; entityIds: string[]; limit?: number; userId?: string }
2407
+ | { type: "memories_relations_upsert"; requestId: string; chatId: string; relation: MemoryRelationUpsertDTO; chunkId?: string | null; userId?: string }
2408
+ | { type: "memories_consolidations_list"; requestId: string; chatId: string; tier?: number; userId?: string }
2409
+ | { type: "memories_consolidations_latest_arc"; requestId: string; chatId: string; userId?: string }
2410
+ | { type: "memories_consolidations_run"; requestId: string; chatId: string; userId?: string }
2411
+ | { type: "memories_salience_get"; requestId: string; chatId: string; limit?: number; offset?: number; userId?: string }
2412
+ | { type: "memories_vaults_list"; requestId: string; userId?: string }
2413
+ | { type: "memories_vaults_get"; requestId: string; vaultId: string; userId?: string }
2414
+ | { type: "memories_vaults_get_chunks"; requestId: string; vaultId: string; userId?: string }
2415
+ | { type: "memories_vaults_create"; requestId: string; input: VaultCreateDTO; userId?: string }
2416
+ | { type: "memories_vaults_rename"; requestId: string; vaultId: string; name: string; userId?: string }
2417
+ | { type: "memories_vaults_delete"; requestId: string; vaultId: string; userId?: string }
2418
+ | { type: "memories_vaults_reindex"; requestId: string; vaultId: string; userId?: string }
2419
+ | { type: "memories_links_list"; requestId: string; chatId: string; userId?: string }
2420
+ | { type: "memories_links_attach"; requestId: string; input: ChatLinkAttachDTO; userId?: string }
2421
+ | { type: "memories_links_remove"; requestId: string; chatId: string; linkId: string; userId?: string }
2422
+ | { type: "memories_links_toggle"; requestId: string; chatId: string; linkId: string; enabled: boolean; userId?: string }
2423
+ | { type: "memories_chat_chunks_list"; requestId: string; chatId: string; userId?: string }
2424
+ | { type: "memories_chat_memory_get"; requestId: string; chatId: string; topK?: number; userId?: string }
2425
+ | { type: "memories_chat_memory_warm"; requestId: string; chatId: string; force?: boolean; userId?: string }
2426
+ | { type: "memories_chat_memory_invalidate"; requestId: string; chatId: string; userId?: string }
2427
+ | { type: "memories_stats_usage"; requestId: string; chatId: string; userId?: string }
2428
+ | { type: "memories_stats_ingestion_status"; requestId: string; chatId: string; userId?: string }
2429
+ | { type: "memories_stats_ingestion_telemetry"; requestId: string; chatId: string; userId?: string }
2284
2430
  // ─── Toast (free tier) ───────────────────────────────────────────────
2285
2431
  | { type: "toast_show"; toastType: "success" | "warning" | "error" | "info"; message: string; title?: string; duration?: number; userId?: string }
2286
2432
  // ─── Push Notifications (gated: "push_notification") ────────────────
@@ -2395,7 +2541,17 @@ export type WorkerToHost =
2395
2541
  model?: string;
2396
2542
  modelSource?: TokenModelSourceDTO;
2397
2543
  userId?: string;
2398
- };
2544
+ }
2545
+ // ─── Web Search (gated: "web_search") ─────────────────────────────────
2546
+ | {
2547
+ type: "web_search_query";
2548
+ requestId: string;
2549
+ query: string;
2550
+ count?: number;
2551
+ scrape?: boolean;
2552
+ userId?: string;
2553
+ }
2554
+ | { type: "web_search_get_settings"; requestId: string; userId?: string };
2399
2555
 
2400
2556
  // ─── Host → Worker messages ──────────────────────────────────────────────
2401
2557
 
package/src/index.ts CHANGED
@@ -155,6 +155,12 @@ export type {
155
155
  MessageContentProcessorOrigin,
156
156
  MessageContentProcessorCtxDTO,
157
157
  MessageContentProcessorResultDTO,
158
+ WebSearchProviderDTO,
159
+ WebSearchSettingsDTO,
160
+ WebSearchResultDTO,
161
+ WebSearchDocumentDTO,
162
+ WebSearchRequestDTO,
163
+ WebSearchResponseDTO,
158
164
  WorkerToHost,
159
165
  HostToWorker,
160
166
  } from "./api";
@@ -210,6 +216,58 @@ export type {
210
216
 
211
217
  export type { ExtensionInfo } from "./extension-info";
212
218
 
219
+ export type {
220
+ EntityTypeDTO,
221
+ EntityStatusDTO,
222
+ MentionRoleDTO,
223
+ RelationTypeDTO,
224
+ RelationStatusDTO,
225
+ EmotionalTagDTO,
226
+ NarrativeFlagDTO,
227
+ ContradictionFlagDTO,
228
+ FactExtractionStatusDTO,
229
+ EntityConfidenceDTO,
230
+ SalienceSourceDTO,
231
+ ChatLinkTypeDTO,
232
+ SalienceBreakdownDTO,
233
+ MemoryEntityDTO,
234
+ MemoryEntityUpsertDTO,
235
+ MemoryEntityStatusUpdateDTO,
236
+ MemoryMentionDTO,
237
+ MemoryRelationDTO,
238
+ MemoryRelationUpsertDTO,
239
+ StatusChangeDTO,
240
+ MemorySalienceDTO,
241
+ MemoryConsolidationDTO,
242
+ CortexQueryDTO,
243
+ CortexMemoryComponentsDTO,
244
+ CortexMemoryDTO,
245
+ EntitySnapshotRelationshipDTO,
246
+ EntitySnapshotDTO,
247
+ RelationEdgeDTO,
248
+ CortexStatsDTO,
249
+ CortexResultDTO,
250
+ VaultDTO,
251
+ VaultChunkDTO,
252
+ VaultEntityDTO,
253
+ VaultRelationDTO,
254
+ VaultCreateDTO,
255
+ VaultWithContentsDTO,
256
+ VaultReindexResultDTO,
257
+ ChatLinkDTO,
258
+ ChatLinkAttachDTO,
259
+ VaultCortexDataDTO,
260
+ InterlinkCortexDataDTO,
261
+ LinkedCortexResultDTO,
262
+ MemoryCortexConfigDTO,
263
+ CortexUsageStatsDTO,
264
+ CortexIngestionTimingsDTO,
265
+ CortexIngestionStatusDTO,
266
+ CortexIngestionTelemetryDTO,
267
+ ChatChunkDTO,
268
+ ChatMemoryWarmupResultDTO,
269
+ } from "./memories";
270
+
213
271
  export type {
214
272
  SpindleAPI,
215
273
  FrontendProcessHandle,
@@ -0,0 +1,607 @@
1
+ /**
2
+ * Memory Cortex & Long-Term Chat Memory DTOs.
3
+ *
4
+ * Surfaces the Lumiverse hybrid memory architecture to extensions via the
5
+ * `memories` permission. Covers:
6
+ *
7
+ * - Memory Cortex
8
+ * • config (entity tracking, salience scoring, retrieval tuning)
9
+ * • retrieval (cortex query, linked-cortex query, vault query)
10
+ * • entity graph (entities, relations, mentions)
11
+ * • consolidations & salience records
12
+ * • vaults (snapshots of cortex state) and chat links (vault attach /
13
+ * bidirectional interlinks)
14
+ * • ingestion / maintenance telemetry
15
+ *
16
+ * - Long-Term Chat Memory
17
+ * • vectorized chat chunks (list, get, warm)
18
+ * • cached retrieval result for the {{memories}} macro
19
+ *
20
+ * Service-layer shapes use camelCase; SQLite row shapes (snake_case) are not
21
+ * exposed to extensions — DTOs are normalised here.
22
+ */
23
+
24
+ // ─── Enums ─────────────────────────────────────────────────────────────
25
+
26
+ export type EntityTypeDTO =
27
+ | "character"
28
+ | "location"
29
+ | "item"
30
+ | "faction"
31
+ | "concept"
32
+ | "event";
33
+
34
+ export type EntityStatusDTO =
35
+ | "active"
36
+ | "inactive"
37
+ | "deceased"
38
+ | "destroyed"
39
+ | "unknown";
40
+
41
+ export type MentionRoleDTO =
42
+ | "subject"
43
+ | "object"
44
+ | "present"
45
+ | "referenced"
46
+ | "absent";
47
+
48
+ export type RelationTypeDTO =
49
+ | "ally"
50
+ | "enemy"
51
+ | "lover"
52
+ | "parent"
53
+ | "child"
54
+ | "sibling"
55
+ | "mentor"
56
+ | "rival"
57
+ | "owns"
58
+ | "member_of"
59
+ | "located_in"
60
+ | "fears"
61
+ | "serves"
62
+ | "custom";
63
+
64
+ export type RelationStatusDTO = "active" | "broken" | "dormant" | "former";
65
+
66
+ export type EmotionalTagDTO =
67
+ | "grief"
68
+ | "joy"
69
+ | "tension"
70
+ | "dread"
71
+ | "intimacy"
72
+ | "betrayal"
73
+ | "revelation"
74
+ | "resolve"
75
+ | "humor"
76
+ | "melancholy"
77
+ | "awe"
78
+ | "fury";
79
+
80
+ export type NarrativeFlagDTO =
81
+ | "first_meeting"
82
+ | "death"
83
+ | "promise"
84
+ | "confession"
85
+ | "departure"
86
+ | "transformation"
87
+ | "battle"
88
+ | "discovery"
89
+ | "reunion"
90
+ | "loss";
91
+
92
+ export type ContradictionFlagDTO = "none" | "temporal" | "complex" | "suspect";
93
+
94
+ export type FactExtractionStatusDTO = "never" | "attempted_empty" | "ok";
95
+
96
+ export type EntityConfidenceDTO = "confirmed" | "provisional";
97
+
98
+ export type SalienceSourceDTO = "heuristic" | "sidecar";
99
+
100
+ export type ChatLinkTypeDTO = "vault" | "interlink";
101
+
102
+ // ─── Entity Graph ──────────────────────────────────────────────────────
103
+
104
+ export interface SalienceBreakdownDTO {
105
+ mentionComponent: number;
106
+ arcComponent: number;
107
+ graphComponent: number;
108
+ total: number;
109
+ }
110
+
111
+ export interface MemoryEntityDTO {
112
+ id: string;
113
+ chatId: string;
114
+ name: string;
115
+ entityType: EntityTypeDTO;
116
+ aliases: string[];
117
+ description: string;
118
+ firstSeenChunkId: string | null;
119
+ lastSeenChunkId: string | null;
120
+ firstSeenAt: number | null;
121
+ lastSeenAt: number | null;
122
+ mentionCount: number;
123
+ salienceAvg: number;
124
+ status: EntityStatusDTO;
125
+ statusChangedAt: number | null;
126
+ facts: string[];
127
+ emotionalValence: Record<string, number>;
128
+ metadata: Record<string, unknown>;
129
+ createdAt: number;
130
+ updatedAt: number;
131
+ factExtractionStatus: FactExtractionStatusDTO;
132
+ factExtractionLastAttempt: number | null;
133
+ salienceBreakdown: SalienceBreakdownDTO;
134
+ lastMentionTimestamp: number | null;
135
+ recentMentionCount: number;
136
+ confidence: EntityConfidenceDTO;
137
+ userEditedAt: number | null;
138
+ }
139
+
140
+ /**
141
+ * Input shape for upserting an entity. Matches the heuristic / sidecar
142
+ * extraction shape so an extension can replay its own NER results into the
143
+ * graph. `confidence` is the raw [0, 1] extractor confidence; entities below
144
+ * the configured threshold are dropped by the host.
145
+ */
146
+ export interface MemoryEntityUpsertDTO {
147
+ name: string;
148
+ type: EntityTypeDTO;
149
+ aliases?: string[];
150
+ confidence?: number;
151
+ role?: MentionRoleDTO;
152
+ /** Marks the entity as needing corroboration before promotion. */
153
+ provisional?: boolean;
154
+ }
155
+
156
+ export interface MemoryEntityStatusUpdateDTO {
157
+ status: EntityStatusDTO;
158
+ statusChangedAt?: number;
159
+ }
160
+
161
+ export interface MemoryMentionDTO {
162
+ id: string;
163
+ entityId: string;
164
+ chunkId: string;
165
+ chatId: string;
166
+ role: MentionRoleDTO;
167
+ excerpt: string | null;
168
+ sentiment: number;
169
+ createdAt: number;
170
+ }
171
+
172
+ // ─── Relations ─────────────────────────────────────────────────────────
173
+
174
+ export interface MemoryRelationDTO {
175
+ id: string;
176
+ chatId: string;
177
+ sourceEntityId: string;
178
+ targetEntityId: string;
179
+ relationType: RelationTypeDTO;
180
+ relationLabel: string | null;
181
+ strength: number;
182
+ sentiment: number;
183
+ evidenceChunkIds: string[];
184
+ firstEstablishedAt: number | null;
185
+ lastReinforcedAt: number | null;
186
+ status: RelationStatusDTO;
187
+ metadata: Record<string, unknown>;
188
+ createdAt: number;
189
+ updatedAt: number;
190
+ contradictionFlag: ContradictionFlagDTO;
191
+ contradictionPeerId: string | null;
192
+ sentimentRange: [number, number] | null;
193
+ supersededBy: string | null;
194
+ arcIds: string[];
195
+ firstSeenArcId: string | null;
196
+ lastSeenArcId: string | null;
197
+ lastEvidenceTimestamp: number | null;
198
+ decayRate: number;
199
+ edgeSalience: number;
200
+ labelAliases: string[];
201
+ canonicalEdgeId: string | null;
202
+ mergedInto: string | null;
203
+ }
204
+
205
+ /**
206
+ * Input shape for upserting a relation. Uses entity *names* (not ids) so
207
+ * extensions can produce relations symbolically — the host resolves canonical
208
+ * ids server-side. Both endpoints must already exist in the entity graph;
209
+ * the relation is silently dropped otherwise.
210
+ */
211
+ export interface MemoryRelationUpsertDTO {
212
+ source: string;
213
+ target: string;
214
+ type: RelationTypeDTO;
215
+ label: string;
216
+ sentiment: number;
217
+ }
218
+
219
+ // ─── Salience ──────────────────────────────────────────────────────────
220
+
221
+ export interface StatusChangeDTO {
222
+ entity: string;
223
+ change: string;
224
+ detail: string;
225
+ }
226
+
227
+ export interface MemorySalienceDTO {
228
+ chunkId: string;
229
+ chatId: string;
230
+ score: number;
231
+ scoreSource: SalienceSourceDTO;
232
+ emotionalTags: EmotionalTagDTO[];
233
+ statusChanges: StatusChangeDTO[];
234
+ narrativeFlags: NarrativeFlagDTO[];
235
+ hasDialogue: boolean;
236
+ hasAction: boolean;
237
+ hasInternalThought: boolean;
238
+ wordCount: number;
239
+ scoredAt: number;
240
+ scoredBy: string | null;
241
+ }
242
+
243
+ // ─── Consolidations ────────────────────────────────────────────────────
244
+
245
+ export interface MemoryConsolidationDTO {
246
+ id: string;
247
+ chatId: string;
248
+ tier: number;
249
+ title: string | null;
250
+ summary: string;
251
+ sourceChunkIds: string[];
252
+ sourceConsolidationIds: string[];
253
+ entityIds: string[];
254
+ messageRangeStart: number | null;
255
+ messageRangeEnd: number | null;
256
+ timeRangeStart: number | null;
257
+ timeRangeEnd: number | null;
258
+ salienceAvg: number;
259
+ emotionalTags: EmotionalTagDTO[];
260
+ tokenCount: number;
261
+ vectorizedAt: number | null;
262
+ vectorModel: string | null;
263
+ createdAt: number;
264
+ updatedAt: number;
265
+ }
266
+
267
+ // ─── Cortex Retrieval ──────────────────────────────────────────────────
268
+
269
+ /**
270
+ * Input to a Memory Cortex retrieval query.
271
+ *
272
+ * The `userId` field is normally inferred from the calling extension's
273
+ * effective user; pass it explicitly only for operator-scoped extensions.
274
+ */
275
+ export interface CortexQueryDTO {
276
+ chatId: string;
277
+ queryText: string;
278
+ entityFilter?: string[];
279
+ timeRange?: { start?: number; end?: number };
280
+ emotionalContext?: EmotionalTagDTO[];
281
+ generationType?: string;
282
+ topK?: number;
283
+ includeConsolidations?: boolean;
284
+ includeRelationships?: boolean;
285
+ excludeMessageIds?: string[];
286
+ userId?: string;
287
+ }
288
+
289
+ export interface CortexMemoryComponentsDTO {
290
+ semantic: number;
291
+ salience: number;
292
+ recency: number;
293
+ reinforcement: number;
294
+ emotional: number;
295
+ entity: number;
296
+ }
297
+
298
+ export interface CortexMemoryDTO {
299
+ source: "chunk" | "consolidation";
300
+ sourceId: string;
301
+ content: string;
302
+ finalScore: number;
303
+ components: CortexMemoryComponentsDTO;
304
+ emotionalTags: EmotionalTagDTO[];
305
+ entityNames: string[];
306
+ messageRange: [number, number];
307
+ timeRange: [number, number];
308
+ }
309
+
310
+ export interface EntitySnapshotRelationshipDTO {
311
+ targetName: string;
312
+ type: RelationTypeDTO;
313
+ label: string | null;
314
+ strength: number;
315
+ sentiment: number;
316
+ }
317
+
318
+ export interface EntitySnapshotDTO {
319
+ id: string;
320
+ name: string;
321
+ type: EntityTypeDTO;
322
+ status: EntityStatusDTO;
323
+ description: string;
324
+ lastSeenAt: number | null;
325
+ mentionCount: number;
326
+ topFacts: string[];
327
+ emotionalProfile: Record<string, number>;
328
+ relationships: EntitySnapshotRelationshipDTO[];
329
+ }
330
+
331
+ export interface RelationEdgeDTO {
332
+ sourceName: string;
333
+ targetName: string;
334
+ type: RelationTypeDTO;
335
+ label: string | null;
336
+ strength: number;
337
+ sentiment: number;
338
+ }
339
+
340
+ export interface CortexStatsDTO {
341
+ candidatePoolSize: number;
342
+ vectorSearchResults: number;
343
+ entitiesMatched: number;
344
+ scoreFusionApplied: boolean;
345
+ topScore: number;
346
+ retrievalTimeMs: number;
347
+ /** Set when the retrieval hit its internal time budget. */
348
+ timedOut?: boolean;
349
+ /** Set when retrieval bailed out because the caller's AbortSignal fired. */
350
+ aborted?: boolean;
351
+ }
352
+
353
+ export interface CortexResultDTO {
354
+ memories: CortexMemoryDTO[];
355
+ entityContext: EntitySnapshotDTO[];
356
+ activeRelationships: RelationEdgeDTO[];
357
+ arcContext: string | null;
358
+ stats: CortexStatsDTO;
359
+ }
360
+
361
+ // ─── Vaults & Linked Cortex ────────────────────────────────────────────
362
+
363
+ export interface VaultDTO {
364
+ id: string;
365
+ userId: string;
366
+ sourceChatId: string | null;
367
+ sourceChatName: string | null;
368
+ name: string;
369
+ description: string;
370
+ entityCount: number;
371
+ relationCount: number;
372
+ chunkCount: number;
373
+ createdAt: number;
374
+ }
375
+
376
+ export interface VaultChunkDTO {
377
+ id: string;
378
+ vaultId: string;
379
+ sourceChunkId: string;
380
+ content: string;
381
+ salienceScore: number | null;
382
+ emotionalTags: string[];
383
+ entityNames: string[];
384
+ sourceCreatedAt: number;
385
+ copiedAt: number;
386
+ }
387
+
388
+ export interface VaultEntityDTO {
389
+ id: string;
390
+ vaultId: string;
391
+ name: string;
392
+ entityType: EntityTypeDTO;
393
+ aliases: string[];
394
+ description: string;
395
+ status: EntityStatusDTO;
396
+ facts: string[];
397
+ emotionalValence: Record<string, number>;
398
+ salienceAvg: number;
399
+ }
400
+
401
+ export interface VaultRelationDTO {
402
+ id: string;
403
+ vaultId: string;
404
+ sourceEntityName: string;
405
+ targetEntityName: string;
406
+ relationType: RelationTypeDTO;
407
+ relationLabel: string | null;
408
+ strength: number;
409
+ sentiment: number;
410
+ status: RelationStatusDTO;
411
+ }
412
+
413
+ export interface VaultCreateDTO {
414
+ chatId: string;
415
+ name: string;
416
+ description?: string;
417
+ }
418
+
419
+ export interface VaultWithContentsDTO {
420
+ vault: VaultDTO;
421
+ entities: VaultEntityDTO[];
422
+ relations: VaultRelationDTO[];
423
+ }
424
+
425
+ export interface VaultReindexResultDTO {
426
+ mode: string;
427
+ chunkCount: number;
428
+ }
429
+
430
+ export interface ChatLinkDTO {
431
+ id: string;
432
+ userId: string;
433
+ chatId: string;
434
+ linkType: ChatLinkTypeDTO;
435
+ vaultId: string | null;
436
+ vaultName: string | null;
437
+ vaultEntityCount: number | null;
438
+ vaultRelationCount: number | null;
439
+ targetChatId: string | null;
440
+ targetChatName: string | null;
441
+ targetChatExists: boolean;
442
+ label: string;
443
+ enabled: boolean;
444
+ priority: number;
445
+ createdAt: number;
446
+ }
447
+
448
+ export interface ChatLinkAttachDTO {
449
+ chatId: string;
450
+ linkType: ChatLinkTypeDTO;
451
+ vaultId?: string;
452
+ targetChatId?: string;
453
+ label?: string;
454
+ /** Interlinks only. When true, also creates the reverse link on the target chat. */
455
+ bidirectional?: boolean;
456
+ }
457
+
458
+ export interface VaultCortexDataDTO {
459
+ vaultId: string;
460
+ vaultName: string;
461
+ sourceChatId?: string;
462
+ entities: EntitySnapshotDTO[];
463
+ relations: RelationEdgeDTO[];
464
+ /** Retrieved memories from the vault's chunk snapshot (when queryText supplied). */
465
+ memories?: CortexMemoryDTO[];
466
+ arcContext?: string | null;
467
+ }
468
+
469
+ export interface InterlinkCortexDataDTO {
470
+ targetChatId: string;
471
+ targetChatName: string;
472
+ result: CortexResultDTO;
473
+ }
474
+
475
+ export interface LinkedCortexResultDTO {
476
+ vaults: VaultCortexDataDTO[];
477
+ interlinks: InterlinkCortexDataDTO[];
478
+ }
479
+
480
+ // ─── Cortex Config ─────────────────────────────────────────────────────
481
+
482
+ /**
483
+ * Memory Cortex configuration.
484
+ *
485
+ * The host owns the canonical schema (see `MemoryCortexConfig` in the
486
+ * backend). The DTO is intentionally permissive — only the top-level toggles
487
+ * extensions are likely to flip are typed, and the index signature passes
488
+ * advanced fields (retrieval tuning, sidecar configuration, decay curves,
489
+ * consolidation thresholds, entity pruning, etc.) straight through. New
490
+ * fields added by the host are visible immediately to extensions without
491
+ * a coordinated type bump.
492
+ */
493
+ export interface MemoryCortexConfigDTO {
494
+ enabled: boolean;
495
+ entityTracking: boolean;
496
+ entityExtractionMode: string;
497
+ salienceScoring: boolean;
498
+ salienceScoringMode?: string;
499
+ thoughtMarkers?: unknown;
500
+ entityWhitelist?: string[];
501
+ entityExtractionFilters?: unknown;
502
+ retrieval?: Record<string, unknown>;
503
+ decay?: Record<string, unknown>;
504
+ consolidation?: Record<string, unknown>;
505
+ entityPruning?: Record<string, unknown>;
506
+ sidecar?: Record<string, unknown>;
507
+ sidecarTimeoutMs?: number;
508
+ retrievalTimeoutMs?: number;
509
+ [key: string]: unknown;
510
+ }
511
+
512
+ // ─── Telemetry & Stats ─────────────────────────────────────────────────
513
+
514
+ export interface CortexUsageStatsDTO {
515
+ entityCount: number;
516
+ relationCount: number;
517
+ salienceRecordCount: number;
518
+ consolidationCount: number;
519
+ /** Advanced fields exposed by the host's gc module (mention counts, last
520
+ * GC timestamp, etc.) pass through unchanged. */
521
+ [key: string]: number | string | boolean | null | undefined;
522
+ }
523
+
524
+ export interface CortexIngestionTimingsDTO {
525
+ mode: "heuristic" | "sidecar" | "mixed";
526
+ fontMs: number;
527
+ heuristicMs: number;
528
+ heuristicSalienceMs: number;
529
+ heuristicEntityMs: number;
530
+ heuristicRelationshipMs: number;
531
+ heuristicAliasMs: number;
532
+ sidecarMs: number;
533
+ graphMs: number;
534
+ dbMs: number;
535
+ totalMs: number;
536
+ completedAt: number;
537
+ chunkId: string;
538
+ }
539
+
540
+ export interface CortexIngestionStatusDTO {
541
+ chatId: string;
542
+ status: "idle" | "processing" | "complete" | "error";
543
+ phase:
544
+ | "queued"
545
+ | "font"
546
+ | "heuristics"
547
+ | "sidecar"
548
+ | "persisting"
549
+ | "complete"
550
+ | "error";
551
+ chunkId: string | null;
552
+ startedAt: number | null;
553
+ updatedAt: number;
554
+ pendingJobs: number;
555
+ error?: string;
556
+ timings?: CortexIngestionTimingsDTO | null;
557
+ }
558
+
559
+ export interface CortexIngestionTelemetryDTO {
560
+ samples: number;
561
+ last: CortexIngestionTimingsDTO | null;
562
+ averages: {
563
+ fontMs: number;
564
+ heuristicMs: number;
565
+ sidecarMs: number;
566
+ graphMs: number;
567
+ dbMs: number;
568
+ totalMs: number;
569
+ };
570
+ }
571
+
572
+ // ─── Long-Term Chat Memory ─────────────────────────────────────────────
573
+
574
+ /**
575
+ * A vectorized chat chunk — the basic unit of long-term chat memory. Chunks
576
+ * are produced from contiguous same-role messages bounded by token target
577
+ * and scene-break heuristics; the {{memories}} macro retrieves the top-K
578
+ * chunks by hybrid vector + BM25 score during prompt assembly.
579
+ */
580
+ export interface ChatChunkDTO {
581
+ id: string;
582
+ chatId: string;
583
+ startMessageId: string;
584
+ endMessageId: string;
585
+ messageIds: string[];
586
+ content: string;
587
+ tokenCount: number;
588
+ messageCount: number;
589
+ vectorizedAt: number | null;
590
+ vectorModel: string | null;
591
+ retrievalCount: number;
592
+ lastRetrievedAt: number | null;
593
+ createdAt: number;
594
+ updatedAt: number;
595
+ }
596
+
597
+ /**
598
+ * Result of a long-term chat memory warmup. `status` describes the action
599
+ * taken; `reason` is a short human-readable string useful for diagnostics
600
+ * when nothing was queued.
601
+ */
602
+ export interface ChatMemoryWarmupResultDTO {
603
+ status: "skipped" | "complete" | "rebuilding" | "queued" | "error";
604
+ reason?: string;
605
+ rebuilt?: boolean;
606
+ vectorizationsQueued?: number;
607
+ }
@@ -15,7 +15,13 @@
15
15
  * - "presets" — CRUD on user presets and prompt blocks
16
16
  * - "personas" — CRUD on personas
17
17
  * - "databanks" — CRUD on databanks and their documents
18
+ * - "memories" — CRUD on the Memory Cortex (entities, relations, vaults, chat
19
+ * links, consolidations) and long-term chat memory (vectorized
20
+ * chat-chunk retrieval, warmup, cache).
18
21
  * - "macro_interceptor" — transform raw templates before macro parsing/dispatch
22
+ * - "web_search" — execute searches via the user's configured web search
23
+ * provider (e.g. SearXNG) and read the safe view of their
24
+ * web search settings (never the API key).
19
25
  */
20
26
  export type SpindlePermission =
21
27
  | "generation"
@@ -35,12 +41,14 @@ export type SpindlePermission =
35
41
  | "world_books"
36
42
  | "regex_scripts"
37
43
  | "databanks"
44
+ | "memories"
38
45
  | "personas"
39
46
  | "push_notification"
40
47
  | "image_gen"
41
48
  | "images"
42
49
  | "generation_parameters"
43
- | "macro_interceptor";
50
+ | "macro_interceptor"
51
+ | "web_search";
44
52
 
45
53
  export const ALL_PERMISSIONS: readonly SpindlePermission[] = [
46
54
  "generation",
@@ -60,12 +68,14 @@ export const ALL_PERMISSIONS: readonly SpindlePermission[] = [
60
68
  "world_books",
61
69
  "regex_scripts",
62
70
  "databanks",
71
+ "memories",
63
72
  "personas",
64
73
  "push_notification",
65
74
  "image_gen",
66
75
  "images",
67
76
  "generation_parameters",
68
77
  "macro_interceptor",
78
+ "web_search",
69
79
  ] as const;
70
80
 
71
81
  export function isValidPermission(p: string): p is SpindlePermission {
@@ -103,7 +103,35 @@ import type {
103
103
  MessageContentProcessorResultDTO,
104
104
  SharedRpcRequestContextDTO,
105
105
  SharedRpcEndpointPolicyDTO,
106
+ WebSearchRequestDTO,
107
+ WebSearchResponseDTO,
108
+ WebSearchSettingsDTO,
106
109
  } from "./api";
110
+ import type {
111
+ ChatChunkDTO,
112
+ ChatLinkAttachDTO,
113
+ ChatLinkDTO,
114
+ ChatMemoryWarmupResultDTO,
115
+ CortexIngestionStatusDTO,
116
+ CortexIngestionTelemetryDTO,
117
+ CortexQueryDTO,
118
+ CortexResultDTO,
119
+ CortexUsageStatsDTO,
120
+ LinkedCortexResultDTO,
121
+ MemoryConsolidationDTO,
122
+ MemoryCortexConfigDTO,
123
+ MemoryEntityDTO,
124
+ MemoryEntityStatusUpdateDTO,
125
+ MemoryEntityUpsertDTO,
126
+ MemoryRelationDTO,
127
+ MemoryRelationUpsertDTO,
128
+ MemorySalienceDTO,
129
+ VaultChunkDTO,
130
+ VaultCreateDTO,
131
+ VaultDTO,
132
+ VaultReindexResultDTO,
133
+ VaultWithContentsDTO,
134
+ } from "./memories";
107
135
 
108
136
  export interface FrontendProcessHandle {
109
137
  /** Host-assigned process ID unique within the extension runtime. */
@@ -827,6 +855,193 @@ export interface SpindleAPI {
827
855
  };
828
856
  };
829
857
 
858
+ /**
859
+ * Memory Cortex & Long-Term Chat Memory (permission: "memories").
860
+ *
861
+ * Lumiverse's hybrid memory architecture exposed as a single CRUD surface:
862
+ *
863
+ * - **`cortex`** — config get/put, retrieval (cortex query, linked-cortex
864
+ * query for vaults + interlinks), warm-cache reads, cache invalidation.
865
+ * - **`entities`** / **`relations`** — entity graph CRUD: list, find,
866
+ * upsert, status updates, fact appends, emotional valence, plus active
867
+ * + unfiltered relation reads and `upsert` for symbolic edges.
868
+ * - **`consolidations`** — list arcs by tier, fetch the latest arc, and
869
+ * `run()` to trigger a background consolidation pass.
870
+ * - **`salience`** — read salience records (score, emotional tags,
871
+ * narrative flags) for chunks already ingested.
872
+ * - **`vaults`** — frozen snapshots of cortex state. `create()` snapshots
873
+ * a chat's entities/relations/chunks, `reindex()` re-copies the chunk
874
+ * snapshot after a LanceDB reset, plus list / get / rename / delete.
875
+ * - **`links`** — attach vaults to chats or set up bidirectional
876
+ * chat-to-chat interlinks; list / remove / toggle.
877
+ * - **`chatMemory`** — the long-term chat memory layer used by the
878
+ * `{{memories}}` macro: list vectorized chunks, fetch top-K retrieval,
879
+ * warm a chat (rebuild + queue vectorization), invalidate cache.
880
+ * - **`stats`** — usage counters, live ingestion phase, and ingestion
881
+ * timing telemetry.
882
+ *
883
+ * For user-scoped extensions, `userId` is inferred from the extension
884
+ * owner. For operator-scoped extensions, pass `userId` to scope a call
885
+ * to a specific user.
886
+ *
887
+ * All chat-scoped calls verify chat ownership against the resolved user
888
+ * — extensions cannot read or mutate cortex state for chats they do not
889
+ * own.
890
+ */
891
+ memories: {
892
+ cortex: {
893
+ /** Get the user's Memory Cortex configuration. */
894
+ getConfig(userId?: string): Promise<MemoryCortexConfigDTO>;
895
+ /** Patch the user's Memory Cortex configuration. Unspecified fields are left untouched. */
896
+ putConfig(patch: Partial<MemoryCortexConfigDTO>, userId?: string): Promise<MemoryCortexConfigDTO>;
897
+ /**
898
+ * Execute a cortex-enhanced memory retrieval. Combines vector search,
899
+ * entity-context fan-out, recency, reinforcement, and emotional
900
+ * components into a unified `CortexResultDTO`.
901
+ *
902
+ * Results are cached server-side; the next call within ~5 minutes
903
+ * for the same chat + query shape returns the cached value.
904
+ */
905
+ query(query: CortexQueryDTO): Promise<CortexResultDTO>;
906
+ /**
907
+ * Read the most recent cortex result from the warm cache without
908
+ * triggering a re-query. Returns `null` if no cached result exists
909
+ * or it expired. Synchronous from the caller's perspective (no
910
+ * vector search runs); useful when an extension just needs the same
911
+ * memories the active generation saw.
912
+ */
913
+ getCached(chatId: string): Promise<CortexResultDTO | null>;
914
+ /**
915
+ * Resolve linked-cortex data for a chat — every attached vault plus
916
+ * every bidirectional interlink target. When `queryText` is supplied,
917
+ * vault retrieval and interlink queries are enriched with relevance
918
+ * to the current conversation.
919
+ */
920
+ queryLinked(chatId: string, options?: { queryText?: string; userId?: string }): Promise<LinkedCortexResultDTO>;
921
+ /** Read the cached linked-cortex result for a chat. Mirrors {@link getCached}. */
922
+ getCachedLinked(chatId: string): Promise<LinkedCortexResultDTO | null>;
923
+ /** Invalidate the cortex retrieval cache for a chat. */
924
+ invalidateCache(chatId: string): Promise<void>;
925
+ /** Invalidate the linked-cortex retrieval cache for a chat. */
926
+ invalidateLinkedCache(chatId: string): Promise<void>;
927
+ };
928
+
929
+ entities: {
930
+ /**
931
+ * List entities for a chat. By default returns only entities with
932
+ * `status: "active"`, ordered by salience; pass `activeOnly: false`
933
+ * to include retired / deceased / destroyed entities.
934
+ */
935
+ list(chatId: string, options?: { activeOnly?: boolean; limit?: number; userId?: string }): Promise<MemoryEntityDTO[]>;
936
+ get(entityId: string, userId?: string): Promise<MemoryEntityDTO | null>;
937
+ findByName(chatId: string, name: string, userId?: string): Promise<MemoryEntityDTO | null>;
938
+ /**
939
+ * Upsert an entity. Matches against canonical name and known aliases;
940
+ * inserts a new row on miss. `chunkId` and `createdAt` attribute the
941
+ * mention used to fill `lastSeen*` fields — pass them when replaying
942
+ * an extractor over a specific chunk.
943
+ */
944
+ upsert(
945
+ chatId: string,
946
+ entity: MemoryEntityUpsertDTO,
947
+ options?: { chunkId?: string | null; createdAt?: number; userId?: string },
948
+ ): Promise<MemoryEntityDTO>;
949
+ updateStatus(entityId: string, patch: MemoryEntityStatusUpdateDTO, userId?: string): Promise<MemoryEntityDTO>;
950
+ addFacts(entityId: string, facts: string[], userId?: string): Promise<MemoryEntityDTO>;
951
+ getFacts(entityId: string, userId?: string): Promise<string[]>;
952
+ updateEmotionalValence(entityId: string, valence: Record<string, number>, userId?: string): Promise<MemoryEntityDTO>;
953
+ };
954
+
955
+ relations: {
956
+ /** Active relations for a chat (excludes superseded / merged edges). */
957
+ list(chatId: string, userId?: string): Promise<MemoryRelationDTO[]>;
958
+ /** Every relation row including superseded / merged edges — for diagnostics. */
959
+ listAll(chatId: string, userId?: string): Promise<MemoryRelationDTO[]>;
960
+ forEntity(chatId: string, entityId: string, userId?: string): Promise<MemoryRelationDTO[]>;
961
+ forEntities(chatId: string, entityIds: string[], options?: { limit?: number; userId?: string }): Promise<MemoryRelationDTO[]>;
962
+ /**
963
+ * Upsert a relation. Both endpoints must already exist in the entity
964
+ * graph (use `entities.upsert` first); the relation is silently
965
+ * dropped otherwise. `chunkId` attributes the evidence to a chunk.
966
+ */
967
+ upsert(
968
+ chatId: string,
969
+ relation: MemoryRelationUpsertDTO,
970
+ options?: { chunkId?: string | null; userId?: string },
971
+ ): Promise<MemoryRelationDTO | null>;
972
+ };
973
+
974
+ consolidations: {
975
+ /** List arcs for a chat. Pass `tier` to filter to one tier (1 = scene, 2 = chapter, …). */
976
+ list(chatId: string, options?: { tier?: number; userId?: string }): Promise<MemoryConsolidationDTO[]>;
977
+ /** The most recent arc across all tiers, or `null` if none have been produced. */
978
+ latestArc(chatId: string, userId?: string): Promise<MemoryConsolidationDTO | null>;
979
+ /**
980
+ * Trigger an async consolidation pass over the chat's chunks. Returns
981
+ * immediately; new arcs become visible via `list()` once the
982
+ * background job completes.
983
+ */
984
+ run(chatId: string, userId?: string): Promise<void>;
985
+ };
986
+
987
+ salience: {
988
+ /** List salience records for a chat's chunks, ordered by `scoredAt`. */
989
+ list(chatId: string, options?: { limit?: number; offset?: number; userId?: string }): Promise<MemorySalienceDTO[]>;
990
+ };
991
+
992
+ vaults: {
993
+ list(userId?: string): Promise<VaultDTO[]>;
994
+ /** Fetch a vault with its entities + relations. Returns `null` if not found or not owned by the resolved user. */
995
+ get(vaultId: string, userId?: string): Promise<VaultWithContentsDTO | null>;
996
+ getChunks(vaultId: string, userId?: string): Promise<VaultChunkDTO[]>;
997
+ /**
998
+ * Snapshot a chat's cortex state into a new vault. Copies entities,
999
+ * relations and chunk content; LanceDB embeddings are copied in the
1000
+ * background.
1001
+ */
1002
+ create(input: VaultCreateDTO, userId?: string): Promise<VaultDTO>;
1003
+ rename(vaultId: string, name: string, userId?: string): Promise<boolean>;
1004
+ delete(vaultId: string, userId?: string): Promise<boolean>;
1005
+ /** Re-run the LanceDB chunk copy for a vault (e.g. after an embedding model swap). */
1006
+ reindex(vaultId: string, userId?: string): Promise<VaultReindexResultDTO>;
1007
+ };
1008
+
1009
+ links: {
1010
+ list(chatId: string, userId?: string): Promise<ChatLinkDTO[]>;
1011
+ /**
1012
+ * Attach a vault or set up a chat-to-chat interlink. For interlinks,
1013
+ * pass `bidirectional: true` to also create the reverse link.
1014
+ */
1015
+ attach(input: ChatLinkAttachDTO, userId?: string): Promise<ChatLinkDTO[]>;
1016
+ remove(chatId: string, linkId: string, userId?: string): Promise<boolean>;
1017
+ toggle(chatId: string, linkId: string, enabled: boolean, userId?: string): Promise<boolean>;
1018
+ };
1019
+
1020
+ chatMemory: {
1021
+ /**
1022
+ * List the vectorized chunks for a chat. Useful for inspecting the
1023
+ * raw retrieval index used by the `{{memories}}` macro.
1024
+ */
1025
+ listChunks(chatId: string, userId?: string): Promise<ChatChunkDTO[]>;
1026
+ /** Top-K chat memory chunks for a chat via hybrid vector + BM25 search. */
1027
+ get(chatId: string, options?: { topK?: number; userId?: string }): Promise<ChatMemoryResultDTO>;
1028
+ /**
1029
+ * Warm long-term chat memory: rebuilds chunks if stale and queues any
1030
+ * pending chunk vectorizations. Pass `force: true` to rebuild even
1031
+ * when the chunk hash is fresh.
1032
+ */
1033
+ warm(chatId: string, options?: { force?: boolean; userId?: string }): Promise<ChatMemoryWarmupResultDTO>;
1034
+ /** Drop the cached `{{memories}}` retrieval result for a chat. */
1035
+ invalidate(chatId: string, userId?: string): Promise<void>;
1036
+ };
1037
+
1038
+ stats: {
1039
+ usage(chatId: string, userId?: string): Promise<CortexUsageStatsDTO>;
1040
+ ingestionStatus(chatId: string, userId?: string): Promise<CortexIngestionStatusDTO | null>;
1041
+ ingestionTelemetry(chatId: string, userId?: string): Promise<CortexIngestionTelemetryDTO>;
1042
+ };
1043
+ };
1044
+
830
1045
  /**
831
1046
  * Personas CRUD (permission: "personas").
832
1047
  * Manage user personas (identity profiles).
@@ -1158,6 +1373,74 @@ export interface SpindleAPI {
1158
1373
  }>;
1159
1374
  };
1160
1375
 
1376
+ /**
1377
+ * Web search (permission: `"web_search"`).
1378
+ *
1379
+ * Execute searches via the user's configured web search provider
1380
+ * (currently SearXNG; additional providers will be added over time) and
1381
+ * read the safe view of their web search settings. The host enforces all
1382
+ * upstream limits (engine list, max result count, max pages to scrape,
1383
+ * timeouts) — extensions cannot supply their own endpoint or API key.
1384
+ *
1385
+ * For user-scoped extensions, `userId` is inferred from the extension
1386
+ * owner. Operator-scoped extensions should pass the `userId` of the user
1387
+ * whose search engine should run the query.
1388
+ *
1389
+ * @example
1390
+ * ```ts
1391
+ * // Pre-flight: confirm web search is configured before issuing a query.
1392
+ * const settings = await spindle.webSearch.getSettings()
1393
+ * if (!settings.enabled) {
1394
+ * spindle.toast.warning('Configure a web search provider in Settings → Web Search first.')
1395
+ * return
1396
+ * }
1397
+ *
1398
+ * // Full enriched query — scrapes the top-N pages and returns a
1399
+ * // ready-to-inject context block.
1400
+ * const enriched = await spindle.webSearch.query({
1401
+ * query: 'latest LLM benchmark results',
1402
+ * count: 5,
1403
+ * })
1404
+ * await spindle.chat.appendMessage(chatId, {
1405
+ * role: 'system',
1406
+ * content: enriched.context ?? '',
1407
+ * })
1408
+ *
1409
+ * // Lightweight query — titles, URLs, snippets only.
1410
+ * const quick = await spindle.webSearch.query({
1411
+ * query: 'who won the 2026 world cup',
1412
+ * scrape: false,
1413
+ * })
1414
+ * for (const row of quick.results) {
1415
+ * spindle.log.info(`${row.title} — ${row.url}`)
1416
+ * }
1417
+ * ```
1418
+ */
1419
+ webSearch: {
1420
+ /**
1421
+ * Run a search against the user's configured provider.
1422
+ *
1423
+ * Rejects with `"Web search is disabled"` when the user has not
1424
+ * configured a provider, and with `"Web search API URL is not configured"`
1425
+ * when the upstream endpoint is missing. Other upstream errors surface
1426
+ * as `Error("SearXNG returned HTTP NNN")` (or equivalent for future
1427
+ * providers).
1428
+ *
1429
+ * When `input.scrape` is `false`, `documents` and `context` are omitted
1430
+ * from the response. Otherwise the host scrapes up to
1431
+ * `WebSearchSettingsDTO.maxPagesToScrape` results, fills in
1432
+ * `documents[].content`, and assembles a prompt-ready `context` block.
1433
+ */
1434
+ query(input: WebSearchRequestDTO): Promise<WebSearchResponseDTO>;
1435
+ /**
1436
+ * Read the safe view of the user's web search configuration. The raw
1437
+ * API key is never exposed — only `hasApiKey` indicates whether one is
1438
+ * on file. Useful for branching on `enabled` / `provider` /
1439
+ * `maxResultCount` before issuing a query.
1440
+ */
1441
+ getSettings(userId?: string): Promise<WebSearchSettingsDTO>;
1442
+ };
1443
+
1161
1444
  /**
1162
1445
  * Text editor (free tier — no permission needed).
1163
1446
  * Opens the native Lumiverse expanded text editor modal on the user's