@ai-ecoverse/slicc-spectrum 1.109.1 → 1.111.0
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 +25 -5
- package/dist/slicc-dummy.js +47 -0
- package/dist/slicc-dummy.js.map +3 -3
- package/dist/slicc-ui.js +616 -237
- package/dist/slicc-ui.js.map +4 -4
- package/dist/types/app/model-picker.d.ts +16 -0
- package/dist/types/app/sprinkle.d.ts +8 -2
- package/dist/types/dummy/sprinkles.d.ts +1 -0
- package/dist/types/model/types.d.ts +19 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -560,10 +560,10 @@ A rejection shows the error card with its sentence (a check without a `preview`
|
|
|
560
560
|
|
|
561
561
|
A SLICC sprinkle (`.shtml`), as a panel or, with `inline`, as a dip in the chat that grows to fit, even when it loads out of view (the frame measures itself on load, after icons and fonts arrive, and on every resize).
|
|
562
562
|
A dip has a tab-like handle: drag it into the dock (or onto a rail, or use its open-as-panel button, `data-action="detach-sprinkle"`, an extra-small action button whose tap target pads out to 24 × 24 px on a coarse pointer while it looks the same) and it lives on as a sprinkle panel, with a note in the chat where it was and a **Show** button (`data-action="show-sprinkle"`) that brings the panel forward; which dips are out is saved in `slicc-ui.dips`.
|
|
563
|
-
It runs in a sandboxed frame (`allow-scripts`
|
|
564
|
-
It talks back through `slicc.lick({ action, data })`, which reaches the owning cone as a `sprinkle` lick, and, when the port has `call`, reads files
|
|
563
|
+
It runs in a sandboxed frame (`allow-scripts allow-forms`) with SLICC's sprinkle theme (the `--s2-*` tokens and `.sprinkle-*` classes), the app's fonts, and Lucide icons (`<i data-lucide>`, `LucideIcons.render()`).
|
|
564
|
+
It talks back through `slicc.lick({ action, data })`, which reaches the owning cone as a `sprinkle` lick, and, when the port has `call`, reads files, keeps state and runs its own `cgi-bin/` scripts (see [Sprinkle bridge](#sprinkle-bridge)).
|
|
565
565
|
Panel sprinkles sit at the top of the right rail with their Lucide icon.
|
|
566
|
-
On a guest's page (the model's `seat` set), every sprinkle, inline or panel, is view only: its frame is inert, it says "View only on this device",
|
|
566
|
+
On a guest's page (the model's `seat` set), every sprinkle, inline or panel, is view only: its frame is inert, it says "View only on this device", its licks and `setState` calls go nowhere, and its `cgi-bin/` answers every request with a `403`.
|
|
567
567
|
|
|
568
568
|
#### `<slicc-network>`
|
|
569
569
|
|
|
@@ -714,6 +714,25 @@ A refusal or a missing file rejects with an `Error`.
|
|
|
714
714
|
There is no `exec`: a sprinkle that needs a command asks its cone with `slicc.lick()`.
|
|
715
715
|
Without `call`, none of the four exist, so guard them (`typeof slicc.readFile === 'function'`) and fall back.
|
|
716
716
|
|
|
717
|
+
#### `cgi-bin/`
|
|
718
|
+
|
|
719
|
+
A sprinkle's `fetch()` of a URL under `new URL('cgi-bin/', document.baseURI)` runs the sprinkle's own script through the port's `cgi`, so `fetch('cgi-bin/items')` answers with what `<sprinkle>/cgi-bin/items` prints, CGI/1.1 style.
|
|
720
|
+
Every other URL goes to the frame's own `fetch`, unchanged.
|
|
721
|
+
The answer is a real `Response`: `status`, `statusText`, `headers`, `url`, and a body that streams as the script prints, so `json()`, `text()` and `body.getReader()` all work; a `HEAD` and a `101`, `103`, `204`, `205` or `304` have no body.
|
|
722
|
+
The request goes as `{ method, path, query, headers, body }`, as the `Request` constructor made it (a string body gets `text/plain;charset=UTF-8`, a `FormData` its multipart boundary), with `Accept: */*` and the browser's `Accept-Language` when the sprinkle set none; `path` is what follows `cgi-bin/`, still percent-encoded, `query` has no `?`, and the body is base64.
|
|
723
|
+
A `301`, `302`, `303`, `307` or `308` with `Location` is followed as `fetch` follows redirects (a `303`, or a `301` or `302` after a `POST`, turns into a `GET` without a body), up to 20; `redirect: 'manual'` returns the redirect itself and `redirect: 'error'` rejects.
|
|
724
|
+
Aborting the `fetch` (its `signal`) or cancelling the body stops the script (`cgiClose`); a failed `cgi` or `cgiRead` rejects with a `TypeError`, as a network error does.
|
|
725
|
+
|
|
726
|
+
A form never navigates the frame: the bridge cancels every `submit` (except `method="dialog"`) and `form.submit()`.
|
|
727
|
+
A form whose action (or its submitter's `formaction`) is under `cgi-bin/` goes the same way, with its `formmethod`, and as `enctype` says: `application/x-www-form-urlencoded` by default, `multipart/form-data` or `text/plain`; a `GET` form replaces the action's query.
|
|
728
|
+
The bridge cancels the browser's own `submit` before any page handler runs and stops it there, then dispatches a copy (a `SubmitEvent` with the same `submitter`, so `isTrusted` is `false`) to the form, so stopping the copy's propagation can't navigate the frame.
|
|
729
|
+
A handler that calls `preventDefault()` on that copy, on the form or above it, or an `onsubmit` that returns `false`, keeps the bridge from sending it.
|
|
730
|
+
The form then gets a cancelable `slicc-cgi` event with `detail.response` (or `detail.error`); unless it's cancelled, an answer with `Location` rebuilds the sprinkle, with its latest state.
|
|
731
|
+
|
|
732
|
+
The element sends `cgi` with its own sprinkle id, never one from the frame, posts the head as soon as the port answers, then calls `cgiRead` until `done`.
|
|
733
|
+
A guest seat gets a `403` for every request, a frame without `call` a `404`, and a read-only, watch-only or frozen frame may `GET` and `HEAD`; any other method gets a `403` without reaching the port.
|
|
734
|
+
The port does every other check (paths, ownership, limits).
|
|
735
|
+
|
|
717
736
|
### Panel command
|
|
718
737
|
|
|
719
738
|
`panelCommand(app, argv, caller)`, exported from `dist/slicc-ui.js`, runs `panel` for an agent or a terminal, so the agent can arrange the UI.
|
|
@@ -833,8 +852,9 @@ Sprinkles (`inline` ones show as dips in the chat, not in the rail) and `send`,
|
|
|
833
852
|
The rail groups sprinkles by their `agentId`: the selected cone's come first, then the other cones' in the agents list's order, each group set apart by a line (`.rail .cones`) and each button carrying `data-cone`.
|
|
834
853
|
A sprinkle of another cone reads `{cone name} / {title}` in its tooltip, its label and the tab it opens; the name comes from the agents list, so give each sprinkle an `id` that stays stable when a cone is renamed, such as `{coneId}/{name}`.
|
|
835
854
|
The optional `call(id, method, args)` serves the [sprinkle bridge](#sprinkle-bridge) for sprinkle `id`: `readFile` and `exists` (`[path]`) resolve with a string and a boolean, `getState` (`[]`) with the saved value or `null`, and `setState` (`[value]`) once it is saved.
|
|
836
|
-
|
|
837
|
-
The
|
|
855
|
+
`cgi` (`[{ method, path, query, headers, body }]`, with `headers` as `[name, value]` pairs and `body` base64 or `null`) resolves once the script's CGI headers are in, with `{ status, statusText, headers, body, done, stream }`; `cgiRead` (`[stream]`) long-polls for `{ body, done }`, and `cgiClose` (`[stream]`) stops the script (see [`cgi-bin/`](#cgi-bin)).
|
|
856
|
+
The adapter owns path policy and keeps state per sprinkle; `<slicc-sprinkle>` awaits `getState` before it builds a frame, mirrors what the frame saves, and forwards only the first four methods from its own frame's `slicc-call`; the `cgi` ones only through its `fetch` and forms.
|
|
857
|
+
The dummy keeps state in memory and maps `/shared/x` to `/home/x` in its files, refusing anything outside `/home`; it answers every `cgi` with a canned `200` `application/json` (`X-Slicc-Cgi: dummy`) that echoes the method, path, query, content type and body, in two chunks.
|
|
838
858
|
|
|
839
859
|
#### `tray`
|
|
840
860
|
|
package/dist/slicc-dummy.js
CHANGED
|
@@ -4908,11 +4908,29 @@ function homePath(path) {
|
|
|
4908
4908
|
throw new Error(`EACCES: ${text} is outside /home`);
|
|
4909
4909
|
return mapped;
|
|
4910
4910
|
}
|
|
4911
|
+
var base64 = (bytes) => btoa(String.fromCharCode(...bytes));
|
|
4912
|
+
var unbase64 = (text) => new TextDecoder().decode(Uint8Array.from(atob(text), (char) => char.charCodeAt(0)));
|
|
4913
|
+
function cannedCgi(request) {
|
|
4914
|
+
const given = request ?? {};
|
|
4915
|
+
const headers = Array.isArray(given.headers) ? given.headers : [];
|
|
4916
|
+
const type = headers.find(([name2]) => String(name2).toLowerCase() === "content-type");
|
|
4917
|
+
return `${JSON.stringify({
|
|
4918
|
+
ok: true,
|
|
4919
|
+
method: given.method ?? "GET",
|
|
4920
|
+
path: given.path ?? "",
|
|
4921
|
+
query: given.query ?? "",
|
|
4922
|
+
type: type?.[1] ?? null,
|
|
4923
|
+
body: typeof given.body === "string" ? unbase64(given.body) : null
|
|
4924
|
+
})}
|
|
4925
|
+
`;
|
|
4926
|
+
}
|
|
4911
4927
|
var DummySprinkles = class extends Emitter {
|
|
4912
4928
|
#sprinkles;
|
|
4913
4929
|
#agent;
|
|
4914
4930
|
#files;
|
|
4915
4931
|
#state = /* @__PURE__ */ new Map();
|
|
4932
|
+
#streams = /* @__PURE__ */ new Map();
|
|
4933
|
+
#next = 0;
|
|
4916
4934
|
constructor(sprinkles2, agent, files3) {
|
|
4917
4935
|
super();
|
|
4918
4936
|
this.#sprinkles = sprinkles2.map((sprinkle) => ({ ...sprinkle }));
|
|
@@ -4940,10 +4958,39 @@ var DummySprinkles = class extends Emitter {
|
|
|
4940
4958
|
this.#state.set(id, structuredClone(args[0] ?? null));
|
|
4941
4959
|
return void 0;
|
|
4942
4960
|
}
|
|
4961
|
+
if (method === "cgi") return this.#cgi(args[0]);
|
|
4962
|
+
if (method === "cgiRead") return this.#read(String(args[0]));
|
|
4963
|
+
if (method === "cgiClose") {
|
|
4964
|
+
this.#streams.delete(String(args[0]));
|
|
4965
|
+
return null;
|
|
4966
|
+
}
|
|
4943
4967
|
const path = homePath(args[0]);
|
|
4944
4968
|
if (method === "exists") return (await this.#files.list()).some((entry) => entry.path === path);
|
|
4945
4969
|
return this.#files.read(path);
|
|
4946
4970
|
}
|
|
4971
|
+
#cgi(request) {
|
|
4972
|
+
const bytes = new TextEncoder().encode(cannedCgi(request));
|
|
4973
|
+
const half = Math.ceil(bytes.length / 2);
|
|
4974
|
+
const stream = `dummy-cgi-${++this.#next}`;
|
|
4975
|
+
this.#streams.set(stream, bytes.subarray(half));
|
|
4976
|
+
return {
|
|
4977
|
+
status: 200,
|
|
4978
|
+
statusText: "OK",
|
|
4979
|
+
headers: [
|
|
4980
|
+
["Content-Type", "application/json"],
|
|
4981
|
+
["X-Slicc-Cgi", "dummy"]
|
|
4982
|
+
],
|
|
4983
|
+
body: base64(bytes.subarray(0, half)),
|
|
4984
|
+
done: false,
|
|
4985
|
+
stream
|
|
4986
|
+
};
|
|
4987
|
+
}
|
|
4988
|
+
#read(stream) {
|
|
4989
|
+
const rest = this.#streams.get(stream);
|
|
4990
|
+
if (rest === void 0) throw new Error(`there is no cgi-bin stream ${stream}`);
|
|
4991
|
+
this.#streams.delete(stream);
|
|
4992
|
+
return { body: base64(rest), done: true };
|
|
4993
|
+
}
|
|
4947
4994
|
};
|
|
4948
4995
|
|
|
4949
4996
|
// src/dummy/tabs.ts
|