@ai-ecoverse/slicc-spectrum 1.25.0 → 1.27.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 CHANGED
@@ -136,7 +136,7 @@ Text is Adobe Clean Spectrum VF, falling back to Adobe Clean, and code is Spectr
136
136
  | `<slicc-agents>` | Cones with their scoops, status, context fill and unread count. Selecting one makes it the active agent. |
137
137
  | `<slicc-chat>` | One thread's conversation (its `agent`, or the active agent if unset), by day: user messages (with attachments, tagged as steered or follow-up by `delivered`, or delegated from another cone), assistant messages (Markdown with tables, task lists, quotes, code and images; thinking; tool calls, grouped into a collapsible cluster from three in a row, each with its input (a shell command after `$`), an optional `meta` such as a timeout, and an edit's `diff` shown as a diff view; media galleries with images, video and audio; action cards for tools, pull requests and status; plans and checklists; inline diffs; question prompts; delegations; link previews; errors with a fix-it action; a content-filter stop offers **Drop the last turn**, which rewinds that turn and puts its prompt back into the composer), standalone tool results, system messages (compaction markers, error cards, notices) and licks, SLICC's events into a cone, on every channel; a lick's `severity` (`warn` or `error`) turns its channel badge into a notice or negative one with an alert icon. Permission requests are allowed or denied in place. |
138
138
  | `<slicc-composer>` | The message box. <kbd>Enter</kbd> sends, or steers a running reply: the message joins after the current tool round, without interrupting; <kbd>Ctrl</kbd>/<kbd>⌘</kbd>+<kbd>Enter</kbd> queues a follow-up that runs after the reply and can be withdrawn; <kbd>Shift</kbd>+<kbd>Enter</kbd> adds a line; <kbd>Tab</kbd> takes the suggested follow-up; <kbd>↑</kbd>/<kbd>↓</kbd> at the start or end walk this agent's history; <kbd>Esc</kbd> stops. `/` completes commands (`/clear`, `/compact`, `/model`, `/thinking`, `/scoop`, `/stop`, `/theme`) and their arguments, `@` mentions agents and files. Attach from the computer, from SLICC's files, a browser screenshot or a shared secret, or paste and drop files (25 MB each). Queued messages wait above it, to send now or drop. Model and thinking pickers, and dictation where the browser has speech recognition. Drafts are kept per agent. |
139
- | `<slicc-files>` | The file system as a `<slicc-file-tree>`, with pending changes as git status. Opening a file opens it in a tab. |
139
+ | `<slicc-files>` | The file system as a `<slicc-file-tree>`, with pending changes as git status. Opening a file opens it in a tab. With `files.mountFolder`, **Mount a folder…** (`data-action="mount-folder"`) mounts a local folder, then selects and expands it in the tree; with `files.mounts`, a **Mounted** strip lists the mount points, each with **Eject** (`data-action="eject"`, `data-path` the mount point) when the port has `eject`. A failed mount or eject shows its message under the bar. |
140
140
  | `<slicc-file-view>` | One file (`path`), highlighted, with its pending change and a link to the diff. |
141
141
  | `<slicc-changes>` | Pending changes with their author. Opens the diff, accepts or reverts one change or all of them. |
142
142
  | `<slicc-diff-panel>` | The diff of one pending change (`path`), unified or split, with accept and revert. |
@@ -195,7 +195,7 @@ Paths are the agent's: `/shared/…` and `/home/…`, nothing else. A refusal or
195
195
  | Port | Later backed by | |
196
196
  | :--- | :--- | :--- |
197
197
  | `agent` | `@ai-ecoverse/slicc-agent` | Cones and scoops, the active agent, conversations, `send` (with attachments, as a send, a steer or a queued follow-up) and `stop`, the queue, suggestions, answers to questions, permission decisions, compaction, clearing, the model per agent and new scoops. The optional `rewind(agentId, messageId)` drops the user turn before that message and everything after it, and resolves with the turn's prompt (text and attachments) for the composer, or `null`. The optional `drop(agentId)` removes a scoop (its files stay), and `createCone(name)` resolves with a new cone; the dummy rejects a cone name that is taken. Assistant messages stream: the same message is re-emitted as its parts grow and its tool calls go from `running` to `done`. |
