@ai-ecoverse/slicc-spectrum 1.50.0 → 1.51.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/dist/slicc-ui.js CHANGED
@@ -21001,10 +21001,270 @@ var SliccApp = class extends ThemedElement {
21001
21001
  </main>
21002
21002
  ${rails.bottom}
21003
21003
  </div>
21004
+ <slicc-approvals .model=${this.model}></slicc-approvals>
21004
21005
  </sp-theme></div>`;
21005
21006
  }
21006
21007
  };
21007
21008
 
21009
+ // src/app/approvals.ts
21010
+ function countdown(ms) {
21011
+ const seconds = Math.max(0, Math.ceil(ms / 1e3));
21012
+ return `${Math.floor(seconds / 60)}:${String(seconds % 60).padStart(2, "0")}`;
21013
+ }
21014
+ function asking(approval) {
21015
+ const { kind, name, where: where2 } = approval.requester;
21016
+ const place2 = where2 ? ` in ${where2}` : "";
21017
+ return kind === "user" ? `You ran this with sudo${place2} (as ${name})` : `${name} wants to run this with sudo${place2}`;
21018
+ }
21019
+ function sudo(approval) {
21020
+ const as = approval.target && approval.target !== "root" ? `-u ${approval.target} ` : "";
21021
+ return `sudo ${as}${approval.command}`;
21022
+ }
21023
+ function waiting(model, now) {
21024
+ return [...model?.approvals?.list() ?? []].filter((approval) => approval.expiresAt > now).sort((a22, b13) => a22.createdAt - b13.createdAt);
21025
+ }
21026
+ function gone(reason) {
21027
+ return reason === "expired" ? "expired" : "elsewhere";
21028
+ }
21029
+ var SliccApprovals = class extends ModelElement {
21030
+ static properties = {
21031
+ ...ModelElement.properties,
21032
+ now: { state: true },
21033
+ busy: { state: true },
21034
+ failure: { state: true },
21035
+ gone: { state: true }
21036
+ };
21037
+ arming = 500;
21038
+ #armed = false;
21039
+ #arming;
21040
+ #showing = null;
21041
+ #pending = /* @__PURE__ */ new Set();
21042
+ #done = /* @__PURE__ */ new Set();
21043
+ #port;
21044
+ #ended = /* @__PURE__ */ new Map();
21045
+ #timer = null;
21046
+ #shown = "";
21047
+ #trigger = null;
21048
+ constructor() {
21049
+ super();
21050
+ this.now = Date.now();
21051
+ this.busy = "";
21052
+ this.failure = null;
21053
+ this.gone = null;
21054
+ }
21055
+ static styles = [
21056
+ dialogStyles,
21057
+ i`
21058
+ :host {
21059
+ display: contents;
21060
+ }
21061
+ .command {
21062
+ margin: var(--swc-spacing-200) 0 0;
21063
+ padding: var(--swc-spacing-200) var(--swc-spacing-300);
21064
+ border-radius: var(--swc-corner-radius-medium-default);
21065
+ background: var(--swc-gray-100);
21066
+ font-family: var(--swc-code-font-family-stack);
21067
+ font-size: var(--swc-font-size-100);
21068
+ white-space: pre-wrap;
21069
+ overflow-wrap: anywhere;
21070
+ max-block-size: 8lh;
21071
+ overflow: auto;
21072
+ }
21073
+ .cwd,
21074
+ .hint,
21075
+ .timer,
21076
+ .more {
21077
+ margin-block-start: var(--swc-spacing-100);
21078
+ color: var(--swc-neutral-subdued-content-color-default);
21079
+ font-size: var(--swc-font-size-75);
21080
+ }
21081
+ .cwd {
21082
+ overflow-wrap: anywhere;
21083
+ }
21084
+ .timer {
21085
+ margin-block-start: var(--swc-spacing-300);
21086
+ font-variant-numeric: tabular-nums;
21087
+ }
21088
+ .problem {
21089
+ display: flex;
21090
+ align-items: flex-start;
21091
+ gap: var(--swc-spacing-75);
21092
+ margin-block-start: var(--swc-spacing-200);
21093
+ color: var(--swc-negative-content-color-default);
21094
+ --swc-icon-color: var(--swc-negative-content-color-default);
21095
+ }
21096
+ `
21097
+ ];
21098
+ subscribe(model) {
21099
+ const port = model.approvals;
21100
+ if (port !== this.#port) {
21101
+ this.#port = port;
21102
+ this.#showing = null;
21103
+ this.gone = null;
21104
+ this.busy = "";
21105
+ this.failure = null;
21106
+ this.#pending.clear();
21107
+ this.#done.clear();
21108
+ this.#ended.clear();
21109
+ }
21110
+ if (!port) return [];
21111
+ return [
21112
+ port.on("approvals", () => {
21113
+ this.now = Date.now();
21114
+ this.requestUpdate();
21115
+ }),
21116
+ port.on("ended", ({ id, reason }) => {
21117
+ this.#ended.set(id, reason);
21118
+ if (this.gone?.approval.id === id) {
21119
+ this.gone = reason === "withdrawn" ? null : { ...this.gone, reason: gone(reason) };
21120
+ if (!this.gone) this.#showing = null;
21121
+ }
21122
+ this.requestUpdate();
21123
+ })
21124
+ ];
21125
+ }
21126
+ disconnectedCallback() {
21127
+ super.disconnectedCallback();
21128
+ this.#tick(false);
21129
+ clearTimeout(this.#arming);
21130
+ this.#showing = null;
21131
+ }
21132
+ #tick(on) {
21133
+ if (on && !this.#timer) this.#timer = setInterval(() => this.now = Date.now(), 1e3);
21134
+ if (!on && this.#timer) {
21135
+ clearInterval(this.#timer);
21136
+ this.#timer = null;
21137
+ }
21138
+ }
21139
+ async decide(approval, decision) {
21140
+ const port = this.model?.approvals;
21141
+ if (!port?.decide || this.busy || decision !== "deny" && !this.#armed) return;
21142
+ this.busy = decision;
21143
+ this.failure = null;
21144
+ this.#pending.add(approval.id);
21145
+ try {
21146
+ await port.decide(approval.id, decision);
21147
+ this.#done.add(approval.id);
21148
+ } catch (error) {
21149
+ this.failure = {
21150
+ id: approval.id,
21151
+ message: error instanceof Error ? error.message : String(error)
21152
+ };
21153
+ } finally {
21154
+ this.#pending.delete(approval.id);
21155
+ this.busy = "";
21156
+ }
21157
+ }
21158
+ #current() {
21159
+ return this.model?.approvals?.decide ? waiting(this.model, this.now) : [];
21160
+ }
21161
+ willUpdate(changed) {
21162
+ super.willUpdate(changed);
21163
+ const list3 = this.#current();
21164
+ const showing = this.#showing;
21165
+ if (showing && this.#pending.has(showing.id)) return;
21166
+ if (showing && !this.gone && !this.#done.has(showing.id) && !list3.some((approval) => approval.id === showing.id)) {
21167
+ const reason = this.#ended.get(showing.id) ?? (showing.expiresAt <= this.now ? "expired" : "answered");
21168
+ if (reason !== "withdrawn") this.gone = { approval: showing, reason: gone(reason) };
21169
+ }
21170
+ if (this.gone) return;
21171
+ const next = list3[0] ?? null;
21172
+ if (next?.id === showing?.id) return;
21173
+ this.#showing = next;
21174
+ clearTimeout(this.#arming);
21175
+ this.#armed = false;
21176
+ this.#arming = setTimeout(() => this.#armed = true, this.arming);
21177
+ }
21178
+ updated(changed) {
21179
+ super.updated(changed);
21180
+ const all2 = this.model?.approvals?.list() ?? [];
21181
+ this.#tick(this.isConnected && all2.length > 0);
21182
+ const dialog = this.renderRoot.querySelector("dialog");
21183
+ const key2 = dialog ? `${dialog.dataset.approval}:${dialog.dataset.gone ?? ""}` : "";
21184
+ if (key2 === this.#shown) return;
21185
+ if (dialog && !this.#shown) this.#trigger = focused();
21186
+ this.#shown = key2;
21187
+ if (!dialog) {
21188
+ if (this.#trigger?.isConnected) this.#trigger.focus();
21189
+ this.#trigger = null;
21190
+ return;
21191
+ }
21192
+ if (!dialog.open) dialog.showModal();
21193
+ const first2 = dialog.querySelector("[data-action=deny], [data-action=close]");
21194
+ void first2.updateComplete.then(() => first2.focus());
21195
+ }
21196
+ close() {
21197
+ this.gone = null;
21198
+ this.#showing = null;
21199
+ }
21200
+ #cancel(event2, approval) {
21201
+ event2.preventDefault();
21202
+ if (this.gone) this.close();
21203
+ else void this.decide(approval, "deny");
21204
+ }
21205
+ #button(approval, decision, label, variant) {
21206
+ return b`<swc-button
21207
+ slot="button"
21208
+ variant=${variant}
21209
+ fill-style=${variant === "accent" ? A : "outline"}
21210
+ data-action=${decision}
21211
+ ?pending=${this.busy === decision}
21212
+ ?disabled=${!!this.busy && this.busy !== decision}
21213
+ @click=${() => this.decide(approval, decision)}
21214
+ >${label}</swc-button
21215
+ >`;
21216
+ }
21217
+ #dialog(approval, key2, content) {
21218
+ return i17(
21219
+ key2,
21220
+ b`<dialog role="alertdialog" aria-labelledby="title" aria-describedby="asking" data-approval=${approval.id} data-kind=${approval.kind} data-requester=${approval.requester.kind} data-gone=${this.gone?.reason ?? A} @cancel=${(event2) => this.#cancel(event2, approval)}>${content}</dialog>`
21221
+ );
21222
+ }
21223
+ #request(approval) {
21224
+ return b`<p id="asking">${asking(approval)}:</p>
21225
+ <pre class="command" data-command>${sudo(approval)}</pre>`;
21226
+ }
21227
+ render() {
21228
+ if (this.gone) {
21229
+ const { approval: approval2, reason } = this.gone;
21230
+ return this.#dialog(
21231
+ approval2,
21232
+ `gone:${approval2.id}`,
21233
+ b`<sp-alert-dialog variant="information">
21234
+ <h2 id="title" slot="heading">${reason === "expired" ? "Sudo request timed out" : "Answered in another tab"}</h2>
21235
+ ${this.#request(approval2)}
21236
+ ${reason === "expired" ? b`<p class="timer" data-timer>Denied because nobody answered in time.</p>` : A}
21237
+ <swc-button slot="button" variant="secondary" fill-style="outline" data-action="close" @click=${() => this.close()}>Close</swc-button>
21238
+ </sp-alert-dialog>`
21239
+ );
21240
+ }
21241
+ const held = this.#showing && this.#pending.has(this.#showing.id) ? this.#showing : null;
21242
+ const list3 = this.#current();
21243
+ const [approval, ...rest] = held ? [held, ...list3.filter(({ id }) => id !== held.id)] : list3;
21244
+ if (!approval) return A;
21245
+ const failure2 = this.failure?.id === approval.id ? this.failure.message : "";
21246
+ return this.#dialog(
21247
+ approval,
21248
+ approval.id,
21249
+ b`<sp-alert-dialog variant="warning">
21250
+ <h2 id="title" slot="heading">Allow sudo?</h2>
21251
+ ${this.#request(approval)}
21252
+ ${approval.cwd ? b`<p class="cwd">In ${approval.cwd}</p>` : A}
21253
+ ${approval.always ? b`<p class="hint">Always allow lets ${approval.requester.name} run exactly this command with sudo without asking again.</p>` : A}
21254
+ ${failure2 ? b`<p class="problem" role="alert" data-error><swc-icon-alert-triangle size="s" aria-hidden="true"></swc-icon-alert-triangle><span>${failure2}</span></p>` : A}
21255
+ <p class="timer" data-timer>Denied automatically in ${countdown(approval.expiresAt - this.now)}.</p>
21256
+ ${rest.length ? b`<p class="more" data-more>${rest.length === 1 ? "1 more request is waiting." : `${rest.length} more requests are waiting.`}</p>` : A}
21257
+ ${this.#button(approval, "deny", "Deny", "secondary")}
21258
+ ${approval.always ? this.#button(approval, "always", "Always allow", "secondary") : A}
21259
+ ${this.#button(approval, "once", "Allow once", "accent")}
21260
+ </sp-alert-dialog>`
21261
+ );
21262
+ }
21263
+ };
21264
+ function deadline(at) {
21265
+ return new Date(at).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
21266
+ }
21267
+
21008
21268
  // node_modules/@adobe/spectrum-wc/dist/components/close-button/close-button.js
