@sigrun/shared 0.1.0 → 0.1.1

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 {
@@ -422,8 +424,15 @@ export interface MetaResponse {
422
424
  readonly memory: HealthStatus;
423
425
  /** Any contributor's §3 producer degraded → "degraded"; empty → "ok". */
424
426
  readonly providers: HealthStatus;
427
+ /**
428
+ * The gateway engine's health (gateway C2): present ONLY when an
429
+ * engine loaded (a no-engine deploy reports NO gateway health —
430
+ * degenerate "ok" by absence, never a synthesized field); the
431
+ * wrapped engine health (a throwing `health()` reads "degraded";
432
+ * a rejected `start` latches degraded until restart).
433
+ */
434
+ readonly gateway?: HealthStatus;
425
435
  };
426
- /**
427
436
  /**
428
437
  * The auth block (core hardening W1 Task 6, spec §5.7): the effective
429
438
  * authenticator list (`ctx.methods` — loopback-derived or explicit) and
@@ -448,6 +457,13 @@ export interface MetaResponse {
448
457
  readonly backup: boolean;
449
458
  readonly channels: readonly string[];
450
459
  };
460
+ /**
461
+ * The development flag (spec §14.2/§14.4): `server.dev` / `SIGRUN_DEV`,
462
+ * default `false`. Gates the U1 blocks gallery, U3's development
463
+ * error bodies (validation errors in `surface_failed` responses) and
464
+ * U4 — a boot constant, like the rest of meta.
465
+ */
466
+ readonly dev: boolean;
451
467
  /**
452
468
  * The seam inventory (docs/approval-ux-seams.md): per extension, the
453
469
  * interaction kinds / ambient surface keys its factory declared.
@@ -573,6 +589,21 @@ export interface ExtensionInfo {
573
589
  readonly workers: readonly WorkerStatus[];
574
590
  /** The declared HTTP routes, one `{method, path, auth}` each (W5 Task 2). */
575
591
  routes?: readonly ExtensionRouteInfo[];
592
+ /**
593
+ * The channel ids this extension contributes (gateway C1, spec §7):
594
+ * present only on rows of extensions whose factory result carried
595
+ * `channels`; unique across every loaded extension (a collision
596
+ * quarantines the later loader). Absent when the extension declared
597
+ * none.
598
+ */
599
+ readonly channels?: readonly string[];
600
+ /**
601
+ * The inert marker (gateway C2): `"no gateway"` on rows of
602
+ * extensions that contributed `channels` while NO gateway engine
603
+ * loaded — their adapters are inert (nothing drives them; one boot
604
+ * warning says so). Absent everywhere else.
605
+ */
606
+ readonly inert?: "no gateway";
576
607
  /**
577
608
  * Built-in extensions (Task 2, pi parity): `true` marks a core-shipped
578
609
  * built-in (codemode / MCP / tool search); ABSENT on configured
@@ -614,7 +645,11 @@ export type { SessionMeta, SessionMetaPatch } from "./session-meta.js";
614
645
  * The `GET /v1/auth/whoami` payload (core hardening W1 Task 3, spec
615
646
  * §5.8): the resolved principal, verbatim. `user` is absent only under
616
647
  * the naive empty-method path with no `X-Sigrun-User` header;
617
- * `service` marks the env service token.
648
+ * `service` marks the env service token. `method` mirrors the extension
649
+ * contract's `Principal.method` (gateway C4b added `"host"` — an
650
+ * in-process host-built principal; no HTTP route ever resolves one, but
651
+ * the two unions stay aligned so the whoami build from a principal can
652
+ * never drift from the wire type).
618
653
  */
619
654
  export interface WhoamiResponse {
620
655
  readonly user?: string;
@@ -622,7 +657,7 @@ export interface WhoamiResponse {
622
657
  readonly email?: string;
623
658
  readonly admin: boolean;
624
659
  readonly service: boolean;
625
- readonly method: "none" | "token" | "proxy";
660
+ readonly method: "none" | "token" | "proxy" | "host";
626
661
  }
627
662
  /**
628
663
  * The `GET /v1/onboarding` payload (core hardening W1 Task 4, spec
@@ -646,12 +681,19 @@ export interface OnboardingState {
646
681
  * One directory user on the wire (Task 4): the full record — emails are
647
682
  * admin-visible material and the routes that serve this type are
648
683
  * admin-gated. `createdAt` is epoch ms.
684
+ *
685
+ * `admin` is the STORED flag (the users-admin UI toggles it); the proxy
686
+ * group grant rides separately as `groupAdmin` (gateway C8): effective
687
+ * admin is `admin || groupAdmin === true`, and the last-admin guard
688
+ * counts `admin` alone.
649
689
  */
650
690
  export interface UserWire {
651
691
  readonly user: string;
652
692
  readonly name?: string;
653
693
  readonly email?: string;
654
694
  readonly admin: boolean;
695
+ /** The persisted proxy-group grant — present only under `adminGroups`. */
696
+ readonly groupAdmin?: boolean;
655
697
  readonly createdAt: number;
656
698
  readonly source: "proxy" | "invite" | "cli";
657
699
  }
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.1",
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": {