198
- | `files` | slicc-kernel's OPFS | `list`, `read`, `write`, `remove`, and pending changes with their before and after text (`accept`, `revert`). |
198
+ | `files` | slicc-kernel's OPFS | `list`, `read`, `write`, `remove`, and pending changes with their before and after text (`accept`, `revert`). The optional `mountFolder()` asks for a local folder and resolves with its mount point (for example `/mnt/photos`), or `null` when the user cancels; the panel calls it synchronously in the click, so the port can call `showDirectoryPicker()`. The optional `mounts()` lists the mount points, and `eject(path)` unmounts one (the folder stays on disk). The port emits `mounts` and then `files` after every mount and eject, including those made from the shell. |
199
199
  | `terminals` | slicc-kernel's terminal API | Terminal sessions; `backend(id)` returns a `TerminalBackend` for `<slicc-terminal>`. |
200
200
  | `browser` | `@ai-ecoverse/slicc-cdp` | Tabs, the active tab, `open`, `navigate`, `close`, `screenshot`. |
201
201
  | `settings` | local storage | Theme, model, thinking level, composer and diff preferences, the models on offer, and accounts. |
@@ -205,7 +205,7 @@ Paths are the agent's: `/shared/…` and `/home/…`, nothing else. A refusal or
205
205
  | `tray` | the tray protocol | Connection, role, float kind, followers, spend and budget; `reconnect` and `disconnect`. |
206
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. |
207
207
 
208
- The dummy supports `createDummyModel({ updates: 'boot' | 'current' | 'available' | 'restart' | 'failure' })`, also selectable with `?updates=` on `/ui/`. Fixtures stay stable for inspection; `model.updates.setScenario('current')` completes boot. Dummy Update now and Retry simulate download and link progress; Restart agent and Reload only change fixture state, never restart a real process or reload the browser. `updateFixtures` and `DummyUpdates` are exported from `/dummy`.
208
+ The dummy supports `createDummyModel({ updates: 'boot' | 'current' | 'available' | 'restart' | 'failure' })`, also selectable with `?updates=` on `/ui/`. Fixtures stay stable for inspection; `model.updates.setScenario('current')` completes boot. Dummy Update now and Retry simulate download and link progress; Restart agent and Reload only change fixture state, never restart a real process or reload the browser. `updateFixtures` and `DummyUpdates` are exported from `/dummy`. `createDummyModel({ mounts: 'mount' | 'cancel' | 'fail' })` (or `?mounts=` on `/ui/`) turns on the optional mount members, which are off by default: `mountFolder` mounts invented files at `/mnt/photos`, resolves with `null`, or rejects, as `model.files.mount.scenario` says.
209
209
 
210
210
  On phones the tray uses a compact indicator (its full status remains available on activation), the agent picker shrinks to fit, and the bottom rail scrolls when needed. The update status comes first so progress and failures remain reachable on narrow screens.
211
211
 
@@ -376,7 +376,8 @@ export {
376
376
  o2,
377
377
  l,
378
378
  n5 as n,
379
- e3
379
+ e3,
380
+ n6 as n2
380
381
  };
381
382
  /*! Bundled license information:
382
383
 
@@ -418,4 +419,4 @@ lit-html/directives/join.js:
418
419
  * SPDX-License-Identifier: BSD-3-Clause
419
420
  *)
420
421
  */
421
- //# sourceMappingURL=chunk-5I2AIF5I.js.map
422
+ //# sourceMappingURL=chunk-22NCU4NL.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  o
3
- } from "./chunk-5I2AIF5I.js";
3
+ } from "./chunk-22NCU4NL.js";
4
4
  import {
5
5
  defineElement
6
6
  } from "./chunk-6A6NIESE.js";
@@ -231,4 +231,4 @@ export {
231
231
  * SPDX-License-Identifier: BSD-3-Clause
232
232
  *)
233
233
  */
