@ai-ecoverse/slicc-spectrum 2.1.0 → 2.3.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.
@@ -29,7 +29,7 @@ import {
29
29
  skillEdit,
30
30
  tabs,
31
31
  terminals
32
- } from "./chunk-CQDLADJZ.js";
32
+ } from "./chunk-47KEBIYA.js";
33
33
  import {
34
34
  Emitter
35
35
  } from "./chunk-T5NHW4KC.js";
@@ -148,6 +148,86 @@ function trayFixtures(scenario, now2) {
148
148
  const id = scenario === "tray-expired" ? "tray-url" : scenario;
149
149
  return all.filter((approval) => approval.id === id);
150
150
  }
151
+ function secretFixtures(scenario, now2) {
152
+ const base = { createdAt: now2, expiresAt: now2 + 5 * minutes };
153
+ const enter = {
154
+ ...base,
155
+ id: "ask-harbor",
156
+ kind: "secret",
157
+ mode: "enter",
158
+ name: "HARBOR_DEPLOY_TOKEN",
159
+ prompt: "Paste the harbor deploy token from the release checklist.",
160
+ reveal: false,
161
+ requester: { kind: "user", name: "cone", where: "Terminal 1" },
162
+ process: {
163
+ exe: "/usr/bin/fnox",
164
+ argv: ["fnox", "set", "HARBOR_DEPLOY_TOKEN"],
165
+ cwd: "/home/cone/harbor"
166
+ }
167
+ };
168
+ const reveal = {
169
+ ...base,
170
+ id: "ask-lagoon",
171
+ kind: "secret",
172
+ mode: "reveal",
173
+ name: "LAGOON_API_KEY",
174
+ prompt: "The tide report script signs its uploads with this key.",
175
+ reveal: true,
176
+ requester: { kind: "agent", name: "docs-pass", where: "scoop:docs" },
177
+ process: {
178
+ exe: "/usr/bin/sealed",
179
+ argv: ["sealed", "ask", "LAGOON_API_KEY", "--reveal"],
180
+ cwd: "/home/cone/harbor"
181
+ },
182
+ createdAt: now2 + 1
183
+ };
184
+ if (scenario === "secret-enter") return [enter];
185
+ if (scenario === "secret-reveal") return [reveal];
186
+ if (scenario === "secret-long")
187
+ return [
188
+ {
189
+ ...enter,
190
+ id: "ask-long",
191
+ reveal: true,
192
+ requester: { kind: "agent", name: "quiet-otter" },
193
+ prompt: `Paste it\u202Eevil\r${"and wait. ".repeat(30)}`,
194
+ process: {
195
+ exe: "/usr/bin/sh",
196
+ argv: [
197
+ "sh",
198
+ "-c",
199
+ [longCommand, longCommand, longCommand].join(" && \\\n"),
200
+ "tide\rreport"
201
+ ],
202
+ cwd: "/home/cone/harbor\rferry"
203
+ }
204
+ }
205
+ ];
206
+ return [enter, reveal];
207
+ }
208
+ function pendingAsk(approval) {
209
+ const { kind, name: name2, where } = approval.requester;
210
+ return {
211
+ id: approval.id,
212
+ kind: "ask",
213
+ name: approval.name,
214
+ ...approval.prompt ? { prompt: approval.prompt } : {},
215
+ mode: approval.mode,
216
+ reveal: approval.reveal,
217
+ requester: {
218
+ kind,
219
+ name: name2,
220
+ ...where ? { where } : {},
221
+ ...approval.process,
222
+ tty: kind === "user" ? "/dev/pts/1" : null,
223
+ stdin: kind === "user" ? "tty" : "pipe"
224
+ },
225
+ uid: 1e3,
226
+ tag: kind,
227
+ createdAt: approval.createdAt,
228
+ expiresAt: approval.expiresAt
229
+ };
230
+ }
151
231
  function levelFixture(scenario, now2) {
152
232
  const base = {
153
233
  id: `sudo-${scenario}`,
@@ -296,6 +376,7 @@ function approvalFixtures(scenario, now2 = Date.now()) {
296
376
  if (scenario === "guest" || scenario === "guest-follower") return guestFixtures(now2);
297
377
  if (scenario === "guest-always") return guestAlways(now2);
298
378
  if (scenario.startsWith("tray")) return trayFixtures(scenario, now2);
379
+ if (scenario.startsWith("secret-") || scenario === "secret") return secretFixtures(scenario, now2);
299
380
  if (scenario.startsWith("guest-")) return [guestCall(scenario, now2)];
300
381
  if (scenario === "none") return [];
301
382
  if (scenario === "long")
@@ -355,12 +436,20 @@ var DummyApprovals = class extends Emitter {
355
436
  #approvals;
356
437
  #time;
357
438
  #fail;
439
+ #vault;
440
+ #quiet = /* @__PURE__ */ new Set();
358
441
  decisions = [];
359
442
  decide;
360
- constructor(scenario, time) {
443
+ constructor(scenario, time, vault) {
361
444
  super();
362
445
  this.#approvals = approvalFixtures(scenario);
363
446
  this.#time = time;
447
+ this.#vault = vault;
448
+ if (vault) {
449
+ for (const approval of this.#approvals)
450
+ if (approval.kind === "secret") vault.hold(pendingAsk(approval));
451
+ vault.on("status", (status2) => this.#left(status2.waiting));
452
+ }
364
453
  this.#fail = scenario === "fail";
365
454
  if (scenario !== "follower" && scenario !== "guest-follower")
366
455
  this.decide = (id, decision) => this.#decide(id, decision);
@@ -370,10 +459,24 @@ var DummyApprovals = class extends Emitter {
370
459
  void time.sleep(100).then(() => this.end("tray-url", "expired"));
371
460
  }
372
461
  raise(approval) {
462
+ if (approval.kind === "secret") this.#vault?.hold(pendingAsk(approval));
373
463
  this.#approvals = [...this.#approvals.filter(({ id }) => id !== approval.id), approval];
374
464
  this.emit("approvals", this.list());
375
465
  }
466
+ #left(waiting2) {
467
+ const vault = this.#vault;
468
+ for (const approval of this.#approvals) {
469
+ if (approval.kind !== "secret" || waiting2.some(({ id }) => id === approval.id)) continue;
470
+ if (vault.answered.has(approval.id) || this.#quiet.has(approval.id)) this.#drop(approval.id);
471
+ else this.end(approval.id, "answered");
472
+ }
473
+ }
474
+ #drop(id) {
475
+ this.#approvals = this.#approvals.filter((approval) => approval.id !== id);
476
+ this.emit("approvals", this.list());
477
+ }
376
478
  end(id, reason) {
479
+ if (!this.#approvals.some((approval) => approval.id === id)) return;
377
480
  this.emit("ended", { id, reason });
378
481
  this.#approvals = this.#approvals.filter((approval) => approval.id !== id);
379
482
  this.emit("approvals", this.list());
@@ -385,7 +488,14 @@ var DummyApprovals = class extends Emitter {
385
488
  await this.#time.sleep(10);
386
489
  if (this.#fail) throw new Error("The kernel stopped waiting. Run the command again.");
387
490
  if (id === "guest-late") throw new Error("Someone else already answered this.");
491
+ const approval = this.#approvals.find((item) => item.id === id);
492
+ if (approval?.kind === "secret" && decision !== "deny")
493
+ throw new Error("Save or Allow in the vault\u2019s frame.");
388
494
  this.decisions.push([id, decision]);
495
+ if (approval?.kind === "secret" && this.#vault?.ask(id)) {
496
+ this.#quiet.add(id);
497
+ await this.#vault.cancel(id);
498
+ }
389
499
  this.end(id, "answered");
390
500
  }
391
501
  };
@@ -6351,7 +6461,8 @@ function vaultAudit(now2 = Date.now()) {
6351
6461
  secret: "HARBOR_API_KEY",
6352
6462
  from: ["proxy", "device:link-pier"],
6353
6463
  to: ["proxy", "tailscale", "device:link-pier"]
6354
- }
6464
+ },
6465
+ { kind: "reveal", field: "agent", from: true, to: false }
6355
6466
  ]
6356
6467
  },
6357
6468
  {
@@ -6750,6 +6861,151 @@ var DummyVaultButton = class {
6750
6861
  this.#emit({ event: "popup", intent: resolved, state: "done" });
6751
6862
  }
6752
6863
  };
6864
+ var DummyValueFrame = class {
6865
+ element;
6866
+ request;
6867
+ config = null;
6868
+ messages = [];
6869
+ arming = 500;
6870
+ #listeners = /* @__PURE__ */ new Set();
6871
+ #vault;
6872
+ #box;
6873
+ #view = "";
6874
+ #armed = false;
6875
+ #finished = false;
6876
+ #sizes = new ResizeObserver(() => this.#size());
6877
+ constructor(vault, time, request) {
6878
+ this.#vault = vault;
6879
+ this.request = request;
6880
+ this.element = document.createElement("div");
6881
+ this.element.dataset.vaultValue = "";
6882
+ this.#box = document.createElement("div");
6883
+ this.#box.style.padding = "2px";
6884
+ this.element.append(this.#box);
6885
+ this.element.addEventListener("keydown", (event) => {
6886
+ if (event.key !== "Escape" || event.isComposing) return;
6887
+ event.preventDefault();
6888
+ event.stopPropagation();
6889
+ this.#emit({ event: "escape" });
6890
+ });
6891
+ vault.on("status", () => this.#render());
6892
+ this.#sizes.observe(this.#box);
6893
+ void time.sleep(0).then(() => this.#emit({ event: "ready" }));
6894
+ }
6895
+ post(message) {
6896
+ this.messages.push(message);
6897
+ if (message.op === "focus") {
6898
+ this.#box.querySelector("input, swc-button")?.focus();
6899
+ return;
6900
+ }
6901
+ this.request = message.request;
6902
+ this.config = message.config;
6903
+ this.element.style.colorScheme = message.config.theme ?? "";
6904
+ this.#render();
6905
+ }
6906
+ listen(listener) {
6907
+ this.#listeners.add(listener);
6908
+ return () => this.#listeners.delete(listener);
6909
+ }
6910
+ #emit(event) {
6911
+ const message = { type: "slicc-vault", v: 1, ...event };
6912
+ for (const listener of [...this.#listeners]) listener(message);
6913
+ }
6914
+ #size() {
6915
+ const { width, height } = this.#box.getBoundingClientRect();
6916
+ this.#emit({ event: "size", width: Math.ceil(width), height: Math.ceil(height), inset: 2 });
6917
+ }
6918
+ #render() {
6919
+ if (!this.config || this.#finished) return;
6920
+ const ask2 = this.#vault.ask(this.request);
6921
+ if (!ask2) {
6922
+ this.#finished = true;
6923
+ this.#box.replaceChildren();
6924
+ this.#emit({ event: "gone", request: this.request, state: "cancelled" });
6925
+ return;
6926
+ }
6927
+ const view = this.#vault.status()?.locked ? "locked" : ask2.mode ?? "enter";
6928
+ if (view === this.#view) return;
6929
+ if (view !== "locked" && (this.#view === "" || this.#view === "locked"))
6930
+ setTimeout(() => {
6931
+ this.#armed = true;
6932
+ this.#arm();
6933
+ }, this.arming);
6934
+ this.#view = view;
6935
+ this.#box.replaceChildren(...this.#controls(view, ask2));
6936
+ this.#arm();
6937
+ this.#size();
6938
+ }
6939
+ #arm() {
6940
+ for (const button of this.#box.querySelectorAll("[data-action=save], [data-action=allow]"))
6941
+ button.toggleAttribute("disabled", !this.#armed);
6942
+ }
6943
+ #button(action, label2, variant, click) {
6944
+ const button = document.createElement("swc-button");
6945
+ button.dataset.action = action;
6946
+ button.setAttribute("variant", variant);
6947
+ button.textContent = label2;
6948
+ button.addEventListener("click", click);
6949
+ return button;
6950
+ }
6951
+ #controls(view, ask2) {
6952
+ const text = document.createElement("p");
6953
+ text.style.margin = "0 0 8px";
6954
+ const problem = document.createElement("p");
6955
+ problem.dataset.problem = "";
6956
+ problem.style.margin = "8px 0 0";
6957
+ if (view === "locked") {
6958
+ text.textContent = "Unlock the vault to answer.";
6959
+ return [
6960
+ text,
6961
+ this.#button("unlock", "Unlock", "accent", () => {
6962
+ void this.#vault.answer({ kind: "unlock" });
6963
+ })
6964
+ ];
6965
+ }
6966
+ if (view === "reveal") {
6967
+ text.textContent = `${ask2.requester?.exe} gets the plaintext of ${ask2.name}`;
6968
+ return [
6969
+ text,
6970
+ this.#button("allow", "Allow", "negative", () => void this.#answer(problem, "")),
6971
+ problem
6972
+ ];
6973
+ }
6974
+ const label2 = document.createElement("label");
6975
+ label2.textContent = `Value for ${ask2.name}`;
6976
+ label2.style.display = "grid";
6977
+ const input = document.createElement("input");
6978
+ input.type = "password";
6979
+ input.dataset.field = "value";
6980
+ input.autocomplete = "off";
6981
+ label2.append(input);
6982
+ text.append(label2);
6983
+ return [
6984
+ text,
6985
+ this.#button("save", "Save", "accent", () => {
6986
+ const value = input.value;
6987
+ input.value = "";
6988
+ void this.#answer(problem, value);
6989
+ }),
6990
+ problem
6991
+ ];
6992
+ }
6993
+ async #answer(problem, value) {
6994
+ const reveal = this.#view === "reveal";
6995
+ problem.textContent = "";
6996
+ this.#finished = true;
6997
+ try {
6998
+ if (reveal) await this.#vault.allow(this.request);
6999
+ else await this.#vault.seal(this.request, value);
7000
+ this.#emit({ event: "done", request: this.request, state: reveal ? "allowed" : "sealed" });
7001
+ } catch (error) {
7002
+ this.#finished = false;
7003
+ const { code, message } = error;
7004
+ if (code === "EINVAL") problem.textContent = message;
7005
+ else this.#emit({ event: "error", code, message });
7006
+ }
7007
+ }
7008
+ };
6753
7009
  var DummyVault = class extends Emitter {
6754
7010
  #status;
6755
7011
  #list;
@@ -6759,6 +7015,9 @@ var DummyVault = class extends Emitter {
6759
7015
  #older = vaultOlder();
6760
7016
  #serial = 0;
6761
7017
  buttons = [];
7018
+ frames = [];
7019
+ answered = /* @__PURE__ */ new Set();
7020
+ revealed = [];
6762
7021
  puts = [];
6763
7022
  endpointChanges = [];
6764
7023
  settingsCalls = [];
@@ -7033,6 +7292,55 @@ var DummyVault = class extends Emitter {
7033
7292
  this.buttons.push(button);
7034
7293
  return button;
7035
7294
  }
7295
+ valueFrame(request) {
7296
+ const frame = new DummyValueFrame(this, this.#time, request);
7297
+ this.frames.push(frame);
7298
+ return frame;
7299
+ }
7300
+ hold(request) {
7301
+ this.#status = {
7302
+ ...this.#status,
7303
+ waiting: [...this.#status.waiting.filter(({ id }) => id !== request.id), request]
7304
+ };
7305
+ this.emit("status", this.#status);
7306
+ }
7307
+ ask(request) {
7308
+ return this.#status.waiting.find(({ id, kind }) => id === request && kind === "ask");
7309
+ }
7310
+ #asked(request) {
7311
+ this.#unlocked();
7312
+ const found = this.ask(request);
7313
+ if (!found) throw vaultFailure("ENOENT", "That request is no longer waiting.");
7314
+ return found;
7315
+ }
7316
+ async seal(request, value) {
7317
+ await this.#time.sleep(5);
7318
+ const found = this.#asked(request);
7319
+ const problem = valueProblem(value);
7320
+ if (problem) throw vaultFailure("EINVAL", problem);
7321
+ this.answered.add(request);
7322
+ this.#take(request);
7323
+ return this.#seal(found.name, "settings");
7324
+ }
7325
+ async allow(request) {
7326
+ await this.#time.sleep(5);
7327
+ const found = this.#asked(request);
7328
+ this.answered.add(request);
7329
+ this.revealed.push(request);
7330
+ this.#take(request);
7331
+ const { exe, argv, cwd, kind } = found.requester;
7332
+ this.#log(found.name, {
7333
+ type: "reveal",
7334
+ uid: found.uid,
7335
+ tag: found.tag,
7336
+ exe,
7337
+ argv: argv.join(" "),
7338
+ cwd,
7339
+ requester: kind,
7340
+ request
7341
+ });
7342
+ this.emit("list", this.#list);
7343
+ }
7036
7344
  autoLabel() {
7037
7345
  return this.#status.locked ? "Vault locked" : "Vault unlocked";
7038
7346
  }
@@ -7175,6 +7483,7 @@ function createDummyModel({
7175
7483
  if (older === "paged") agent.paginate(6, 4);
7176
7484
  if (hold !== "off") agent.setHold({ reason: hold, held: 2 });
7177
7485
  const noticePort = noticesPort(agent, time, notices, readonly, arrival);
7486
+ const vaultPort = vault === "off" ? void 0 : new DummyVault(vault, time, vaultRecords(accounts2, mcp));
7178
7487
  if (scenario === "driving") void browser.run(drive);
7179
7488
  const base = {
7180
7489
  agent,
@@ -7217,11 +7526,9 @@ function createDummyModel({
7217
7526
  ...changes === "files" ? {} : { changes: new DummyChanges(files3, changes === "git") },
7218
7527
  ...noticePort ? { notices: noticePort } : {},
7219
7528
  ...tabs2 === "off" ? {} : { tabs: new DummyTabs(tabs2, time) },
7220
- ...approvals === "off" ? {} : { approvals: new DummyApprovals(approvals, time) },
7529
+ ...approvals === "off" ? {} : { approvals: new DummyApprovals(approvals, time, vaultPort) },
7221
7530
  ...follow === "off" ? {} : { follow: new DummyFollow(follow, time) },
7222
- ...vault === "off" ? {} : {
7223
- vault: new DummyVault(vault, time, vaultRecords(accounts2, mcp))
7224
- },
7531
+ ...vaultPort ? { vault: vaultPort } : {},
7225
7532
  ...guestsPort(guests, time)
7226
7533
  };
7227
7534
  base.vault?.on("status", (status2) => {
@@ -7244,6 +7551,7 @@ export {
7244
7551
  DummyNotices,
7245
7552
  DummyTabs,
7246
7553
  DummyUpdates,
7554
+ DummyValueFrame,
7247
7555
  DummyVault,
7248
7556
  DummyVaultButton,
7249
7557
  actionLimits,