@sigrun/shared 0.1.0 → 0.1.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/live.d.ts ADDED
@@ -0,0 +1,71 @@
1
+ /**
2
+ * The `docs["pi.live"]` LiveState narrowing (gateway plan 1, Task 1).
3
+ *
4
+ * `pi.live` is the harness's live-run document — `run` while a generation
5
+ * is in flight, `generation.message` (the committed throttled partial of
6
+ * the response), `tools` (live tool slots), `compactions`, and
7
+ * `compactionFailure` (shape per pi-durable dist/harness/live.d.ts).
8
+ * `ConversationViewBase` types `docs` values as `JsonObject`: named
9
+ * LiveDocs are narrowed by their consumers, and this module is that
10
+ * narrowing for `pi.live` — shared by the webui, the tui and the gateway
11
+ * engine so every reader narrows the doc identically.
12
+ *
13
+ * Purity: a shared wire module — `import type` sibling-only (the purity
14
+ * guard in test/views-purity.test.ts); the runtime functions below are
15
+ * dependency-free so both clients (DOM-only webui, NodeNext tui) and the
16
+ * server read the same doc the same way.
17
+ */
18
+ import type { ConversationViewBase } from "./views.js";
19
+ /** One live tool slot of `docs["pi.live"]` (the LiveState tools list). */
20
+ export interface LiveToolSlotWire {
21
+ readonly callId: string;
22
+ readonly name: string;
23
+ readonly status: string;
24
+ readonly output?: string;
25
+ }
26
+ /**
27
+ * The `docs["pi.live"]` LiveState doc, narrowed to what the channels read:
28
+ * `run` (present exactly while busy — the steer field derives from it
29
+ * server-side), `generation.message` (the committed throttled partial of
30
+ * the in-flight response), `tools` (live tool slots), `compactions` (live
31
+ * compaction tasks), `compactionFailure` (the newest failure — a
32
+ * completed compaction clears it; nothing-to-compact or a fault leaves
33
+ * it).
34
+ */
35
+ export interface LiveDocWire {
36
+ readonly run?: unknown;
37
+ readonly generation?: {
38
+ readonly message?: unknown;
39
+ };
40
+ readonly tools?: readonly LiveToolSlotWire[];
41
+ readonly compactions?: readonly unknown[];
42
+ readonly compactionFailure?: {
43
+ readonly error?: unknown;
44
+ };
45
+ }
46
+ /**
47
+ * The view's `pi.live` doc, or undefined when absent (idle and never-run
48
+ * conversations carry no doc). A cast, not a validation: the shape is
49
+ * pinned by the harness writer and the fail-soft reads below absorb
50
+ * drift.
51
+ */
52
+ export declare function liveDoc(view: ConversationViewBase): LiveDocWire | undefined;
53
+ /** The live tool slots of the view's `pi.live` doc (empty when absent). */
54
+ export declare function liveToolSlots(view: ConversationViewBase): readonly LiveToolSlotWire[];
55
+ /**
56
+ * The in-flight generation's committed partial message, or undefined when
57
+ * idle (or the doc shape is unexpected — fail soft to absent; upstream
58
+ * drift surfaces in the schema fixture first).
59
+ */
60
+ export declare function liveGenerationPartial(view: ConversationViewBase): object | undefined;
61
+ /**
62
+ * The in-flight generation's visible text: the partial's text blocks
63
+ * (`{type: "text"}`) joined with `""` — thinking and tool-call blocks
64
+ * contribute nothing. `""` when idle or the content is not an array; a
65
+ * string `content` returns as-is.
66
+ */
67
+ export declare function liveGenerationText(view: ConversationViewBase): string;
68
+ /** How many compaction tasks are live right now (0 = none). */
69
+ export declare function liveCompactions(view: ConversationViewBase): number;
70
+ /** The newest compaction failure's error text, when one is surfaced. */
71
+ export declare function liveCompactionFailure(view: ConversationViewBase): string | undefined;
package/dist/live.js ADDED
@@ -0,0 +1,82 @@
1
+ /**
2
+ * The `docs["pi.live"]` LiveState narrowing (gateway plan 1, Task 1).
3
+ *
4
+ * `pi.live` is the harness's live-run document — `run` while a generation
5
+ * is in flight, `generation.message` (the committed throttled partial of
6
+ * the response), `tools` (live tool slots), `compactions`, and
7
+ * `compactionFailure` (shape per pi-durable dist/harness/live.d.ts).
8
+ * `ConversationViewBase` types `docs` values as `JsonObject`: named
9
+ * LiveDocs are narrowed by their consumers, and this module is that
10
+ * narrowing for `pi.live` — shared by the webui, the tui and the gateway
11
+ * engine so every reader narrows the doc identically.
12
+ *
13
+ * Purity: a shared wire module — `import type` sibling-only (the purity
14
+ * guard in test/views-purity.test.ts); the runtime functions below are
15
+ * dependency-free so both clients (DOM-only webui, NodeNext tui) and the
16
+ * server read the same doc the same way.
17
+ */
18
+ /**
19
+ * The view's `pi.live` doc, or undefined when absent (idle and never-run
20
+ * conversations carry no doc). A cast, not a validation: the shape is
21
+ * pinned by the harness writer and the fail-soft reads below absorb
22
+ * drift.
23
+ */
24
+ export function liveDoc(view) {
25
+ // SAFETY: `docs` values are `JsonObject` by wire contract; `pi.live` is
26
+ // the harness's own LiveState document. The cast narrows the wire type,
27
+ // it does not validate — every reader below is fail-soft against drift.
28
+ return view.docs["pi.live"];
29
+ }
30
+ /** The live tool slots of the view's `pi.live` doc (empty when absent). */
31
+ export function liveToolSlots(view) {
32
+ return liveDoc(view)?.tools ?? [];
33
+ }
34
+ /**
35
+ * The in-flight generation's committed partial message, or undefined when
36
+ * idle (or the doc shape is unexpected — fail soft to absent; upstream
37
+ * drift surfaces in the schema fixture first).
38
+ */
39
+ export function liveGenerationPartial(view) {
40
+ const message = liveDoc(view)?.generation?.message;
41
+ if (typeof message !== "object" || message === null)
42
+ return undefined;
43
+ return message;
44
+ }
45
+ /**
46
+ * The in-flight generation's visible text: the partial's text blocks
47
+ * (`{type: "text"}`) joined with `""` — thinking and tool-call blocks
48
+ * contribute nothing. `""` when idle or the content is not an array; a
49
+ * string `content` returns as-is.
50
+ */
51
+ export function liveGenerationText(view) {
52
+ const partial = liveGenerationPartial(view);
53
+ if (partial === undefined)
54
+ return "";
55
+ const content = partial.content;
56
+ if (typeof content === "string")
57
+ return content;
58
+ if (!Array.isArray(content))
59
+ return "";
60
+ let text = "";
61
+ for (const block of content) {
62
+ if (typeof block !== "object" || block === null)
63
+ continue;
64
+ const typed = block;
65
+ if (typed.type !== "text" || typeof typed.text !== "string")
66
+ continue;
67
+ text += typed.text;
68
+ }
69
+ return text;
70
+ }
71
+ /** How many compaction tasks are live right now (0 = none). */
72
+ export function liveCompactions(view) {
73
+ return liveDoc(view)?.compactions?.length ?? 0;
74
+ }
75
+ /** The newest compaction failure's error text, when one is surfaced. */
76
+ export function liveCompactionFailure(view) {
77
+ const failure = liveDoc(view)?.compactionFailure;
78
+ if (typeof failure !== "object" || failure === null)
79
+ return undefined;
80
+ const error = failure.error;
81
+ return typeof error === "string" && error !== "" ? error : undefined;
82
+ }
@@ -25,9 +25,10 @@ export type { Sharing } from "./access.js";
25
25
  * `invalid_field`.
