@semiont/sdk 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/dist/index.d.ts CHANGED
@@ -1,31 +1,142 @@
1
1
  import * as rxjs from 'rxjs';
2
2
  import { Observable, BehaviorSubject, Subject } from 'rxjs';
3
+ export { firstValueFrom, lastValueFrom } from 'rxjs';
3
4
  import * as _semiont_core from '@semiont/core';
4
- import { components, UserDID, paths, ResourceId, AnnotationId, BodyOperation, EventMap, ResourceDescriptor, Annotation, GraphConnection, Motivation, GatheredContext, JobId, ITransport, EventBus, IContentTransport, BaseUrl, AccessToken, ConnectionState, Selector } from '@semiont/core';
5
- export { ConnectionState, Logger } from '@semiont/core';
6
- import { ActorVM } from '@semiont/api-client';
7
- export { APIError, ActorVM, ActorVMOptions, BusEvent, DEGRADED_THRESHOLD_MS, HttpContentTransport, HttpTransport, HttpTransportConfig, TokenRefresher, createActorVM } from '@semiont/api-client';
5
+ import { ResourceId, components, UserDID, paths, BackendDownload, ProgressEvent, AnnotationId, BodyOperation, EventMap, ResourceDescriptor, Annotation, GraphConnection, Motivation, GatheredContext, JobId, ITransport, EventBus, IContentTransport, IBackendOperations, BaseUrl, AccessToken, SemiontError, ConnectionState, Selector } from '@semiont/core';
6
+ export { AccessToken, Annotation, AnnotationId, BaseUrl, BodyItem, BodyOperation, ConnectionState, EntityType, EventMap, GatheredContext, IContentTransport, ITransport, Logger, Motivation, RefreshToken, ResourceDescriptor, ResourceId, SemiontError, UserId, accessToken, annotationId, baseUrl, entityType, refreshToken, resourceId, userId } from '@semiont/core';
7
+ export { APIError, HttpContentTransport, HttpTransport, HttpTransportConfig, TokenRefresher } from '@semiont/api-client';
8
+
9
+ /**
10
+ * Thenable Observable subclasses.
11
+ *
12
+ * Two thin Observable subclasses that also implement `PromiseLike<T>`. Used as
13
+ * the public return type of namespace methods that emit streams (job
14
+ * lifecycle, generation progress) and cache reads (Browse live queries).
15
+ *
16
+ * The point: scripts can `await` the call directly without `lastValueFrom` /
17
+ * `firstValueFrom` wrappers; reactive consumers keep using `.subscribe(...)`
18
+ * and `.pipe(...)` exactly as before.
19
+ *
20
+ * The asymmetric `.then()` semantics — last-value-on-completion for streams,
21
+ * first-non-undefined-value for caches — is encoded by the subclass name. The
22
+ * docstring on the namespace method tells the consumer which one applies.
23
+ *
24
+ * `.pipe(...)` returns a plain `Observable<T>` (RxJS doesn't propagate
25
+ * subclasses through `pipe`). Once you compose, you've explicitly entered
26
+ * RxJS land; `lastValueFrom` from `rxjs` is the right bridge there.
27
+ */
28
+
29
+ /**
30
+ * Bounded Observable stream — emits zero-or-more progress values, then a
31
+ * final value on completion. Used by job-lifecycle methods like
32
+ * `mark.assist`, `gather.annotation`, `match.search`, `yield.fromAnnotation`.
33
+ *
34
+ * Awaiting resolves to the **last** emitted value (via `lastValueFrom`).
35
+ * Subscribing yields every emission, ending in `complete`.
36
+ */
37
+ declare class StreamObservable<T> extends Observable<T> implements PromiseLike<T> {
38
+ then<R1 = T, R2 = never>(onfulfilled?: ((v: T) => R1 | PromiseLike<R1>) | null, onrejected?: ((e: unknown) => R2 | PromiseLike<R2>) | null): PromiseLike<R1 | R2>;
39
+ /** Wrap an existing Observable's subscribe behavior in a StreamObservable. */
40
+ static from<T>(source: Observable<T>): StreamObservable<T>;
41
+ }
42
+ /**
43
+ * Multicast cache observable — emits `undefined` while the underlying value
44
+ * is loading, then the value, then re-emits when bus events invalidate the
45
+ * cache entry. Used by Browse live-query methods (`browse.resource`,
46
+ * `browse.annotations`, etc.).
47
+ *
48
+ * Awaiting resolves to the **first non-undefined** value (waits past the
49
+ * loading state). Subscribing yields the full sequence including the
50
+ * initial `undefined`, so reactive consumers can render a loading state.
51
+ *
52
+ * The class is parameterized as `CacheObservable<T>` even though the
53
+ * stream's element type is `T | undefined` — `T` is what the consumer
54
+ * gets from `await`, and that's the contract we want to advertise. The
55
+ * `Observable<T | undefined>` shape leaks through `.subscribe` and
56
+ * `.pipe` in the natural way.
57
+ */
58
+ declare class CacheObservable<T> extends Observable<T | undefined> implements PromiseLike<T> {
59
+ then<R1 = T, R2 = never>(onfulfilled?: ((v: T) => R1 | PromiseLike<R1>) | null, onrejected?: ((e: unknown) => R2 | PromiseLike<R2>) | null): PromiseLike<R1 | R2>;
60
+ /**
61
+ * Wrap an existing Observable's subscribe behavior in a `CacheObservable`.
62
+ *
63
+ * Memoizes on source identity: passing the same `source` returns the same
64
+ * wrapper instance. The Browse cache primitive already returns a stable
65
+ * Observable per key (its B4 contract), so this preserves that contract
66
+ * through the awaitable wrapping. Without the memo, every public-method
67
+ * call would produce a fresh wrapper and break referential-equality
68
+ * guarantees that hook-style reactive consumers depend on.
69
+ *
70
+ * Backed by a `WeakMap`, so wrappers are GC'd when their source is.
71
+ */
72
+ static from<T>(source: Observable<T | undefined>): CacheObservable<T>;
73
+ }
74
+ /**
75
+ * Discriminated phases of an upload's lifecycle.
76
+ *
77
+ * - `started` — emitted immediately on `yield.resource(...)` invocation, before any bytes flow.
78
+ * - `progress` — emitted as bytes flow over the wire. Wired by `HttpContentTransport`'s XHR path when a caller passes `onProgress` (or, transitively, when `yield.resource` is the caller — it always wires the hook so subscribers see byte counts). `bytesUploaded` and `totalBytes` carry the running counts; `totalBytes` may be 0 when the transport can't determine the total (rare, e.g. chunked encoding) — UI consumers should render an indeterminate state in that case.
79
+ * - `finished` — emitted on backend acknowledgement, carries the assigned `resourceId`.
80
+ *
81
+ * Failures surface as `Observable.error(...)` (typically an `APIError` from the transport's `errors$` Subject), not as a `phase: 'failed'` event — `subscribe`'s error callback handles them. Cancellation is honored: unsubscribing before `finished` aborts the in-flight HTTP request on the XHR path.
82
+ */
83
+ type UploadProgress = {
84
+ phase: 'started';
85
+ totalBytes: number;
86
+ } | {
87
+ phase: 'progress';
88
+ bytesUploaded: number;
89
+ totalBytes: number;
90
+ } | {
91
+ phase: 'finished';
92
+ resourceId: ResourceId;
93
+ };
94
+ /**
95
+ * Specialized `StreamObservable` for `yield.resource`. Subscribers see the
96
+ * full `UploadProgress` event sequence (started → optional progress → finished).
97
+ * Awaiting resolves specifically to `{ resourceId }` extracted from the
98
+ * `'finished'` event — preserving the pre-Phase-18 awaited shape so existing
99
+ * `await client.yield.resource(...)` callers don't need to narrow the union.
100
+ */
101
+ declare class UploadObservable extends Observable<UploadProgress> implements PromiseLike<{
102
+ resourceId: ResourceId;
103
+ }> {
104
+ then<R1 = {
105
+ resourceId: ResourceId;
106
+ }, R2 = never>(onfulfilled?: ((v: {
107
+ resourceId: ResourceId;
108
+ }) => R1 | PromiseLike<R1>) | null, onrejected?: ((e: unknown) => R2 | PromiseLike<R2>) | null): PromiseLike<R1 | R2>;
109
+ }
8
110
 
9
111
  /**
10
112
  * Verb Namespace Interfaces
11
113
  *
12
- * These interfaces define the public API of the Semiont api-client,
13
- * organized by the 7 domain flows (Browse, Mark, Bind, Gather, Match,
14
- * Yield, Beckon) plus infrastructure namespaces (Job, Auth, Admin).
114
+ * These interfaces define the public API of `@semiont/sdk`, organized by
115
+ * the 7 domain flows (Browse, Mark, Bind, Gather, Match, Yield, Beckon)
116
+ * plus infrastructure namespaces (Job, Auth, Admin).
15
117
  *
16
- * Each namespace maps 1:1 to a flow. Each flow maps to a clear actor
17
- * on the backend. The frontend calls `client.mark.annotation()` and the
18
- * proxy handles HTTP, auth, SSE, and caching internally.
118
+ * Each namespace maps 1:1 to a flow. Each flow maps to a clear actor on
119
+ * the backend. The frontend calls `client.mark.annotation()` and the
120
+ * client handles HTTP, auth, SSE, and caching internally.
19
121
  *
20
122
  * Return type conventions:
21
- * - Browse live queries → Observable (bus gateway driven, cached)
22
- * - Browse one-shot reads Promise (fetch once, no cache)
23
- * - Commands (mark, bind, yield.resource) → Promise (fire-and-forget)
24
- * - Long-running ops (gather, match, yield.fromAnnotation, mark.assist) → Observable (progress + result)
25
- * - Ephemeral signals (beckon) void
123
+ * - Browse live queries → `CacheObservable<T>` (bus-driven, cached;
124
+ * subscribe yields `T | undefined`, await yields `T` after first load)
125
+ * - Browse one-shot reads`Promise<T>` (fetch once, no cache)
126
+ * - Commands (mark, bind, yield.resource) → `Promise<T>` (atomic ops)
127
+ * - Long-running ops (gather, match, yield.fromAnnotation, mark.assist)
128
+ * → `StreamObservable<T>` (progress + result; subscribe yields every
129
+ * emit, await yields the last one)
130
+ * - Ephemeral signals (beckon) → `void`
131
+ *
132
+ * `StreamObservable` and `CacheObservable` are `Observable` subclasses
133
+ * that also implement `PromiseLike<T>` — `await client.X.Y(...)` works
134
+ * directly without `lastValueFrom`/`firstValueFrom` wrappers.
135
+ * `.pipe(...)` returns a plain `Observable<T>` (the thenable subclass
136
+ * does not propagate through pipe — by design).
26
137
  */
27
138
 
28
- type StoredEventResponse$2 = components['schemas']['StoredEventResponse'];
139
+ type StoredEventResponse$1 = components['schemas']['StoredEventResponse'];
29
140
  type GatherProgress = components['schemas']['GatherProgress'];
30
141
  type MatchSearchResult = components['schemas']['MatchSearchResult'];
31
142
  type JobProgress$2 = components['schemas']['JobProgress'];
@@ -92,7 +203,10 @@ interface GenerationOptions {
92
203
  storageUri: string;
93
204
  context: GatheredContext;
94
205
  prompt?: string;
206
+ /** Annotation/resource body locale — language the generated resource is written in (typically the user's UI locale). */
95
207
  language?: string;
208
+ /** Source-resource locale — language of the resource the annotation lives on, used in the prompt so the LLM understands embedded source-context snippets. BCP-47. */
209
+ sourceLanguage?: string;
96
210
  temperature?: number;
97
211
  maxTokens?: number;
98
212
  }
@@ -103,7 +217,10 @@ interface MarkAssistOptions {
103
217
  instructions?: string;
104
218
  density?: number;
105
219
  tone?: string;
220
+ /** Annotation body locale — language the LLM should write generated body text in (comment text, assessment text, tag/reference body language stamp). BCP-47. */
106
221
  language?: string;
222
+ /** Source-resource locale — language of the content being analyzed, used in the prompt so the LLM analyzes non-English source correctly. BCP-47. */
223
+ sourceLanguage?: string;
107
224
  schemaId?: string;
108
225
  categories?: string[];
109
226
  }