234
- //# sourceMappingURL=chunk-CHSUKIQC.js.map
234
+ //# sourceMappingURL=chunk-4F4N6IHX.js.map
@@ -6,7 +6,7 @@ import {
6
6
  } from "./chunk-EN3XGF2M.js";
7
7
  import {
8
8
  o
9
- } from "./chunk-5I2AIF5I.js";
9
+ } from "./chunk-22NCU4NL.js";
10
10
  import {
11
11
  defineElement
12
12
  } from "./chunk-6A6NIESE.js";
@@ -146,7 +146,7 @@ var Tooltip = class extends SpectrumElement {
146
146
  <span id="tip" aria-hidden="true"></span>
147
147
  </sp-tooltip-openable>
148
148
  `;
149
- return this.selfManaged ? (this.dependencyManager.add("sp-overlay"), import("./chunk-DYJTIU34.js"), b`
149
+ return this.selfManaged ? (this.dependencyManager.add("sp-overlay"), import("./chunk-FAVZKAXS.js"), b`
150
150
  <sp-overlay
151
151
  ?open=${this.open && !this.disabled && this.dependencyManager.loaded}
152
152
  ?delayed=${this.delayed}
@@ -178,4 +178,4 @@ o3([n({ type: Boolean })], Tooltip.prototype, "delayed", 2), o3([n({ type: Boole
178
178
 
179
179
  // node_modules/@spectrum-web-components/tooltip/sp-tooltip.js
180
180
  defineElement("sp-tooltip", Tooltip);
181
- //# sourceMappingURL=chunk-Z2IOQT73.js.map
181
+ //# sourceMappingURL=chunk-6DBT3DUR.js.map
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  Overlay
3
- } from "./chunk-LMDPCBR2.js";
3
+ } from "./chunk-MQNH3NIC.js";
4
4
  import "./chunk-IBQ4O4NO.js";
5
5
  import "./chunk-ZMBN6APP.js";
6
6
  import "./chunk-KD66QY2Y.js";
7
7
  import "./chunk-OS3ZND7O.js";
8
8
  import "./chunk-JPIG5BAD.js";
9
- import "./chunk-5I2AIF5I.js";
9
+ import "./chunk-22NCU4NL.js";
10
10
  import {
11
11
  defineElement
12
12
  } from "./chunk-6A6NIESE.js";
@@ -15,4 +15,4 @@ import "./chunk-AXQFWXYZ.js";
15
15
 
16
16
  // node_modules/@spectrum-web-components/overlay/sp-overlay.js
17
17
  defineElement("sp-overlay", Overlay);
18
- //# sourceMappingURL=chunk-DYJTIU34.js.map
18
+ //# sourceMappingURL=chunk-FAVZKAXS.js.map
@@ -1,17 +1,17 @@
1
1
  import {
2
2
  LONGPRESS_INSTRUCTIONS,
3
3
  Overlay
4
- } from "./chunk-LMDPCBR2.js";
4
+ } from "./chunk-MQNH3NIC.js";
5
5
  import "./chunk-IBQ4O4NO.js";
6
6
  import "./chunk-ZMBN6APP.js";
7
7
  import "./chunk-KD66QY2Y.js";
8
8
  import "./chunk-OS3ZND7O.js";
9
9
  import "./chunk-JPIG5BAD.js";
10
- import "./chunk-5I2AIF5I.js";
10
+ import "./chunk-22NCU4NL.js";
11
11
  import "./chunk-WXAII75O.js";
12
12
  import "./chunk-AXQFWXYZ.js";
13
13
  export {
14
14
  LONGPRESS_INSTRUCTIONS,
15
15
  Overlay
16
16
  };
17
- //# sourceMappingURL=chunk-QROWP6N7.js.map
17
+ //# sourceMappingURL=chunk-I57ZVWF7.js.map
@@ -1,8 +1,8 @@
1
- import "./chunk-CHSUKIQC.js";
1
+ import "./chunk-4F4N6IHX.js";
2
2
  import "./chunk-OS3ZND7O.js";
3
3
  import "./chunk-JPIG5BAD.js";
4
- import "./chunk-5I2AIF5I.js";
4
+ import "./chunk-22NCU4NL.js";
5
5
  import "./chunk-6A6NIESE.js";
6
6
  import "./chunk-WXAII75O.js";
7
7
  import "./chunk-AXQFWXYZ.js";
8
- //# sourceMappingURL=chunk-2TAFZNVG.js.map
8
+ //# sourceMappingURL=chunk-LZPC6RUV.js.map
@@ -32,7 +32,7 @@ import {
32
32
  import {
33
33
  o as o2,
34
34
  o2 as o3
35
- } from "./chunk-5I2AIF5I.js";
35
+ } from "./chunk-22NCU4NL.js";
36
36
  import {
37
37
  A,
38
38
  SpectrumElement,
@@ -186,7 +186,7 @@ var AbstractOverlay = class _AbstractOverlay extends SpectrumElement {
186
186
  document.dispatchEvent(e2);
187
187
  }
188
188
  static async open(e2, r3, n2, a) {
189
- await import("./chunk-DYJTIU34.js");
189
+ await import("./chunk-FAVZKAXS.js");
190
190
  const m = arguments.length === 2, l2 = n2 || e2, t = new this();
191
191
  let p = false;
192
192
  t.dispose = () => {
@@ -1413,9 +1413,9 @@ var InteractionController = class {
1413
1413
  return;
1414
1414
  }
1415
1415
  e2 && (customElements.whenDefined("sp-overlay").then(async () => {
1416
- const { Overlay: t } = await import("./chunk-QROWP6N7.js");
1416
+ const { Overlay: t } = await import("./chunk-I57ZVWF7.js");
1417
1417
  this.overlay = new t(), this.overlay.open = true, this.target[lastInteractionType] = this.type;
1418
- }), import("./chunk-DYJTIU34.js"));
1418
+ }), import("./chunk-FAVZKAXS.js"));
1419
1419
  }
1420
1420
  }
1421
1421
  get overlay() {
@@ -1884,4 +1884,4 @@ export {
1884
1884
  LONGPRESS_INSTRUCTIONS,
1885
1885
  Overlay
1886
1886
  };
1887
- //# sourceMappingURL=chunk-LMDPCBR2.js.map
1887
+ //# sourceMappingURL=chunk-MQNH3NIC.js.map
@@ -637,7 +637,7 @@ var DummyAgent = class extends Emitter {
637
637
  }
638
638
  freeze(agentId) {
639
639
  const agent = this.#agent(agentId);
640
- if (agent?.kind !== "cone") return;
640
+ if (agent?.kind !== "cone" || agent.frozen) return;
641
641
  this.stop(agentId);
642
642
  const family = this.#agents.filter(
643
643
  (candidate) => candidate.id === agentId || candidate.parentId === agentId
@@ -669,7 +669,13 @@ var DummyAgent = class extends Emitter {
669
669
  }
670
670
  thaw(id) {
671
671
  const ice = this.#ice.get(id);
672
- if (!ice) return null;
672
+ if (!ice) {
673
+ const agent = this.#agent(id);
674
+ if (!agent?.frozen) return null;
675
+ delete agent.frozen;
676
+ this.#changed();
677
+ return { ...agent };
678
+ }
673
679
  this.#ice.delete(id);
674
680
  this.#frozen = this.#frozen.filter((cone) => cone.id !== id);
675
681
  for (const { agent, messages } of ice) {
@@ -1906,14 +1912,25 @@ function parents(path) {
1906
1912
  return parts.map((_, i) => `/${parts.slice(0, i + 1).join("/")}`);
1907
1913
  }
1908
1914
  var DummyFiles = class extends Emitter {
1915
+ mount;
1916
+ mountFolder;
1917
+ mounts;
1918
+ eject;
1919
+ #mounted = /* @__PURE__ */ new Set();
1909
1920
  #contents = /* @__PURE__ */ new Map();
1910
1921
  #directories = /* @__PURE__ */ new Set();
1911
1922
  #modified = /* @__PURE__ */ new Map();
1912
1923
  #changes = /* @__PURE__ */ new Map();
1913
1924
  #clock;
1914
- constructor(contents, directories2, changes, clock2) {
1925
+ constructor(contents, directories2, changes, clock2, mount = null) {
1915
1926
  super();
1916
1927
  this.#clock = clock2;
1928
+ this.mount = mount;
1929
+ if (mount) {
1930
+ this.mountFolder = () => this.#mountFolder(mount);
1931
+ this.mounts = () => [...this.#mounted];
1932
+ this.eject = (path) => this.#eject(path);
1933
+ }
1917
1934
  for (const directory of directories2) this.#mkdirs(`${directory}/`);
1918
1935
  for (const [path, text] of Object.entries(contents)) this.#put(path, text, 0);
1919
1936
  for (const change of changes) this.#changes.set(change.path, { ...change });
@@ -1998,6 +2015,38 @@ var DummyFiles = class extends Emitter {
1998
2015
  else this.#put(path, change.before, Date.now());
1999
2016
  this.#announce(path);
2000
2017
  }
2018
+ async #mountFolder(mount) {
2019
+ await this.#clock.sleep();
2020
+ if (mount.scenario === "cancel") return null;
2021
+ if (mount.scenario === "fail") throw new Error("The folder couldn\u2019t be mounted.");
2022
+ const files3 = [];
2023
+ for (const [path, text] of Object.entries(mount.files)) {
2024
+ const file = `${mount.point}/${path}`;
2025
+ this.#put(file, text, Date.now());
2026
+ files3.push(file);
2027
+ }
2028
+ this.#mounted.add(mount.point);
2029
+ this.#remount(files3);
2030
+ return mount.point;
2031
+ }
2032
+ async #eject(path) {
2033
+ await this.#clock.sleep();
2034
+ if (!this.#mounted.delete(path)) throw new Error(`Nothing is mounted at ${path}.`);
2035
+ const inside = (candidate) => candidate === path || candidate.startsWith(`${path}/`);
2036
+ const files3 = [...this.#contents.keys()].filter(inside);
2037
+ for (const file of files3) {
2038
+ this.#contents.delete(file);
2039
+ this.#modified.delete(file);
2040
+ }
2041
+ for (const directory of [...this.#directories].filter(inside))
2042
+ this.#directories.delete(directory);
2043
+ this.#remount(files3);
2044
+ }
2045
+ #remount(files3) {
2046
+ this.emit("mounts", [...this.#mounted]);
2047
+ this.emit("files", this.#entries());
2048
+ for (const file of files3) this.emit("file", file);
2049
+ }
2001
2050
  };
2002
2051
 
2003
2052
  // src/dummy/showcase.ts
@@ -2913,6 +2962,13 @@ var files2 = {
2913
2962
  "/tmp/forecast-sample.json": sample
2914
2963
  };
2915
2964
  var directories = ["/sessions", "/home/user/downloads"];
2965
+ var mountPoint = "/mnt/photos";
2966
+ var mounted = {
2967
+ "README.md": "# Photos\n\nHarbor shots for the forecast pages, one album per season.\n",
2968
+ "albums/autumn.json": '{ "title": "Autumn", "shots": ["breakwater-fog", "pier-at-dusk"] }\n',
2969
+ "albums/winter.json": '{ "title": "Winter", "shots": ["ice-on-the-buoys"] }\n',
2970
+ "captions.txt": "breakwater-fog: low cloud over the outer wall\npier-at-dusk: lamps on, tide out\n"
2971
+ };
2916
2972
  var pending = [
2917
2973
  {
2918
2974
  path: "/workspace/harbor/src/lib/cache.ts",
@@ -3034,6 +3090,17 @@ var agents = [
3034
3090
  model: "claude-haiku-4-5",
3035
3091
  contextFill: 0.02,
3036
3092
  unread: 0
3093
+ },
3094
+ {
3095
+ id: "cone-tides",
3096
+ name: "tide-tables",
3097
+ kind: "cone",
3098
+ parentId: null,
3099
+ status: "idle",
3100
+ model: "claude-sonnet-5-5",
3101
+ contextFill: 0.31,
3102
+ unread: 0,
3103
+ frozen: true
3037
3104
  }
3038
3105
  ];
3039
3106
  var start2 = Date.UTC(2026, 9, 5, 8, 30);
@@ -3164,6 +3231,12 @@ var conversations = {
3164
3231
  {
3165
3232
  type: "text",
3166
3233
  text: "Found it. `ForecastCache.get` compared **local** calendar days, and when the day changed it returned the old entry instead of dropping it:\n\n```ts\nif (day !== new Date(now).getDate()) return entry.value;\n```\n\nI changed it to compare UTC dates and delete the entry, so the first request after midnight goes upstream. The tests pass.\n\n| | Before | After |\n| --- | --- | --- |\n| Day compared | local `getDate()` | UTC `toISOString()` |\n| On a new day | returns stale entry | deletes and refetches |\n\nquiet-otter is adding retries for upstream timeouts in a scoop."
3234
+ },
3235
+ {
3236
+ type: "delegation",
3237
+ kind: "scoop",
3238
+ scoop: "quiet-otter",
3239
+ text: "Started a scoop to add retries around the upstream fetch"
3167
3240
  }
3168
3241
  ]
3169
3242
  },
@@ -3180,6 +3253,8 @@ var conversations = {
3180
3253
  {
3181
3254
  id: "m-o1",
3182
3255
  role: "user",
3256
+ origin: "agent",
3257
+ from: "harbor",
3183
3258
  text: "Wrap the upstream fetch in src/routes/forecast.ts with three retries and exponential backoff.",
3184
3259
  createdAt: at2(9)
3185
3260
  },
@@ -3219,6 +3294,26 @@ var conversations = {
3219
3294
  ],
3220
3295
  "scoop-heron": [],
3221
3296
  "scoop-wren": [],
3297
+ "cone-tides": [
3298
+ {
3299
+ id: "m-f1",
3300
+ role: "user",
3301
+ text: "Add a tide table endpoint next to the forecast, with high and low water per day.",
3302
+ createdAt: at2(-2900)
3303
+ },
3304
+ {
3305
+ id: "m-f2",
3306
+ role: "assistant",
3307
+ status: "done",
3308
+ createdAt: at2(-2898),
3309
+ parts: [
3310
+ {
3311
+ type: "text",
3312
+ text: "Added `/v1/tides?city=` with the next seven days of high and low water. It reads from the same upstream as the forecast and shares its cache."
3313
+ }
3314
+ ]
3315
+ }
3316
+ ],
3222
3317
  "cone-release": [
3223
3318
  {
3224
3319
  id: "m-r1",
@@ -3270,6 +3365,15 @@ var conversations = {
3270
3365
  title: "/workspace/notes",
3271
3366
  text: "File watch on /workspace/notes stopped; retrying",
3272
3367
  createdAt: at2(-1429)
3368
+ },
3369
+ {
3370
+ id: "m-r5",
3371
+ role: "lick",
3372
+ channel: "workflow",
3373
+ title: "Release report",
3374
+ text: "The weekly release report is ready",
3375
+ body: "Summary: harbor 1.9.0 shipped three changes this week, the /health endpoint, a 400 for requests without a city and a wrangler update, with no failed deploys, no rollbacks and two flaky test runs in the forecast suite that passed on retry, so the next release can go out on Friday as planned once the docs pass lands.",
3376
+ createdAt: at2(-1428)
3273
3377
  }
3274
3378
  ],
3275
3379
  "cone-triage": [
@@ -3945,10 +4049,12 @@ var DummyUpdates = class extends Emitter {
3945
4049
  function createDummyModel({
3946
4050
  delay = 30,
3947
4051
  storage = null,
3948
- updates = "current"
4052
+ updates = "current",
4053
+ mounts = "off"
3949
4054
  } = {}) {
3950
4055
  const time = clock(delay);
3951
- const files3 = new DummyFiles(files2, directories, pending, time);
4056
+ const mount = mounts === "off" ? null : { scenario: mounts, point: mountPoint, files: mounted };
4057
+ const files3 = new DummyFiles(files2, directories, pending, time, mount);
3952
4058
  const browser = new DummyBrowser(tabs, time);
3953
4059
  const agent = new DummyAgent(
3954
4060
  agents,