@sigrun/sigrun 0.1.4 → 0.1.6
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/CHANGELOG.md +61 -0
- package/README.md +24 -2
- package/THIRD_PARTY_NOTICES.md +23 -0
- package/dist/blocks/builders.js +125 -0
- package/dist/blocks/images.js +57 -0
- package/dist/blocks/index.js +20 -0
- package/dist/blocks/limits.js +26 -0
- package/dist/blocks/placements.js +99 -0
- package/dist/blocks/schema.js +559 -0
- package/dist/blocks/validate.js +602 -0
- package/dist/config.js +31 -0
- package/dist/extension.d.ts +476 -9
- package/dist/harness/agent-env.js +62 -0
- package/dist/harness/boot.js +8 -1
- package/dist/harness/event-bus.js +33 -0
- package/dist/harness/identity/users.js +3 -2
- package/dist/harness/loader.js +369 -13
- package/dist/harness/packages.js +71 -2
- package/dist/harness/seam-host.js +69 -2
- package/dist/harness/settings.js +5 -0
- package/dist/harness/workspace.js +6 -4
- package/dist/jobs/fire.js +11 -0
- package/dist/jobs/spec.js +19 -0
- package/dist/serve.js +29 -39
- package/dist/server/auth/pipeline.js +16 -1
- package/dist/server/conversation-ops.js +482 -10
- package/dist/server/gateway.js +122 -0
- package/dist/server/helpers.js +26 -0
- package/dist/server/host-bridge.js +141 -4
- package/dist/server/index.js +27 -0
- package/dist/server/notifications.js +6 -2
- package/dist/server/routes/conversations.js +37 -93
- package/dist/server/routes/events.js +39 -0
- package/dist/server/routes/ext-assets.js +84 -0
- package/dist/server/routes/extensions.js +31 -1
- package/dist/server/routes/files.js +38 -83
- package/dist/server/routes/meta.js +35 -1
- package/dist/server/routes/providers.js +5 -21
- package/dist/server/routes/submissions.js +36 -111
- package/dist/server/routes/surfaces.js +387 -0
- package/dist/server/routes/users.js +6 -1
- package/dist/server/session-meta.js +12 -6
- package/dist/server/shutdown.js +85 -0
- package/dist/server/surfaces/access-cache.js +43 -0
- package/dist/server/surfaces/dedup.js +67 -0
- package/dist/server/surfaces/instance.js +42 -0
- package/dist/server/surfaces/mime.js +73 -0
- package/dist/server/surfaces/rate-limit.js +63 -0
- package/dist/server/surfaces/service.js +493 -0
- package/dist/server/surfaces/watchers.js +94 -0
- package/node_modules/@sigrun/shared/CHANGELOG.md +9 -0
- package/node_modules/@sigrun/shared/dist/blocks/format.d.ts +28 -0
- package/node_modules/@sigrun/shared/dist/blocks/format.js +199 -0
- package/node_modules/@sigrun/shared/dist/blocks/icons.d.ts +12 -0
- package/node_modules/@sigrun/shared/dist/blocks/icons.js +64 -0
- package/node_modules/@sigrun/shared/dist/blocks/index.d.ts +25 -0
- package/node_modules/@sigrun/shared/dist/blocks/index.js +21 -0
- package/node_modules/@sigrun/shared/dist/blocks/instance.d.ts +29 -0
- package/node_modules/@sigrun/shared/dist/blocks/instance.js +51 -0
- package/node_modules/@sigrun/shared/dist/blocks/lttb.d.ts +16 -0
- package/node_modules/@sigrun/shared/dist/blocks/lttb.js +67 -0
- package/node_modules/@sigrun/shared/dist/blocks/ops.d.ts +44 -0
- package/node_modules/@sigrun/shared/dist/blocks/ops.js +294 -0
- package/node_modules/@sigrun/shared/dist/blocks/text.d.ts +43 -0
- package/node_modules/@sigrun/shared/dist/blocks/text.js +790 -0
- package/node_modules/@sigrun/shared/dist/blocks/transport.d.ts +77 -0
- package/node_modules/@sigrun/shared/dist/blocks/transport.js +11 -0
- package/node_modules/@sigrun/shared/dist/blocks/types.d.ts +541 -0
- package/node_modules/@sigrun/shared/dist/blocks/types.js +17 -0
- package/node_modules/@sigrun/shared/dist/jobs.d.ts +9 -0
- package/node_modules/@sigrun/shared/dist/live.d.ts +71 -0
- package/node_modules/@sigrun/shared/dist/live.js +82 -0
- package/node_modules/@sigrun/shared/dist/session-meta.d.ts +19 -3
- package/node_modules/@sigrun/shared/dist/settings.d.ts +7 -0
- package/node_modules/@sigrun/shared/dist/views.d.ts +81 -3
- package/node_modules/@sigrun/shared/package.json +10 -2
- package/package.json +11 -5
- package/webui/dist/assets/index-CiYInlLx.css +1 -0
- package/webui/dist/assets/index-JDfOQV9z.js +52 -0
- package/webui/dist/index.html +2 -2
- package/webui/dist/assets/index-BiSMljer.css +0 -1
- package/webui/dist/assets/index-hVLvsizV.js +0 -50
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,67 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.1.6] - 2026-10-24
|
|
11
|
+
### Added
|
|
12
|
+
- Extension surfaces: typed block rendering, actions and uploads, live
|
|
13
|
+
transports, per-principal access control, and web/TUI surface views.
|
|
14
|
+
- Web and TUI block renderers: content, charts, collections, forms,
|
|
15
|
+
navigation, live updates, and extension-provided views.
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
- The container image preserves the core, webui, shared, and TUI build
|
|
19
|
+
output paths and compiles the TUI workspace. The published 0.1.5 image
|
|
20
|
+
flattened core `dist/` into `/app`, so `node dist/index.js` could not
|
|
21
|
+
start. CI now smoke-checks the built image's runtime layout.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
- `@sigrun/shared` 0.1.2: surface and block-instance wire types.
|
|
25
|
+
- `@sigrun/tui` 0.1.1: extension surfaces and the full block renderer.
|
|
26
|
+
- Fastify 5.12.7 and `@fastify/static` 10.1.7 address the server's
|
|
27
|
+
header-validation and static-file path/authorization advisories; the
|
|
28
|
+
patched `fast-uri` transitive releases address its URI-normalization
|
|
29
|
+
advisories. `npm audit` reports zero vulnerabilities.
|
|
30
|
+
|
|
31
|
+
### Security
|
|
32
|
+
- Agent shells no longer inherit sigrun's environment. pi-durable's bash
|
|
33
|
+
tool ran every command with the server's full environment, so an
|
|
34
|
+
agent's `env` printed `SIGRUN_API_TOKEN`, `SIGRUN_PROXY_SECRET` and
|
|
35
|
+
provider keys. Every command now gets an allowlist (`PATH`, `HOME`,
|
|
36
|
+
`TMPDIR`, `LANG`, `LC_*`, `TZ`, `TERM`, `USER`, `LOGNAME`, `SHELL`,
|
|
37
|
+
`NPM_CONFIG_CACHE`, `SSH_AUTH_SOCK`) plus whatever the calling tool
|
|
38
|
+
sets. `NODE_ENV=production` from the image no longer reaches agent
|
|
39
|
+
shells, so an agent's `npm install` installs devDependencies again.
|
|
40
|
+
The bundled MCP extension's stdio servers get the same allowlist.
|
|
41
|
+
|
|
42
|
+
## [0.1.5] - 2026-10-23
|
|
43
|
+
### Added
|
|
44
|
+
- The image + chart release pipeline: multi-arch (amd64/arm64) container
|
|
45
|
+
image on `ghcr.io/sigrun-ai/sigrun` and the Helm chart on GHCR's OCI
|
|
46
|
+
registry (`charts/sigrun/sigrun`), built and published by GitHub Actions
|
|
47
|
+
on `v*` tags. CI builds and lints both on every PR and main push.
|
|
48
|
+
- The chart manages the extension fleet declaratively: a `sigrun:` values
|
|
49
|
+
block seeds `settings.json` (fleet list + chart-owned keys re-asserted
|
|
50
|
+
every boot, per-slug extension settings seed-once), passes secret
|
|
51
|
+
references through envFrom (never values), and installs deploy-time
|
|
52
|
+
tools (kubectl/helm/talosctl/flux/k9s) hash-cached onto persistent
|
|
53
|
+
storage via an initContainer.
|
|
54
|
+
- Deploy hardening: non-root image with a privilege-drop entrypoint (uid
|
|
55
|
+
1000 default, overridable), agent userland baked in (ripgrep, fd, jq,
|
|
56
|
+
git, sqlite3), hardened compose defaults (read-only rootfs, caps
|
|
57
|
+
dropped, init, no-new-privileges).
|
|
58
|
+
- Block-set foundation (`@sigrun/shared/blocks`): the canonical block
|
|
59
|
+
tree wire types, ops, text rendering and transport shared by server and
|
|
60
|
+
clients.
|
|
61
|
+
- Gateway core seams: extension `channels` contribution field, the
|
|
62
|
+
gateway engine field with lifecycle + shutdown ordering, host-surface
|
|
63
|
+
conversation ops, upload/readFile/models/commands, interaction answer
|
|
64
|
+
attribution (`interactionSettled`), group-derived admin persisted for
|
|
65
|
+
host-side checks, and deliver-targets on prompt jobs
|
|
66
|
+
(`@sigrun/shared/live` narrows the live-doc wire).
|
|
67
|
+
|
|
68
|
+
### Changed
|
|
69
|
+
- `@sigrun/shared` 0.1.1: new `./live` and `./blocks` export subpaths
|
|
70
|
+
(all 0.1.0 subpaths unchanged).
|
|
10
71
|
## [0.1.4] - 2026-10-22
|
|
11
72
|
### Added
|
|
12
73
|
- The version is visible where operators look: the boot log line (`sigrun
|
package/README.md
CHANGED
|
@@ -286,6 +286,20 @@ configurable) when the request comes from a peer inside
|
|
|
286
286
|
proxy and nothing else: every peer in `trustedProxies` can forge any
|
|
287
287
|
identity, including an admin.
|
|
288
288
|
|
|
289
|
+
**Group-derived admin (persisted).** `server.auth.adminGroups` lists
|
|
290
|
+
the proxy groups (Authelia's `Remote-Groups`) whose members act as
|
|
291
|
+
admins. The request pipeline computes the grant per request and
|
|
292
|
+
writes it through to the user's directory record as `groupAdmin`
|
|
293
|
+
whenever it differs — so host-side surfaces with no request headers
|
|
294
|
+
(chat commands, channel adapters) see effective admin (`admin ||
|
|
295
|
+
groupAdmin`), while `/v1/users` keeps `admin` as the stored flag the
|
|
296
|
+
users-admin UI toggles and shows `groupAdmin` alongside. The grant
|
|
297
|
+
moves only on proxy-authenticated requests while `adminGroups` is
|
|
298
|
+
non-empty: removal from the admin group takes effect at that user's
|
|
299
|
+
next web request. Group-derived admin never counts toward the
|
|
300
|
+
last-admin guard — demoting or deleting the only stored admin is
|
|
301
|
+
still refused.
|
|
302
|
+
|
|
289
303
|
**Onboarding (the first claim).** An unclaimed server serves
|
|
290
304
|
`GET /v1/onboarding` + `POST /v1/onboarding/claim`. On a loopback bind
|
|
291
305
|
or through a trusted proxy identity the claim needs no code; otherwise
|
|
@@ -640,8 +654,16 @@ is read-only for them (`PATCH`/`DELETE` return `409 job_declared`).
|
|
|
640
654
|
A `prompt` action submits its prompt as a conversation — `"new"`
|
|
641
655
|
creates one per fire (ownerless, `source: "cron"`), a fixed id submits
|
|
642
656
|
into that conversation (the job's `model`/`thinking` apply only to
|
|
643
|
-
`"new"`). A `
|
|
644
|
-
|
|
657
|
+
`"new"`). A `prompt` action may also carry `deliver`: an array of
|
|
658
|
+
1–8 gateway delivery targets (each 1–256 chars), valid only with
|
|
659
|
+
`conversation: "new"` — the run's answer is delivered to those chats
|
|
660
|
+
by the loaded gateway engine. The targets are stamped onto the run
|
|
661
|
+
conversation's meta alongside the owning `jobId` (both server-owned,
|
|
662
|
+
never PATCHable); with no engine loaded each fire logs one
|
|
663
|
+
`deliver targets ignored: no gateway` warning, and editing the targets
|
|
664
|
+
is a job revision like any other spec change. A `task` action creates
|
|
665
|
+
a pi-durable task owned by the schedule task (deleting the job
|
|
666
|
+
cascades): `{"kind": "task",
|
|
645
667
|
"task": "<registered task name>", "input": …}`.
|
|
646
668
|
|
|
647
669
|
**Runs:** a cron run is an ORDINARY CONTINUABLE CONVERSATION — open the
|
package/THIRD_PARTY_NOTICES.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Third-party notices
|
|
2
2
|
|
|
3
|
+
## Lucide
|
|
4
|
+
|
|
5
|
+
- <https://lucide.dev>
|
|
6
|
+
- The webui's block-set icons (`webui/src/blocks/icons.tsx`) are inline
|
|
7
|
+
SVGs whose stroke geometry is copied from Lucide (v0.577.0), resized
|
|
8
|
+
and coloured by the block renderer.
|
|
9
|
+
|
|
10
|
+
ISC License
|
|
11
|
+
|
|
12
|
+
Copyright (c) 2020, Lucide Contributors
|
|
13
|
+
|
|
14
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
15
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
16
|
+
copyright notice and this permission notice appear in all copies.
|
|
17
|
+
|
|
18
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
19
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
20
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
|
21
|
+
SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
22
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
23
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
|
|
24
|
+
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
25
|
+
|
|
3
26
|
## hermes-webui
|
|
4
27
|
|
|
5
28
|
- <https://github.com/nesquena/hermes-webui>
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The typed builders (spec §14.1): one function per block and field,
|
|
3
|
+
* plus `bind(pointer)` and `tree(root)`. Builders return plain JSON —
|
|
4
|
+
* there is no runtime besides them.
|
|
5
|
+
*
|
|
6
|
+
* Each builder takes its block's props object and returns the block
|
|
7
|
+
* with the `type` literal stamped in. The literal is spread LAST, so a
|
|
8
|
+
* stray `type` inside a JavaScript caller's props object can never
|
|
9
|
+
* override it; TypeScript callers cannot pass one at all (`Props` omits
|
|
10
|
+
* it). The tree BLOCK builder is exported as `treeBlock`, keeping the
|
|
11
|
+
* `tree` name for the version-stamping root wrapper.
|
|
12
|
+
*
|
|
13
|
+
* `tree(root)` stamps {@link BLOCK_SET_VERSION} from the shared wire
|
|
14
|
+
* package — the version the SERVER validates against, which is why
|
|
15
|
+
* extensions import these builders from `@sigrun/sigrun/blocks` (the
|
|
16
|
+
* host-linked copy, spec §14.1) instead of bundling their own.
|
|
17
|
+
*/
|
|
18
|
+
import { BLOCK_SET_VERSION } from "@sigrun/shared/blocks";
|
|
19
|
+
/** `bind("/cards")` → `{ $: "/cards" }` — a pointer into the surface's data model (§4.2). */
|
|
20
|
+
export function bind(pointer) {
|
|
21
|
+
return { $: pointer };
|
|
22
|
+
}
|
|
23
|
+
/** Wrap a root block with the server's block-set version (§4.4). */
|
|
24
|
+
export function tree(root) {
|
|
25
|
+
return { v: BLOCK_SET_VERSION, root };
|
|
26
|
+
}
|
|
27
|
+
// ─── Shell (§5.1) ─────────────────────────────────────────────────────────
|
|
28
|
+
export function page(props) {
|
|
29
|
+
return { ...props, type: "page" };
|
|
30
|
+
}
|
|
31
|
+
// ─── Layout (§5.2) ────────────────────────────────────────────────────────
|
|
32
|
+
export function stack(props) {
|
|
33
|
+
return { ...props, type: "stack" };
|
|
34
|
+
}
|
|
35
|
+
export function columns(props) {
|
|
36
|
+
return { ...props, type: "columns" };
|
|
37
|
+
}
|
|
38
|
+
export function grid(props) {
|
|
39
|
+
return { ...props, type: "grid" };
|
|
40
|
+
}
|
|
41
|
+
export function section(props) {
|
|
42
|
+
return { ...props, type: "section" };
|
|
43
|
+
}
|
|
44
|
+
// ─── Content (§5.3) ───────────────────────────────────────────────────────
|
|
45
|
+
export function markdown(props) {
|
|
46
|
+
return { ...props, type: "markdown" };
|
|
47
|
+
}
|
|
48
|
+
export function fields(props) {
|
|
49
|
+
return { ...props, type: "fields" };
|
|
50
|
+
}
|
|
51
|
+
export function stat(props) {
|
|
52
|
+
return { ...props, type: "stat" };
|
|
53
|
+
}
|
|
54
|
+
export function callout(props) {
|
|
55
|
+
return { ...props, type: "callout" };
|
|
56
|
+
}
|
|
57
|
+
export function image(props) {
|
|
58
|
+
return { ...props, type: "image" };
|
|
59
|
+
}
|
|
60
|
+
// ─── Chart (§5.4) ─────────────────────────────────────────────────────────
|
|
61
|
+
export function chart(props) {
|
|
62
|
+
return { ...props, type: "chart" };
|
|
63
|
+
}
|
|
64
|
+
// ─── Meter / log / tree / diff / details (§5.3) ───────────────────────────
|
|
65
|
+
export function meter(props) {
|
|
66
|
+
return { ...props, type: "meter" };
|
|
67
|
+
}
|
|
68
|
+
export function log(props) {
|
|
69
|
+
return { ...props, type: "log" };
|
|
70
|
+
}
|
|
71
|
+
/** The tree BLOCK builder (§5.3) — `tree` is the root wrapper. */
|
|
72
|
+
export function treeBlock(props) {
|
|
73
|
+
return { ...props, type: "tree" };
|
|
74
|
+
}
|
|
75
|
+
export function diff(props) {
|
|
76
|
+
return { ...props, type: "diff" };
|
|
77
|
+
}
|
|
78
|
+
export function details(props) {
|
|
79
|
+
return { ...props, type: "details" };
|
|
80
|
+
}
|
|
81
|
+
// ─── Collection (§5.5) and the card template (§5.5.1) ────────────────────
|
|
82
|
+
export function collection(props) {
|
|
83
|
+
return { ...props, type: "collection" };
|
|
84
|
+
}
|
|
85
|
+
/** The collection item template (§5.5.1) — a slot bundle, not a block. */
|
|
86
|
+
export function card(props) {
|
|
87
|
+
return { ...props };
|
|
88
|
+
}
|
|
89
|
+
// ─── Form and fields (§5.6) ───────────────────────────────────────────────
|
|
90
|
+
export function form(props) {
|
|
91
|
+
return { ...props, type: "form" };
|
|
92
|
+
}
|
|
93
|
+
export function text(props) {
|
|
94
|
+
return { ...props, type: "text" };
|
|
95
|
+
}
|
|
96
|
+
export function longtext(props) {
|
|
97
|
+
return { ...props, type: "longtext" };
|
|
98
|
+
}
|
|
99
|
+
export function number(props) {
|
|
100
|
+
return { ...props, type: "number" };
|
|
101
|
+
}
|
|
102
|
+
export function toggle(props) {
|
|
103
|
+
return { ...props, type: "toggle" };
|
|
104
|
+
}
|
|
105
|
+
export function choice(props) {
|
|
106
|
+
return { ...props, type: "choice" };
|
|
107
|
+
}
|
|
108
|
+
export function date(props) {
|
|
109
|
+
return { ...props, type: "date" };
|
|
110
|
+
}
|
|
111
|
+
export function datetime(props) {
|
|
112
|
+
return { ...props, type: "datetime" };
|
|
113
|
+
}
|
|
114
|
+
export function daterange(props) {
|
|
115
|
+
return { ...props, type: "daterange" };
|
|
116
|
+
}
|
|
117
|
+
export function secret(props) {
|
|
118
|
+
return { ...props, type: "secret" };
|
|
119
|
+
}
|
|
120
|
+
export function file(props) {
|
|
121
|
+
return { ...props, type: "file" };
|
|
122
|
+
}
|
|
123
|
+
export function list(props) {
|
|
124
|
+
return { ...props, type: "list" };
|
|
125
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image source rules (spec §11.1): `asset:` paths, `data:` URLs of the
|
|
3
|
+
* four accepted MIME types (capped at 192 KiB of base64 text), and
|
|
4
|
+
* `https:` URLs. SVG and AVIF are never accepted (script payload; no
|
|
5
|
+
* TUI decoder), and `http:` URLs are rejected. The proxy that rewrites
|
|
6
|
+
* and fetches is U7; this module only validates.
|
|
7
|
+
*/
|
|
8
|
+
/** Max base64 characters in a `data:` image URL (spec §11.1: 192 KiB). */
|
|
9
|
+
const MAX_DATA_URL_CHARS = 192 * 1024;
|
|
10
|
+
/** MIME types a `data:` URL may carry. */
|
|
11
|
+
const DATA_MIME_TYPES = new Set(["image/png", "image/jpeg", "image/gif", "image/webp"]);
|
|
12
|
+
/**
|
|
13
|
+
* Check one image `src` against the §11.1 source rules. Bindings
|
|
14
|
+
* (`{$: …}` objects) are not checked here — their resolved string is,
|
|
15
|
+
* where the data model holds one (the data-size limit already covers
|
|
16
|
+
* the byte cost).
|
|
17
|
+
*/
|
|
18
|
+
export function checkImageSource(src) {
|
|
19
|
+
if (src.startsWith("asset:")) {
|
|
20
|
+
const path = src.slice("asset:".length);
|
|
21
|
+
if (path.length === 0)
|
|
22
|
+
return { ok: false, message: "asset: path is empty" };
|
|
23
|
+
return { ok: true, kind: "asset" };
|
|
24
|
+
}
|
|
25
|
+
if (src.startsWith("data:")) {
|
|
26
|
+
const comma = src.indexOf(",");
|
|
27
|
+
if (comma === -1)
|
|
28
|
+
return { ok: false, message: "data: URL has no payload" };
|
|
29
|
+
const meta = src.slice("data:".length, comma);
|
|
30
|
+
const payload = src.slice(comma + 1);
|
|
31
|
+
const [type, ...params] = meta.split(";");
|
|
32
|
+
const normalized = type.toLowerCase();
|
|
33
|
+
if (!DATA_MIME_TYPES.has(normalized)) {
|
|
34
|
+
return {
|
|
35
|
+
ok: false,
|
|
36
|
+
message: `data: URL type ${type || "(empty)"} is not one of png, jpeg, gif, webp (svg and avif are never accepted)`,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
if (!params.includes("base64")) {
|
|
40
|
+
return { ok: false, message: "data: URL must be base64" };
|
|
41
|
+
}
|
|
42
|
+
if (payload.length > MAX_DATA_URL_CHARS) {
|
|
43
|
+
return {
|
|
44
|
+
ok: false,
|
|
45
|
+
message: `data: URL payload is ${payload.length} base64 characters, over the 192 KiB cap`,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
return { ok: true, kind: "data" };
|
|
49
|
+
}
|
|
50
|
+
if (/^https:\/\//.test(src)) {
|
|
51
|
+
return { ok: true, kind: "https" };
|
|
52
|
+
}
|
|
53
|
+
if (/^http:\/\//.test(src)) {
|
|
54
|
+
return { ok: false, message: "http: image URLs are rejected; use https" };
|
|
55
|
+
}
|
|
56
|
+
return { ok: false, message: "image src must be asset:, data: (png/jpeg/gif/webp) or https:" };
|
|
57
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@sigrun/sigrun/blocks` — the extension authoring surface for the
|
|
3
|
+
* block set (spec §14.1): the typed builders, the wire types, and
|
|
4
|
+
* `validateTree` (§14.2) so an extension can unit-test its trees
|
|
5
|
+
* against the same validation the server runs.
|
|
6
|
+
*
|
|
7
|
+
* Extensions import this subpath, never a bundled copy: the package is
|
|
8
|
+
* a `peerDependency: "*"` (see docs/extending.md) and
|
|
9
|
+
* `@sigrun/sigrun` is a HOST RUNTIME MODULE (src/harness/packages.ts)
|
|
10
|
+
* — the host links its own running copy into every extension packages
|
|
11
|
+
* dir, so `tree(...)` always stamps the server's
|
|
12
|
+
* {@link BLOCK_SET_VERSION}. The `exports` map points here at
|
|
13
|
+
* `dist/blocks/index.js` (never `src/`), which is what the built
|
|
14
|
+
* server ships.
|
|
15
|
+
*/
|
|
16
|
+
export * from "./builders.js";
|
|
17
|
+
export { validateTree, validateData, VALID_PLACEMENTS, PLACEMENT_RULES, LIMITS, } from "./validate.js";
|
|
18
|
+
// The wire contract (§4/§5/§6.1) and the block-set version constant,
|
|
19
|
+
// verbatim from the shared leaf — one import site for extension code.
|
|
20
|
+
export { BLOCK_SET_VERSION } from "@sigrun/shared/blocks";
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The per-placement size limits of spec §4.5: max blocks per tree, and
|
|
3
|
+
* max UTF-8 bytes of the serialized JSON for the tree and the data.
|
|
4
|
+
*
|
|
5
|
+
* Over a limit the server rejects with `413 surface_too_large` naming
|
|
6
|
+
* the limit; `validateTree`/`validateData` surface the same error
|
|
7
|
+
* strings. A collection counts as one block however many items it
|
|
8
|
+
* binds, and a `data:` image URL counts toward whichever of the tree or
|
|
9
|
+
* the data holds it (the sizes are measured over the serialized JSON,
|
|
10
|
+
* so both hold by construction).
|
|
11
|
+
*/
|
|
12
|
+
const KiB = 1024;
|
|
13
|
+
const MiB = 1024 * 1024;
|
|
14
|
+
/** Spec §4.5, one row per placement. */
|
|
15
|
+
export const LIMITS = {
|
|
16
|
+
chat: { blocks: 200, treeBytes: 256 * KiB, dataBytes: 256 * KiB },
|
|
17
|
+
interaction: { blocks: 100, treeBytes: 64 * KiB, dataBytes: 64 * KiB },
|
|
18
|
+
ambient: { blocks: 100, treeBytes: 64 * KiB, dataBytes: 64 * KiB },
|
|
19
|
+
view: { blocks: 2000, treeBytes: MiB, dataBytes: 4 * MiB },
|
|
20
|
+
panel: { blocks: 2000, treeBytes: MiB, dataBytes: 4 * MiB },
|
|
21
|
+
settings: { blocks: 2000, treeBytes: MiB, dataBytes: 4 * MiB },
|
|
22
|
+
};
|
|
23
|
+
/** UTF-8 byte length of a value's serialized JSON (spec §4.5 sizing). */
|
|
24
|
+
export function jsonBytes(value) {
|
|
25
|
+
return Buffer.byteLength(JSON.stringify(value), "utf8");
|
|
26
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The placement rules of spec §5.7, as data: which blocks, which
|
|
3
|
+
* collection displays and which form fields each placement allows,
|
|
4
|
+
* plus the root-block and `page.sidebar` constraints the table carries.
|
|
5
|
+
*
|
|
6
|
+
* `validateTree` walks a tree against these rules; the tests pin the
|
|
7
|
+
* table's literal cells so this data cannot drift from the spec.
|
|
8
|
+
*/
|
|
9
|
+
const ALL_BLOCKS = [
|
|
10
|
+
"page",
|
|
11
|
+
"stack",
|
|
12
|
+
"columns",
|
|
13
|
+
"grid",
|
|
14
|
+
"section",
|
|
15
|
+
"markdown",
|
|
16
|
+
"fields",
|
|
17
|
+
"stat",
|
|
18
|
+
"callout",
|
|
19
|
+
"image",
|
|
20
|
+
"chart",
|
|
21
|
+
"meter",
|
|
22
|
+
"log",
|
|
23
|
+
"tree",
|
|
24
|
+
"diff",
|
|
25
|
+
"details",
|
|
26
|
+
"collection",
|
|
27
|
+
"form",
|
|
28
|
+
];
|
|
29
|
+
const ALL_FIELDS = [
|
|
30
|
+
"text",
|
|
31
|
+
"longtext",
|
|
32
|
+
"number",
|
|
33
|
+
"toggle",
|
|
34
|
+
"choice",
|
|
35
|
+
"date",
|
|
36
|
+
"datetime",
|
|
37
|
+
"daterange",
|
|
38
|
+
"secret",
|
|
39
|
+
"file",
|
|
40
|
+
"list",
|
|
41
|
+
];
|
|
42
|
+
function rule(init) {
|
|
43
|
+
return {
|
|
44
|
+
blocks: new Set(init.blocks),
|
|
45
|
+
rootBlock: init.rootBlock,
|
|
46
|
+
pageSidebar: init.pageSidebar ?? true,
|
|
47
|
+
collectionDisplays: init.collectionDisplays,
|
|
48
|
+
fields: new Set(init.fields),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
const CONTENT = ["markdown", "fields", "callout", "diff", "details"];
|
|
52
|
+
const STAT_METER_LOG = ["stat", "meter", "log"];
|
|
53
|
+
const CHART_IMAGE_TREE = ["chart", "image", "tree"];
|
|
54
|
+
const ALL_COLLECTION_DISPLAYS = ["list", "table", "lanes", "grid", "calendar"];
|
|
55
|
+
/** Spec §5.7, one row per placement. */
|
|
56
|
+
export const PLACEMENT_RULES = {
|
|
57
|
+
// view: everything; a required page root.
|
|
58
|
+
view: rule({
|
|
59
|
+
blocks: ALL_BLOCKS,
|
|
60
|
+
rootBlock: "page",
|
|
61
|
+
pageSidebar: true,
|
|
62
|
+
fields: ALL_FIELDS,
|
|
63
|
+
}),
|
|
64
|
+
// panel: everything but a sidebar on the page root.
|
|
65
|
+
panel: rule({
|
|
66
|
+
blocks: ALL_BLOCKS,
|
|
67
|
+
rootBlock: "page",
|
|
68
|
+
pageSidebar: false,
|
|
69
|
+
fields: ALL_FIELDS,
|
|
70
|
+
}),
|
|
71
|
+
// settings: section + content + stat-free; forms only at the root;
|
|
72
|
+
// collections limited to list/table; all fields but file.
|
|
73
|
+
settings: rule({
|
|
74
|
+
blocks: ["section", ...CONTENT, "collection", "form"],
|
|
75
|
+
rootBlock: "form",
|
|
76
|
+
collectionDisplays: ["list", "table"],
|
|
77
|
+
fields: ALL_FIELDS.filter((f) => f !== "file"),
|
|
78
|
+
}),
|
|
79
|
+
// chat: everything but page; all fields but secret.
|
|
80
|
+
chat: rule({
|
|
81
|
+
blocks: ALL_BLOCKS.filter((b) => b !== "page"),
|
|
82
|
+
fields: ALL_FIELDS.filter((f) => f !== "secret"),
|
|
83
|
+
}),
|
|
84
|
+
// interaction: stack/section layout, content, list collections, and a
|
|
85
|
+
// required form root with the ask-user subset of fields.
|
|
86
|
+
interaction: rule({
|
|
87
|
+
blocks: ["stack", "section", ...CONTENT, "collection", "form"],
|
|
88
|
+
rootBlock: "form",
|
|
89
|
+
collectionDisplays: ["list"],
|
|
90
|
+
fields: ["text", "longtext", "number", "toggle", "choice", "secret"],
|
|
91
|
+
}),
|
|
92
|
+
// ambient: stack/columns layout, content, stat/meter/log, list
|
|
93
|
+
// collections; no forms (§9.2 records render read-only state).
|
|
94
|
+
ambient: rule({
|
|
95
|
+
blocks: ["stack", "columns", ...CONTENT, ...STAT_METER_LOG, "collection"],
|
|
96
|
+
collectionDisplays: ["list"],
|
|
97
|
+
fields: [],
|
|
98
|
+
}),
|
|
99
|
+
};
|