@@ -171,17 +288,17 @@ type YieldGenerationEvent = {
171
288
  * Event prefix: browse:*
172
289
  */
173
290
  interface BrowseNamespace$1 {
174
- resource(resourceId: ResourceId): Observable<ResourceDescriptor | undefined>;
291
+ resource(resourceId: ResourceId): CacheObservable<ResourceDescriptor>;
175
292
  resources(filters?: {
176
293
  limit?: number;
177
294
  archived?: boolean;
178
295
  search?: string;
179
- }): Observable<ResourceDescriptor[] | undefined>;
180
- annotations(resourceId: ResourceId): Observable<Annotation[] | undefined>;
181
- annotation(resourceId: ResourceId, annotationId: AnnotationId): Observable<Annotation | undefined>;
182
- entityTypes(): Observable<string[] | undefined>;
183
- referencedBy(resourceId: ResourceId): Observable<ReferencedByEntry[] | undefined>;
184
- events(resourceId: ResourceId): Observable<StoredEventResponse$2[] | undefined>;
296
+ }): CacheObservable<ResourceDescriptor[]>;
297
+ annotations(resourceId: ResourceId): CacheObservable<Annotation[]>;
298
+ annotation(resourceId: ResourceId, annotationId: AnnotationId): CacheObservable<Annotation>;
299
+ entityTypes(): CacheObservable<string[]>;
300
+ referencedBy(resourceId: ResourceId): CacheObservable<ReferencedByEntry[]>;
301
+ events(resourceId: ResourceId): CacheObservable<StoredEventResponse$1[]>;
185
302
  resourceContent(resourceId: ResourceId): Promise<string>;
