@paneui/core 0.0.9 → 0.0.10

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.js CHANGED
@@ -3,5 +3,6 @@
3
3
  export { PaneClient, PaneApiError } from "./client.js";
4
4
  export { openStream } from "./stream.js";
5
5
  export { registerAgent } from "./register.js";
6
- export { artifactSchema, callbackSchema, createSessionSchema, artifactTypeSchema, createArtifactSchema, createArtifactVersionSchema, patchArtifactMetadataSchema, feedbackTypeSchema, submitFeedbackSchema, listSessionsStatusSchema, listSessionsQuerySchema, mintParticipantSchema, } from "./schemas.js";
6
+ export { artifactSchema, callbackSchema, createPaneSchema, artifactTypeSchema, createArtifactSchema, createArtifactVersionSchema, patchArtifactMetadataSchema, feedbackTypeSchema, submitFeedbackSchema, listPanesStatusSchema, listPanesQuerySchema, mintParticipantSchema, upgradePaneSchema, } from "./schemas.js";
7
+ export { validateIconEmoji, isValidIconEmoji, isRasterImageMime, RASTER_ICON_MIME_ALLOWLIST, MAX_ICON_EMOJI_BYTES, } from "./icons.js";
7
8
  export { MAX_EVENT_TYPE_LENGTH, MAX_IDEMPOTENCY_KEY_LENGTH, MAX_RESPONSE_SNIPPET_LENGTH, MAX_FRAME_SNIPPET_LENGTH, } from "./limits.js";
package/dist/register.js CHANGED
@@ -4,7 +4,7 @@
4
4
  // the call that *obtains* one. Whether the relay endpoint is reachable depends
5
5
  // on its REGISTRATION_MODE: a `secret`-mode relay requires the shared
6
6
  // registration secret to be passed as a Bearer token (see the `secret` option
7
- // below). Abuse is bounded server-side by a per-IP rate limit (a 429 surfaces
7
+ // below). Abuse is bounded server-side by a per-IP rate limit (a 429 panes
8
8
  // here as a PaneApiError with status 429).
9
9
  import { PaneApiError } from "./client.js";
10
10
  import { MAX_RESPONSE_SNIPPET_LENGTH } from "./limits.js";
package/dist/schemas.d.ts CHANGED
@@ -15,11 +15,13 @@ export declare const callbackSchema: z.ZodObject<{
15
15
  events: z.ZodArray<z.ZodString>;
16
16
  secret: z.ZodString;
17
17
  }, z.core.$strip>;