26
26
  *
27
27
  * Server-owned fields (`autoName`, `autoNameAttempts`,
28
- * `lastActivityAt`) are written by host code only (activity.ts, the A2
29
- * auto-namer); they are NOT patch surface — the strict validator keeps
30
- * rejecting them as `invalid_field`.
28
+ * `lastActivityAt`, `jobId`, `deliver`) are written by host code only
29
+ * (activity.ts, the A2 auto-namer, fire.ts's job stamp); they are NOT
30
+ * patch surface — the strict validator keeps rejecting them as
31
+ * `invalid_field`.
31
32
  */
32
33
  /** The per-conversation session metadata (spec §5). */
33
34
  export type SessionMeta = {
@@ -69,6 +70,21 @@ export type SessionMeta = {
69
70
  * patchable — the strict validator rejects the key as `invalid_field`.
70
71
  */
71
72
  lastActivityAt?: string;
73
+ /**
74
+ * Server-owned job stamp (gateway C7, spec §8.0): the id of the job
75
+ * whose fire created this conversation (`fire.ts` writes it inside the
76
+ * creating commit). Never patchable — the strict validator rejects
77
+ * the key as `invalid_field`.
78
+ */
79
+ jobId?: string;
80
+ /**
81
+ * Server-owned job delivery targets (gateway C7, spec §8.0): the job
82
+ * action's `deliver` array, stamped onto the run conversation by
83
+ * `fire.ts` in the creating commit — the gateway's runSettled delivery
84
+ * reads it to route the answer to chats. Never patchable — the
85
+ * strict validator rejects the key as `invalid_field`.
86
+ */
87
+ deliver?: string[];
72
88
  /**
73
89
  * The §5.9 per-resource sharing state; absent = private (owner and
74
90
  * superuser only). Patchable by the OWNER or a superuser (403
@@ -195,6 +195,13 @@ export interface ServerSettings {
195
195
  readonly metrics?: {
196
196
  readonly public?: boolean;
197
197
  };
198
+ /**
199
+ * The development flag (spec §14.2/§14.4): `SIGRUN_DEV` (strict
200
+ * true/false — the spec's `SIGRUN_DEV=1` form reads as true) over this
201
+ * key, default `false`. Surfaces on `GET /v1/meta` (`dev`); gates the
202
+ * U1 blocks gallery, U3's development error bodies, and U4.
203
+ */
204
+ readonly dev?: boolean;
198
205
  /**
199
206
  * The backup target — exactly one of the two shapes (mirroring the
200
207
  * env rule: `SIGRUN_BACKUP_PATH` XOR `SIGRUN_BACKUP_S3_BUCKET`), plus
package/dist/turns.d.ts CHANGED
@@ -78,10 +78,27 @@ export type Turn<E extends TurnEntry> = {
78
78
  readonly tools: readonly ToolCall[];
79
79
  readonly text: string;
80
80
  readonly lastEntryId: number;
81
+ /** The turn's blocks in OCCURRENCE order — the chronological
82
+ * view interleaves thinking / tool calls / text exactly as they
83
+ * happened, instead of folding everything under the final text
84
+ * (the grouped view's shape). Text blocks CONSECUTIVE in the
85
+ * stream merge into one segment (multi-block answers read as one). */
86
+ readonly timeline: readonly TimelineEvent[];
81
87
  } | {
82
88
  readonly kind: "other";
83
89
  readonly entry: E;
84
90
  };
91
+ /** One chronological segment of an assistant turn. */
92
+ export type TimelineEvent = {
93
+ readonly kind: "thinking";
94
+ readonly text: string;
95
+ } | {
96
+ readonly kind: "tool";
97
+ readonly tool: ToolCall;
98
+ } | {
99
+ readonly kind: "text";
100
+ readonly text: string;
101
+ };
85
102
  export declare function groupTurns<E extends TurnEntry>(entries: readonly E[]): Turn<E>[];
86
103
  export declare function toolPreview(name: string, args: unknown): string;
87
104
  /**
package/dist/turns.js CHANGED
@@ -52,6 +52,22 @@ export function groupTurns(entries) {
52
52
  const flush = () => {
53
53
  if (pending === null)
54
54
  return;
55
+ // Merge consecutive text segments for the timeline (a multi-block
56
+ // answer reads as one run of prose, not N chopped paragraphs).
57
+ const timeline = [];
58
+ for (const event of pending.timeline) {
59
+ const last = timeline[timeline.length - 1];
60
+ if (event.kind === "text" &&
61
+ last !== undefined &&
62
+ last.kind === "text") {
63
+ timeline[timeline.length - 1] = {
64
+ kind: "text",
65
+ text: `${last.text}\n\n${event.text}`,
66
+ };
67
+ continue;
68
+ }
69
+ timeline.push(event);
70
+ }
55
71
  turns.push({
56
72
  kind: "assistant",
57
73
  entries: pending.entries,
@@ -59,29 +75,44 @@ export function groupTurns(entries) {
59
75
  tools: pending.tools,
60
76
  text: pending.texts.join("\n\n"),
61
77
  lastEntryId: pending.entries[pending.entries.length - 1].id,
78
+ timeline,
62
79
  });
63
80
  pending = null;
64
81
  };
65
82
  for (const entry of entries) {
66
83
  if (entry.kind === "pi.assistant" || entry.kind === "pi.tool-result") {
67
- pending ??= { entries: [], thinking: [], tools: [], texts: [] };
84
+ pending ??= {
85
+ entries: [],
86
+ thinking: [],
87
+ tools: [],
88
+ texts: [],
89
+ timeline: [],
90
+ };
68
91
  pending.entries.push(entry);
69
92
  if (entry.kind === "pi.assistant") {
70
93
  for (const message of entry.model ?? []) {
71
94
  for (const block of blocks(message)) {
72
95
  if (block.type === "text" && block.text !== undefined) {
73
96
  pending.texts.push(block.text);
97
+ pending.timeline.push({ kind: "text", text: block.text });
74
98
  }
75
- else if (block.type === "thinking" && block.thinking !== undefined) {
99
+ else if (block.type === "thinking" &&
100
+ block.thinking !== undefined) {
76
101
  pending.thinking.push(block.thinking);
102
+ pending.timeline.push({
103
+ kind: "thinking",
104
+ text: block.thinking,
105
+ });
77
106
  }
78
107
  else if (block.type === "toolCall") {
79
- pending.tools.push({
108
+ const call = {
80
109
  callId: block.id ?? "",
81
110
  name: block.name ?? "",
82
111
  preview: toolPreview(block.name ?? "", block.arguments),
83
112
  isError: false,
84
- });
113
+ };
114
+ pending.tools.push(call);
115
+ pending.timeline.push({ kind: "tool", tool: call });
85
116
  }
86
117
  }
87
118
  }
package/dist/views.d.ts CHANGED
@@ -388,6 +388,8 @@ export type WorkerStatus = {
388
388
  };
389
389
  /** The `GET /v1/meta` payload (spec §5; the health block is Task 9e). */
390
390
  export interface MetaResponse {
391
+ /** The sigrun server's own version (the package manifest). */
392
+ readonly version: string;
391
393
  readonly harnessVersion: string;
392
394
  readonly schemaVersion: string;
393
395
  readonly extensions: readonly {
@@ -400,6 +402,14 @@ export interface MetaResponse {
400
402
  * worker's first run.
401
403
  */
402
404
  readonly workers?: readonly WorkerStatus[];
405
+ /**
406
+ * The declared surfaces (UI-blocks U3 Task 5): the flattened
407
+ * view + panel rows the channels register their rail/panel
408
+ * entries from. Absent when the extension declared none. The
409
+ * meta response carries the DECLARATION (id/title/icon/
410
+ * placement); access filtering is render-time (§6.5).
411
+ */
412
+ readonly surfaces?: readonly ExtensionSurfaceInfo[];
403
413
  }[];
404
414
  /**
405
415
  * §5 health: pure aggregation over boot-retained §3 producers (spec
@@ -422,8 +432,15 @@ export interface MetaResponse {
422
432
  readonly memory: HealthStatus;
423
433
  /** Any contributor's §3 producer degraded → "degraded"; empty → "ok". */
424
434
  readonly providers: HealthStatus;
435
+ /**
436
+ * The gateway engine's health (gateway C2): present ONLY when an
437
+ * engine loaded (a no-engine deploy reports NO gateway health —
438
+ * degenerate "ok" by absence, never a synthesized field); the
439
+ * wrapped engine health (a throwing `health()` reads "degraded";
440
+ * a rejected `start` latches degraded until restart).
441
+ */
442
+ readonly gateway?: HealthStatus;
425
443
  };
426
- /**
427
444
  /**
428
445
  * The auth block (core hardening W1 Task 6, spec §5.7): the effective
429
446
  * authenticator list (`ctx.methods` — loopback-derived or explicit) and
@@ -448,6 +465,13 @@ export interface MetaResponse {
448
465
  readonly backup: boolean;
449
466
  readonly channels: readonly string[];
450
467
  };
468
+ /**
469
+ * The development flag (spec §14.2/§14.4): `server.dev` / `SIGRUN_DEV`,
470
+ * default `false`. Gates the U1 blocks gallery, U3's development
471
+ * error bodies (validation errors in `surface_failed` responses) and
472
+ * U4 — a boot constant, like the rest of meta.
473
+ */
474
+ readonly dev: boolean;
451
475
  /**
452
476
  * The seam inventory (docs/approval-ux-seams.md): per extension, the
453
477
  * interaction kinds / ambient surface keys its factory declared.
@@ -573,6 +597,23 @@ export interface ExtensionInfo {
573
597
  readonly workers: readonly WorkerStatus[];
574
598
  /** The declared HTTP routes, one `{method, path, auth}` each (W5 Task 2). */
575
599
  routes?: readonly ExtensionRouteInfo[];
600
+ /** The declared surfaces, one `{id, title, icon, placement}` each (U3 Task 1); absent when none. */
601
+ surfaces?: readonly ExtensionSurfaceInfo[];
602
+ /**
603
+ * The channel ids this extension contributes (gateway C1, spec §7):
604
+ * present only on rows of extensions whose factory result carried
605
+ * `channels`; unique across every loaded extension (a collision
606
+ * quarantines the later loader). Absent when the extension declared
607
+ * none.
608
+ */
609
+ readonly channels?: readonly string[];
610
+ /**
611
+ * The inert marker (gateway C2): `"no gateway"` on rows of
612
+ * extensions that contributed `channels` while NO gateway engine
613
+ * loaded — their adapters are inert (nothing drives them; one boot
614
+ * warning says so). Absent everywhere else.
615
+ */
616
+ readonly inert?: "no gateway";
576
617
  /**
577
618
  * Built-in extensions (Task 2, pi parity): `true` marks a core-shipped
578
619
  * built-in (codemode / MCP / tool search); ABSENT on configured
@@ -591,6 +632,26 @@ export interface ExtensionRouteInfo {
591
632
  readonly path: string;
592
633
  readonly auth: string;
593
634
  }
635
+ /**
636
+ * One `GET /v1/extensions` surface row (UI-blocks U3 Task 1, spec
637
+ * §7.1): the extension-declared views and panels — what the channels'
638
+ * rail (views) and right panel (panels) register from. One row per
639
+ * declared surface, in declaration order (views before panels).
640
+ */
641
+ export interface ExtensionSurfaceInfo {
642
+ readonly id: string;
643
+ readonly title: string;
644
+ readonly icon: string;
645
+ /** The declared placement; `settings` sections (§13.3) are not in this workstream. */
646
+ readonly placement: "view" | "panel";
647
+ /** The declared refresh cadence (spec §7.1); channels refetch data on
648
+ * this interval while the surface is visible. Absent → no cadence. */
649
+ readonly refreshMs?: number;
650
+ /** §7.3 shared-data flag: a sharedData surface's `since` fetches may
651
+ * answer with `ops` and NO `data` — channels fall back to a full
652
+ * fetch (ops folding is U1). Absent → per-instance data. */
653
+ readonly sharedData?: boolean;
654
+ }
594
655
  /** The `GET /v1/extensions` payload (spec §7.7). */
595
656
  export interface ExtensionsResponse {
596
657
  readonly extensions: readonly ExtensionInfo[];
@@ -614,7 +675,11 @@ export type { SessionMeta, SessionMetaPatch } from "./session-meta.js";
614
675
  * The `GET /v1/auth/whoami` payload (core hardening W1 Task 3, spec
615
676
  * §5.8): the resolved principal, verbatim. `user` is absent only under
616
677
  * the naive empty-method path with no `X-Sigrun-User` header;
617
- * `service` marks the env service token.
678
+ * `service` marks the env service token. `method` mirrors the extension
679
+ * contract's `Principal.method` (gateway C4b added `"host"` — an
680
+ * in-process host-built principal; no HTTP route ever resolves one, but
681
+ * the two unions stay aligned so the whoami build from a principal can
682
+ * never drift from the wire type).
618
683
  */
619
684
  export interface WhoamiResponse {
620
685
  readonly user?: string;
@@ -622,7 +687,7 @@ export interface WhoamiResponse {
622
687
  readonly email?: string;
623
688
  readonly admin: boolean;
624
689
  readonly service: boolean;
625
- readonly method: "none" | "token" | "proxy";
690
+ readonly method: "none" | "token" | "proxy" | "host";
626
691
  }
627
692
  /**
628
693
  * The `GET /v1/onboarding` payload (core hardening W1 Task 4, spec
@@ -646,12 +711,19 @@ export interface OnboardingState {
646
711
  * One directory user on the wire (Task 4): the full record — emails are
647
712
  * admin-visible material and the routes that serve this type are
648
713
  * admin-gated. `createdAt` is epoch ms.
714
+ *
715
+ * `admin` is the STORED flag (the users-admin UI toggles it); the proxy
716
+ * group grant rides separately as `groupAdmin` (gateway C8): effective
717
+ * admin is `admin || groupAdmin === true`, and the last-admin guard
718
+ * counts `admin` alone.
649
719
  */
650
720
  export interface UserWire {
651
721
  readonly user: string;
652
722
  readonly name?: string;
653
723
  readonly email?: string;
654
724
  readonly admin: boolean;
725
+ /** The persisted proxy-group grant — present only under `adminGroups`. */
726
+ readonly groupAdmin?: boolean;
655
727
  readonly createdAt: number;
656
728
  readonly source: "proxy" | "invite" | "cli";
657
729
  }
@@ -739,4 +811,12 @@ export type HostEventWire = {
739
811
  /** W4 Task 4 (§8.4): the changed job's summary, WITHOUT `access` — the events stream computes `accessOf` per principal and adds it before delivery (a hidden job's event is withheld entirely). */
740
812
  readonly type: "job.changed";
741
813
  readonly job: Omit<JobWire, "access">;
814
+ } | {
815
+ /** UI-blocks U3 Task 4 (spec §7.3): one surface instance changed. `k` is the hashed instance key (sha256(slug + "\0" + id + "\0" + key) hex) — the event NEVER carries params or data (render/data are principal-specific; the watcher refetches as itself). Delivered only to streams in the watcher registry whose stored access is not `"none"`. */
816
+ readonly type: "surface.changed";
817
+ readonly slug: string;
818
+ readonly id: string;
819
+ readonly k: string;
820
+ readonly rev: number;
821
+ readonly treeRev: number;
742
822
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@sigrun/shared",
3
- "version": "0.1.0",
4
- "description": "sigrun shared leaf package — theme tokens, turn/conversation shaping, and the channel wire types (pure leaf, zero dependencies)",
3
+ "version": "0.1.2",
4
+ "description": "sigrun shared leaf package \u2014 theme tokens, turn/conversation shaping, and the channel wire types (pure leaf, zero dependencies)",
5
5
  "type": "module",
6
6
  "exports": {
7
7
  "./themes": {
@@ -44,6 +44,10 @@
44
44
  "types": "./dist/views.d.ts",
45
45
  "default": "./dist/views.js"
46
46
  },
47
+ "./live": {
48
+ "types": "./dist/live.d.ts",
49
+ "default": "./dist/live.js"
50
+ },
47
51
  "./view-delta": {
48
52
  "types": "./dist/view-delta.d.ts",
49
53
  "default": "./dist/view-delta.js"
@@ -51,6 +55,10 @@
51
55
  "./jobs": {
52
56
  "types": "./dist/jobs.d.ts",
53
57
  "default": "./dist/jobs.js"
58
+ },
59
+ "./blocks": {
60
+ "types": "./dist/blocks/index.d.ts",
61
+ "default": "./dist/blocks/index.js"
54
62
  }
55
63
  },
56
64
  "scripts": {