186
303
  resourceRepresentation(resourceId: ResourceId, options?: {
187
304
  accept?: string;
@@ -195,7 +312,7 @@ interface BrowseNamespace$1 {
195
312
  stream: ReadableStream<Uint8Array>;
196
313
  contentType: string;
197
314
  }>;
198
- resourceEvents(resourceId: ResourceId): Promise<StoredEventResponse$2[]>;
315
+ resourceEvents(resourceId: ResourceId): Promise<StoredEventResponse$1[]>;
199
316
  annotationHistory(resourceId: ResourceId, annotationId: AnnotationId): Promise<AnnotationHistoryResponse>;
200
317
  connections(resourceId: ResourceId): Promise<GraphConnection[]>;
201
318
  backlinks(resourceId: ResourceId): Promise<Annotation[]>;
@@ -205,7 +322,33 @@ interface BrowseNamespace$1 {
205
322
  navigateReference(resourceId: ResourceId): void;
206
323
  }
207
324
  /**
208
- * Markannotation CRUD, entity types, AI assist
325
+ * Frameschema-layer flow (the eighth flow).
326
+ *
327
+ * Frame operates on the KB's conceptual vocabulary — what *kinds* of
328
+ * things exist (entity types) and, in the future, what taxonomies are
329
+ * recognized (tag schemas), what relations are typed (predicate types),
330
+ * and how schemas are imported (ontology I/O). The other seven flows
331
+ * (yield, mark, match, bind, gather, browse, beckon) operate on
332
+ * content; Frame operates on the schema layer that content is expressed
333
+ * in.
334
+ *
335
+ * MVP scope is small: entity-type vocabulary writes only. Live reads of
336
+ * the entity-type vocabulary stay on Browse (`browse.entityTypes()` is
337
+ * a `CacheObservable<string[]>` consumed by 8+ call sites). Frame owns
338
+ * writes; Browse owns reads — the same asymmetry that already holds for
339
+ * resources and annotations.
340
+ *
341
+ * Backend actor: Stower
342
+ * Event prefix: frame:*
343
+ */
344
+ interface FrameNamespace$1 {
345
+ /** Add a single entity type to the KB's vocabulary. Idempotent — adding an existing type is a no-op. */
346
+ addEntityType(type: string): Promise<void>;
347
+ /** Add multiple entity types in one call. Convenience over a loop of `addEntityType`. */
348
+ addEntityTypes(types: string[]): Promise<void>;
349
+ }
350
+ /**
351
+ * Mark — annotation CRUD, AI assist, resource lifecycle
209
352
  *
210
353
  * Commands return Promises that resolve on HTTP acceptance (202).
211
354
  * Results appear on browse Observables via bus gateway.
@@ -215,17 +358,15 @@ interface BrowseNamespace$1 {
215
358
  * Event prefix: mark:*
216
359
  */
217
360
  interface MarkNamespace$1 {
218
- annotation(resourceId: ResourceId, input: CreateAnnotationInput): Promise<{
219
- annotationId: string;
361
+ annotation(input: CreateAnnotationInput): Promise<{
362
+ annotationId: AnnotationId;
220
363
  }>;
221
364
  delete(resourceId: ResourceId, annotationId: AnnotationId): Promise<void>;
222
- entityType(type: string): Promise<void>;
223
- entityTypes(types: string[]): Promise<void>;
224
365
  archive(resourceId: ResourceId): Promise<void>;
225
366
  unarchive(resourceId: ResourceId): Promise<void>;
226
- assist(resourceId: ResourceId, motivation: Motivation, options: MarkAssistOptions): Observable<MarkAssistEvent>;
367
+ assist(resourceId: ResourceId, motivation: Motivation, options: MarkAssistOptions): StreamObservable<MarkAssistEvent>;
227
368
  request(selector: components['schemas']['MarkRequestedEvent']['selector'], motivation: Motivation): void;
228
- /** Fire-and-forget variant of `assist` — mark-vm orchestrates the call and its progress Observable. */
369
+ /** Fire-and-forget variant of `assist` — mark-state-unit orchestrates the call and its progress Observable. */
229
370
  requestAssist(motivation: Motivation, options: MarkAssistOptions, correlationId?: string): void;
230
371
  /** Submit the currently pending annotation with its selector and optional body. */
231
372
  submit(input: components['schemas']['MarkSubmitEvent']): void;
@@ -263,12 +404,12 @@ interface BindNamespace$1 {
263
404
  * Event prefix: gather:*
264
405
  */
265
406
  interface GatherNamespace$1 {
266
- annotation(annotationId: AnnotationId, resourceId: ResourceId, options?: {
407
+ annotation(resourceId: ResourceId, annotationId: AnnotationId, options?: {
267
408
  contextWindow?: number;
268
- }): Observable<GatherAnnotationProgress>;
409
+ }): StreamObservable<GatherAnnotationProgress>;
269
410
  resource(resourceId: ResourceId, options?: {
270
411
  contextWindow?: number;
271
- }): Observable<GatherAnnotationProgress>;
412
+ }): StreamObservable<GatherAnnotationProgress>;
272
413
  }
273
414
  /**
274
415
  * Match — search and ranking
@@ -280,11 +421,11 @@ interface GatherNamespace$1 {
280
421
  * Event prefix: match:*
281
422
  */
282
423
  interface MatchNamespace$1 {
283
- search(resourceId: ResourceId, referenceId: string, context: GatheredContext, options?: {
424
+ search(resourceId: ResourceId, referenceId: AnnotationId, context: GatheredContext, options?: {
284
425
  limit?: number;
285
426
  useSemanticScoring?: boolean;
286
- }): Observable<MatchSearchProgress>;
287
- /** Fire-and-forget variant: match-vm orchestrates the call and its result Observable. */
427
+ }): StreamObservable<MatchSearchProgress>;
428
+ /** Fire-and-forget variant: match-state-unit orchestrates the call and its result Observable. */
288
429
  requestSearch(input: components['schemas']['MatchSearchRequest']): void;
289
430
  }
290
431
  /**
@@ -297,17 +438,15 @@ interface MatchNamespace$1 {
297
438
  * Event prefix: yield:*
298
439
  */
299
440
  interface YieldNamespace$1 {
300
- resource(data: CreateResourceInput): Promise<{
301
- resourceId: string;
302
- }>;
303
- fromAnnotation(resourceId: ResourceId, annotationId: AnnotationId, options: GenerationOptions): Observable<YieldGenerationEvent>;
441
+ resource(data: CreateResourceInput): UploadObservable;
442
+ fromAnnotation(resourceId: ResourceId, annotationId: AnnotationId, options: GenerationOptions): StreamObservable<YieldGenerationEvent>;
304
443
  cloneToken(resourceId: ResourceId): Promise<{
305
444
  token: string;
306
445
  expiresAt: string;
307
446
  }>;
308
447
  fromToken(token: string): Promise<ResourceDescriptor>;
309
448
  createFromToken(options: CreateFromTokenOptions): Promise<{
310
- resourceId: string;
449
+ resourceId: ResourceId;
311
450
  }>;
312
451
  /** UI signal: user invoked the clone action from the resource-info panel. */
313
452
  clone(): void;
@@ -322,7 +461,7 @@ interface YieldNamespace$1 {
322
461
  * Event prefix: beckon:*
323
462
  */
324
463
  interface BeckonNamespace$1 {
325
- attention(annotationId: AnnotationId, resourceId: ResourceId): void;
464
+ attention(resourceId: ResourceId, annotationId: AnnotationId): void;
326
465
  hover(annotationId: AnnotationId | null): void;
327
466
  sparkle(annotationId: AnnotationId): void;
328
467
  }
@@ -344,7 +483,7 @@ interface JobNamespace$1 {
344
483
  timeout?: number;
345
484
  onProgress?: (status: JobStatusResponse$1) => void;
346
485
  }): Promise<JobStatusResponse$1>;
347
- cancel(jobId: JobId, type: string): Promise<void>;
486
+ cancelByType(jobType: 'annotation' | 'generation'): Promise<void>;
348
487
  /** UI signal: cancel all active jobs of a given type (e.g. "annotation"). */
349
488
  cancelRequest(jobType: 'annotation' | 'generation'): void;
350
489
  }
@@ -375,31 +514,21 @@ interface AdminNamespace$1 {
375
514
  oauthConfig(): Promise<OAuthConfigResponse$1>;
376
515
  healthCheck(): Promise<ResponseContent<paths['/api/health']['get']>>;
377
516
  status(): Promise<ResponseContent<paths['/api/status']['get']>>;
378
- backup(): Promise<Response>;
379
- restore(file: File, onProgress?: (event: {
380
- phase: string;
381
- message?: string;
382
- result?: Record<string, unknown>;
383
- }) => void): Promise<{
384
- phase: string;
385
- message?: string;
386
- result?: Record<string, unknown>;
387
- }>;
517
+ backup(): Promise<BackendDownload>;
518
+ /**
519
+ * Restore from a backup archive. Returns a `StreamObservable` that
520
+ * emits each `ProgressEvent` as the operation runs (`'started'`,
521
+ * `'parsing'`, `'importing'`, ..., `'complete'`). Subscribers see
522
+ * every step; awaiters get the final event via the PromiseLike sugar.
523
+ */
524
+ restore(file: File): StreamObservable<ProgressEvent>;
388
525
  exportKnowledgeBase(params?: {
389
526
  includeArchived?: boolean;
390
- }): Promise<Response>;
391
- importKnowledgeBase(file: File, onProgress?: (event: {
392
- phase: string;
393
- message?: string;
394
- result?: Record<string, unknown>;
395
- }) => void): Promise<{
396
- phase: string;
397
- message?: string;
398
- result?: Record<string, unknown>;
399
- }>;
527
+ }): Promise<BackendDownload>;
528
+ importKnowledgeBase(file: File): StreamObservable<ProgressEvent>;
400
529
  }
401
530
 
402
- type StoredEventResponse$1 = components['schemas']['StoredEventResponse'];
531
+ type StoredEventResponse = components['schemas']['StoredEventResponse'];
403
532
  type ResourceListFilters = {
404
533
  limit?: number;
405
534
  archived?: boolean;
@@ -437,13 +566,13 @@ declare class BrowseNamespace implements BrowseNamespace$1 {
437
566
  */
438
567
  private readonly annotationListObs;
439
568
  constructor(transport: ITransport, bus: EventBus, content: IContentTransport);
440
- resource(resourceId: ResourceId): Observable<ResourceDescriptor | undefined>;
441
- resources(filters?: ResourceListFilters): Observable<ResourceDescriptor[] | undefined>;
442
- annotations(resourceId: ResourceId): Observable<Annotation[] | undefined>;
443
- annotation(resourceId: ResourceId, annotationId: AnnotationId): Observable<Annotation | undefined>;
444
- entityTypes(): Observable<string[] | undefined>;
445
- referencedBy(resourceId: ResourceId): Observable<ReferencedByEntry[] | undefined>;
446
- events(resourceId: ResourceId): Observable<StoredEventResponse$1[] | undefined>;
569
+ resource(resourceId: ResourceId): CacheObservable<ResourceDescriptor>;
570
+ resources(filters?: ResourceListFilters): CacheObservable<ResourceDescriptor[]>;
571
+ annotations(resourceId: ResourceId): CacheObservable<Annotation[]>;
572
+ annotation(resourceId: ResourceId, annotationId: AnnotationId): CacheObservable<Annotation>;
573
+ entityTypes(): CacheObservable<string[]>;
574
+ referencedBy(resourceId: ResourceId): CacheObservable<ReferencedByEntry[]>;
575
+ events(resourceId: ResourceId): CacheObservable<StoredEventResponse[]>;
447
576
  resourceContent(resourceId: ResourceId): Promise<string>;
448
577
  resourceRepresentation(resourceId: ResourceId, options?: {
449
578
  accept?: string;
@@ -457,7 +586,7 @@ declare class BrowseNamespace implements BrowseNamespace$1 {
457
586
  stream: ReadableStream<Uint8Array>;
458
587
  contentType: string;
459
588
  }>;
460
- resourceEvents(resourceId: ResourceId): Promise<StoredEventResponse$1[]>;
589
+ resourceEvents(resourceId: ResourceId): Promise<StoredEventResponse[]>;
461
590
  annotationHistory(resourceId: ResourceId, annotationId: AnnotationId): Promise<AnnotationHistoryResponse>;
462
591
  connections(_resourceId: ResourceId): Promise<GraphConnection[]>;
463
592
  backlinks(_resourceId: ResourceId): Promise<Annotation[]>;
@@ -506,15 +635,13 @@ declare class MarkNamespace implements MarkNamespace$1 {
506
635
  private readonly transport;
507
636
  private readonly bus;
508
637
  constructor(transport: ITransport, bus: EventBus);
509
- annotation(resourceId: ResourceId, input: CreateAnnotationInput): Promise<{
510
- annotationId: string;
638
+ annotation(input: CreateAnnotationInput): Promise<{
639
+ annotationId: AnnotationId;
511
640
  }>;
512
641
  delete(resourceId: ResourceId, annotationId: AnnotationId): Promise<void>;
513
- entityType(type: string): Promise<void>;
514
- entityTypes(types: string[]): Promise<void>;
515
642
  archive(resourceId: ResourceId): Promise<void>;
516
643
  unarchive(resourceId: ResourceId): Promise<void>;
517
- assist(resourceId: ResourceId, motivation: Motivation, options: MarkAssistOptions): Observable<MarkAssistEvent>;
644
+ assist(resourceId: ResourceId, motivation: Motivation, options: MarkAssistOptions): StreamObservable<MarkAssistEvent>;
518
645
  request(selector: components['schemas']['MarkRequestedEvent']['selector'], motivation: Motivation): void;
519
646
  requestAssist(motivation: Motivation, options: MarkAssistOptions, correlationId?: string): void;
520
647
  submit(input: components['schemas']['MarkSubmitEvent']): void;
@@ -539,12 +666,12 @@ declare class GatherNamespace implements GatherNamespace$1 {
539
666
  private readonly transport;
540
667
  private readonly bus;
541
668
  constructor(transport: ITransport, bus: EventBus);
542
- annotation(annotationId: AnnotationId, resourceId: ResourceId, options?: {
669
+ annotation(resourceId: ResourceId, annotationId: AnnotationId, options?: {
543
670
  contextWindow?: number;
544
- }): Observable<GatherAnnotationProgress>;
671
+ }): StreamObservable<GatherAnnotationProgress>;
545
672
  resource(_resourceId: ResourceId, _options?: {
546
673
  contextWindow?: number;
547
- }): Observable<GatherAnnotationProgress>;
674
+ }): StreamObservable<GatherAnnotationProgress>;
548
675
  }
549
676
 
550
677
  declare class MatchNamespace implements MatchNamespace$1 {
@@ -552,10 +679,10 @@ declare class MatchNamespace implements MatchNamespace$1 {
552
679
  private readonly bus;
553
680
  constructor(transport: ITransport, bus: EventBus);
554
681
  requestSearch(input: components['schemas']['MatchSearchRequest']): void;
555
- search(resourceId: ResourceId, referenceId: string, context: GatheredContext, options?: {
682
+ search(resourceId: ResourceId, referenceId: AnnotationId, context: GatheredContext, options?: {
556
683
  limit?: number;
557
684
  useSemanticScoring?: boolean;
558
- }): Observable<MatchSearchProgress>;
685
+ }): StreamObservable<MatchSearchProgress>;
559
686
  }
560
687
 
561
688
  declare class YieldNamespace implements YieldNamespace$1 {
@@ -563,17 +690,15 @@ declare class YieldNamespace implements YieldNamespace$1 {
563
690
  private readonly bus;
564
691
  private readonly content;
565
692
  constructor(transport: ITransport, bus: EventBus, content: IContentTransport);
566
- resource(data: CreateResourceInput): Promise<{
567
- resourceId: string;
568
- }>;
569
- fromAnnotation(resourceId: ResourceId, annotationId: AnnotationId, options: GenerationOptions): Observable<YieldGenerationEvent>;
693
+ resource(data: CreateResourceInput): UploadObservable;
694
+ fromAnnotation(resourceId: ResourceId, annotationId: AnnotationId, options: GenerationOptions): StreamObservable<YieldGenerationEvent>;
570
695
  cloneToken(resourceId: ResourceId): Promise<{
571
696
  token: string;
572
697
  expiresAt: string;
573
698
  }>;
574
699
  fromToken(token: string): Promise<ResourceDescriptor>;
575
700
  createFromToken(options: CreateFromTokenOptions): Promise<{
576
- resourceId: string;
701
+ resourceId: ResourceId;
577
702
  }>;
578
703
  clone(): void;
579
704
  }
@@ -582,11 +707,38 @@ declare class BeckonNamespace implements BeckonNamespace$1 {
582
707
  private readonly transport;
583
708
  private readonly bus;
584
709
  constructor(transport: ITransport, bus: EventBus);
585
- attention(annotationId: AnnotationId, resourceId: ResourceId): void;
710
+ attention(resourceId: ResourceId, annotationId: AnnotationId): void;
586
711
  hover(annotationId: AnnotationId | null): void;
587
712
  sparkle(annotationId: AnnotationId): void;
588
713
  }
589
714
 
715
+ /**
716
+ * FrameNamespace — the eighth flow's surface.
717
+ *
718
+ * Frame operates on the KB's **schema layer** — the conceptual vocabulary
719
+ * the other seven flows are expressed in. Where yield/mark/match/bind/
720
+ * gather/browse/beckon act on content (resources, annotations, references,
721
+ * attention), Frame acts on what *kinds* of things exist: entity types,
722
+ * eventually tag schemas, relation/predicate types, ontology imports.
723
+ *
724
+ * The MVP owns a single primitive — entity-type vocabulary writes on the
725
+ * `frame:add-entity-type` channel. See `docs/protocol/flows/FRAME.md`
726
+ * for the per-flow contract.
727
+ *
728
+ * Live reads of the entity-type vocabulary stay on Browse
729
+ * (`browse.entityTypes()` is a `CacheObservable<string[]>`). Frame owns
730
+ * writes; Browse owns reads. The asymmetry is intentional — re-implementing
731
+ * Browse's cache primitives on Frame for a single read would duplicate
732
+ * machinery without benefit.
733
+ */
734
+
735
+ declare class FrameNamespace implements FrameNamespace$1 {
736
+ private readonly transport;
737
+ constructor(transport: ITransport);
738
+ addEntityType(type: string): Promise<void>;
739
+ addEntityTypes(types: string[]): Promise<void>;
740
+ }
741
+
590
742
  type JobStatusResponse = components['schemas']['JobStatusResponse'];
591
743
  declare class JobNamespace implements JobNamespace$1 {
592
744
  private readonly transport;
@@ -610,19 +762,19 @@ declare class JobNamespace implements JobNamespace$1 {
610
762
  timeout?: number;
611
763
  onProgress?: (status: JobStatusResponse) => void;
612
764
  }): Promise<JobStatusResponse>;
613
- cancel(_jobId: JobId, type: string): Promise<void>;
765
+ cancelByType(jobType: 'annotation' | 'generation'): Promise<void>;
614
766
  cancelRequest(jobType: 'annotation' | 'generation'): void;
615
767
  }
616
768
 
617
769
  /**
618
- * AuthNamespace — authentication. Pure wire, no bus.
770
+ * AuthNamespace — authentication. Backend ops only; no bus.
619
771
  */
620
772
 
621
773
  type AuthResponse = components['schemas']['AuthResponse'];
622
774
  type TokenRefreshResponse = components['schemas']['TokenRefreshResponse'];
623
775
  declare class AuthNamespace implements AuthNamespace$1 {
624
- private readonly transport;
625
- constructor(transport: ITransport);
776
+ private readonly backend;
777
+ constructor(backend: IBackendOperations);
626
778
  password(emailStr: string, passwordStr: string): Promise<AuthResponse>;
627
779
  google(credential: string): Promise<AuthResponse>;
628
780
  refresh(token: string): Promise<TokenRefreshResponse>;
@@ -638,42 +790,26 @@ declare class AuthNamespace implements AuthNamespace$1 {
638
790
  }
639
791
 
640
792
  /**
641
- * AdminNamespace — administration. Pure wire, no bus.
793
+ * AdminNamespace — administration. Backend ops only; no bus.
642
794
  */
643
795
 
644
796
  type AdminUserStatsResponse = components['schemas']['AdminUserStatsResponse'];
645
797
  type OAuthConfigResponse = components['schemas']['OAuthConfigResponse'];
646
798
  declare class AdminNamespace implements AdminNamespace$1 {
647
- private readonly transport;
648
- constructor(transport: ITransport);
799
+ private readonly backend;
800
+ constructor(backend: IBackendOperations);
649
801
  users(): Promise<User[]>;
650
802
  userStats(): Promise<AdminUserStatsResponse>;
651
803
  updateUser(userId: UserDID, data: RequestContent<paths['/api/admin/users/{id}']['patch']>): Promise<User>;
652
804
  oauthConfig(): Promise<OAuthConfigResponse>;
653
805
  healthCheck(): Promise<ResponseContent<paths['/api/health']['get']>>;
654
806
  status(): Promise<ResponseContent<paths['/api/status']['get']>>;
655
- backup(): Promise<Response>;
656
- restore(file: File, onProgress?: (event: {
657
- phase: string;
658
- message?: string;
659
- result?: Record<string, unknown>;
660
- }) => void): Promise<{
661
- phase: string;
662
- message?: string;
663
- result?: Record<string, unknown>;
664
- }>;
807
+ backup(): Promise<BackendDownload>;
808
+ restore(file: File): StreamObservable<ProgressEvent>;
665
809
  exportKnowledgeBase(params?: {
666
810
  includeArchived?: boolean;
667
- }): Promise<Response>;
668
- importKnowledgeBase(file: File, onProgress?: (event: {
669
- phase: string;
670
- message?: string;
671
- result?: Record<string, unknown>;
672
- }) => void): Promise<{
673
- phase: string;
674
- message?: string;
675
- result?: Record<string, unknown>;
676
- }>;
811
+ }): Promise<BackendDownload>;
812
+ importKnowledgeBase(file: File): StreamObservable<ProgressEvent>;
677
813
  }
678
814
 
679
815
  declare class SemiontClient {
@@ -694,6 +830,7 @@ declare class SemiontClient {
694
830
  */
695
831
  readonly bus: EventBus;
696
832
  readonly baseUrl: BaseUrl;
833
+ readonly frame: FrameNamespace;
697
834
  readonly browse: BrowseNamespace;
698
835
  readonly mark: MarkNamespace;
699
836
  readonly bind: BindNamespace;
@@ -702,8 +839,8 @@ declare class SemiontClient {
702
839
  readonly yield: YieldNamespace;
703
840
  readonly beckon: BeckonNamespace;
704
841
  readonly job: JobNamespace;
705
- readonly auth: AuthNamespace;
706
- readonly admin: AdminNamespace;
842
+ readonly auth: AuthNamespace | undefined;
843
+ readonly admin: AdminNamespace | undefined;
707
844
  /**
708
845
  * The client *owns* its bus. The constructor creates a fresh `EventBus`
709
846
  * and hands it to the transport via `transport.bridgeInto(this.bus)`.
@@ -716,16 +853,78 @@ declare class SemiontClient {
716
853
  * Callers do not pass a bus in. If they need to interact with the bus
717
854
  * (e.g. for tests or to subscribe to arbitrary channels), they read it
718
855
  * back via `client.bus`.
856
+ *
857
+ * `backend` is optional. When provided, the `auth` and `admin`
858
+ * namespaces are constructed against it; when omitted, they're
859
+ * `undefined`. For HTTP setups this is conventionally the same
860
+ * `HttpTransport` instance that's also passed as `transport` (HTTP
861
+ * implements both `ITransport` and `IBackendOperations`).
719
862
  */
720
- constructor(transport: ITransport, content: IContentTransport);
863
+ constructor(transport: ITransport, content: IContentTransport, backend?: IBackendOperations);
721
864
  /** Transport-level connection state. HTTP reflects SSE health; local is always 'connected'. */
722
865
  get state$(): rxjs.Observable<_semiont_core.ConnectionState>;
723
866
  subscribeToResource(resourceId: ResourceId): () => void;
724
867
  dispose(): void;
868
+ /**
869
+ * Convenience factory for the default HTTP setup. Constructs a
870
+ * `BehaviorSubject<AccessToken | null>` internally, plus an
871
+ * `HttpTransport` and `HttpContentTransport`, and returns the wired
872
+ * `SemiontClient`.
873
+ *
874
+ * Use this for one-shot scripts, CLI commands, or any consumer that
875
+ * doesn't need to drive the token from outside (no manual refresh,
876
+ * no cross-tab sync). For long-running scripts that need refresh,
877
+ * use `SemiontSession.fromHttp(...)` (with a token already on hand)
878
+ * or `SemiontSession.signInHttp(...)` (credentials-first) instead —
879
+ * either owns the same transport/client wiring plus the
880
+ * proactive-refresh + storage machinery.
881
+ *
882
+ * Strings are accepted for `baseUrl` and `token`; they are branded
883
+ * via `baseUrl()` / `accessToken()` from `@semiont/core` automatically.
884
+ * Pass the already-branded values if you have them.
885
+ *
886
+ * Omit `token` for unauthenticated usage (public endpoints only).
887
+ */
888
+ static fromHttp(opts: {
889
+ baseUrl: BaseUrl | string;
890
+ token?: AccessToken | string | null;
891
+ }): SemiontClient;
892
+ /**
893
+ * Async factory for the credentials-first script case. Builds a
894
+ * transient HTTP transport, calls `auth.password(email, password)`
895
+ * to acquire an access token, and returns the wired client with
896
+ * the token populated.
897
+ *
898
+ * This is the right entry point for skills, CLI scripts, and any
899
+ * consumer that starts with email + password rather than a JWT
900
+ * already on hand. For consumers that already hold a token (CLI
901
+ * cached-token path, env-var token, embedded auth flow), use
902
+ * `fromHttp({ baseUrl, token })` instead.
903
+ *
904
+ * For long-running scripts that need refresh, use
905
+ * `SemiontSession.signInHttp(...)` — same credentials shape, plus
906
+ * the session machinery for proactive refresh and persistence.
907
+ *
908
+ * Named `signInHttp` because email+password authentication is
909
+ * inherently an HTTP-shaped operation in the current backend; an
910
+ * in-process `LocalTransport` doesn't have a credentials login
911
+ * path. Non-HTTP transports construct the client directly from
912
+ * their package's transport instance.
913
+ *
914
+ * Throws if authentication fails. The transient client is disposed
915
+ * before the throw, so no resources leak on failure.
916
+ */
917
+ static signInHttp(opts: {
918
+ baseUrl: BaseUrl | string;
919
+ email: string;
920
+ password: string;
921
+ }): Promise<SemiontClient>;
725
922
  }
726
923
 
727
- declare class BusRequestError extends Error {
728
- constructor(message: string);
924
+ type BusRequestErrorCode = 'bus.timeout' | 'bus.rejected' | 'bus.bad-payload' | 'bus.unauthorized' | 'bus.forbidden' | 'bus.not-found';
925
+ declare class BusRequestError extends SemiontError {
926
+ code: BusRequestErrorCode;
927
+ constructor(message: string, code: BusRequestErrorCode, details?: Record<string, unknown>);
729
928
  }
730
929
  /**
731
930
  * Subset of ITransport that `busRequest` needs: a way to send a command and
@@ -739,76 +938,49 @@ interface BusRequestPrimitive {
739
938
  declare function busRequest<TResult>(bus: BusRequestPrimitive, emitChannel: string, payload: Record<string, unknown>, resultChannel: string, failureChannel: string, timeoutMs?: number): Promise<TResult>;
740
939
 
741
940
  /**
742
- * RxJS-native read-through cache primitive.
743
- *
744
- * Behavioral contract: packages/api-client/docs/CACHE-SEMANTICS.md (B1–B13).
745
- *
746
- * Framework-agnostic: no React, no dependency on any namespace. Used by
747
- * `BrowseNamespace` to back its per-key stores, but equally usable from
748
- * CLI, MCP, or worker code.
749
- *
750
- * Shape:
751
- * - `observe(key)`: returns an Observable<V | undefined> that triggers
752
- * a fetch on first subscription for a missing key, dedup-joins any
753
- * concurrent fetch, and emits the stored value thereafter.
754
- * - `invalidate(key)`: stale-while-revalidate — keeps the current
755
- * value visible to observers, clears the in-flight guard, starts a
756
- * fresh fetch. If the previous fetch was orphaned (SSE torn down,
757
- * response lost), this is how the cache recovers.
758
- * - `remove(key)`: drops the cache entry entirely. Used for entity
759
- * deletions (B13a). No refetch.
760
- * - `set(key, value)`: writes through without a fetch. Used when a
761
- * bus event carries the new value inline (B13b).
762
- * - `invalidateAll()`: per-key SWR refetch of every currently-cached
763
- * entry. Used by gap-detection paths.
764
- * - `dispose()`: completes the store so observers unsubscribe.
765
- *
766
- * What's deliberately out:
767
- * - No subscriber ref-counting / GC of unobserved keys. The per-key
768
- * observable memo grows with the set of observed keys for the cache's
769
- * lifetime (B11). Acceptable given cache lifetime == client lifetime.
770
- * - No TTL / cacheTime. Entries are evicted only by explicit remove.
771
- * - No retry / backoff. A failing fetch leaves the cache unchanged
772
- * (B6); the caller drives retry via invalidate.
773
- */
774
-
775
- interface Cache<K, V> {
776
- /** Observable stream of the value at `key`. Triggers a fetch if not cached. */
777
- observe(key: K): Observable<V | undefined>;
778
- /** Synchronous snapshot of the current value, without triggering a fetch. */
779
- get(key: K): V | undefined;
780
- /** Iterator of currently-cached keys. For invalidateAll and diagnostics. */
781
- keys(): K[];
782
- /**
783
- * Mark the entry stale and refetch. Keeps the previous value visible
784
- * to observers during the refetch (stale-while-revalidate).
785
- */
786
- invalidate(key: K): void;
787
- /** Drop the entry from the cache. No refetch. */
788
- remove(key: K): void;
789
- /** Write-through: set the value directly without a fetch. */
790
- set(key: K, value: V): void;
791
- /** Per-key SWR refetch of every currently-cached entry. */
792
- invalidateAll(): void;
793
- /** Release the underlying subject. Observers complete. */
794
- dispose(): void;
795
- }
796
- declare function createCache<K, V>(fetchFn: (key: K) => Promise<V>): Cache<K, V>;
797
-
798
- /**
799
- * KnowledgeBase — a connection to a Semiont backend instance.
941
+ * KnowledgeBase a connection to a Semiont knowledge system.
942
+ *
943
+ * The KB type itself is uniform. The transport-shape variation lives in
944
+ * the nested `endpoint` field, which is a discriminated union:
945
+ *
946
+ * - `endpoint.kind === 'http'` — a remote backend reached over HTTP+SSE.
947
+ * Carries `host`/`port`/`protocol`.
948
+ * - `endpoint.kind === 'local'` — an in-process knowledge system reached
949
+ * via `LocalTransport` from
950
+ * `@semiont/make-meaning`. Carries an
951
+ * opaque `kbId` identifying the local
952
+ * instance to the host process.
800
953
  *
801
- * Each KB has its own JWT, its own API base URL, and its own session.
802
- * The user is "authenticated against KB X" never globally authenticated.
954
+ * Code that doesn't know how to make a transport (`SemiontSession`,
955
+ * `SemiontBrowser`, the frontend KB list UI) treats `KnowledgeBase` as
956
+ * uniform and never inspects `endpoint`. Code that *does* construct
957
+ * transports (the transport-factory passed to `SemiontBrowser`,
958
+ * `kbBackendUrl` for HTTP URL construction) inspects `endpoint.kind`
959
+ * and dispatches.
960
+ *
961
+ * Each KB has its own session, its own credentials (where applicable),
962
+ * and its own JWT (HTTP only). The user is "authenticated against KB X" —
963
+ * never globally authenticated.
803
964
  */
965
+ /** Fields shared by every KB regardless of endpoint kind. */
804
966
  interface KnowledgeBase {
805
967
  id: string;
806
968
  label: string;
969
+ email: string;
970
+ gitBranch?: string;
971
+ endpoint: KbEndpoint;
972
+ }
973
+ type KbEndpoint = HttpEndpoint | LocalEndpoint;
974
+ interface HttpEndpoint {
975
+ kind: 'http';
807
976
  host: string;
808
977
  port: number;
809
978
  protocol: 'http' | 'https';
810
- email: string;
811
- gitBranch?: string;
979
+ }
980
+ interface LocalEndpoint {
981
+ kind: 'local';
982
+ /** Opaque identifier for the in-process KB instance the host has loaded. */
983
+ kbId: string;
812
984
  }
813
985
  /**
814
986
  * Input shape for adding a new KB. The id is generated by the provider.
@@ -819,18 +991,63 @@ type NewKnowledgeBase = Omit<KnowledgeBase, 'id'>;
819
991
  * presence and validity of the JWT in session storage.
820
992
  */
821
993
  type KbSessionStatus = 'authenticated' | 'expired' | 'signed-out' | 'unreachable';
994
+ /**
995
+ * Construct a `KnowledgeBase` for an HTTP-backed Semiont backend without
996
+ * spelling out the nested `endpoint` literal. Convenience for tests,
997
+ * worker bootstraps, and one-off scripts.
998
+ *
999
+ * ```ts
1000
+ * const kb = httpKb({
1001
+ * id: 'my-watcher',
1002
+ * label: 'My Watcher',
1003
+ * email: 'me@example.com',
1004
+ * host: 'localhost',
1005
+ * port: 4000,
1006
+ * protocol: 'http',
1007
+ * });
1008
+ * ```
1009
+ *
1010
+ * Equivalent to:
1011
+ *
1012
+ * ```ts
1013
+ * const kb: KnowledgeBase = {
1014
+ * id, label, email,
1015
+ * endpoint: { kind: 'http', host, port, protocol },
1016
+ * };
1017
+ * ```
1018
+ *
1019
+ * UI hosts that have a structured form (host / port / protocol pickers)
1020
+ * already construct the literal directly — they don't need this helper.
1021
+ * Local-endpoint KBs construct the literal directly too; the local
1022
+ * endpoint shape (`{ kind: 'local', kbId }`) is one line and doesn't
1023
+ * earn a helper.
1024
+ */
1025
+ declare function httpKb(opts: {
1026
+ id: string;
1027
+ label: string;
1028
+ email: string;
1029
+ host: string;
1030
+ port: number;
1031
+ protocol: 'http' | 'https';
1032
+ gitBranch?: string;
1033
+ }): KnowledgeBase;
822
1034
 
823
1035
  /**
824
1036
  * Session-level error surface. Emitted on `SemiontBrowser.error$` for
825
1037
  * failures that make the session itself unusable (auth failed, actor
826
1038
  * couldn't start, token refresh terminally exhausted). Per-request
827
1039
  * errors stay with the caller as normal Promise rejections.
1040
+ *
1041
+ * `SemiontSessionError` extends `SemiontError` (the unified Semiont base)
1042
+ * so consumers can catch with `instanceof SemiontError` for any error
1043
+ * surfaced through the SDK.
828
1044
  */
829
- type SemiontErrorCode = 'session.construct-failed' | 'session.auth-failed' | 'session.refresh-exhausted' | 'browser.sign-in-failed';
830
- declare class SemiontError extends Error {
831
- readonly code: SemiontErrorCode;
1045
+
1046
+ type SemiontSessionErrorCode = 'session.construct-failed' | 'session.auth-failed' | 'session.refresh-exhausted' | 'browser.sign-in-failed';
1047
+ declare class SemiontSessionError extends SemiontError {
1048
+ code: SemiontSessionErrorCode;
832
1049
  readonly kbId: string | null;
833
- constructor(code: SemiontErrorCode, message: string, kbId?: string | null);
1050
+ constructor(code: SemiontSessionErrorCode, message: string, kbId?: string | null);
834
1051
  }
835
1052
 
836
1053
  /**
@@ -882,7 +1099,7 @@ declare class InMemorySessionStorage implements SessionStorage {
882
1099
  *
883
1100
  * Headless by design. Runs in browsers, CLIs, workers, and tests.
884
1101
  * UI-specific state (session-expired/permission-denied modals) lives
885
- * in `FrontendSessionSignals`, which wraps a session — the session
1102
+ * in `SessionSignals`, which wraps a session — the session
886
1103
  * itself has no modal observables, no user-facing notifications.
887
1104
  *
888
1105
  * Auth is parameterized via callbacks passed at construction:
@@ -898,9 +1115,9 @@ declare class InMemorySessionStorage implements SessionStorage {
898
1115
  * worker omits this (service principals have no user record).
899
1116
  *
900
1117
  * - `onAuthFailed(message)` — optional. Invoked when refresh
901
- * terminally fails (expired token, no recovery possible). The
902
- * frontend wires this to `FrontendSessionSignals.notifySessionExpired`
903
- * so the modal surfaces; headless consumers typically just log.
1118
+ * terminally fails (expired token, no recovery possible). UI hosts
1119
+ * typically wire this to `SessionSignals.notifySessionExpired` so a
1120
+ * modal surfaces; headless consumers typically just log.
904
1121
  *
905
1122
  * Persistence goes through a `SessionStorage` adapter provided at
906
1123
  * construction — the session never touches `localStorage` or `window`
@@ -944,7 +1161,7 @@ interface SemiontSessionConfig {
944
1161
  */
945
1162
  onAuthFailed?: (message: string | null) => void;
946
1163
  /** Called for session-level failures (auth, refresh exhaustion). */
947
- onError?: (err: SemiontError) => void;
1164
+ onError?: (err: SemiontSessionError) => void;
948
1165
  }
949
1166
  declare class SemiontSession {
950
1167
  readonly kb: KnowledgeBase;
@@ -952,6 +1169,18 @@ declare class SemiontSession {
952
1169
  readonly token$: BehaviorSubject<AccessToken | null>;
953
1170
  readonly user$: BehaviorSubject<UserInfo | null>;
954
1171
  readonly streamState$: Observable<ConnectionState>;
1172
+ /**
1173
+ * Stream of `SemiontError` instances surfaced by the underlying transport
1174
+ * just before they're thrown to the caller. For `HttpTransport` this is
1175
+ * an `APIError` (status-coded); other transports emit their own subclass.
1176
+ * Surfaced here so a host layer (e.g. `SemiontBrowser`) can route by
1177
+ * `err.code` to global notifications without every call site handling
1178
+ * errors itself. Headless consumers can subscribe for logging.
1179
+ *
1180
+ * Re-published from `client.transport.errors$` per the `ITransport`
1181
+ * contract — the session is purely a passthrough.
1182
+ */
1183
+ readonly errors$: Observable<SemiontError>;
955
1184
  /** Resolves after the initial validation round-trip completes (success or failure). */
956
1185
  readonly ready: Promise<void>;
957
1186
  private readonly storage;
@@ -1001,6 +1230,67 @@ declare class SemiontSession {
1001
1230
  */
1002
1231
  subscribe<K extends keyof EventMap>(channel: K, handler: (payload: EventMap[K]) => void): () => void;
1003
1232
  dispose(): Promise<void>;
1233
+ /**
1234
+ * Convenience factory for the default HTTP setup. Constructs the
1235
+ * shared `BehaviorSubject<AccessToken | null>`, an `HttpTransport`,
1236
+ * an `HttpContentTransport`, and a `SemiontClient`, then wires
1237
+ * the session over them. Removes the load-bearing
1238
+ * "same-token$-instance" invariant from the caller's hands.
1239
+ *
1240
+ * Strings are accepted for `baseUrl` and `token`; they are branded
1241
+ * via `baseUrl()` / `accessToken()` from `@semiont/core` automatically.
1242
+ *
1243
+ * The remaining options (`refresh`, `validate`, `onAuthFailed`,
1244
+ * `onError`) match `SemiontSessionConfig` exactly.
1245
+ */
1246
+ static fromHttp(opts: {
1247
+ kb: KnowledgeBase;
1248
+ storage: SessionStorage;
1249
+ baseUrl: BaseUrl | string;
1250
+ token?: AccessToken | string | null;
1251
+ refresh?: () => Promise<string | null>;
1252
+ validate?: (token: AccessToken) => Promise<UserInfo | null>;
1253
+ onAuthFailed?: (message: string | null) => void;
1254
+ onError?: (err: SemiontSessionError) => void;
1255
+ }): SemiontSession;
1256
+ /**
1257
+ * Async factory for the credentials-first long-running script case.
1258
+ * Builds the HTTP transport stack, calls `auth.password(email,
1259
+ * password)` to acquire access + refresh tokens, persists them via
1260
+ * the storage adapter, wires a default `refresh` callback that
1261
+ * exchanges the refresh token via `auth.refresh(...)`, and returns
1262
+ * the ready session.
1263
+ *
1264
+ * The consumer-supplied `refresh` callback becomes optional — only
1265
+ * needed for non-standard refresh flows (worker-pool shared secret,
1266
+ * OAuth refresh-token grant, interactive re-prompt). The default
1267
+ * uses the refresh token returned by `auth.password`.
1268
+ *
1269
+ * `kb` is required and must be a full `KnowledgeBase`. The `id` field
1270
+ * is the storage key for this session — distinct scripts sharing the
1271
+ * same `SessionStorage` instance must use distinct ids to avoid
1272
+ * trampling each other's tokens. The factory does not synthesize a
1273
+ * default; the consumer makes the choice.
1274
+ *
1275
+ * Named `signInHttp` because email+password authentication is
1276
+ * inherently an HTTP-shaped operation in the current backend; an
1277
+ * in-process `LocalTransport` doesn't have a credentials login
1278
+ * path. Non-HTTP transports construct the session directly from
1279
+ * their package's transport instance.
1280
+ *
1281
+ * Throws on auth failure with no resources leaked. On success, the
1282
+ * returned session's `ready` promise has already resolved.
1283
+ */
1284
+ static signInHttp(opts: {
1285
+ kb: KnowledgeBase;
1286
+ storage: SessionStorage;
1287
+ baseUrl: BaseUrl | string;
1288
+ email: string;
1289
+ password: string;
1290
+ validate?: (token: AccessToken) => Promise<UserInfo | null>;
1291
+ onAuthFailed?: (message: string | null) => void;
1292
+ onError?: (err: SemiontSessionError) => void;
1293
+ }): Promise<SemiontSession>;
1004
1294
  }
1005
1295
 
1006
1296
  /**
@@ -1026,36 +1316,33 @@ interface OpenResource {
1026
1316
  }
1027
1317
 
1028
1318
  /**
1029
- * FrontendSessionSignalsmodal state that belongs to the UI, not
1030
- * the session itself.
1319
+ * SessionSignalsUI-facing notification state that belongs to the host
1320
+ * surface, not to the session itself.
1031
1321
  *
1032
1322
  * `SemiontSession` is a headless per-backend client + token + user
1033
1323
  * holder. It can run in any process: browser, worker, CLI, test. But
1034
- * the session-expired / permission-denied *modals* only make sense
1035
- * in a UI context. Keeping those observables on `SemiontSession`
1036
- * meant workers and CLIs carried four dead BehaviorSubjects that
1037
- * nothing would ever fire.
1324
+ * the session-expired / permission-denied *notifications* are inherently
1325
+ * a UI-host concern. Keeping those observables on `SemiontSession` meant
1326
+ * workers and CLIs carried four dead BehaviorSubjects that nothing would
1327
+ * ever fire.
1038
1328
  *
1039
- * `FrontendSessionSignals` owns the modal state and has no hard
1040
- * reference to a session. `SemiontBrowser` constructs one alongside
1041
- * every frontend session and wires:
1329
+ * `SessionSignals` owns the notification state and has no hard reference
1330
+ * to a session. A UI host (e.g. `SemiontBrowser`) constructs one alongside
1331
+ * every active session and wires:
1042
1332
  *
1043
1333
  * - `session.onAuthFailed` → `signals.notifySessionExpired` so
1044
- * proactive-refresh failures surface as modals
1045
- * - `notify` module handlers → the active signals' methods so
1046
- * external callers (e.g. React Query's QueryCache.onError) can
1047
- * trigger the modals without touching the session
1334
+ * proactive-refresh failures surface as a notification
1048
1335
  *
1049
- * React consumers that need modal state subscribe here; consumers
1050
- * that need bus/HTTP access continue to subscribe to the session.
1336
+ * UI consumers that need to render modal/banner state subscribe here;
1337
+ * consumers that need bus/HTTP access continue to subscribe to the session.
1051
1338
  *
1052
1339
  * Session auth-state cleanup (clearing token, clearing storage) is
1053
1340
  * the session's own responsibility inside `refresh()` — by the time
1054
1341
  * `notifySessionExpired` runs, the session has already torn down.
1055
- * Signals only surfaces the modal.
1342
+ * Signals only surfaces the notification.
1056
1343
  */
1057
1344
 
1058
- declare class FrontendSessionSignals {
1345
+ declare class SessionSignals {
1059
1346
  readonly sessionExpiredAt$: BehaviorSubject<number | null>;
1060
1347
  readonly sessionExpiredMessage$: BehaviorSubject<string | null>;
1061
1348
  readonly permissionDeniedAt$: BehaviorSubject<number | null>;
@@ -1068,14 +1355,46 @@ declare class FrontendSessionSignals {
1068
1355
  dispose(): void;
1069
1356
  }
1070
1357
 
1358
+ /**
1359
+ * SessionFactory — the injection point that lets `SemiontBrowser` stay
1360
+ * transport-agnostic.
1361
+ *
1362
+ * The browser knows how to manage the *lifecycle* of an active session
1363
+ * (track it in `activeSession$`, dispose on KB switch, serialize
1364
+ * overlapping activations) but does not know how to *construct* one —
1365
+ * because that's where transport choice lives. The construction step
1366
+ * is parameterized via this factory.
1367
+ *
1368
+ * The HTTP factory is provided by `createHttpSessionFactory`. A
1369
+ * future in-process variant from `@semiont/make-meaning` would expose
1370
+ * its own factory.
1371
+ */
1372
+
1373
+ interface SessionFactoryOptions {
1374
+ /** The KB the session is being constructed for. */
1375
+ kb: KnowledgeBase;
1376
+ /** Persistence adapter — same one the browser uses. */
1377
+ storage: SessionStorage;
1378
+ /** Modal-signal sink for auth-failed / permission-denied notifications. */
1379
+ signals: SessionSignals;
1380
+ /** Receives session-level errors (auth-failed, refresh-exhausted, ...). */
1381
+ onError: (err: SemiontSessionError) => void;
1382
+ }
1383
+ type SessionFactory = (opts: SessionFactoryOptions) => SemiontSession;
1384
+
1071
1385
  /**
1072
1386
  * SemiontBrowser — top-level app-facing container for non-KB state.
1073
1387
  *
1074
1388
  * Holds the list of configured KBs, the active KB selection, the active
1075
1389
  * SemiontSession, the identity token, the open-resources list, and a
1076
- * session-level error stream. Module-scoped singleton survives every
1077
- * React re-render, remount, and route change. `SemiontProvider` hands
1078
- * the singleton to the React tree; `useSemiont()` returns it.
1390
+ * session-level error stream. Held as a process-wide instance for the
1391
+ * host's lifetime see `getBrowser()` in `registry.ts` for the canonical
1392
+ * accessor.
1393
+ *
1394
+ * Transport-agnostic: the browser orchestrates session *lifecycle* but
1395
+ * delegates session *construction* to a `SessionFactory` injected at
1396
+ * construction. HTTP-backed apps pass `createHttpSessionFactory()` from
1397
+ * `@semiont/sdk`; in-process apps pass their own factory.
1079
1398
  *
1080
1399
  * Persistence goes through a `SessionStorage` adapter provided at
1081
1400
  * construction — the browser never touches `localStorage` or `window`
@@ -1085,6 +1404,14 @@ declare class FrontendSessionSignals {
1085
1404
  interface SemiontBrowserConfig {
1086
1405
  /** Persistence adapter. The browser reads/writes all persisted state via this. */
1087
1406
  storage: SessionStorage;
1407
+ /**
1408
+ * Builds a `SemiontSession` for a KB. The browser is transport-
1409
+ * agnostic — every HTTP-vs-local construction concern lives in the
1410
+ * factory. HTTP-backed apps pass `createHttpSessionFactory()` from
1411
+ * `@semiont/sdk`; a future in-process variant from `@semiont/make-meaning`
1412
+ * would expose its own factory.
1413
+ */
1414
+ sessionFactory: SessionFactory;
1088
1415
  }
1089
1416
  declare class SemiontBrowser {
1090
1417
  readonly kbs$: BehaviorSubject<KnowledgeBase[]>;
@@ -1096,9 +1423,9 @@ declare class SemiontBrowser {
1096
1423
  * non-null when `activeSession$` is non-null, always null when it
1097
1424
  * is. Extracted from the session itself so headless sessions
1098
1425
  * (workers, CLIs, tests) don't carry dead modal observables.
1099
- * See [FrontendSessionSignals](./frontend-session-signals.ts).
1426
+ * See [SessionSignals](./session-signals.ts).
1100
1427
  */
1101
- readonly activeSignals$: BehaviorSubject<FrontendSessionSignals | null>;
1428
+ readonly activeSignals$: BehaviorSubject<SessionSignals | null>;
1102
1429
  /**
1103
1430
  * True while a session is actively being constructed (setActiveKb /
1104
1431
  * signIn in flight, awaiting `session.ready`). Distinguishes the
@@ -1109,9 +1436,10 @@ declare class SemiontBrowser {
1109
1436
  */
1110
1437
  readonly sessionActivating$: BehaviorSubject<boolean>;
1111
1438
  readonly openResources$: BehaviorSubject<OpenResource[]>;
1112
- readonly error$: Subject<SemiontError>;
1439
+ readonly error$: Subject<SemiontSessionError>;
1113
1440
  readonly identityToken$: BehaviorSubject<string | null>;
1114
1441
  private readonly storage;
1442
+ private readonly sessionFactory;
1115
1443
  /**
1116
1444
  * App-scoped EventBus. Hosts UI-shell events that must work regardless
1117
1445
  * of whether a KB session is active: panel toggles, sidebar state,
@@ -1120,18 +1448,9 @@ declare class SemiontBrowser {
1120
1448
  * (mark:*, beckon:*, gather:*, match:*, bind:*, yield:*, browse:click).
1121
1449
  */
1122
1450
  private readonly eventBus;
1123
- private unregisterNotify;
1124
1451
  private unsubscribeStorage;
1125
1452
  private disposed;
1126
1453
  private activating;
1127
- /**
1128
- * Per-KB in-flight refresh dedup. Simultaneous 401s for the same
1129
- * KB converge on a single `/api/tokens/refresh` network call.
1130
- * Was previously module-scoped in `refresh.ts`; moved here when
1131
- * that file was deleted — SemiontBrowser is a singleton so the
1132
- * scoping is equivalent.
1133
- */
1134
- private readonly inFlightRefreshes;
1135
1454
  constructor(config: SemiontBrowserConfig);
1136
1455
  /** Emit an event on the browser's app-scoped bus. */
1137
1456
  emit<K extends keyof EventMap>(channel: K, payload: EventMap[K]): void;
@@ -1140,14 +1459,25 @@ declare class SemiontBrowser {
1140
1459
  /** Read-only observable for an app-scoped channel. */
1141
1460
  stream<K extends keyof EventMap>(channel: K): Observable<EventMap[K]>;
1142
1461
  /**
1143
- * Set the app-level identity token (from NextAuth's useSession).
1144
- * Called at the root layout via a single `useEffect`. No other site
1145
- * in the codebase should call this.
1462
+ * Set the app-level identity token. Sourced from whatever the host
1463
+ * environment uses for OAuth sessions (e.g. NextAuth in a browser app).
1464
+ * Should be called once from the host's startup-and-on-change site;
1465
+ * no other code should write to this slot.
1146
1466
  */
1147
1467
  setIdentityToken(token: string | null): void;
1148
1468
  addKb(input: NewKnowledgeBase, access: string, refresh: string): KnowledgeBase;
1149
1469
  removeKb(id: string): void;
1150
- updateKb(id: string, updates: Partial<KnowledgeBase>): void;
1470
+ /**
1471
+ * Patch a KB in the list. Restricted to the common, endpoint-agnostic
1472
+ * fields (`label`, `email`, `gitBranch`) — the `endpoint` shape isn't
1473
+ * editable in place; remove and re-add to change the connection
1474
+ * target.
1475
+ */
1476
+ updateKb(id: string, updates: {
1477
+ label?: string;
1478
+ email?: string;
1479
+ gitBranch?: string;
1480
+ }): void;
1151
1481
  /**
1152
1482
  * Read the locally-stored credential status for a KB. Pure / synchronous —
1153
1483
  * does not subscribe to context changes. Used by KB-list UI to color status
@@ -1182,46 +1512,45 @@ declare class SemiontBrowser {
1182
1512
  removeOpenResource(id: string): void;
1183
1513
  updateOpenResourceName(id: string, name: string): void;
1184
1514
  reorderOpenResources(oldIndex: number, newIndex: number): void;
1185
- /**
1186
- * Refresh the active KB's access token. Returns the new token on
1187
- * success, null on failure. Concurrent calls for the same KB
1188
- * dedupe through `inFlightRefreshes`, so simultaneous 401s trigger
1189
- * only one `/api/tokens/refresh` round trip.
1190
- *
1191
- * Uses a throwaway `SemiontClient` with no `tokenRefresher` —
1192
- * a refresh call returning 401 would otherwise re-enter this
1193
- * function infinitely.
1194
- */
1195
- private performRefresh;
1196
- /**
1197
- * Validate an access token by calling `auth.me` on a throwaway
1198
- * client. The session uses this once at startup to populate
1199
- * `user$`; 401 triggers a refresh-then-retry inside the session.
1200
- *
1201
- * The throwaway transport is seeded with the specific token to
1202
- * validate so the request actually carries it (HttpTransport
1203
- * sources `Authorization` from its `token$`).
1204
- */
1205
- private performValidate;
1206
1515
  dispose(): Promise<void>;
1207
1516
  }
1208
1517
 
1209
1518
  /**
1210
- * Module-scoped singleton for SemiontBrowser. Constructed lazily on first
1211
- * `getBrowser()` call, survives every React re-render, remount, and route
1212
- * change.
1213
- *
1214
- * The caller provides a `SessionStorage` implementation there is no
1215
- * default here because storage-backend selection is environment-specific
1216
- * (browsers use `WebBrowserStorage`, CLI uses a filesystem adapter,
1217
- * tests use `InMemorySessionStorage`). The first call to `getBrowser`
1218
- * wins; subsequent calls return the cached instance regardless of the
1219
- * storage passed.
1519
+ * createHttpSessionFactory the default `SessionFactory` for HTTP-backed
1520
+ * KBs. Owns every HTTP-specific construction concern that used to live in
1521
+ * `SemiontBrowser`: building `HttpTransport`/`HttpContentTransport`,
1522
+ * wiring the `tokenRefresher` callback, deduplicating concurrent 401
1523
+ * refresh round trips, and invoking the auth endpoints for token refresh
1524
+ * and user-validate.
1525
+ *
1526
+ * Returned as a closure so a single `inFlightRefreshes` map is shared
1527
+ * across every session this factory builds the dedup is meaningful
1528
+ * across concurrent session reactivations for the same KB id.
1529
+ */
1530
+
1531
+ declare function createHttpSessionFactory(): SessionFactory;
1532
+
1533
+ /**
1534
+ * Process-wide accessor for `SemiontBrowser`. Constructed lazily on the
1535
+ * first `getBrowser()` call and held for the host's lifetime — a single
1536
+ * instance owns the KB list, identity token, and active-session state,
1537
+ * so callers throughout the host get the same view of "which KB am I
1538
+ * talking to right now" regardless of where they pick it up.
1539
+ *
1540
+ * The caller provides a `SessionStorage` implementation and a
1541
+ * `SessionFactory` — both are environment-specific (browsers use
1542
+ * `WebBrowserStorage` + `createHttpSessionFactory()`, CLI/embedded
1543
+ * hosts use a filesystem adapter and possibly a local-process
1544
+ * factory, tests use `InMemorySessionStorage` and stubs). The first
1545
+ * call to `getBrowser` wins; subsequent calls return the cached
1546
+ * instance regardless of the options passed.
1220
1547
  */
1221
1548
 
1222
1549
  interface GetBrowserOptions {
1223
1550
  /** Persistence adapter used to construct the singleton on first call. */
1224
1551
  storage: SessionStorage;
1552
+ /** Session factory used to build a `SemiontSession` per active KB. */
1553
+ sessionFactory: SessionFactory;
1225
1554
  }
1226
1555
  declare function getBrowser(options: GetBrowserOptions): SemiontBrowser;
1227
1556
 
@@ -1248,16 +1577,45 @@ interface StoredSession {
1248
1577
  declare function setStoredSession(storage: SessionStorage, kbId: string, session: StoredSession): void;
1249
1578
  declare function defaultProtocol(host: string): 'http' | 'https';
1250
1579
  declare function isValidHostname(host: string): boolean;
1251
- declare function kbBackendUrl(kb: KnowledgeBase): string;
1252
-
1253
- declare function notifySessionExpired(message?: string): void;
1254
- declare function notifyPermissionDenied(message?: string): void;
1580
+ /**
1581
+ * Build the wire URL for an HTTP KB endpoint. HTTP-shaped helper —
1582
+ * lives next to the KB list machinery because the frontend Panel needs
1583
+ * it for the auth round-trip when adding a KB. Code that holds a
1584
+ * uniform `KnowledgeBase` should not call this; it should hand the KB
1585
+ * to a transport factory and let the factory inspect `endpoint.kind`.
1586
+ */
1587
+ declare function kbBackendUrl(endpoint: HttpEndpoint): string;
1255
1588
 
1256
- interface ViewModel {
1589
+ /**
1590
+ * Marker for the state-unit pattern: a stateful, lifecycled object with an
1591
+ * RxJS-shaped public surface, constructed by a factory function
1592
+ * (`createFooStateUnit`), with internal state held in a closure.
1593
+ *
1594
+ * The structural contract is `dispose()` — the rest of the pattern
1595
+ * (closure-based identity, Observable public surface, internal Subjects
1596
+ * exposed as `.asObservable()` views, no leaked subscriptions, composition
1597
+ * by parameter rather than ownership) is convention enforced by review,
1598
+ * not the type system.
1599
+ *
1600
+ * See `packages/sdk/docs/STATE-UNITS.md` for the full axioms and rationale.
1601
+ */
1602
+ interface StateUnit {
1603
+ /**
1604
+ * Idempotent, total teardown. Completes every Subject the unit owns,
1605
+ * unsubscribes every internal subscription, releases timers / abort
1606
+ * controllers / network handles. Safe to call multiple times — the
1607
+ * second call is a no-op.
1608
+ */
1257
1609
  dispose(): void;
1258
1610
  }
1611
+ /**
1612
+ * Compose multiple disposers into a single `dispose()` call. Accepts either
1613
+ * a `StateUnit` (whose `dispose()` will be invoked) or a plain teardown
1614
+ * function. The returned object is itself disposable; call its `dispose()`
1615
+ * once to tear down everything that was added.
1616
+ */
1259
1617
  declare function createDisposer(): {
1260
- add(vm: ViewModel | (() => void)): void;
1618
+ add(item: StateUnit | (() => void)): void;
1261
1619
  dispose(): void;
1262
1620
  };
1263
1621
 
@@ -1267,10 +1625,9 @@ declare function createDisposer(): {
1267
1625
  * A debounced-search RxJS pipeline factory. Combines an input Subject with a
1268
1626
  * downstream fetch function and emits typed `{ results, isSearching }` state.
1269
1627
  *
1270
- * Designed to be created once per component instance via React's
1271
- * `useState(() => createSearchPipeline(...))` lazy initializer, then consumed
1272
- * via `useObservable(pipeline.state$)`. The pipeline holds no React state and
1273
- * has no React imports — it's pure RxJS, unit-testable without a renderer.
1628
+ * Designed to be created once per consumer instance and held for its lifetime
1629
+ * (e.g. by a view layer's lazy initializer), then observed via `state$`. The
1630
+ * pipeline is pure RxJS unit-testable without any view-layer dependency.
1274
1631
  *
1275
1632
  * The fetch function is expected to return `Observable<T[] | undefined>`,
1276
1633
  * matching the cache-miss-then-data shape of `BrowseNamespace` Observables:
@@ -1283,13 +1640,13 @@ interface SearchState<T> {
1283
1640
  isSearching: boolean;
1284
1641
  }
1285
1642
  interface SearchPipeline<T> {
1286
- /** Latest query string. Bind to a controlled input via `useObservable`. */
1643
+ /** Latest query string. Bind to a controlled input. */
1287
1644
  query$: Observable<string>;
1288
1645
  /** Latest search state — results plus a loading flag. */
1289
1646
  state$: Observable<SearchState<T>>;
1290
1647
  /** Push a new query value. Triggers the debounced fetch. */
1291
1648
  setQuery(value: string): void;
1292
- /** Tear down the input Subject. Call from `useEffect` cleanup. */
1649
+ /** Tear down the input Subject. Call from the consumer's cleanup hook. */
1293
1650
  dispose(): void;
1294
1651
  }
1295
1652
  interface SearchPipelineOptions {
@@ -1300,13 +1657,34 @@ interface SearchPipelineOptions {
1300
1657
  }
1301
1658
  declare function createSearchPipeline<T>(fetch: (query: string) => Observable<T[] | undefined>, options?: SearchPipelineOptions): SearchPipeline<T>;
1302
1659
 
1303
- interface BeckonVM extends ViewModel {
1660
+ /**
1661
+ * WorkerBus — minimal channel-bus surface that worker-side adapters
1662
+ * (e.g. `JobClaimAdapter` in `@semiont/jobs`, `SmelterActorStateUnit` in
1663
+ * `@semiont/make-meaning`) need.
1664
+ *
1665
+ * Transport-neutral by design. HTTP `ActorStateUnit` (from `@semiont/api-client`)
1666
+ * satisfies it directly; an in-process worker can pass a small shim around
1667
+ * an `EventBus` with a `() => Promise<void>` `emit` that calls into the
1668
+ * actor system.
1669
+ *
1670
+ * `addChannels` is optional because in-process buses receive every emit
1671
+ * implicitly — only HTTP needs to widen its SSE subscription set to
1672
+ * include worker-only channels (`job:queued`, `yield:created`, etc.).
1673
+ */
1674
+
1675
+ interface WorkerBus {
1676
+ on$<T = Record<string, unknown>>(channel: string): Observable<T>;
1677
+ emit(channel: string, payload: Record<string, unknown>): Promise<void>;
1678
+ addChannels?(channels: readonly string[]): void;
1679
+ }
1680
+
1681
+ interface BeckonStateUnit extends StateUnit {
1304
1682
  hoveredAnnotationId$: Observable<AnnotationId | null>;
1305
1683
  hover(annotationId: AnnotationId | null): void;
1306
1684
  focus(annotationId: AnnotationId): void;
1307
1685
  sparkle(annotationId: AnnotationId): void;
1308
1686
  }
1309
- declare function createBeckonVM(client: SemiontClient): BeckonVM;
1687
+ declare function createBeckonStateUnit(client: SemiontClient): BeckonStateUnit;
1310
1688
  /** Default milliseconds the mouse must dwell before beckon:hover is emitted. */
1311
1689
  declare const HOVER_DELAY_MS = 150;
1312
1690
  type EmitHover = (annotationId: AnnotationId | null) => void;
@@ -1317,372 +1695,48 @@ interface HoverHandlers {
1317
1695
  }
1318
1696
  declare function createHoverHandlers(emit: EmitHover, delayMs: number): HoverHandlers;
1319
1697
 
1320
- /**
1321
- * ShellVM — app-shell state: which toolbar panel is open, tab-bar
1322
- * coordination helpers, scroll-to-annotation signals. Lives on
1323
- * `SemiontBrowser`'s app-scoped bus (not the per-session client bus)
1324
- * because panel toggles and shell chrome must work regardless of
1325
- * whether a KB session is active.
1326
- *
1327
- * Channels: `panel:toggle`, `panel:open`, `panel:close`.
1328
- */
1329
-
1330
- type ToolbarPanelType = 'history' | 'info' | 'annotations' | 'settings' | 'collaboration' | 'user' | 'jsonld' | 'knowledge-base';
1331
- declare const COMMON_PANELS: readonly ToolbarPanelType[];
1332
- declare const RESOURCE_PANELS: readonly ToolbarPanelType[];
1333
- interface ShellVM extends ViewModel {
1334
- activePanel$: Observable<ToolbarPanelType | null>;
1335
- scrollToAnnotationId$: Observable<string | null>;
1336
- panelInitialTab$: Observable<{
1337
- tab: string;
1338
- generation: number;
1339
- } | null>;
1340
- openPanel(panel: string): void;
1341
- closePanel(): void;
1342
- togglePanel(panel: string): void;
1343
- onScrollCompleted(): void;
1344
- }
1345
- interface ShellVMOptions {
1346
- initialPanel?: ToolbarPanelType | null;
1347
- onPanelChange?: (panel: ToolbarPanelType | null) => void;
1348
- }
1349
- declare function createShellVM(browser: SemiontBrowser, options?: ShellVMOptions): ShellVM;
1350
-
1351
- interface GatherVM extends ViewModel {
1698
+ interface GatherStateUnit extends StateUnit {
1352
1699
  context$: Observable<GatheredContext | null>;
1353
1700
  loading$: Observable<boolean>;
1354
1701
  error$: Observable<Error | null>;
1355
1702
  annotationId$: Observable<AnnotationId | null>;
1356
1703
  }
1357
- declare function createGatherVM(client: SemiontClient, resourceId: ResourceId): GatherVM;
1704
+ declare function createGatherStateUnit(client: SemiontClient, resourceId: ResourceId): GatherStateUnit;
1358
1705
 
1359
- interface MatchVM extends ViewModel {
1706
+ interface MatchStateUnit extends StateUnit {
1360
1707
  }
1361
- declare function createMatchVM(client: SemiontClient, _resourceId: ResourceId): MatchVM;
1708
+ declare function createMatchStateUnit(client: SemiontClient, _resourceId: ResourceId): MatchStateUnit;
1362
1709
 
1363
1710
  type JobProgress$1 = components['schemas']['JobProgress'];
1364
1711
  interface GenerateDocumentOptions {
1365
1712
  title: string;
1366
1713
  storageUri: string;
1367
1714
  prompt?: string;
1715
+ /** Body locale — language the generated resource is written in. Falls back to the state unit's UI locale when unset. */
1368
1716
  language?: string;
1717
+ /** Source-resource locale — language of the resource the annotation lives on. Forwarded to the prompt for context-snippet awareness. BCP-47. */
1718
+ sourceLanguage?: string;
1369
1719
  temperature?: number;
1370
1720
  maxTokens?: number;
1371
1721
  context: GatheredContext;
1372
1722
  }
1373
- interface YieldVM extends ViewModel {
1723
+ interface YieldStateUnit extends StateUnit {
1374
1724
  isGenerating$: Observable<boolean>;
1375
1725
  progress$: Observable<JobProgress$1 | null>;
1376
1726
  generate(referenceId: string, options: GenerateDocumentOptions): void;
1377
1727
  }
1378
- declare function createYieldVM(client: SemiontClient, resourceId: ResourceId, locale: string): YieldVM;
1728
+ declare function createYieldStateUnit(client: SemiontClient, resourceId: ResourceId, locale: string): YieldStateUnit;
1379
1729
 
1380
1730
  type JobProgress = components['schemas']['JobProgress'];
1381
1731
  interface PendingAnnotation {
1382
1732
  selector: Selector | Selector[];
1383
1733
  motivation: Motivation;
1384
1734
  }
1385
- interface MarkVM extends ViewModel {
1735
+ interface MarkStateUnit extends StateUnit {
1386
1736
  pendingAnnotation$: Observable<PendingAnnotation | null>;
1387
1737
  assistingMotivation$: Observable<Motivation | null>;
1388
1738
  progress$: Observable<JobProgress | null>;
1389
1739
  }
1390
- declare function createMarkVM(client: SemiontClient, resourceId: ResourceId): MarkVM;
1391
-
1392
- interface DiscoverVM extends ViewModel {
1393
- browse: ShellVM;
1394
- search: SearchPipeline<ResourceDescriptor>;
1395
- recentResources$: Observable<ResourceDescriptor[]>;
1396
- entityTypes$: Observable<string[]>;
1397
- isLoadingRecent$: Observable<boolean>;
1398
- }
1399
- declare function createDiscoverVM(client: SemiontClient, browse: ShellVM): DiscoverVM;
1400
-
1401
- interface EntityTagsVM extends ViewModel {
1402
- browse: ShellVM;
1403
- entityTypes$: Observable<string[]>;
1404
- isLoading$: Observable<boolean>;
1405
- newTag$: Observable<string>;
1406
- error$: Observable<string>;
1407
- isAdding$: Observable<boolean>;
1408
- setNewTag(value: string): void;
1409
- addTag(): Promise<void>;
1410
- }
1411
- declare function createEntityTagsVM(client: SemiontClient, browse: ShellVM): EntityTagsVM;
1412
-
1413
- interface ImportPreview {
1414
- format: string;
1415
- version: number;
1416
- sourceUrl: string;
1417
- stats: Record<string, number>;
1418
- }
1419
- interface ExchangeVM extends ViewModel {
1420
- browse: ShellVM;
1421
- selectedFile$: Observable<File | null>;
1422
- preview$: Observable<ImportPreview | null>;
1423
- importPhase$: Observable<string | null>;
1424
- importMessage$: Observable<string | undefined>;
1425
- importResult$: Observable<Record<string, unknown> | undefined>;
1426
- isExporting$: Observable<boolean>;
1427
- isImporting$: Observable<boolean>;
1428
- selectFile(file: File): void;
1429
- cancelImport(): void;
1430
- doExport(): Promise<{
1431
- blob: Blob;
1432
- filename: string;
1433
- }>;
1434
- doImport(): Promise<void>;
1435
- }
1436
- declare function createExchangeVM(browse: ShellVM, exportFn: (params?: {
1437
- includeArchived?: boolean;
1438
- }) => Promise<Response>, importFn: (file: File, options?: {
1439
- onProgress?: (event: {
1440
- phase: string;
1441
- message?: string;
1442
- result?: Record<string, unknown>;
1443
- }) => void;
1444
- }) => Promise<{
1445
- phase: string;
1446
- message?: string;
1447
- result?: Record<string, unknown>;
1448
- }>): ExchangeVM;
1449
-
1450
- interface AdminUsersVM extends ViewModel {
1451
- browse: ShellVM;
1452
- users$: Observable<unknown[]>;
1453
- stats$: Observable<unknown | null>;
1454
- usersLoading$: Observable<boolean>;
1455
- statsLoading$: Observable<boolean>;
1456
- updateUser(id: string, data: {
1457
- isAdmin?: boolean;
1458
- isActive?: boolean;
1459
- }): Promise<void>;
1460
- }
1461
- declare function createAdminUsersVM(client: SemiontClient, browse: ShellVM): AdminUsersVM;
1462
-
1463
- interface AdminSecurityVM extends ViewModel {
1464
- browse: ShellVM;
1465
- providers$: Observable<unknown[]>;
1466
- allowedDomains$: Observable<string[]>;
1467
- isLoading$: Observable<boolean>;
1468
- }
1469
- declare function createAdminSecurityVM(client: SemiontClient, browse: ShellVM): AdminSecurityVM;
1470
-
1471
- interface WelcomeVM extends ViewModel {
1472
- userData$: Observable<{
1473
- termsAcceptedAt?: string;
1474
- } | null>;
1475
- isProcessing$: Observable<boolean>;
1476
- acceptTerms(): Promise<void>;
1477
- }
1478
- declare function createWelcomeVM(client: SemiontClient): WelcomeVM;
1479
-
1480
- interface ResourceLoaderVM extends ViewModel {
1481
- resource$: Observable<ResourceDescriptor | undefined>;
1482
- isLoading$: Observable<boolean>;
1483
- invalidate(): void;
1484
- }
1485
- declare function createResourceLoaderVM(client: SemiontClient, resourceId: ResourceId): ResourceLoaderVM;
1486
-
1487
- interface SessionVM extends ViewModel {
1488
- isLoggingOut$: Observable<boolean>;
1489
- logout(): Promise<void>;
1490
- }
1491
- declare function createSessionVM(client: SemiontClient): SessionVM;
1492
-
1493
- interface SmelterEvent {
1494
- type: string;
1495
- resourceId?: string;
1496
- payload: Record<string, unknown>;
1497
- }
1498
- interface SmelterActorVMOptions {
1499
- baseUrl: string;
1500
- token: string;
1501
- reconnectMs?: number;
1502
- }
1503
- interface SmelterActorVM extends ViewModel {
1504
- events$: Observable<SmelterEvent>;
1505
- state$: Observable<ConnectionState>;
1506
- emit(channel: string, payload: Record<string, unknown>): Promise<void>;
1507
- start(): void;
1508
- stop(): void;
1509
- }
1510
- declare function createSmelterActorVM(options: SmelterActorVMOptions): SmelterActorVM;
1511
-
1512
- /**
1513
- * Job Claim Adapter — worker-side job lifecycle glue on top of a
1514
- * shared `ActorVM`.
1515
- *
1516
- * Replaces the old `WorkerVM`, which owned its own actor and
1517
- * duplicated the SSE connection that `SemiontClient` already held.
1518
- * Workers now construct a `SemiontSession` normally (one actor, one
1519
- * SSE connection) and use this adapter to attach job-claim behaviour
1520
- * on top of `session.client.actor`.
1521
- *
1522
- * The adapter is intentionally thin: it subscribes to `job:queued`
1523
- * on the actor, claims jobs via the existing request-response
1524
- * protocol (`job:claim` → `job:claimed` / `job:claim-failed`), and
1525
- * exposes observables for job orchestration. It does **not** own
1526
- * the actor, has no HTTP concerns, and has no modal state.
1527
- */
1528
-
1529
- interface JobAssignment {
1530
- jobId: string;
1531
- type: string;
1532
- resourceId: string;
1533
- }
1534
- interface ActiveJob {
1535
- jobId: string;
1536
- type: string;
1537
- resourceId: string;
1538
- userId: string;
1539
- params: Record<string, unknown>;
1540
- }
1541
- interface JobClaimAdapterOptions {
1542
- /** Shared actor (typically `session.client.actor`). */
1543
- actor: ActorVM;
1544
- /**
1545
- * Job types this worker can process. Jobs of other types that
1546
- * arrive on `job:queued` are ignored. Empty array = accept any.
1547
- */
1548
- jobTypes: string[];
1549
- }
1550
- interface JobClaimAdapter {
1551
- /** Currently-claimed job, or null when idle. */
1552
- readonly activeJob$: Observable<ActiveJob | null>;
1553
- /** True while a claim is in flight or a job is being processed. */
1554
- readonly isProcessing$: Observable<boolean>;
1555
- /** Monotonically-incrementing count of successfully-completed jobs. */
1556
- readonly jobsCompleted$: Observable<number>;
1557
- /** Stream of job failures (including claim-failed and processing errors). */
1558
- readonly errors$: Observable<{
1559
- jobId: string;
1560
- error: string;
1561
- }>;
1562
- /**
1563
- * Subscribe to `job:queued` events (adding the channel to the actor
1564
- * if not already subscribed) and begin claiming matching jobs.
1565
- * Idempotent — calling `start()` twice is a no-op.
1566
- */
1567
- start(): void;
1568
- /** Stop claiming new jobs. Does not cancel an in-flight job. */
1569
- stop(): void;
1570
- /** Signal successful completion of `activeJob$`. */
1571
- completeJob(): void;
1572
- /** Signal failure of `activeJob$`. Emits on `errors$`. */
1573
- failJob(jobId: string, error: string): void;
1574
- /** Release observables. Does not dispose the shared actor. */
1575
- dispose(): void;
1576
- }
1577
- /**
1578
- * Attach job-claim behaviour to a shared `ActorVM`.
1579
- */
1580
- declare function createJobClaimAdapter(options: JobClaimAdapterOptions): JobClaimAdapter;
1581
-
1582
- interface Job {
1583
- jobId: string;
1584
- type: string;
1585
- status: string;
1586
- resourceId: string;
1587
- /** DID of the user who initiated the job (audit). */
1588
- userId: string;
1589
- created: string;
1590
- startedAt?: string;
1591
- completedAt?: string;
1592
- error?: string;
1593
- progress?: Record<string, unknown>;
1594
- result?: Record<string, unknown>;
1595
- }
1596
- interface JobQueueVM extends ViewModel {
1597
- jobs$: Observable<Job[]>;
1598
- pendingByType$: Observable<Map<string, number>>;
1599
- runningJobs$: Observable<Job[]>;
1600
- jobCreated$: Observable<Job>;
1601
- jobCompleted$: Observable<Job>;
1602
- jobFailed$: Observable<Job>;
1603
- }
1604
- declare function createJobQueueVM(client: SemiontClient): JobQueueVM;
1605
-
1606
- interface AnnotationGroups {
1607
- highlights: Annotation[];
1608
- comments: Annotation[];
1609
- assessments: Annotation[];
1610
- references: Annotation[];
1611
- tags: Annotation[];
1612
- }
1613
- type StoredEventResponse = components['schemas']['StoredEventResponse'];
1614
- interface WizardState {
1615
- open: boolean;
1616
- annotationId: string | null;
1617
- resourceId: string | null;
1618
- defaultTitle: string;
1619
- entityTypes: string[];
1620
- }
1621
- interface ResourceViewerPageVM extends ViewModel {
1622
- beckon: BeckonVM;
1623
- browse: ShellVM;
1624
- mark: MarkVM;
1625
- gather: GatherVM;
1626
- yield: YieldVM;
1627
- annotations$: Observable<Annotation[]>;
1628
- annotationGroups$: Observable<AnnotationGroups>;
1629
- entityTypes$: Observable<string[]>;
1630
- events$: Observable<StoredEventResponse[]>;
1631
- referencedBy$: Observable<ReferencedByEntry[]>;
1632
- content$: Observable<string>;
1633
- contentLoading$: Observable<boolean>;
1634
- mediaToken$: Observable<string | null>;
1635
- wizard$: Observable<WizardState>;
1636
- closeWizard(): void;
1637
- }
1638
- declare function createResourceViewerPageVM(client: SemiontClient, resourceId: ResourceId, locale: string, browse: ShellVM, options?: {
1639
- mediaType?: string;
1640
- }): ResourceViewerPageVM;
1641
-
1642
- type ComposeMode = 'new' | 'clone' | 'reference';
1643
- interface ComposeParams {
1644
- mode?: string | undefined;
1645
- token?: string | undefined;
1646
- annotationUri?: string | undefined;
1647
- sourceDocumentId?: string | undefined;
1648
- name?: string | undefined;
1649
- entityTypes?: string | undefined;
1650
- storedContext?: string | undefined;
1651
- }
1652
- interface CloneData {
1653
- sourceResource: ResourceDescriptor;
1654
- sourceContent: string;
1655
- }
1656
- interface ReferenceData {
1657
- annotationUri: string;
1658
- sourceDocumentId: string;
1659
- name: string;
1660
- entityTypes: string[];
1661
- }
1662
- interface SaveResourceParams {
1663
- mode: ComposeMode;
1664
- name: string;
1665
- storageUri: string;
1666
- content?: string;
1667
- file?: File;
1668
- format?: string;
1669
- charset?: string;
1670
- entityTypes?: string[];
1671
- language: string;
1672
- archiveOriginal?: boolean;
1673
- annotationUri?: string;
1674
- sourceDocumentId?: string;
1675
- }
1676
- interface ComposePageVM extends ViewModel {
1677
- browse: ShellVM;
1678
- mode$: Observable<ComposeMode>;
1679
- loading$: Observable<boolean>;
1680
- cloneData$: Observable<CloneData | null>;
1681
- referenceData$: Observable<ReferenceData | null>;
1682
- gatheredContext$: Observable<GatheredContext | null>;
1683
- entityTypes$: Observable<string[]>;
1684
- save(params: SaveResourceParams): Promise<string>;
1685
- }
1686
- declare function createComposePageVM(client: SemiontClient, browse: ShellVM, params: ComposeParams, auth?: AccessToken): ComposePageVM;
1740
+ declare function createMarkStateUnit(client: SemiontClient, resourceId: ResourceId): MarkStateUnit;
1687
1741
 
1688
- export { type ActiveJob, AdminNamespace, type AdminSecurityVM, type AdminUsersVM, type AnnotationGroups, type AnnotationHistoryResponse, AuthNamespace, BeckonNamespace, type BeckonVM, BindNamespace, BrowseNamespace, BusRequestError, type BusRequestPrimitive, COMMON_PANELS, type Cache, type CloneData, type ComposeMode, type ComposePageVM, type ComposeParams, type CreateAnnotationInput, type CreateFromTokenOptions, type CreateResourceInput, type DiscoverVM, type EntityTagsVM, type ExchangeVM, FrontendSessionSignals, type GatherAnnotationProgress, GatherNamespace, type GatherVM, type GenerateDocumentOptions, type GenerationOptions, type GetBrowserOptions, HOVER_DELAY_MS, type HoverHandlers, type ImportPreview, InMemorySessionStorage, type Job, type JobAssignment, type JobClaimAdapter, type JobClaimAdapterOptions, JobNamespace, type JobQueueVM, type KbSessionStatus, type KnowledgeBase, type MarkAssistEvent, type MarkAssistOptions, type MarkAssistProgress, MarkNamespace, type MarkVM, MatchNamespace, type MatchSearchProgress, type MatchVM, type NewKnowledgeBase, type OpenResource, type PendingAnnotation, RESOURCE_PANELS, type ReferenceData, type ReferencedByEntry, type RequestContent, type ResourceLoaderVM, type ResourceViewerPageVM, type ResponseContent, type SaveResourceParams, type SearchPipeline, type SearchPipelineOptions, type SearchState, SemiontBrowser, type SemiontBrowserConfig, SemiontClient, SemiontError, type SemiontErrorCode, SemiontSession, type SemiontSessionConfig, type SessionStorage, type SessionVM, type ShellVM, type ShellVMOptions, type SmelterActorVM, type SmelterActorVMOptions, type SmelterEvent, type StoredSession, type ToolbarPanelType, type User, type UserInfo, type ViewModel, type WelcomeVM, type WizardState, type YieldGenerationEvent, YieldNamespace, type YieldVM, busRequest, createAdminSecurityVM, createAdminUsersVM, createBeckonVM, createCache, createComposePageVM, createDiscoverVM, createDisposer, createEntityTagsVM, createExchangeVM, createGatherVM, createHoverHandlers, createJobClaimAdapter, createJobQueueVM, createMarkVM, createMatchVM, createResourceLoaderVM, createResourceViewerPageVM, createSearchPipeline, createSessionVM, createShellVM, createSmelterActorVM, createWelcomeVM, createYieldVM, defaultProtocol, getBrowser, isValidHostname, kbBackendUrl, notifyPermissionDenied, notifySessionExpired, setStoredSession };
1742
+ export { AdminNamespace, type AnnotationHistoryResponse, AuthNamespace, BeckonNamespace, type BeckonStateUnit, BindNamespace, BrowseNamespace, BusRequestError, type BusRequestErrorCode, type BusRequestPrimitive, CacheObservable, type CreateAnnotationInput, type CreateFromTokenOptions, type CreateResourceInput, FrameNamespace, type GatherAnnotationProgress, GatherNamespace, type GatherStateUnit, type GenerateDocumentOptions, type GenerationOptions, type GetBrowserOptions, HOVER_DELAY_MS, type HoverHandlers, type HttpEndpoint, InMemorySessionStorage, JobNamespace, type KbEndpoint, type KbSessionStatus, type KnowledgeBase, type LocalEndpoint, type MarkAssistEvent, type MarkAssistOptions, type MarkAssistProgress, MarkNamespace, type MarkStateUnit, MatchNamespace, type MatchSearchProgress, type MatchStateUnit, type NewKnowledgeBase, type OpenResource, type PendingAnnotation, type ReferencedByEntry, type RequestContent, type ResponseContent, type SearchPipeline, type SearchPipelineOptions, type SearchState, SemiontBrowser, type SemiontBrowserConfig, SemiontClient, SemiontSession, type SemiontSessionConfig, SemiontSessionError, type SemiontSessionErrorCode, type SessionFactory, type SessionFactoryOptions, SessionSignals, type SessionStorage, type StateUnit, type StoredSession, StreamObservable, UploadObservable, type UploadProgress, type User, type UserInfo, type WorkerBus, type YieldGenerationEvent, YieldNamespace, type YieldStateUnit, busRequest, createBeckonStateUnit, createDisposer, createGatherStateUnit, createHoverHandlers, createHttpSessionFactory, createMarkStateUnit, createMatchStateUnit, createSearchPipeline, createYieldStateUnit, defaultProtocol, getBrowser, httpKb, isValidHostname, kbBackendUrl, setStoredSession };