21009
21269
  var t77 = i`:host{display:inline-flex;vertical-align:top}*{box-sizing:border-box}.swc-CloseButton{--_swc-close-button-border-width: 2px;-webkit-tap-highlight-color:transparent;display:inline-flex;position:relative;align-items:center;justify-content:center;inline-size:var(--swc-close-button-size, var(--swc-component-height-100));block-size:var(--swc-close-button-size, var(--swc-component-height-100));padding:0;margin:0;color:var(--swc-close-button-icon-color-default, var(--swc-gray-800));background-color:var(--swc-close-button-background-color-default, transparent);border-color:transparent;border-style:solid;border-width:var(--_swc-close-button-border-width);border-radius:var(--swc-close-button-border-radius, 50%);overflow:visible;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;outline:0 solid transparent;transition-timing-function:cubic-bezier(.45,0,.4,1);transition-duration:.13s;transition-property:outline,border-color,color,background-color,transform}.swc-CloseButton:focus-visible{color:var(--swc-close-button-icon-color-focus, var(--swc-gray-900));background-color:var(--swc-close-button-background-color-focus, var(--swc-gray-100));outline:var(--swc-close-button-focus-indicator-thickness, 2px) solid var(--swc-close-button-focus-indicator-color, var(--swc-blue-800));outline-offset:var(--swc-close-button-focus-indicator-gap, 2px)}.swc-CloseButton:hover{color:var(--swc-close-button-icon-color-hover, var(--swc-gray-900));background-color:var(--swc-close-button-background-color-hover, var(--swc-gray-100))}.swc-CloseButton:active{color:var(--swc-close-button-icon-color-down, var(--swc-gray-900));background-color:var(--swc-close-button-background-color-down, var(--swc-gray-100));transform:perspective(var(--swc-close-button-size, var(--swc-component-height-100))) translateZ(-2px);will-change:transform}.swc-CloseButton:disabled:is(*,:hover){color:var(--swc-close-button-icon-color-disabled, var(--swc-gray-400));background-color:var(--swc-close-button-background-color-default, transparent);cursor:default;transform:none}.swc-CloseButton-icon{display:block;flex-shrink:0;inline-size:var(--swc-close-button-icon-size, var(--swc-ui-icon-medium));block-size:var(--swc-close-button-icon-size, var(--swc-ui-icon-medium))}.swc-CloseButton-icon swc-ui-icon{display:block;inline-size:100%;block-size:100%}:host([size=\"s\"]){--swc-close-button-icon-size: var(--swc-ui-icon-small);--swc-close-button-size: var(--swc-component-height-75)}:host([size=\"m\"]){--swc-close-button-icon-size: var(--swc-ui-icon-medium);--swc-close-button-size: var(--swc-component-height-100)}:host([size=\"l\"]){--swc-close-button-icon-size: var(--swc-ui-icon-large);--swc-close-button-size: var(--swc-component-height-200)}:host([size=\"xl\"]){--swc-close-button-icon-size: var(--swc-ui-icon-extra-large);--swc-close-button-size: var(--swc-component-height-300)}:host([static-color=\"white\"]){--swc-close-button-background-color-default: transparent;--swc-close-button-background-color-down: rgba(255, 255, 255, .11);--swc-close-button-background-color-focus: rgba(255, 255, 255, .11);--swc-close-button-background-color-hover: rgba(255, 255, 255, .11);--swc-close-button-focus-indicator-color: var(--swc-white);--swc-close-button-icon-color-default: rgba(255, 255, 255, .85);--swc-close-button-icon-color-disabled: var(--swc-transparent-white-100);--swc-close-button-icon-color-down: rgba(255, 255, 255, .94);--swc-close-button-icon-color-focus: rgba(255, 255, 255, .94);--swc-close-button-icon-color-hover: rgba(255, 255, 255, .94)}:host([static-color=\"black\"]){--swc-close-button-background-color-default: transparent;--swc-close-button-background-color-down: rgba(0, 0, 0, .09);--swc-close-button-background-color-focus: rgba(0, 0, 0, .09);--swc-close-button-background-color-hover: rgba(0, 0, 0, .09);--swc-close-button-focus-indicator-color: var(--swc-black);--swc-close-button-icon-color-default: rgba(0, 0, 0, .84);--swc-close-button-icon-color-disabled: var(--swc-transparent-black-100);--swc-close-button-icon-color-down: rgba(0, 0, 0, .93);--swc-close-button-icon-color-focus: rgba(0, 0, 0, .93);--swc-close-button-icon-color-hover: rgba(0, 0, 0, .93)}`;