18
- export declare const createSessionSchema: z.ZodObject<{
18
+ export declare const createPaneSchema: z.ZodObject<{
19
19
  template: z.ZodUnion<readonly [z.ZodObject<{
20
20
  id: z.ZodString;
21
21
  version: z.ZodOptional<z.ZodNumber>;
22
22
  }, z.core.$strip>, z.ZodObject<{
23
+ name: z.ZodString;
24
+ slug: z.ZodOptional<z.ZodString>;
23
25
  source: z.ZodString;
24
26
  type: z.ZodEnum<{
25
27
  "html-inline": "html-inline";
@@ -27,6 +29,7 @@ export declare const createSessionSchema: z.ZodObject<{
27
29
  }>;
28
30
  event_schema: z.ZodOptional<z.ZodUnknown>;
29
31
  input_schema: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
32
+ record_schema: z.ZodOptional<z.ZodUnknown>;
30
33
  }, z.core.$strip>]>;
31
34
  input_data: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
32
35
  participants: z.ZodOptional<z.ZodObject<{
@@ -40,7 +43,10 @@ export declare const createSessionSchema: z.ZodObject<{
40
43
  secret: z.ZodString;
41
44
  }, z.core.$strip>>;
42
45
  title: z.ZodOptional<z.ZodString>;
46
+ preamble: z.ZodOptional<z.ZodString>;
43
47
  context_key: z.ZodOptional<z.ZodString>;
48
+ icon_emoji: z.ZodOptional<z.ZodString>;
49
+ icon_attachment_id: z.ZodOptional<z.ZodString>;
44
50
  }, z.core.$strip>;
45
51
  export declare const createArtifactSchema: z.ZodObject<{
46
52
  name: z.ZodString;
@@ -54,6 +60,9 @@ export declare const createArtifactSchema: z.ZodObject<{
54
60
  }>;
55
61
  event_schema: z.ZodOptional<z.ZodUnknown>;
56
62
  input_schema: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
63
+ record_schema: z.ZodOptional<z.ZodUnknown>;
64
+ template_record_schema: z.ZodOptional<z.ZodUnknown>;
65
+ icon_emoji: z.ZodOptional<z.ZodString>;
57
66
  }, z.core.$strip>;
58
67
  export declare const createArtifactVersionSchema: z.ZodObject<{
59
68
  source: z.ZodString;
@@ -63,12 +72,16 @@ export declare const createArtifactVersionSchema: z.ZodObject<{
63
72
  }>;
64
73
  event_schema: z.ZodOptional<z.ZodUnknown>;
65
74
  input_schema: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
75
+ record_schema: z.ZodOptional<z.ZodUnknown>;
76
+ template_record_schema: z.ZodOptional<z.ZodUnknown>;
66
77
  }, z.core.$strip>;
67
78
  export declare const patchArtifactMetadataSchema: z.ZodObject<{
68
79
  name: z.ZodOptional<z.ZodString>;
69
80
  slug: z.ZodOptional<z.ZodString>;
70
81
  description: z.ZodOptional<z.ZodString>;
71
82
  tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
83
+ icon_emoji: z.ZodOptional<z.ZodNullable<z.ZodString>>;
84
+ icon_attachment_id: z.ZodOptional<z.ZodNullable<z.ZodString>>;
72
85
  }, z.core.$strip>;
73
86
  export declare const feedbackTypeSchema: z.ZodEnum<{
74
87
  bug: "bug";
@@ -82,17 +95,17 @@ export declare const submitFeedbackSchema: z.ZodObject<{
82
95
  note: "note";
83
96
  }>;
84
97
  message: z.ZodPipe<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>, z.ZodString>;
85
- surface_id: z.ZodOptional<z.ZodString>;
98
+ pane_id: z.ZodOptional<z.ZodString>;
86
99
  }, z.core.$strip>;
87
- /** @deprecated use `CreateSessionRequest` from ./types.js (same type). */
88
- export type CreateSessionInput = z.infer<typeof createSessionSchema>;
89
- export declare const listSessionsStatusSchema: z.ZodEnum<{
100
+ /** @deprecated use `CreatePaneRequest` from ./types.js (same type). */
101
+ export type CreatePaneInput = z.infer<typeof createPaneSchema>;
102
+ export declare const listPanesStatusSchema: z.ZodEnum<{
90
103
  open: "open";
91
104
  closed: "closed";
92
105
  all: "all";
93
106
  }>;
94
- export type ListSessionsStatus = z.infer<typeof listSessionsStatusSchema>;
95
- export declare const listSessionsQuerySchema: z.ZodObject<{
107
+ export type ListPanesStatus = z.infer<typeof listPanesStatusSchema>;
108
+ export declare const listPanesQuerySchema: z.ZodObject<{
96
109
  status: z.ZodOptional<z.ZodEnum<{
97
110
  open: "open";
98
111
  closed: "closed";
@@ -102,8 +115,16 @@ export declare const listSessionsQuerySchema: z.ZodObject<{
102
115
  cursor: z.ZodOptional<z.ZodString>;
103
116
  template_id: z.ZodOptional<z.ZodString>;
104
117
  }, z.core.$strip>;
105
- export type ListSessionsQuery = z.infer<typeof listSessionsQuerySchema>;
118
+ export type ListPanesQuery = z.infer<typeof listPanesQuerySchema>;
106
119
  export declare const mintParticipantSchema: z.ZodObject<{
107
120
  kind: z.ZodLiteral<"human">;
108
121
  }, z.core.$strip>;
109
122
  export type MintParticipantInput = z.infer<typeof mintParticipantSchema>;
123
+ export declare const upgradePaneSchema: z.ZodObject<{
124
+ template_version: z.ZodOptional<z.ZodNumber>;
125
+ compat: z.ZodOptional<z.ZodEnum<{
126
+ strict: "strict";
127
+ force: "force";
128
+ }>>;
129
+ }, z.core.$strip>;
130
+ export type UpgradePaneInput = z.infer<typeof upgradePaneSchema>;
package/dist/schemas.js CHANGED
@@ -2,6 +2,13 @@
2
2
  // other clients) validate user-supplied input — e.g. an inline JSON template
3
3
  // or callback config — before it hits the relay, producing clear errors.
4
4
  import { z } from "zod";
5
+ import { validateIconEmoji } from "./icons.js";
6
+ // A validated icon emoji: exactly one emoji grapheme (see ./icons.ts). Used in
7
+ // template/pane create + patch payloads. Rejects letters/digits/control chars
8
+ // and multi-grapheme strings with a clear message.
9
+ const iconEmojiSchema = z.string().refine((s) => validateIconEmoji(s).ok, {
10
+ message: "icon_emoji must be exactly one emoji (a single grapheme)",
11
+ });
5
12
  // The template `type` discriminant. `html-inline` carries raw HTML in `source`;
6
13
  // `html-ref` carries a URL. The relay rejects `html-ref` in this release.
7
14
  export const artifactTypeSchema = z.enum(["html-inline", "html-ref"]);
@@ -16,35 +23,47 @@ export const callbackSchema = z.object({
16
23
  events: z.array(z.string().min(1)).min(1),
17
24
  secret: z.string().min(8),
18
25
  });
19
- // The inline template form for POST /v1/surfaces — carries the event schema
26
+ // The inline template form for POST /v1/panes — carries the event schema
20
27
  // INSIDE the template object (one-off, no registered template). The relay
21
28
  // transparently creates an anonymous template behind it.
22
29
  const inlineArtifactSchema = z.object({
30
+ // Inline panes now name their auto-created template so the owner-shell UI
31
+ // has a readable label instead of falling back to the template's cuid id.
32
+ // Keeps the reference and inline forms symmetric: both yield a named
33
+ // template (name required min(1), slug optional — same as
34
+ // createArtifactSchema below).
35
+ name: z.string().min(1),
36
+ slug: z.string().min(1).optional(),
23
37
  source: z.string().min(1),
24
38
  type: artifactTypeSchema,
25
39
  // Optional: omit for a view-only one-off (a report/dashboard the human only
26
- // views — the surface then accepts no page/agent events).
40
+ // views — the pane then accepts no page/agent events).
27
41
  event_schema: z.unknown().optional(),
28
- // Optional: when present, the surface's `input_data` is validated against
29
- // this JSON Schema before the surface row is created — and any attachment refs
42
+ // Optional: when present, the pane's `input_data` is validated against
43
+ // this JSON Schema before the pane row is created — and any attachment refs
30
44
  // declared at `format: pane-attachment-id` sites become reachable from the page
31
45
  // via `window.pane.downloadBlob()`. Without this, attachment refs in
32
- // `input_data` are silently unreachable for inline surfaces (the
46
+ // `input_data` are silently unreachable for inline panes (the
33
47
  // participant attachment-download bridge walks input_data against the template
34
48
  // version's inputSchema; no schema means no walkable sites). See #208.
35
49
  input_schema: z.record(z.string(), z.unknown()).optional(),
50
+ // Optional JSON Schema 2020-12 document with an `x-pane-collections`
51
+ // extension declaring the template's record collections (#287 / #289).
52
+ // Validated by the relay at create time. Persistence ships once the
53
+ // schema migration (#288) lands.
54
+ record_schema: z.unknown().optional(),
36
55
  });
37
- // The reference form for POST /v1/surfaces — instances an existing named
56
+ // The reference form for POST /v1/panes — instances an existing named
38
57
  // template. `id` accepts the template id or its slug; `version` is optional
39
58
  // and defaults to the template's latest version.
40
59
  const refArtifactSchema = z.object({
41
60
  id: z.string().min(1),
42
61
  version: z.number().int().positive().optional(),
43
62
  });
44
- // The surface-create `template` field: exactly one of the two forms. A union
63
+ // The pane-create `template` field: exactly one of the two forms. A union
45
64
  // (not a discriminated union — the two forms share no discriminant key) with a
46
65
  // refine enforcing exactly-one-of `id` / `source`.
47
- const sessionArtifactSchema = z
66
+ const paneTemplateSchema = z
48
67
  .union([refArtifactSchema, inlineArtifactSchema])
49
68
  .refine((a) => {
50
69
  const hasId = "id" in a && a.id !== undefined;
@@ -53,20 +72,26 @@ const sessionArtifactSchema = z
53
72
  }, {
54
73
  message: "template must carry exactly one of `id` (reference an existing template) or `source` (inline a one-off template)",
55
74
  });
56
- export const createSessionSchema = z.object({
57
- template: sessionArtifactSchema,
75
+ export const createPaneSchema = z.object({
76
+ template: paneTemplateSchema,
58
77
  input_data: z.record(z.string(), z.unknown()).optional(),
59
78
  participants: z.object({ humans: z.number().int().positive() }).optional(),
60
79
  ttl: z.number().int().positive().optional(),
61
80
  metadata: z.record(z.string(), z.unknown()).optional(),
62
81
  callback: callbackSchema.optional(),
63
82
  // Tab title for the human's browser. Optional on the wire because the relay
64
- // also accepts the implicit fallback (an Template.name on the reference
65
- // form). The relay enforces "required-or-fallback" + length/control-char
66
- // rules Zod only confirms it's a string here.
83
+ // also accepts the implicit fallback: the reference form falls back to the
84
+ // existing Template.name, and the inline form now carries its own `name`
85
+ // (see inlineArtifactSchema above) so it can fall back to that too. The
86
+ // relay enforces "required-or-fallback" + length/control-char rules — Zod
87
+ // only confirms it's a string here.
67
88
  title: z.string().optional(),
89
+ // Optional context preamble shown in the shell band above the iframe so the
90
+ // human reads "who is asking, why" before the artifact. Wire cap is 300 to
91
+ // leave the relay's trimmed 280-char rejection as the one callers see.
92
+ preamble: z.string().max(300).optional(),
68
93
  // Phase G — natural-key dedup. When set, the relay collapses repeated
69
- // creates with the same (template, owner, context_key) into one surface
94
+ // creates with the same (template, owner, context_key) into one pane
70
95
  // row. NULL = ad-hoc, no dedup. See HUMAN-SIDE-PROPOSAL.md §7.1.
71
96
  context_key: z
72
97
  .string()
@@ -74,6 +99,12 @@ export const createSessionSchema = z.object({
74
99
  .max(256)
75
100
  .regex(/^[A-Za-z0-9_:.-]+$/, "context_key must be a short identifier")
76
101
  .optional(),
102
+ // Per-pane icon override. `icon_emoji` is a single emoji grapheme;
103
+ // `icon_attachment_id` references a ready, agent-accessible raster image
104
+ // attachment (validated server-side). NULL/absent = inherit the template's
105
+ // icon. NO external URLs.
106
+ icon_emoji: iconEmojiSchema.optional(),
107
+ icon_attachment_id: z.string().min(1).optional(),
77
108
  });
78
109
  // POST /v1/templates — create a named, reusable template plus its v1 content.
79
110
  export const createArtifactSchema = z.object({
@@ -86,6 +117,17 @@ export const createArtifactSchema = z.object({
86
117
  // Optional: omit for a view-only template (no event vocabulary).
87
118
  event_schema: z.unknown().optional(),
88
119
  input_schema: z.record(z.string(), z.unknown()).optional(),
120
+ // Optional records declaration (#287 / #289). See inlineArtifactSchema above.
121
+ record_schema: z.unknown().optional(),
122
+ // Optional template-level records declaration. Same JSON Schema 2020-12 +
123
+ // x-pane-collections grammar as record_schema; stored separately on the
124
+ // version so the publisher can curate shared content visible to every
125
+ // derived pane.
126
+ template_record_schema: z.unknown().optional(),
127
+ // Optional template icon emoji (a single emoji grapheme). Image icons are
128
+ // set post-create via PATCH /v1/templates/:id, since the uploaded
129
+ // attachment must reference this template's id first.
130
+ icon_emoji: iconEmojiSchema.optional(),
89
131
  });
90
132
  // POST /v1/templates/:id/versions — append a new version (content only).
91
133
  export const createArtifactVersionSchema = z.object({
@@ -94,6 +136,10 @@ export const createArtifactVersionSchema = z.object({
94
136
  // Optional: omit for a view-only template (no event vocabulary).
95
137
  event_schema: z.unknown().optional(),
96
138
  input_schema: z.record(z.string(), z.unknown()).optional(),
139
+ // Optional records declaration (#287 / #289). See inlineArtifactSchema above.
140
+ record_schema: z.unknown().optional(),
141
+ // Optional template-level records declaration. Same grammar as record_schema.
142
+ template_record_schema: z.unknown().optional(),
97
143
  });
98
144
  // PATCH /v1/templates/:id — update head metadata only (never content).
99
145
  export const patchArtifactMetadataSchema = z.object({
@@ -101,6 +147,12 @@ export const patchArtifactMetadataSchema = z.object({
101
147
  slug: z.string().min(1).optional(),
102
148
  description: z.string().optional(),
103
149
  tags: z.array(z.string().min(1)).optional(),
150
+ // Template icon. Pass a single emoji grapheme (icon_emoji) or a ready,
151
+ // template-scoped raster image attachment id (icon_attachment_id). Pass
152
+ // `null` to CLEAR that side. Setting an image and an emoji are independent —
153
+ // the renderer prefers the image when both are present. NO external URLs.
154
+ icon_emoji: iconEmojiSchema.nullable().optional(),
155
+ icon_attachment_id: z.string().min(1).nullable().optional(),
104
156
  });
105
157
  // POST /v1/feedback — an agent submits a bug report, feature request, or note
106
158
  // to the relay operator. Message is trimmed before length check so whitespace
@@ -112,21 +164,32 @@ export const submitFeedbackSchema = z.object({
112
164
  .string()
113
165
  .transform((s) => s.trim())
114
166
  .pipe(z.string().min(1).max(4000)),
115
- surface_id: z.string().min(1).optional(),
167
+ pane_id: z.string().min(1).optional(),
116
168
  });
117
- // GET /v1/surfaces — list the calling agent's surfaces. The relay also
169
+ // GET /v1/panes — list the calling agent's panes. The relay also
118
170
  // re-parses these on its side (defence in depth); this schema is for the CLI
119
171
  // to fail fast with a clear error before a round trip.
120
- export const listSessionsStatusSchema = z.enum(["open", "closed", "all"]);
121
- export const listSessionsQuerySchema = z.object({
122
- status: listSessionsStatusSchema.optional(),
172
+ export const listPanesStatusSchema = z.enum(["open", "closed", "all"]);
173
+ export const listPanesQuerySchema = z.object({
174
+ status: listPanesStatusSchema.optional(),
123
175
  limit: z.number().int().positive().max(200).optional(),
124
176
  cursor: z.string().min(1).optional(),
125
177
  template_id: z.string().min(1).optional(),
126
178
  });
127
- // POST /v1/surfaces/:id/participants — mint a fresh participant URL for an
128
- // existing surface. v1 supports human participants only (the agent token is
129
- // minted at surface-create and cannot be re-minted via this endpoint).
179
+ // POST /v1/panes/:id/participants — mint a fresh participant URL for an
180
+ // existing pane. v1 supports human participants only (the agent token is
181
+ // minted at pane-create and cannot be re-minted via this endpoint).
130
182
  export const mintParticipantSchema = z.object({
131
183
  kind: z.literal("human"),
132
184
  });
185
+ // POST /v1/panes/:id/upgrade — re-pin a live pane to a newer version
186
+ // of the same template (#267). `template_version` is optional; the relay
187
+ // defaults to the template's latest version. `compat` defaults to "strict",
188
+ // which makes the relay refuse 422 if the target schema isn't a superset
189
+ // of the current one (events written under the old schema would no longer
190
+ // validate). "force" overrides the gate — used sparingly when the operator
191
+ // knows data loss is acceptable.
192
+ export const upgradePaneSchema = z.object({
193
+ template_version: z.number().int().positive().optional(),
194
+ compat: z.enum(["strict", "force"]).optional(),
195
+ });
package/dist/stream.d.ts CHANGED
@@ -1,21 +1,40 @@
1
1
  import { WebSocket } from "ws";
2
- import type { PaneEvent } from "./types.js";
2
+ import type { PaneEvent, RecordDeltaMessage } from "./types.js";
3
3
  export interface OpenStreamOptions {
4
4
  /** WebSocket base URL, e.g. wss://pane.example.com (no trailing slash). */
5
5
  wsBaseUrl: string;
6
- /** Surface id. */
7
- surfaceId: string;
6
+ /** Pane id. */
7
+ paneId: string;
8
8
  /** Agent (or participant) bearer token. */
9
9
  token: string;
10
10
  /** Opaque cursor: replay only events strictly after this id. */
11
11
  since?: string | null;
12
+ /**
13
+ * #297 — subscribe to record-collection deltas. `"*"` expands to every
14
+ * declared collection on the pane's template; a comma list filters
15
+ * to those names. Absent = no record traffic (legacy event-only stream).
16
+ */
17
+ subscribeRecords?: string;
18
+ /**
19
+ * #297 — per-collection record-replay cursors. Map of collection name →
20
+ * last observed seq, sent as `?since_record_seq.<name>=<seq>` so the
21
+ * relay's replay (#295) skips already-observed rows on reconnect.
22
+ */
23
+ sinceRecordSeq?: Record<string, number>;
12
24
  }
13
25
  /** Callbacks for a live stream. */
14
26
  export interface StreamHandlers {
15
27
  /** Fired for every event envelope (replayed and live). */
16
28
  onEvent?: (event: PaneEvent) => void;
17
- /** Fired once when the initial replay finishes. */
29
+ /** Fired once when the initial event replay finishes. */
18
30
  onReplayComplete?: () => void;
31
+ /**
32
+ * #297 — fired for every record-delta message (record.upsert /
33
+ * record.delete / record.replay.complete) on the stream. Per-collection
34
+ * record.replay.complete fires once per subscribed collection after
35
+ * the replay set has been drained.
36
+ */
37
+ onRecord?: (msg: RecordDeltaMessage) => void;
19
38
  /** Fired on a relay error frame. */
20
39
  onRelayError?: (error: {
21
40
  code?: string;
@@ -32,7 +51,7 @@ export interface StreamHandlers {
32
51
  }
33
52
  /** A live handle to an open stream. */
34
53
  export interface StreamHandle {
35
- /** Send an event frame into the surface. */
54
+ /** Send an event frame into the pane. */
36
55
  send(frame: {
37
56
  type: string;
38
57
  data?: unknown;
@@ -45,7 +64,7 @@ export interface StreamHandle {
45
64
  readonly socket: WebSocket;
46
65
  }
47
66
  /**
48
- * Open a WebSocket stream to a Pane surface. Replays on connect, then streams
67
+ * Open a WebSocket stream to a Pane pane. Replays on connect, then streams
49
68
  * live. Returns a handle for sending frames and closing.
50
69
  */
51
70
  export declare function openStream(opts: OpenStreamOptions, handlers: StreamHandlers): StreamHandle;
package/dist/stream.js CHANGED
@@ -1,4 +1,4 @@
1
- // WebSocket client for WS /v1/surfaces/:id/stream.
1
+ // WebSocket client for WS /v1/panes/:id/stream.
2
2
  //
3
3
  // The relay protocol (see the relay's src/ws/handler.ts):
4
4
  // - on connect, the relay replays every event since `?since=` (or from the
@@ -16,15 +16,23 @@
16
16
  import { WebSocket } from "ws";
17
17
  import { MAX_FRAME_SNIPPET_LENGTH } from "./limits.js";
18
18
  /**
19
- * Open a WebSocket stream to a Pane surface. Replays on connect, then streams
19
+ * Open a WebSocket stream to a Pane pane. Replays on connect, then streams
20
20
  * live. Returns a handle for sending frames and closing.
21
21
  */
22
22
  export function openStream(opts, handlers) {
23
23
  const base = opts.wsBaseUrl.replace(/\/$/, "");
24
- const u = new URL(`${base}/v1/surfaces/${encodeURIComponent(opts.surfaceId)}/stream`);
24
+ const u = new URL(`${base}/v1/panes/${encodeURIComponent(opts.paneId)}/stream`);
25
25
  if (opts.since != null && opts.since !== "") {
26
26
  u.searchParams.set("since", opts.since);
27
27
  }
28
+ if (opts.subscribeRecords && opts.subscribeRecords.length > 0) {
29
+ u.searchParams.set("subscribe_records", opts.subscribeRecords);
30
+ }
31
+ if (opts.sinceRecordSeq) {
32
+ for (const [name, seq] of Object.entries(opts.sinceRecordSeq)) {
33
+ u.searchParams.set(`since_record_seq.${name}`, String(seq));
34
+ }
35
+ }
28
36
  // Token via Authorization header (Node ws supports it); the relay also
29
37
  // accepts ?token= but the header keeps it out of any URL access log.
30
38
  const socket = new WebSocket(u.toString(), {
@@ -38,7 +46,7 @@ export function openStream(opts, handlers) {
38
46
  }
39
47
  catch (e) {
40
48
  // A malformed frame must never be silently dropped — a dropped event
41
- // makes `watch --type X` hang forever. Surface it as a transport error.
49
+ // makes `watch --type X` hang forever. Pane it as a transport error.
42
50
  const snippet = text.length > MAX_FRAME_SNIPPET_LENGTH
43
51
  ? text.slice(0, MAX_FRAME_SNIPPET_LENGTH) + "…"
44
52
  : text;
@@ -59,7 +67,15 @@ export function openStream(opts, handlers) {
59
67
  return;
60
68
  }
61
69
  if ("ack" in obj) {
62
- // Ack for a frame we sent; nothing to surface by default.
70
+ // Ack for a frame we sent; nothing to pane by default.
71
+ return;
72
+ }
73
+ // #297 — record deltas. record.upsert / record.delete / record.replay.complete.
74
+ const kind = obj["kind"];
75
+ if (kind === "record.upsert" ||
76
+ kind === "record.delete" ||
77
+ kind === "record.replay.complete") {
78
+ handlers.onRecord?.(obj);
63
79
  return;
64
80
  }
65
81
  if (typeof obj["id"] === "string" && typeof obj["type"] === "string") {
package/dist/types.d.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  import type { z } from "zod";
2
- import type { createSessionSchema } from "./schemas.js";
2
+ import type { createPaneSchema } from "./schemas.js";
3
3
  export type AuthorKind = "human" | "agent" | "system";
4
4
  /** A single event envelope as emitted by the relay. */
5
5
  export interface PaneEvent {
6
6
  id: string;
7
- surface_id: string;
7
+ pane_id: string;
8
8
  author: {
9
9
  kind: AuthorKind;
10
10
  id: string;
@@ -14,7 +14,60 @@ export interface PaneEvent {
14
14
  data: unknown;
15
15
  causation_id: string | null;
16
16
  idempotency_key: string | null;
17
+ /**
18
+ * The template version this event was written under — the pane's pinned
19
+ * templateVersionId at the moment of the write. Stamped at write time and
20
+ * never rewritten, so a downstream upgrade (#267) can read old events
21
+ * under the new schema (Level 1 polymorphic render). Nullable for events
22
+ * written before #268 landed; the relay's one-shot migration backfilled
23
+ * those from the pane's current pin where possible.
24
+ */
25
+ template_version_id: string | null;
26
+ /** Denormalised integer version number for `template_version_id`. */
27
+ template_version: number | null;
17
28
  }
29
+ /**
30
+ * One record on the wire (#287). Returned by the records CRUD routes
31
+ * (#292) and by the WS record-delta messages (#294). Structurally
32
+ * identical to the relay-side SerializedRecord; mirrored here so the
33
+ * core package is self-contained.
34
+ */
35
+ export interface SerializedRecord {
36
+ id: string;
37
+ collection: string;
38
+ key: string;
39
+ data: unknown;
40
+ version: number;
41
+ seq: number;
42
+ author: {
43
+ kind: AuthorKind;
44
+ id: string;
45
+ };
46
+ created_at: string;
47
+ updated_at: string;
48
+ deleted_at: string | null;
49
+ }
50
+ /** Wire shape for a soft-deleted record on the WS channel. */
51
+ export interface DeletedRecordRef {
52
+ id: string;
53
+ key: string;
54
+ seq: number;
55
+ deleted_at: string;
56
+ }
57
+ /** Discriminated wire shape for record-state changes. */
58
+ export type RecordDeltaMessage = {
59
+ kind: "record.upsert";
60
+ collection: string;
61
+ record: SerializedRecord;
62
+ } | {
63
+ kind: "record.delete";
64
+ collection: string;
65
+ record: DeletedRecordRef;
66
+ } | {
67
+ kind: "record.replay.complete";
68
+ collection: string;
69
+ seq: number;
70
+ };
18
71
  /** The template content type. `html-ref` is rejected by the relay for now. */
19
72
  export type TemplateType = "html-inline" | "html-ref";
20
73
  /**
@@ -36,13 +89,13 @@ export interface Callback {
36
89
  secret: string;
37
90
  }
38
91
  /**
39
- * Request body for POST /v1/surfaces. Derived from `createSessionSchema` so the
92
+ * Request body for POST /v1/panes. Derived from `createPaneSchema` so the
40
93
  * runtime validator and the static type cannot drift.
41
94
  */
42
- export type CreateSessionRequest = z.infer<typeof createSessionSchema>;
43
- /** Response from POST /v1/surfaces. */
44
- export interface CreateSessionResponse {
45
- surface_id: string;
95
+ export type CreatePaneRequest = z.infer<typeof createPaneSchema>;
96
+ /** Response from POST /v1/panes. */
97
+ export interface CreatePaneResponse {
98
+ pane_id: string;
46
99
  tokens: {
47
100
  humans: string[];
48
101
  agent: string;
@@ -52,31 +105,31 @@ export interface CreateSessionResponse {
52
105
  agent_stream: string;
53
106
  };
54
107
  expires_at: string;
55
- /** The resolved tab title persisted on the surface (the agent's value, or
108
+ /** The resolved tab title persisted on the pane (the agent's value, or
56
109
  * the Template.name fallback). */
57
110
  title: string;
58
111
  }
59
- /** Response from GET /v1/surfaces/:id. */
60
- export interface SurfaceState {
61
- surface_id: string;
112
+ /** Response from GET /v1/panes/:id. */
113
+ export interface PaneState {
114
+ pane_id: string;
62
115
  status: string;
63
- /** The template version this surface is pinned to. */
116
+ /** The template version this pane is pinned to. */
64
117
  template_id: string;
65
118
  template_version_id: string;
66
119
  template_version: number;
67
- /** The tab title this surface was created with (frozen for its lifetime). */
120
+ /** The tab title this pane was created with (frozen for its lifetime). */
68
121
  title: string;
69
122
  metadata: Record<string, unknown> | null;
70
123
  input_data: Record<string, unknown> | null;
71
124
  created_at: string;
72
125
  expires_at: string;
73
126
  }
74
- /** Response from GET /v1/surfaces/:id/events. */
127
+ /** Response from GET /v1/panes/:id/events. */
75
128
  export interface EventsPage {
76
129
  events: PaneEvent[];
77
130
  next_cursor: string | null;
78
131
  }
79
- /** A non-secret summary of one participant on a surface — safe to list. */
132
+ /** A non-secret summary of one participant on a pane — safe to list. */
80
133
  export interface ParticipantSummary {
81
134
  /** The revoke handle (Participant.id). */
82
135
  participant_id: string;
@@ -89,9 +142,9 @@ export interface ParticipantSummary {
89
142
  /** ISO timestamp the participant was revoked; null while still active. */
90
143
  revoked_at: string | null;
91
144
  }
92
- /** A row in the GET /v1/surfaces list response (no secrets, lean). */
93
- export interface SurfaceSummary {
94
- surface_id: string;
145
+ /** A row in the GET /v1/panes list response (no secrets, lean). */
146
+ export interface PaneSummary {
147
+ pane_id: string;
95
148
  /** Tab title (required column; agent input or Template.name fallback). */
96
149
  title: string;
97
150
  /** Effective status — respects expiresAt projection (the column may say
@@ -102,30 +155,50 @@ export interface SurfaceSummary {
102
155
  template_version_id: string;
103
156
  template_version: number;
104
157
  /** Count of active (non-revoked) human participants. The full participant
105
- * array is intentionally NOT inlined here — agents with many surfaces
158
+ * array is intentionally NOT inlined here — agents with many panes
106
159
  * would pay the bandwidth on every list call. Fetch
107
- * `GET /v1/surfaces/:id/participants` when you need the rows. */
160
+ * `GET /v1/panes/:id/participants` when you need the rows. */
108
161
  active_human_participants: number;
109
162
  created_at: string;
110
163
  expires_at: string;
111
- /** Whether the surface has a webhook callback configured (URL is NOT
164
+ /** Whether the pane has a webhook callback configured (URL is NOT
112
165
  * returned — it may carry a secret in the path). */
113
166
  has_callback: boolean;
114
167
  }
115
- /** Response from GET /v1/surfaces/:id/participants — every participant on
116
- * one surface (active and revoked). Bounded by MAX_PARTICIPANTS_PER_SESSION
168
+ /** Response from GET /v1/panes/:id/participants — every participant on
169
+ * one pane (active and revoked). Bounded by MAX_PARTICIPANTS_PER_PANE
117
170
  * on the relay so no pagination is needed. */
118
171
  export interface ParticipantsList {
119
- surface_id: string;
172
+ pane_id: string;
120
173
  items: ParticipantSummary[];
121
174
  }
122
- /** Response from GET /v1/surfaces. */
123
- export interface SurfacesPage {
124
- items: SurfaceSummary[];
175
+ /** Response from GET /v1/panes. */
176
+ export interface PanesPage {
177
+ items: PaneSummary[];
125
178
  /** Opaque cursor for the next page; null when no more rows. */
126
179
  next_cursor: string | null;
127
180
  }
128
- /** Response from POST /v1/surfaces/:id/participants one-shot, includes the
181
+ /** A trashed pane in the GET /v1/trash response (#306). */
182
+ export interface TrashedPaneEntry {
183
+ pane_id: string;
184
+ title: string;
185
+ agent_name: string;
186
+ /** ISO-8601 timestamp the pane was soft-deleted. Always present here. */
187
+ deleted_at: string;
188
+ }
189
+ /** A trashed template in the GET /v1/trash response (#306). */
190
+ export interface TrashedTemplateEntry {
191
+ template_id: string;
192
+ name: string | null;
193
+ slug: string | null;
194
+ deleted_at: string;
195
+ }
196
+ /** Response from GET /v1/trash (#306). */
197
+ export interface TrashListResponse {
198
+ panes: TrashedPaneEntry[];
199
+ templates: TrashedTemplateEntry[];
200
+ }
201
+ /** Response from POST /v1/panes/:id/participants — one-shot, includes the
129
202
  * plaintext token exactly once. The relay stores only the hash. */
130
203
  export interface MintParticipantResponse {
131
204
  participant_id: string;
@@ -220,7 +293,7 @@ export interface FeedbackRecord {
220
293
  id: string;
221
294
  type: FeedbackType;
222
295
  message: string;
223
- surface_id: string | null;
296
+ pane_id: string | null;
224
297
  created_at: string;
225
298
  }
226
299
  /** Response from GET /v1/feedback — page of the calling agent's own submissions. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@paneui/core",
3
- "version": "0.0.9",
3
+ "version": "0.0.10",
4
4
  "description": "Pane relay client: typed HTTP + WebSocket operations against a Pane relay. Framework-free.",
5
5
  "license": "MIT",
6
6
  "type": "module",