@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/README.md +2 -2
- package/dist/client.d.ts +253 -40
- package/dist/client.js +304 -45
- package/dist/icons.d.ts +24 -0
- package/dist/icons.js +99 -0
- package/dist/index.d.ts +6 -4
- package/dist/index.js +2 -1
- package/dist/register.js +1 -1
- package/dist/schemas.d.ts +29 -8
- package/dist/schemas.js +85 -22
- package/dist/stream.d.ts +25 -6
- package/dist/stream.js +21 -5
- package/dist/types.d.ts +102 -29
- package/package.json +1 -1
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,
|
|
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
|
|
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
|
|
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
|
-
|
|
98
|
+
pane_id: z.ZodOptional<z.ZodString>;
|
|
86
99
|
}, z.core.$strip>;
|
|
87
|
-
/** @deprecated use `
|
|
88
|
-
export type
|
|
89
|
-
export declare const
|
|
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
|
|
95
|
-
export declare const
|
|
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
|
|
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/
|
|
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
|
|
40
|
+
// views — the pane then accepts no page/agent events).
|
|
27
41
|
event_schema: z.unknown().optional(),
|
|
28
|
-
// Optional: when present, the
|
|
29
|
-
// this JSON Schema before the
|
|
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
|
|
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/
|
|
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
|
|
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
|
|
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
|
|
57
|
-
template:
|
|
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
|
|
65
|
-
//
|
|
66
|
-
//
|
|
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
|
|
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
|
-
|
|
167
|
+
pane_id: z.string().min(1).optional(),
|
|
116
168
|
});
|
|
117
|
-
// GET /v1/
|
|
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
|
|
121
|
-
export const
|
|
122
|
-
status:
|
|
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/
|
|
128
|
-
// existing
|
|
129
|
-
// minted at
|
|
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
|
-
/**
|
|
7
|
-
|
|
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
|
|
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
|
|
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/
|
|
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
|
|
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/
|
|
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.
|
|
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
|
|
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 {
|
|
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
|
-
|
|
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/
|
|
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
|
|
43
|
-
/** Response from POST /v1/
|
|
44
|
-
export interface
|
|
45
|
-
|
|
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
|
|
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/
|
|
60
|
-
export interface
|
|
61
|
-
|
|
112
|
+
/** Response from GET /v1/panes/:id. */
|
|
113
|
+
export interface PaneState {
|
|
114
|
+
pane_id: string;
|
|
62
115
|
status: string;
|
|
63
|
-
/** The template version this
|
|
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
|
|
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/
|
|
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
|
|
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/
|
|
93
|
-
export interface
|
|
94
|
-
|
|
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
|
|
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/
|
|
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
|
|
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/
|
|
116
|
-
* one
|
|
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
|
-
|
|
172
|
+
pane_id: string;
|
|
120
173
|
items: ParticipantSummary[];
|
|
121
174
|
}
|
|
122
|
-
/** Response from GET /v1/
|
|
123
|
-
export interface
|
|
124
|
-
items:
|
|
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
|
-
/**
|
|
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
|
-
|
|
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. */
|