@ai-ecoverse/slicc-spectrum 1.23.3 → 1.24.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 +14 -3
- package/dist/slicc-dummy.js +52 -2
- package/dist/slicc-dummy.js.map +3 -3
- package/dist/slicc-ui.js +119 -16
- package/dist/slicc-ui.js.map +3 -3
- package/dist/types/app/sprinkle.d.ts +7 -1
- package/dist/types/dummy/sprinkles.d.ts +4 -2
- package/dist/types/model/types.d.ts +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -147,7 +147,7 @@ Text is Adobe Clean Spectrum VF, falling back to Adobe Clean, and code is Spectr
|
|
|
147
147
|
| `<slicc-memory>` | What agents remember, globally or per cone, grouped by section: search, filter by tag, expand, edit, add and forget. |
|
|
148
148
|
| `<slicc-monitor>` | The live monitor: vitals with sparklines (active agents, spend, budget, fullest context), alerts, and the topology (cones and scoops, terminals, tabs, pending changes, sprinkles, tray followers). |
|
|
149
149
|
| `<slicc-freezer>` | Frozen cones, archived with their scoops: thaw to bring one back, delete, or freeze the active cone (also `/freeze`). |
|
|
150
|
-
| `<slicc-sprinkle>` | A SLICC sprinkle (`.shtml`), as a panel or, with `inline`, as a dip in the chat that grows to fit. A dip has a tab-like handle: drag it into the dock (or onto a rail, or use its open-as-panel button) and it lives on as a sprinkle panel, with a note in the chat where it was; which dips are out is saved in `slicc-ui.dips`. It runs in a sandboxed frame (`allow-scripts` only) with SLICC's sprinkle theme (the `--s2-*` tokens and `.sprinkle-*` classes), the app's fonts, and Lucide icons (`<i data-lucide>`, `LucideIcons.render()`).
|
|
150
|
+
| `<slicc-sprinkle>` | A SLICC sprinkle (`.shtml`), as a panel or, with `inline`, as a dip in the chat that grows to fit. A dip has a tab-like handle: drag it into the dock (or onto a rail, or use its open-as-panel button) and it lives on as a sprinkle panel, with a note in the chat where it was; which dips are out is saved in `slicc-ui.dips`. It runs in a sandboxed frame (`allow-scripts` only) with SLICC's sprinkle theme (the `--s2-*` tokens and `.sprinkle-*` classes), the app's fonts, and Lucide icons (`<i data-lucide>`, `LucideIcons.render()`). 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 and keeps state (see [Sprinkle bridge](#sprinkle-bridge)). Panel sprinkles sit at the top of the right rail with their Lucide icon. |
|
|
151
151
|
| `<slicc-tray>` | The tray indicator in the header: connection, the float's name, followers and budget; its panel adds role, runtime, spend, the follower list, the join link, and disconnect or reconnect. |
|
|
152
152
|
| `<slicc-confirm>` | The confirmation dialog `confirm()` opens. Not for direct use. |
|
|
153
153
|
| `<slicc-prompt>` | The name dialog `prompt()` opens. Not for direct use. |
|
|
@@ -177,6 +177,17 @@ import { prompt } from './dist/slicc-ui.js';
|
|
|
177
177
|
const name = await prompt({ title: 'New cone', label: 'Name', action: 'Create', submit: (name) => model.agent.createCone(name).then(() => {}) });
|
|
178
178
|
```
|
|
179
179
|
|
|
180
|
+
### Sprinkle bridge
|
|
181
|
+
|
|
182
|
+
A sprinkle's frame gets `window.slicc` with `name` and `lick(event)`. When the model's `sprinkles` port has `call`, it also gets:
|
|
183
|
+
|
|
184
|
+
- `slicc.readFile(path)` resolves with the file's text.
|
|
185
|
+
- `slicc.exists(path)` resolves with a boolean.
|
|
186
|
+
- `slicc.getState()` returns the sprinkle's saved value, or `null`, synchronously: the frame is built with it, so `var saved = slicc.getState()` works on load.
|
|
187
|
+
- `slicc.setState(value)` updates that value at once and resolves when it is saved. State is JSON: what `JSON.stringify` drops or changes is gone (a `Map` becomes `{}`, `NaN` becomes `null`), and a value it can't serialize, like a cycle or a function, rejects and leaves state as it was. It outlives the frame: reopening the panel or rebuilding the sprinkle starts with it.
|
|
188
|
+
|
|
189
|
+
Paths are the agent's: `/shared/…` and `/home/…`, nothing else. A refusal or a missing file rejects with an `Error`. There is no `exec`: a sprinkle that needs a command asks its cone with `slicc.lick()`. Without `call`, none of the four exist, so guard them (`typeof slicc.readFile === 'function'`) and fall back.
|
|
190
|
+
|
|
180
191
|
### Model
|
|
181
192
|
|
|
182
193
|
`SliccModel` (`src/model/types.ts`) has one port per future backend, so adapters can replace the dummy without UI changes. Every port is subscribable with `on(type, listener)`, which returns an unsubscribe function.
|
|
@@ -190,7 +201,7 @@ const name = await prompt({ title: 'New cone', label: 'Name', action: 'Create',
|
|
|
190
201
|
| `settings` | local storage | Theme, model, thinking level, composer and diff preferences, the models on offer, and accounts. |
|
|
191
202
|
| `memory` | `@ai-ecoverse/slicc-agent` | Memories with scope, section, tag and body; `save` and `remove`. |
|
|
192
203
|
| `monitor` | all of them | A snapshot of vitals, alerts and sections, re-emitted as the system changes, and `resync`. |
|
|
193
|
-
| `sprinkles` | `@ai-ecoverse/slicc-agent` | Sprinkles (`inline` ones show as dips in the chat, not in the rail) and `send`, which turns a sprinkle's lick into a lick on its cone. |
|
|
204
|
+
| `sprinkles` | `@ai-ecoverse/slicc-agent` | Sprinkles (`inline` ones show as dips in the chat, not in the rail) and `send`, which turns a sprinkle's lick into a lick on its cone. 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. 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 these four methods from its own frame. The dummy keeps state in memory and maps `/shared/x` to `/home/x` in its files, refusing anything outside `/home`. |
|
|
194
205
|
| `tray` | the tray protocol | Connection, role, float kind, followers, spend and budget; `reconnect` and `disconnect`. |
|
|
195
206
|
| `updates` (optional) | BIOS installers | `list()` returns `UpdateItem` rows for BIOS, kernel, agent, grammars, global pnpm packages, skills and UI. `ready()` explicitly reports agent readiness, independently of agent activity. Emit `items` whenever rows or readiness change. `act(id, action)` resolves when dispatched or rejects with a displayed error; actions are `retry`, `update-now`, `restart-agent` and `reload`. Missing ports preserve existing adapters and report disconnected information in the panel. Real installer wiring belongs to ai-ecoverse/slicc-bios#70. |
|
|
196
207
|
|
|
@@ -222,7 +233,7 @@ Each section is its own port, so an embedder can mix real and dummy per section,
|
|
|
222
233
|
|
|
223
234
|
`<slicc-file-view>` has **Edit**: the file opens in a plain text area, and **Save** or `Mod+S` writes it through `files.write`; `Escape` cancels.
|
|
224
235
|
|
|
225
|
-
The `kitchen-sink` cone holds every kind of message, content and lick in one conversation, for design work and screenshots. The dummy's fixtures are invented: a small forecast API called harbor, with cones, scoops, a conversation per agent, pending changes, browser tabs and accounts. Its only sprinkles are SLICC's own: `welcome` (the onboarding wizard, as a dip in the sliccy cone) and `suggestions`, copied from `slicc` (`packages/vfs-root/shared/sprinkles/`, commit `aa29784`) with their comments stripped, as is the sprinkle theme in `src/app/sprinkle-theme.css`.
|
|
236
|
+
The `kitchen-sink` cone holds every kind of message, content and lick in one conversation, for design work and screenshots. The dummy's fixtures are invented: a small forecast API called harbor, with cones, scoops, a conversation per agent, pending changes, browser tabs and accounts. Its only sprinkles are SLICC's own: `welcome` (the onboarding wizard, as a dip in the sliccy cone) and `suggestions`, copied from `slicc` (`packages/vfs-root/shared/sprinkles/`, commit `aa29784`) with their comments stripped, as is the sprinkle theme in `src/app/sprinkle-theme.css`. `suggestions` reads its stream from the invented `/home/.gelatiere/suggestions.json` through the bridge; there is no `/home/.welcomed`, so `welcome` starts its wizard. Its replies are scripted by keyword (tests, fix or add, open or docs, files, anything else), and their tool calls act on the other ports: an edit writes a file and shows up as a change, a browse opens a tab.
|
|
226
237
|
|
|
227
238
|
### Distribution
|
|
228
239
|
|
package/dist/slicc-dummy.js
CHANGED
|
@@ -2861,6 +2861,34 @@ var sample = `{
|
|
|
2861
2861
|
var bashrc = `export PS1='\\u@slicc:\\w\\$ '
|
|
2862
2862
|
alias ll='ls -la'
|
|
2863
2863
|
`;
|
|
2864
|
+
var suggestions2 = `${JSON.stringify(
|
|
2865
|
+
[
|
|
2866
|
+
{
|
|
2867
|
+
id: "sug-units",
|
|
2868
|
+
kind: "skill",
|
|
2869
|
+
title: "Unit conversions",
|
|
2870
|
+
body: "You converted Fahrenheit to Celsius by hand in three chats this week. A skill can do it for you.",
|
|
2871
|
+
skill: "unit-converter",
|
|
2872
|
+
install: "skill install unit-converter"
|
|
2873
|
+
},
|
|
2874
|
+
{
|
|
2875
|
+
id: "sug-notes",
|
|
2876
|
+
kind: "use-case",
|
|
2877
|
+
title: "Release notes from the week",
|
|
2878
|
+
body: "You write release notes for harbor every Friday. Let the cone draft them from the commits.",
|
|
2879
|
+
prompt: "Draft release notes for harbor from the commits since last Friday."
|
|
2880
|
+
},
|
|
2881
|
+
{
|
|
2882
|
+
id: "sug-cache",
|
|
2883
|
+
kind: "tip",
|
|
2884
|
+
title: "Name your cache keys",
|
|
2885
|
+
body: "Forecast cache keys mix the city and the units. Splitting them makes stale entries easier to find."
|
|
2886
|
+
}
|
|
2887
|
+
],
|
|
2888
|
+
null,
|
|
2889
|
+
2
|
|
2890
|
+
)}
|
|
2891
|
+
`;
|
|
2864
2892
|
var files2 = {
|
|
2865
2893
|
"/workspace/harbor/README.md": readme,
|
|
2866
2894
|
"/workspace/harbor/package.json": pkg,
|
|
@@ -2877,6 +2905,7 @@ var files2 = {
|
|
|
2877
2905
|
"/shared/MEMORY.md": memory,
|
|
2878
2906
|
"/shared/notes/2026-10-04-standup.md": standup,
|
|
2879
2907
|
"/home/user/.bashrc": bashrc,
|
|
2908
|
+
"/home/.gelatiere/suggestions.json": suggestions2,
|
|
2880
2909
|
"/tmp/forecast-sample.json": sample
|
|
2881
2910
|
};
|
|
2882
2911
|
var directories = ["/sessions", "/home/user/downloads"];
|
|
@@ -3472,13 +3501,24 @@ var DummyMonitor = class extends Emitter {
|
|
|
3472
3501
|
};
|
|
3473
3502
|
|
|
3474
3503
|
// src/dummy/sprinkles.ts
|
|
3504
|
+
function homePath(path) {
|
|
3505
|
+
const text = String(path);
|
|
3506
|
+
const mapped = text.startsWith("/shared/") ? `/home/${text.slice("/shared/".length)}` : text;
|
|
3507
|
+
const parts = mapped.split("/");
|
|
3508
|
+
if (!mapped.startsWith("/home/") || parts.some((part) => part === "." || part === ".."))
|
|
3509
|
+
throw new Error(`EACCES: ${text} is outside /home`);
|
|
3510
|
+
return mapped;
|
|
3511
|
+
}
|
|
3475
3512
|
var DummySprinkles = class extends Emitter {
|
|
3476
3513
|
#sprinkles;
|
|
3477
3514
|
#agent;
|
|
3478
|
-
|
|
3515
|
+
#files;
|
|
3516
|
+
#state = /* @__PURE__ */ new Map();
|
|
3517
|
+
constructor(sprinkles2, agent, files3) {
|
|
3479
3518
|
super();
|
|
3480
3519
|
this.#sprinkles = sprinkles2.map((sprinkle) => ({ ...sprinkle }));
|
|
3481
3520
|
this.#agent = agent;
|
|
3521
|
+
this.#files = files3;
|
|
3482
3522
|
}
|
|
3483
3523
|
list() {
|
|
3484
3524
|
return this.#sprinkles.map((sprinkle) => ({ ...sprinkle }));
|
|
@@ -3495,6 +3535,16 @@ var DummySprinkles = class extends Emitter {
|
|
|
3495
3535
|
JSON.stringify(data.data ?? null, null, 2)
|
|
3496
3536
|
);
|
|
3497
3537
|
}
|
|
3538
|
+
async call(id, method, args) {
|
|
3539
|
+
if (method === "getState") return structuredClone(this.#state.get(id) ?? null);
|
|
3540
|
+
if (method === "setState") {
|
|
3541
|
+
this.#state.set(id, structuredClone(args[0] ?? null));
|
|
3542
|
+
return void 0;
|
|
3543
|
+
}
|
|
3544
|
+
const path = homePath(args[0]);
|
|
3545
|
+
if (method === "exists") return (await this.#files.list()).some((entry) => entry.path === path);
|
|
3546
|
+
return this.#files.read(path);
|
|
3547
|
+
}
|
|
3498
3548
|
};
|
|
3499
3549
|
|
|
3500
3550
|
// src/dummy/shell.ts
|
|
@@ -3917,7 +3967,7 @@ function createDummyModel({
|
|
|
3917
3967
|
time
|
|
3918
3968
|
),
|
|
3919
3969
|
memory: new DummyMemory(memories),
|
|
3920
|
-
sprinkles: new DummySprinkles(sprinkles, agent),
|
|
3970
|
+
sprinkles: new DummySprinkles(sprinkles, agent, files3),
|
|
3921
3971
|
tray: new DummyTray(tray, time),
|
|
3922
3972
|
updates: new DummyUpdates(updates, time)
|
|
3923
3973
|
};
|