openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
845 lines (793 loc) • 34.7 kB
JavaScript
import{d as e,f as t,g as n,i as r,l as i,m as a,p as o,t as s}from"./lit-runtime-BImxIzGR.js";import{p as c,v as l}from"./index-B6YtUsIL.js";var u=class extends t{constructor(...e){super(...e),this.files=[],this.activePath=``,this.query=``,this.label=`Support files`,this.listLabel=`Files`,this.searchPlaceholder=`Search files...`,this.contextLabel=``,this.readOnlyLabel=`read-only`,this.emptyTitle=`No files match`,this.emptySubtitle=`Try another file name or content search.`,this.handleQueryInput=e=>{let t=e.target.value??``;this.dispatchEvent(new CustomEvent(`file-preview-query-change`,{bubbles:!0,composed:!0,detail:t}))},this.preventItemPointerFocus=e=>{e.preventDefault()},this.handleKeydown=e=>{switch(e.key){case`Escape`:e.preventDefault(),e.stopPropagation(),this.emitClose();return;case`ArrowDown`:this.moveSelection(1,e);return;case`ArrowUp`:this.moveSelection(-1,e);default:}},this.emitClose=()=>{this.dispatchEvent(new CustomEvent(`file-preview-close`,{bubbles:!0,composed:!0}))}}static{this.styles=n`
:host {
position: fixed;
inset: 0;
z-index: 50;
display: block;
}
.backdrop {
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.6);
backdrop-filter: blur(6px);
animation: fade 140ms ease-out;
}
@keyframes fade {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes pop {
from {
transform: translate(-50%, -48%) scale(0.97);
opacity: 0;
}
to {
transform: translate(-50%, -50%) scale(1);
opacity: 1;
}
}
.modal {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: min(1100px, 92vw);
height: min(780px, 86vh);
background: var(--bg);
border: 1px solid var(--border-strong);
border-radius: var(--radius-lg);
box-shadow: 0 24px 80px rgba(0, 0, 0, 0.6);
display: flex;
flex-direction: column;
overflow: hidden;
animation: pop 160ms ease-out;
}
.head {
display: flex;
align-items: center;
gap: 12px;
padding: 16px 20px;
border-bottom: 1px solid var(--border);
background: var(--bg);
}
.search-icon {
color: var(--muted);
font-size: 18px;
}
.search {
flex: 1;
background: transparent;
border: none;
outline: none;
color: var(--text-strong);
font: inherit;
font-size: 18px;
font-weight: 400;
padding: 4px 0;
}
.search:focus,
.search:focus-visible {
outline: none;
border: none;
box-shadow: none;
}
.search::placeholder {
color: var(--muted);
}
.state {
display: inline-flex;
align-items: center;
gap: 8px;
font-size: 12px;
color: var(--muted);
padding: 5px 10px;
border: 1px solid var(--border);
border-radius: var(--radius-md);
background: var(--bg-elevated);
}
.esc,
.kbd {
font-family: var(--mono);
border: 1px solid var(--border);
color: var(--muted);
}
.esc {
font-size: 10px;
padding: 1px 5px;
border-radius: 3px;
background: var(--bg);
}
.body {
flex: 1;
display: grid;
grid-template-columns: 360px 1fr;
min-height: 0;
}
.list {
border-right: 1px solid var(--border);
padding: 14px 10px;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 2px;
}
.list-section {
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--muted);
padding: 4px 12px 8px;
}
.item {
display: grid;
grid-template-columns: 16px 1fr auto;
gap: 12px;
align-items: center;
padding: 12px 14px;
border-radius: var(--radius-md);
border: none;
background: transparent;
color: var(--text);
cursor: pointer;
font: inherit;
outline: none;
text-align: left;
}
.item:focus-visible {
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 55%, transparent);
}
.item:hover {
background: var(--bg-elevated);
}
.item.is-active {
background: var(--accent-subtle);
}
.item.is-active .item-name {
color: var(--text-strong);
}
.item-icon {
width: 16px;
height: 16px;
display: inline-flex;
align-items: center;
justify-content: center;
color: var(--muted);
opacity: 0.85;
}
.item.is-active .item-icon {
color: var(--accent);
opacity: 1;
}
.item-icon svg {
width: 16px;
height: 16px;
stroke: currentColor;
fill: none;
stroke-width: 1.5px;
stroke-linecap: round;
stroke-linejoin: round;
}
.item-name {
font-family: var(--mono);
font-size: 14px;
color: var(--text);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.item-meta {
color: var(--muted);
font-size: 12px;
}
.empty-list {
color: var(--muted);
font-size: 13px;
padding: 12px;
}
.detail {
display: flex;
flex-direction: column;
min-width: 0;
min-height: 0;
}
.detail.empty {
align-items: center;
justify-content: center;
text-align: center;
padding: 24px;
}
.detail-head {
padding: 20px 24px 14px;
border-bottom: 1px solid var(--border);
}
.title {
margin: 0 0 10px;
font-family: var(--mono);
font-size: 22px;
color: var(--text-strong);
font-weight: 700;
letter-spacing: -0.01em;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.chips {
display: flex;
gap: 6px;
flex-wrap: wrap;
}
.chip {
display: inline-flex;
align-items: center;
padding: 3px 10px;
border-radius: 999px;
font-size: 11.5px;
background: var(--bg-elevated);
border: 1px solid var(--border);
color: var(--muted);
}
.chip.accent {
background: var(--accent-subtle);
border-color: color-mix(in srgb, var(--accent) 30%, transparent);
color: var(--accent);
}
.chip.ok {
background: color-mix(in srgb, var(--ok) 12%, transparent);
border-color: color-mix(in srgb, var(--ok) 30%, transparent);
color: var(--ok);
}
.detail-body {
flex: 1;
overflow: auto;
padding: 20px 24px 24px;
}
.pre {
margin: 0;
font-family: var(--mono);
font-size: 13px;
line-height: 1.7;
color: var(--text);
background: transparent;
border: none;
white-space: pre-wrap;
word-break: break-word;
}
.foot {
display: flex;
align-items: center;
gap: 18px;
padding: 12px 20px;
border-top: 1px solid var(--border);
background: var(--bg);
font-size: 12px;
color: var(--muted);
}
.foot-group {
display: inline-flex;
align-items: center;
gap: 6px;
}
.kbd {
font-size: 10.5px;
padding: 2px 6px;
border-radius: 4px;
background: var(--bg-elevated);
color: var(--text);
}
.spacer {
flex: 1;
}
.button {
height: 36px;
padding: 0 14px;
border-radius: var(--radius-md);
border: 1px solid var(--border);
background: var(--bg-elevated);
color: var(--text);
font-weight: 600;
cursor: pointer;
}
.button:hover {
border-color: var(--border-strong);
color: var(--text-strong);
}
.empty-title {
font-size: 16px;
font-weight: 600;
color: var(--text-strong);
margin: 0 0 8px;
}
.empty-subtitle {
margin: 0;
font-size: 13px;
color: var(--muted);
max-width: 380px;
}
`}render(){let e=this.filterFiles(),t=this.resolveActiveFile(e),n=e.length===this.files.length?`${this.files.length} files`:`${e.length}/${this.files.length} files`;return a`
<div class="backdrop" @click=${this.emitClose}></div>
<div
class="modal"
role="dialog"
aria-label=${this.label}
aria-modal="true"
tabindex="-1"
@keydown=${this.handleKeydown}
>
<header class="head">
<span class="search-icon">⌕</span>
<input
class="search"
placeholder=${this.searchPlaceholder}
.value=${this.query}
@input=${this.handleQueryInput}
autofocus
/>
<span class="state">${n} <span class="esc">esc</span></span>
</header>
<div class="body">
<aside class="list">
<div class="list-section">${this.listLabel} · ${e.length}</div>
${e.length===0?a`<div class="empty-list">No files match.</div>`:e.map(e=>a`
<button
class="item ${e.path===t?.path?`is-active`:``}"
@pointerdown=${this.preventItemPointerFocus}
@mousedown=${this.preventItemPointerFocus}
@click=${()=>this.emitSelect(e.path)}
>
<span class="item-icon">${p(e.path)}</span>
<span class="item-name">${e.path}</span>
<span class="item-meta">${e.size}</span>
</button>
`)}
</aside>
${t?this.renderFile(t):this.renderEmpty()}
</div>
<footer class="foot">
<span class="foot-group"><span class="kbd">↑↓</span> navigate</span>
<span class="spacer"></span>
<button class="button" @click=${this.emitClose}>
Close <span class="kbd">esc</span>
</button>
</footer>
</div>
`}renderFile(e){return a`
<section class="detail">
<div class="detail-head">
<h2 class="title">${e.path}</h2>
<div class="chips">
<span class="chip accent">${d(e.path)}</span>
<span class="chip">${e.size}</span>
<span class="chip">${this.readOnlyLabel}</span>
${this.contextLabel?a`<span class="chip ok">${this.contextLabel}</span>`:``}
</div>
</div>
<div class="detail-body">
<pre class="pre">${e.contents}</pre>
</div>
</section>
`}renderEmpty(){return a`
<section class="detail empty">
<p class="empty-title">${this.emptyTitle}</p>
<p class="empty-subtitle">${this.emptySubtitle}</p>
</section>
`}filterFiles(){let e=this.query.trim().toLowerCase();return e?this.files.filter(t=>`${t.path}\n${t.contents}`.toLowerCase().includes(e)):this.files}resolveActiveFile(e){return e.find(e=>e.path===this.activePath)??e[0]}firstUpdated(){this.focusModal()}updated(e){(e.has(`activePath`)||e.has(`query`)||e.has(`files`))&&this.scrollActiveFileIntoView()}focusModal(){(this.searchInput??this.shadowRoot?.querySelector(`.modal`))?.focus({preventScroll:!0})}moveSelection(e,t){t.preventDefault(),t.stopPropagation();let n=this.filterFiles();if(n.length===0)return;let r=this.resolveActiveFile(n),i=r?n.findIndex(e=>e.path===r.path):-1,a=n[Math.max(0,Math.min(n.length-1,i+e))];a&&a.path!==r?.path&&this.emitSelect(a.path)}scrollActiveFileIntoView(){this.updateComplete.then(()=>{this.isConnected&&this.shadowRoot?.querySelector(`.item.is-active`)?.scrollIntoView({block:`nearest`})}).catch(()=>{})}emitSelect(e){this.dispatchEvent(new CustomEvent(`file-preview-select`,{bubbles:!0,composed:!0,detail:e})),this.focusModal()}};c([e({attribute:!1})],u.prototype,`files`,void 0),c([e()],u.prototype,`activePath`,void 0),c([e()],u.prototype,`query`,void 0),c([e()],u.prototype,`label`,void 0),c([e()],u.prototype,`listLabel`,void 0),c([e()],u.prototype,`searchPlaceholder`,void 0),c([e()],u.prototype,`contextLabel`,void 0),c([e()],u.prototype,`readOnlyLabel`,void 0),c([e()],u.prototype,`emptyTitle`,void 0),c([e()],u.prototype,`emptySubtitle`,void 0),c([i(`.search`)],u.prototype,`searchInput`,void 0);function d(e){let t=e.split(`.`).pop()?.toLowerCase()??``;return{md:`Markdown`,txt:`Text`,json:`JSON`,yaml:`YAML`,yml:`YAML`,ts:`TypeScript`,js:`JavaScript`,py:`Python`,sh:`Shell`}[t]??(t?t.toUpperCase():`File`)}customElements.get(`openclaw-file-preview-modal`)||customElements.define(`openclaw-file-preview-modal`,u);var f=new Set(`ts.tsx.js.jsx.mjs.cjs.py.sh.bash.zsh.rb.go.rs.java.kt.swift.c.cc.cpp.h.hpp.json.yaml.yml.toml.xml.html.css.scss.sql`.split(`.`));function p(e){let t=e.split(`.`).pop()?.toLowerCase()??``;return f.has(t)?l.fileCode:l.fileText}var m=[`all`,`pending`,`applied`,`rejected`,`quarantined`,`stale`],h={all:`All`,pending:`Pending`,applied:`Applied`,rejected:`Rejected`,quarantined:`Quarantined`,stale:`Stale`},g=3,_=120,v={today:`Today`,yesterday:`Yesterday`,earlier:`Earlier this week`};function y(e){let t=q(e.proposals,e.statusFilter,e.query),n=t.find(t=>t.key===e.selectedKey)??t[0],i=J(t),s=n&&e.filePreviewKey?n.supportFiles.find(t=>t.path===e.filePreviewKey):null,c=e.revisionKey?e.proposals.find(t=>t.key===e.revisionKey):null,l=e.proposals.filter(e=>e.status===`pending`),u=n??l[0]??e.proposals[0],d=e.proposals.length===0&&!e.loading&&!e.error?P(e):e.mode===`today`?F(e,u,l):x(e,t,i,n);return a`
<section class="skill-workshop sw-mode-${e.mode}">
${e.error?a`<div class="sw-error" role="status">${e.error}</div>`:o}
<div class="sw-view" data-mode=${e.mode}>
${r(e.mode,a`<div class="sw-view__pane">${d}</div>`)}
</div>
</section>
${s&&n?a`
<openclaw-file-preview-modal
.files=${n.supportFiles}
.activePath=${s.path}
.query=${e.filePreviewQuery}
.contextLabel=${`in ${n.slug}`}
@file-preview-query-change=${t=>e.onFilePreviewQueryChange(t.detail)}
@file-preview-select=${t=>e.onPreviewFile(n.key,t.detail)}
@file-preview-close=${e.onClosePreview}
></openclaw-file-preview-modal>
`:o}
${c?b(e,c):o}
`}function b(e,t){let n=e.actionBusy?.key===t.key&&e.actionBusy.action===`revise`,r=e.revisionDraft.trim().length>0&&!e.actionBusy,i=e.mode===`board`?`Revise`:`Tweak`;return a`
<div class="sw-revision-backdrop" role="presentation" @click=${e.onRevisionCancel}>
<section
class="sw-revision-dialog ${n?`sw-revision-dialog--sending`:``}"
role="dialog"
aria-modal="true"
aria-labelledby="sw-revision-title"
@click=${e=>e.stopPropagation()}
>
<div class="sw-revision-dialog__head">
<div>
<div class="sw-revision-dialog__eyebrow">${i} proposal</div>
<h2 id="sw-revision-title">${t.slug}</h2>
</div>
<button
class="sw-revision-dialog__close"
title="Close"
aria-label="Close"
?disabled=${!!e.actionBusy}
@click=${e.onRevisionCancel}
>
×
</button>
</div>
<p class="sw-revision-dialog__copy">
Tell the agent what should change. The proposal stays pending and the workshop will create
a revised version.
</p>
<textarea
class="sw-revision-dialog__input"
autofocus
placeholder="Example: Make this use Gmail labels instead of unread search, and add a safer dry-run step."
.value=${e.revisionDraft}
?disabled=${!!e.actionBusy}
@input=${t=>e.onRevisionDraftChange(t.target.value??``)}
></textarea>
${n?a`
<div class="sw-revision-dialog__status" role="status">
<span class="sw-revision-dialog__status-dot" aria-hidden="true"></span>
<span>Preparing revision handoff</span>
</div>
`:o}
<div class="sw-revision-dialog__actions">
<button
class="sw-btn sw-btn--ghost"
?disabled=${!!e.actionBusy}
@click=${e.onRevisionCancel}
>
Cancel
</button>
<button
class="sw-btn sw-btn--primary ${n?`is-busy`:``}"
?disabled=${!r}
@click=${()=>e.onRevisionSubmit(t.key)}
>
${n?`Sending…`:`Send revision`}
</button>
</div>
</section>
</div>
`}function x(e,t,n,r){return a`
${T(e)}
<div class="sw-triage" style=${s({"--sw-queue-width":`${e.queueWidth}px`})}>
${E(e,n,r)} ${S(e)}
${r?O(e,r):j(e)}
</div>
`}function S(e){return a`
<div
class="sw-queue-resizer"
role="separator"
aria-label="Resize proposal list"
aria-orientation="vertical"
tabindex="0"
@pointerdown=${t=>C(t,e)}
@keydown=${t=>w(t,e)}
></div>
`}function C(e,t){e.preventDefault(),e.stopPropagation();let n=e.clientX,r=t.queueWidth,i=document.body,a=i.style.cursor,o=i.style.userSelect;i.style.cursor=`col-resize`,i.style.userSelect=`none`;let s=()=>{window.removeEventListener(`pointermove`,c),window.removeEventListener(`pointerup`,l),window.removeEventListener(`pointercancel`,l),i.style.cursor=a,i.style.userSelect=o},c=e=>{t.onQueueWidthChange(r+e.clientX-n)},l=()=>{s()};window.addEventListener(`pointermove`,c),window.addEventListener(`pointerup`,l),window.addEventListener(`pointercancel`,l)}function w(e,t){if(e.key!==`ArrowLeft`&&e.key!==`ArrowRight`)return;e.preventDefault();let n=e.key===`ArrowLeft`?-24:24;t.onQueueWidthChange(t.queueWidth+n)}function T(e){return a`
<div class="sw-lifecycle-tabs">
${m.map(t=>{let n=e.statusFilter===t,r=e.counts[t]??0;return a`
<button
class="sw-lifecycle-tab ${n?`is-active`:``}"
@click=${()=>e.onStatusFilterChange(t)}
>
${h[t]} <span class="sw-lifecycle-tab__count">${r}</span>
</button>
`})}
</div>
`}function E(e,t,n){let r=t.reduce((e,t)=>e+t.items.length,0);return a`
<aside class="sw-queue">
<div class="sw-queue__search">
<input
placeholder="Search proposals…"
.value=${e.query}
@input=${t=>e.onQueryChange(t.target.value??``)}
/>
</div>
<div class="sw-queue__body">
${r===0?a`<div class="sw-queue__empty">${Y(e)}</div>`:t.map(t=>a`
<div class="sw-queue__group">
${t.label} <span class="sw-queue__group-pill">${t.items.length}</span>
</div>
${t.items.map(t=>D(e,t,n))}
`)}
</div>
</aside>
`}function D(e,t,n){let r=n?.key===t.key;return a`
<button
class="sw-row ${t.isNew?`is-new`:`is-seen`} ${r?`is-selected`:``}"
@click=${()=>e.onSelect(t.key)}
>
<span class="sw-row__dot"></span>
<span>
<span class="sw-row__title">${t.name}</span>
<span class="sw-row__desc">${t.oneLine}</span>
</span>
<span class="sw-row__meta">${t.ageLabel}</span>
</button>
`}function O(e,t){let n=t.updatedAt&&t.updatedAt>t.createdAt?t.updatedAt:null,r=n?`Edited ${X(n)}`:`Created ${X(t.createdAt)}`,i=e.inspectingKey===t.key&&!t.body;return a`
<div class="sw-detail">
<div class="sw-detail__head">
<div class="sw-detail__head-left">
<h1 class="sw-detail__title">${t.name}</h1>
<div class="sw-detail__one-line">${t.oneLine}</div>
<div class="sw-detail__meta">
<span>${r}</span>
<span>·</span>
<span>v${t.version}</span>
<span>·</span>
${t.supportFiles.length>0?a`<button
class="sw-detail__meta-link"
@click=${()=>e.onPreviewFile(t.key,t.supportFiles[0].path)}
>
${t.supportFiles.length} support files
</button>`:a`<span>0 support files</span>`}
</div>
</div>
<div class="sw-detail__nav">
<button title="Previous" @click=${e.onPrev}>↑</button>
<button title="Next" @click=${e.onNext}>↓</button>
</div>
</div>
<div class="sw-detail__body">
<div class="sw-body-card">
<h1>${t.slug}</h1>
${i?a`<p class="sw-muted">Loading proposal…</p>`:G(t.body)}
</div>
${t.supportFiles.length>0?a`
<div class="sw-section" style="margin-top: 18px;">
<h3 class="sw-section__label">Support files</h3>
<div class="sw-files">
${t.supportFiles.map(n=>a`
<button
class="sw-file"
@click=${()=>e.onPreviewFile(t.key,n.path)}
>
<span>📄</span>
<span class="sw-file__name">${n.path}</span>
<span class="sw-file__size"
>${n.size} <span class="sw-file__hint">· click to preview</span></span
>
</button>
`)}
</div>
</div>
`:o}
</div>
${e.actionNotice?.key===t.key?k(e.actionNotice):o}
${t.status===`pending`?A(e,t):o}
</div>
`}function k(e){return a`
<div class="sw-action-toast" role="status" aria-live="polite">
<span>${e.label}</span>
<strong>${e.slug}</strong>
<span>·</span>
</div>
`}function A(e,t){let n=e.actionBusy?.key===t.key?e.actionBusy.action:null,r=!!e.actionBusy;return a`
<div class="sw-action-bar" aria-busy=${n?`true`:`false`}>
<button
class="sw-btn sw-btn--primary ${n===`apply`?`is-busy`:``}"
?disabled=${r}
@click=${()=>e.onApply(t.key)}
>
${n===`apply`?`Applying…`:`Apply`}
</button>
<button
class="sw-btn ${n===`revise`?`is-busy`:``}"
?disabled=${r}
@click=${()=>e.onRevise(t.key)}
>
${n===`revise`?`Opening…`:`Revise`}
</button>
<button
class="sw-btn sw-btn--ghost sw-btn--danger ${n===`reject`?`is-busy`:``}"
?disabled=${r}
@click=${()=>e.onReject(t.key)}
>
${n===`reject`?`Rejecting…`:`Reject`}
</button>
</div>
`}function j(e){let t=M(e);return a`
<div class="sw-detail sw-detail--empty">
<div class="sw-filter-empty">
<div class="sw-filter-empty__icon" aria-hidden="true">
${N(t.icon)}
</div>
<p class="sw-empty__title">${t.title}</p>
<p class="sw-empty__sub">${t.body}</p>
</div>
</div>
`}function M(e){if(e.query.trim())return{icon:`search`,title:`No matching proposals`,body:`Clear the search or try a different keyword.`};switch(e.statusFilter){case`pending`:return{icon:`clock`,title:`No pending proposals`,body:`New drafts will appear here when they need review.`};case`applied`:return{icon:`check`,title:`Nothing applied yet`,body:`Use a pending proposal and it will appear here as a live skill.`};case`rejected`:return{icon:`x`,title:`No rejected proposals`,body:`Skipped proposals will stay here for a clean review history.`};case`quarantined`:return{icon:`shield`,title:`Nothing quarantined`,body:`Scanner-blocked or safety-held proposals will appear here.`};case`stale`:return{icon:`refresh`,title:`No stale proposals`,body:`Proposals that can no longer apply cleanly will appear here.`};case`all`:return{icon:`search`,title:`No proposals here`,body:`Skill Workshop proposals will appear here when your agent drafts them.`}}return{icon:`search`,title:`No proposals here`,body:`Skill Workshop proposals will appear here when your agent drafts them.`}}function N(e){switch(e){case`clock`:return a`
<svg viewBox="0 0 24 24">
<circle cx="12" cy="12" r="8"></circle>
<path d="M12 7v5l3 2"></path>
</svg>
`;case`check`:return a`
<svg viewBox="0 0 24 24">
<path d="M5 12.5l4 4L19 7"></path>
</svg>
`;case`x`:return a`
<svg viewBox="0 0 24 24">
<path d="M7 7l10 10"></path>
<path d="M17 7L7 17"></path>
</svg>
`;case`shield`:return a`
<svg viewBox="0 0 24 24">
<path d="M12 3l7 3v5c0 4.2-2.8 7.8-7 10-4.2-2.2-7-5.8-7-10V6l7-3z"></path>
<path d="M9 12l2 2 4-5"></path>
</svg>
`;case`refresh`:return a`
<svg viewBox="0 0 24 24">
<path d="M17 2v5h-5"></path>
<path d="M7 22v-5h5"></path>
<path d="M19 10a7 7 0 0 0-12-4l-2 2"></path>
<path d="M5 14a7 7 0 0 0 12 4l2-2"></path>
</svg>
`;case`search`:return a`
<svg viewBox="0 0 24 24">
<circle cx="11" cy="11" r="6"></circle>
<path d="M16 16l4 4"></path>
</svg>
`}return o}function P(e){return a`
<div class="sw-empty-state">
<section class="sw-empty-state__panel" aria-label="No Skill Workshop proposals">
<div class="sw-empty-state__glyph" aria-hidden="true">
<span></span>
<span></span>
<span></span>
</div>
<p class="sw-empty-state__eyebrow">Skill Workshop</p>
<h2>No proposals yet</h2>
<p>${e.assistantName.trim()||`Your agent`} hasn't drafted any skill proposals.</p>
<div class="sw-empty-state__footer">New proposals will appear here for review.</div>
</section>
</div>
`}function F(e,t,n){if(!t)return a`
<div class="sw-today sw-today--empty">
<p class="sw-empty__title">Nothing waiting today</p>
<p class="sw-empty__sub">
Your agent hasn't drafted anything new. Switch to Board to browse history.
</p>
</div>
`;let r=Math.max(0,n.findIndex(e=>e.key===t.key)),i=Math.max(n.length,1),s=n.filter(e=>e.key!==t.key).slice(0,3),c=e.proposals.filter(e=>e.status===`applied`).slice(0,3),l=t.isNew?`NEW`:t.status===`pending`?`WAITING`:`REVIEWED`,u=t.ageLabel,d=W(Date.now()),f=t.status===`pending`,p=e.actionBusy?.key===t.key?e.actionBusy.action:null,m=!!e.actionBusy,h=e.assistantName.trim()||`agent`;return a`
<div class="sw-today">
<div class="sw-today__head">
<div class="sw-today__date">${d}</div>
<h1 class="sw-today__h1">${n.length} proposals waiting</h1>
${n.length===0?a`<div class="sw-today__sub">Browse what's already applied.</div>`:o}
${n.length>0?a`
<div class="sw-today__progress">
<span>${r+1} of ${i}</span>
<div class="sw-today__dots">
${n.map((e,t)=>a`
<span
class="sw-today__dot ${t<r?`is-done`:t===r?`is-now`:``}"
></span>
`)}
</div>
</div>
`:o}
</div>
<article class="sw-today__hero">
<div class="sw-today__label">
<span class="sw-today__ping"></span>
${l} · ${u}
</div>
<h2 class="sw-today__name">${t.slug}</h2>
<p class="sw-today__one-liner">${t.oneLine}</p>
${I(t)}
<div class="sw-today__author">
<span class="sw-today__avatar">v${t.version}</span>
<span>
Drafted by <strong>${h}</strong> · ${u}.
${t.supportFiles.length>0?a`
<button
class="sw-today__files-link"
@click=${()=>e.onPreviewFile(t.key,t.supportFiles[0].path)}
>
${t.supportFiles.length}
${t.supportFiles.length===1?`support file`:`support files`}
</button>
come with it.
`:o}
</span>
</div>
${f?a`
<div class="sw-today__actions" aria-busy=${p?`true`:`false`}>
<button
class="sw-today__big sw-today__big--primary ${p===`apply`?`is-busy`:``}"
?disabled=${m}
@click=${()=>e.onApply(t.key)}
>
${p===`apply`?`Applying…`:`Use it`}
<span class="sw-today__big-sub">Add to your skills</span>
</button>
<button
class="sw-today__big sw-today__big--tweak ${p===`revise`?`is-busy`:``}"
?disabled=${m}
@click=${()=>e.onRevise(t.key)}
>
${p===`revise`?`Opening…`:`Tweak it`}
<span class="sw-today__big-sub">Ask the agent to change something</span>
</button>
<button
class="sw-today__big sw-today__big--skip ${p===`reject`?`is-busy`:``}"
?disabled=${m}
@click=${()=>e.onReject(t.key)}
>
${p===`reject`?`Skipping…`:`Skip`}
<span class="sw-today__big-sub">Not for me</span>
</button>
</div>
`:o}
${e.actionNotice?.key===t.key?k(e.actionNotice):o}
</article>
${s.length>0?a`
<section class="sw-today__section">
<header class="sw-today__section-head">
<h3>Up next · ${n.length-1} more waiting</h3>
<button class="sw-today__link" @click=${()=>e.onModeChange(`board`)}>
See all proposals →
</button>
</header>
<div class="sw-today__upnext">
${s.map(t=>a`
<button class="sw-today__mini" @click=${()=>e.onSelect(t.key)}>
<div class="sw-today__mini-name">${t.slug}</div>
<div class="sw-today__mini-desc">${t.oneLine}</div>
<div class="sw-today__mini-meta">${t.ageLabel}</div>
</button>
`)}
</div>
</section>
`:o}
${c.length>0?a`
<section class="sw-today__section">
<header class="sw-today__section-head">
<h3>Your collection · ${e.counts.applied} in use</h3>
<button
class="sw-today__link sw-today__link--muted"
@click=${()=>e.onModeChange(`board`)}
>
Manage →
</button>
</header>
<div class="sw-today__applied">
${c.map(t=>a`
<button
class="sw-today__applied-row"
@click=${()=>{e.onSelect(t.key),e.onModeChange(`board`)}}
>
<span class="sw-today__check">✓</span>
<span class="sw-today__applied-name">
<strong>${t.slug}</strong> — ${t.oneLine}
</span>
<span class="sw-today__applied-when">${t.ageLabel}</span>
</button>
`)}
</div>
</section>
`:o}
</div>
`}function I(e){let t=L(e.body);return t?a`
<div class="sw-today__does">
<div class="sw-today__does-h">${t.heading}</div>
<ul>
${t.items.map(e=>a`<li>${e}</li>`)}
</ul>
</div>
`:o}function L(e){let t=R(e),n=z(t,[`workflow`,`procedure`,`steps`,`agent workflow`,`process`]),r=n?V(n.lines):[];if(r.length>0)return{heading:`How the agent will use it`,items:r.slice(0,g)};let i=z(t,[`when to use`,`use when`,`applies when`,`trigger`,`triggers`]),a=i?V(i.lines):[];return a.length>0?{heading:`When the agent should use it`,items:a.slice(0,g)}:null}function R(e){let t=[],n=null,r=!1;for(let i of e.split(`
`)){let e=i.trim();e.startsWith("```")&&(r=!r);let a=r?null:/^(#{2,4})\s+(.+?)\s*$/.exec(e);if(a){n={title:B(a[2]),lines:[]},t.push(n);continue}n?.lines.push(i)}return t}function z(e,t){let n=new Set(t.map(B));return e.find(e=>n.has(e.title))}function B(e){return e.replace(/[#*_`[\]().:]/g,` `).replace(/\s+/g,` `).trim().toLowerCase()}function V(e){let t=[];for(let n of e){if(/^\s{2,}/.test(n))continue;let e=n.trim(),r=/^(?:[-*]|\d+\.)\s+(.+)/.exec(e);r&&t.push(H(r[1]))}return t.filter(Boolean)}function H(e){return U(e.replace(/^\*\*[^*]+\*\*\s*/,``).replace(/\[([^\]]+)\]\([^)]+\)/g,`$1`).replace(/`([^`]+)`/g,`$1`).replace(/\s+/g,` `).trim(),_)}function U(e,t){if(e.length<=t)return e;let n=e.slice(0,t-1),r=n.lastIndexOf(` `);return`${(r>48?n.slice(0,r):n).trimEnd()}…`}function W(e){let t=new Date(e);return`${t.toLocaleDateString(void 0,{weekday:`long`})} · ${t.toLocaleDateString(void 0,{month:`short`,day:`numeric`})}`}function G(e){let t=e.split(`
`),n=[],r=[],i=[],o=!1,s=[],c=()=>{r.length&&(n.push(a`<p>${K(r.join(` `))}</p>`),r=[])},l=()=>{if(i.length){let e=i;n.push(a`
<ol>
${e.map(e=>a`<li>${K(e)}</li>`)}
</ol>
`),i=[]}};for(let e of t){let t=e.trimEnd();if(t.startsWith("```")){c(),l(),o?(n.push(a`<pre>${s.join(`
`)}</pre>`),s=[],o=!1):o=!0;continue}if(o){s.push(e);continue}if(t===``){c(),l();continue}if(t.startsWith(`## `)){c(),l(),n.push(a`<h3>${t.slice(3)}</h3>`);continue}if(t.startsWith(`# `)){c(),l(),n.push(a`<h3>${t.slice(2)}</h3>`);continue}let u=/^\d+\.\s+(.+)/.exec(t);if(u){c(),i.push(u[1]);continue}r.push(t)}return c(),l(),o&&s.length&&n.push(a`<pre>${s.join(`
`)}</pre>`),n}function K(e){let t=[],n=/(`[^`]+`|\*\*[^*]+\*\*)/g,r=0,i;for(;i=n.exec(e);){i.index>r&&t.push(e.slice(r,i.index));let n=i[0];n.startsWith("`")?t.push(a`<code>${n.slice(1,-1)}</code>`):t.push(a`<strong>${n.slice(2,-2)}</strong>`),r=i.index+n.length}return r<e.length&&t.push(e.slice(r)),t}function q(e,t,n){let r=n.trim().toLowerCase();return e.filter(e=>!(t!==`all`&&e.status!==t||r&&!`${e.name} ${e.oneLine} ${e.slug}`.toLowerCase().includes(r)))}function J(e){let t=new Map;for(let n of e){let e=t.get(n.recencyGroup)??[];e.push(n),t.set(n.recencyGroup,e)}return[`today`,`yesterday`,`earlier`].filter(e=>t.has(e)).map(e=>({label:v[e],items:t.get(e)??[]}))}function Y(e){return e.error?`Could not load proposals.`:e.loading?`Loading proposals…`:e.statusFilter===`all`?`No proposals match the current filter.`:`No ${h[e.statusFilter].toLowerCase()} proposals.`}function X(e){let t=Math.max(0,Date.now()-e),n=Math.floor(t/1e3);if(n<60)return`${n}s ago`;let r=Math.floor(n/60);if(r<60)return`${r} minutes ago`;let i=Math.floor(r/60);if(i<24)return`${i}h ago`;let a=Math.floor(i/24);return a<7?`${a}d ago`:new Date(e).toLocaleDateString()}export{q as filterSkillWorkshopProposals,y as renderSkillWorkshop};
//# sourceMappingURL=skill-workshop-DlL-Lvle.js.map