21010
21270
 
@@ -25396,6 +25656,10 @@ var SliccNotices = class extends ModelElement {
25396
25656
  p {
25397
25657
  margin: 0;
25398
25658
  }
25659
+ code {
25660
+ font-family: var(--swc-code-font-family-stack);
25661
+ overflow-wrap: anywhere;
25662
+ }
25399
25663
  .actions {
25400
25664
  display: flex;
25401
25665
  flex-wrap: wrap;
@@ -25419,7 +25683,10 @@ var SliccNotices = class extends ModelElement {
25419
25683
  this.pending = /* @__PURE__ */ new Map();
25420
25684
  this.failures = /* @__PURE__ */ new Map();
25421
25685
  }
25422
- const tabs = model.tabs ? [model.tabs.on("tabs", () => this.requestUpdate())] : [];
25686
+ const tabs = [
25687
+ ...model.tabs ? [model.tabs.on("tabs", () => this.requestUpdate())] : [],
25688
+ ...model.approvals ? [model.approvals.on("approvals", () => this.requestUpdate())] : []
25689
+ ];
25423
25690
  if (!port) return tabs;
25424
25691
  return [
25425
25692
  ...tabs,
@@ -25484,12 +25751,23 @@ var SliccNotices = class extends ModelElement {
25484
25751
  </div>
25485
25752
  </div>`;
25486
25753
  }
25754
+ #approval(approval) {
25755
+ return b`<div class="notice" role="status" data-approval-notice=${approval.id} data-tone="warning">
25756
+ <swc-icon-alert-triangle></swc-icon-alert-triangle>
25757
+ <div class="text">
25758
+ <span class="title">Waiting for sudo approval</span>
25759
+ <p>${asking(approval)}: <code>${sudo(approval)}</code></p>
25760
+ <p>Answer it in the tab running SLICC. Denied automatically at ${deadline(approval.expiresAt)}.</p>
25761
+ </div>
25762
+ </div>`;
25763
+ }
25487
25764
  render() {
25488
25765
  const port = this.model?.notices;
25489
25766
  const notices = (port?.list() ?? []).filter((notice) => !this.dismissed.has(notice.id));
25490
25767
  const tabs = this.#tabs();
25491
- if (!notices.length && tabs === A) return A;
25492
- return b`<div class="notices">${tabs}${c(
25768
+ const approvals = this.model?.approvals?.decide ? [] : waiting(this.model, Date.now());
25769
+ if (!notices.length && !approvals.length && tabs === A) return A;
25770
+ return b`<div class="notices">${tabs}${approvals.map((approval) => this.#approval(approval))}${c(
25493
25771
  notices,
25494
25772
  (notice) => notice.id,
25495
25773
  (notice) => this.#notice(notice)
@@ -77732,6 +78010,7 @@ var elements = {
77732
78010
  "slicc-updates": SliccUpdates,
77733
78011
  "slicc-network": SliccNetwork,
77734
78012
  "slicc-notices": SliccNotices,
78013
+ "slicc-approvals": SliccApprovals,
77735
78014
  "slicc-confirm": SliccConfirm,
77736
78015
  "slicc-prompt": SliccPrompt,
77737
78016
  "slicc-app": SliccApp
@@ -77750,6 +78029,7 @@ export {
77750
78029
  Emitter,
77751
78030
  SliccAgents,
77752
78031
  SliccApp,
78032
+ SliccApprovals,
77753
78033
  SliccBrowser,
77754
78034
  SliccChanges,
77755
78035
  SliccChat,