@north-light/cli 0.3.568 → 0.3.570

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.
@@ -1,2 +1,2 @@
1
- var K=Object.defineProperty;var a=(n,s)=>K(n,"name",{value:s,configurable:!0});import L from"string-width";import{isResponseBearingSlot as I}from"../../../core/human/page-schema.js";import{answerCommentAnchorLabel as Q,describePageAnswer as U}from"../../../core/human/answer.js";import{renderMarkdownBlockAwareLines as X}from"../../../core/termrender/termrender.js";import{rewriteMarkdownDocLinks as Z}from"../../attach/render/markdown-source.js";import{commentAnchors as nn,describeSlot as E,pageComplete as en,slotActions as tn,slotAnswered as j,tableMarkdown as sn,terminalAnswerable as R}from"./slots.js";import{RESET as r,BOLD as M,DIM as u,GREEN as W,YELLOW as O,CYAN as y,REVERSE as N,sanitize as x,singleLine as g,truncate as q,hline as w,wrap as k,centerHorizontal as T,clipLine as H,panLine as on,visibleWidth as C}from"./ansi.js";const rn=8,S=a((n,s,t)=>X(Z(n),s,t),"mdLines");function D(n){return U(n.manifest,Object.fromEntries(n.responses),{completedAt:""})}a(D,"pageAnswer");function ln(n,s){return s.id===void 0?void 0:n.slots.find(t=>t.id===s.id)}a(ln,"answerSlot");function P(n){if(n===void 0||n.state==="unanswered")return"unanswered";if(n.raw!==void 0)return"custom response";const s=n.text!==void 0?[n.text.edited?"edited":"accepted"]:n.groups.map(t=>t.picked.length===0?"none":t.picked.map(e=>e.label).join(", "));return n.freetext!==void 0&&s.push(n.freetext),n.comments.length>0&&s.push(`${n.comments.length} comment${n.comments.length===1?"":"s"}`),s.join("; ")||"answered"}a(P,"answerSummary");function z(n,s){return Q(n,s)}a(z,"commentLabel");function gn(n,s,t){const e=Math.min(Math.max(20,s-4),120),o=Math.max(20,s-4),l=["",...v(g(n.manifest.title),e,"",0,`${M}${y}`)];if(n.manifest.subtitle)for(const h of k(x(n.manifest.subtitle),e))l.push(` ${u}${h}${r}`);if(n.displayMarkdown!==""){l.push("");for(const h of S(n.displayMarkdown,e,o))l.push(` ${h}`)}else n.manifest.dialect==="html"&&l.push("",` ${u}HTML page \u2014 open in Northlight to read this${r}`);const i=n.slots.length>0;i&&l.push(` ${u}${w(e)}${r}`,"");const f=D(n);let c=-1,$;for(let h=0;h<n.slots.length;h++){const p=n.slots[h];n.manifest.steps>1&&p.step!==c&&(c!==-1&&l.push(""),l.push(` ${M}Step ${p.step+1}/${n.manifest.steps}${r}`),c=p.step);const b=p.id===void 0?void 0:n.responses.get(p.id),A=I(p)?j(n,p)?`${W}\u2713${r}`:`${u}\u25CB${r}`:`${u}\xB7${r}`,J=h===n.currentIndex?`${y}\u25B8${r} `:" ";h===n.currentIndex&&($=l.length),l.push(` ${J}${A} ${q(g(E(p)),Math.max(10,s-16))}`),b!==void 0&&l.push(` ${u}${q(g(P(ln(f,p))),Math.max(10,s-10))}${r}`)}if(n.manifest.slots.some(h=>I(h)&&!R(h))){l.push("");for(const h of k("This page must be answered in Northlight. Leaving it here keeps it in your inbox; x on the list cancels it.",e-2))l.push(` ${O}${h}${r}`)}i&&l.push(""),l.push(` ${u}${w(e)}${r}`);const d=!n.manifest.slots.some(I)?` ${u}u/d${r} scroll ${u}q${r} dismiss`:` ${u}enter${r} review ${u}\u2191\u2193/jk${r} navigate ${u}u/d${r} scroll ${u}q${r} close`;return _(l,d,n,s,t,e+2,n.followSelection?$:void 0)}a(gn,"renderOverview");function cn(n,s,t){const e=[...n],o=Math.max(0,Math.min(s,e.length)),l=F(n,t);let i=0;for(let $=0;$<l.length;$++)l[$].start<=o&&(i=$);const f=l.map(($,m)=>{if(m!==i)return e.slice($.start,$.end).join("");const d=e.slice($.start,o).join(""),h=o<$.end?e[o]:" ",p=o<$.end?e.slice(o+1,$.end).join(""):"";return`${d}${N}${h}${r}${p}`}),c=l[i];return o===c.end&&L(e.slice(c.start,c.end).join(""))>=t&&(f[i]=e.slice(c.start,c.end).join(""),f.splice(i+1,0,`${N} ${r}`)),f}a(cn,"renderInputBuffer");function wn(n,s){const t=[...n];let e=Math.max(0,Math.min(s,t.length));for(;e>0&&/\s/.test(t[e-1]);)e--;for(;e>0&&!/\s/.test(t[e-1]);)e--;return e}a(wn,"wordLeftIndex");function kn(n,s){const t=[...n];let e=Math.max(0,Math.min(s,t.length));for(;e<t.length&&/\s/.test(t[e]);)e++;for(;e<t.length&&!/\s/.test(t[e]);)e++;return e}a(kn,"wordRightIndex");function F(n,s){const t=[...n],e=Math.max(1,s),o=[];let l=0,i=0;for(let f=0;f<t.length;f++){const c=t[f];if(c===`
2
- `){o.push({start:l,end:f}),l=f+1,i=0;continue}const $=L(c);i+$>e?(o.push({start:l,end:f}),l=f,i=$):i+=$}return o.push({start:l,end:t.length}),o}a(F,"wrappedRowBounds");function bn(n,s,t,e){const o=F(n,e),l=Math.max(0,Math.min(s,[...n].length));let i=0;for(let c=0;c<o.length;c++)o[c].start<=l&&(i=c);const f=o[i+t];return f===void 0?l:Math.min(f.start+(l-o[i].start),f.end)}a(bn,"verticalCursor");function fn(n,s){return s.id===void 0?[]:n.responses.get(s.id)?.comments??[]}a(fn,"responseComments");function v(n,s,t,e,o){return k(n,Math.max(10,s-e)).map((l,i)=>` ${i===0?t:" ".repeat(e)}${o}${l}${r}`)}a(v,"hangingWrap");function un(n,s,t,e){if(s.id===void 0)return!1;const o=n.responses.get(s.id);return t==="option"?o?.selectedOptionIds?.includes(e)===!0:t==="card"?o?.selectedCardIds?.includes(e)===!0:t==="row"?o?.selectedRowIds?.includes(e)===!0:o?.selectedColumnIds?.includes(e)===!0}a(un,"selected");function Y(n,s){const t=n.slots[n.currentIndex],e=Math.min(Math.max(20,s-4),120),o=Math.max(20,s-4),l=`[${n.currentIndex+1}/${n.slots.length}]`,i=["",...v(g(n.manifest.title),e,`${M}${y}${l}${r} `,L(l)+2,u),` ${u}${w(e)}${r}`,"",...v(g(E(t)),e,"",0,M)],f=t.config.body;if(typeof f=="string"&&f.length>0){i.push("");for(const m of S(f,e,o))i.push(` ${m}`)}if(R(t)){if(t.kind==="chart"){const m=t.config,d=m.title??`${m.x} vs ${(m.y??[]).map(h=>h.label??h.field).join(", ")}`;i.push("",` ${u}chart \u2014 ${x(m.kind)}: ${x(d)}${r}`)}else if(t.kind==="table"){i.push("");for(const d of S(sn(t.config),e,o))i.push(` ${d}`);const m=t.config.label;m&&i.push(` ${u}${x(m)}${r}`)}else if(t.kind==="text"){const d=(t.id===void 0?void 0:n.responses.get(t.id))?.text??t.config.initialText;i.push("");for(const h of S(d,e,o))i.push(` ${h}`)}}else{i.push("");for(const m of k(`${x(t.kind)} \u2014 must be answered in Northlight; the terminal cannot submit this page`,Math.max(10,e-2)))i.push(` ${O}${m}${r}`);i.push(` ${u}q leaves it in your inbox; x on the list cancels it${r}`)}i.push("",` ${u}${w(e)}${r}`);let c;if(n.inputMode!==null){const m=n.inputMode.kind==="comment"?`Comment on ${z(t,n.inputMode.anchor)}`:n.inputMode.kind==="text"?"Edit text":"Other response";i.push(` ${M}${m}${r}`);for(const d of cn(n.inputMode.buffer,n.inputMode.cursor,Math.max(10,e-4)))i.push(` ${d}`);c=` ${u}enter save esc cancel${n.editorAvailable?" ^o editor":""}${r}`}else{tn(t).forEach((d,h)=>{const p=h===n.selectedAction?`${y}\u25B8${r}`:" ";if(d.kind==="select"){const b=un(n,t,d.group,d.id)?`${W}\u2713${r}`:" ";if(i.push(` ${p} [${b}] ${d.shortcut?`[${d.shortcut}] `:""}${x(d.label)}`),d.description)for(const A of k(x(d.description),Math.max(10,e-6)))i.push(` ${u}${A}${r}`)}else{const b=d.kind==="freetext"?"r":d.kind==="edit"?"e":"a";i.push(` ${p} [${b}] ${x(d.label)}`)}});for(const d of fn(n,t))for(const h of k(`attached: ${z(t,d.anchor)} \u2014 ${x(d.text)}`,Math.max(10,e-2)))i.push(` ${u}${h}${r}`);n.hint&&i.push(` ${O}${x(n.hint)}${r}`),c=` ${u}n/p next/previous \u2191\u2193/jk actions${nn(t).length>0?" c comment":""} u/d scroll \u2190\u2192/hl pan q close${r}`}const $=Math.max(0,...i.map(C));return{lines:i,footer:c,maxW:e,maxH:Math.max(0,$-Math.max(1,s))}}a(Y,"itemLines");function On(n,s,t){if(n.slots[n.currentIndex]===void 0){n.scrollOffset=0,n.hscrollOffset=0,n.hscrollMax=0;return}const e=Y(n,s);n.hscrollMax=e.maxH,n.hscrollOffset=Math.max(0,Math.min(n.hscrollOffset,n.hscrollMax)),n.scrollOffset=Math.max(0,Math.min(n.scrollOffset,Math.max(0,e.lines.length-B(t))))}a(On,"clampItemReviewScroll");function yn(n,s){n.hscrollOffset=Math.max(0,Math.min(n.hscrollMax,n.hscrollOffset+s*rn))}a(yn,"panItemReview");function In(n,s,t){const e=Y(n,s),o=B(t),l=n.scrollOffset,i=e.lines.slice(l,l+o);for(;i.length<o;)i.push("");const f=n.hscrollOffset>0?i.map($=>on($,n.hscrollOffset,s)):i,c=Math.min(s,e.maxW+2);return o<t&&f.push(V(e.footer,G(l,o,e.lines.length),e.maxW+2)),T(f.map($=>H($,s)),s,c)}a(In,"renderItemReview");function Sn(n,s,t){const e=Math.min(Math.max(20,s-4),120),o=["",...v(`${g(n.manifest.title)} \u2014 Summary`,e,"",0,`${M}${y}`),` ${u}${w(e)}${r}`,""],l=D(n);for(const c of l.slots){const $=n.manifest.slots.find(h=>h.id===c.id),d=$!==void 0&&j(n,$)?`${W}\u2713${r}`:`${O}\u25CB${r}`;o.push(` ${d} ${M}${x($===void 0?c.kind:E($))}${r} ${u}${x(P(c))}${r}`)}const i=en(n);if(!i){o.push("",` ${O}Cannot submit yet.${r}`);for(const c of n.slots.filter(I))j(n,c)||(o.push(` ${u}${c.id??c.kind} is unanswered${r}`),R(c)||o.push(` ${u}${c.id} must be answered in Northlight${r}`))}o.push("",` ${u}${w(e)}${r}`);const f=i?` ${u}enter${r} submit ${u}u/d${r} scroll ${u}q${r} back`:` ${u}u/d${r} scroll ${u}q${r} back`;return _(o,f,n,s,t,e+2)}a(Sn,"renderFinal");function B(n){return n>=2?n-1:n}a(B,"bodyRows");function G(n,s,t){if(s<=0||t<=s)return{marks:"",full:""};const e=Math.min(t,n+s),o=`${n>0?"\u25B4":" "}${e<t?"\u25BE":" "}`;return{marks:`${u}${o}${r}`,full:`${u}${o} ${n+1}\u2013${e}/${t}${r}`}}a(G,"scrollBadge");function V(n,s,t){if(s.full==="")return n;const e=a(o=>t-C(n)-C(o)-2,"room");for(const o of[s.full,s.marks])if(e(o)>=2)return`${n}${" ".repeat(e(o))}${o}`;return` ${s.marks} ${n.trimStart()}`}a(V,"pinnedFooter");function _(n,s,t,e,o,l,i){const f=B(o),c=Math.max(0,n.length-f);t.scrollOffset=Math.max(0,Math.min(t.scrollOffset,c)),i!==void 0&&f>0&&(i<t.scrollOffset?t.scrollOffset=i:i>=t.scrollOffset+f&&(t.scrollOffset=Math.min(c,i-f+1)));const $=n.slice(t.scrollOffset,t.scrollOffset+f);for(;$.length<f;)$.push("");return f<o&&$.push(V(s,G(t.scrollOffset,f,n.length),l)),T($.map(m=>H(m,e)),e,Math.min(e,l))}a(_,"centeredWindow");export{On as clampItemReviewScroll,yn as panItemReview,Sn as renderFinal,cn as renderInputBuffer,In as renderItemReview,gn as renderOverview,bn as verticalCursor,wn as wordLeftIndex,kn as wordRightIndex,F as wrappedRowBounds};
1
+ var U=Object.defineProperty;var a=(n,s)=>U(n,"name",{value:s,configurable:!0});import v from"string-width";import{isResponseBearingSlot as A}from"../../../core/human/page-schema.js";import{answerCommentAnchorLabel as X,describePageAnswer as Z}from"../../../core/human/answer.js";import{renderMarkdownBlockAwareLines as nn}from"../../../core/termrender/termrender.js";import{rewriteMarkdownDocLinks as en}from"../../attach/render/markdown-source.js";import{commentAnchors as tn,describeSlot as j,pageComplete as sn,slotActions as on,slotAnswered as R,tableMarkdown as rn,terminalAnswerable as C}from"./slots.js";import{RESET as r,BOLD as w,DIM as u,GREEN as B,YELLOW as I,CYAN as E,REVERSE as T,sanitize as x,singleLine as b,truncate as H,hline as O,wrap as M,centerHorizontal as D,clipLine as P,panLine as cn,visibleWidth as N}from"./ansi.js";const ln=8,L=a((n,s,t)=>nn(en(n),s,t),"mdLines");function z(n){return Z(n.manifest,Object.fromEntries(n.responses),{completedAt:""})}a(z,"pageAnswer");function fn(n,s){return s.id===void 0?void 0:n.slots.find(t=>t.id===s.id)}a(fn,"answerSlot");function F(n){if(n===void 0||n.state==="unanswered")return"unanswered";if(n.raw!==void 0)return"custom response";const s=n.text!==void 0?[n.text.edited?"edited":"accepted"]:n.groups.map(t=>t.picked.length===0?"none":t.picked.map(e=>e.label).join(", "));return n.freetext!==void 0&&s.push(n.freetext),n.comments.length>0&&s.push(`${n.comments.length} comment${n.comments.length===1?"":"s"}`),s.join("; ")||"answered"}a(F,"answerSummary");function Y(n,s){return X(n,s)}a(Y,"commentLabel");function kn(n,s,t){const e=Math.min(Math.max(20,s-4),120),i=Math.max(20,s-4),c=["",...W(b(n.manifest.title),e,"",0,`${w}${E}`)];if(n.manifest.subtitle)for(const d of M(x(n.manifest.subtitle),e))c.push(` ${u}${d}${r}`);if(n.displayMarkdown!==""){c.push("");for(const d of L(n.displayMarkdown,e,i))c.push(` ${d}`)}else n.manifest.dialect==="html"&&c.push("",` ${u}HTML page \u2014 open in Northlight to read this${r}`);const o=n.slots.length>0;o&&c.push(` ${u}${O(e)}${r}`,"");const f=z(n);let l=-1,$;for(let d=0;d<n.slots.length;d++){const p=n.slots[d];n.manifest.steps>1&&p.step!==l&&(l!==-1&&c.push(""),c.push(` ${w}Step ${p.step+1}/${n.manifest.steps}${r}`),l=p.step);const y=p.id===void 0?void 0:n.responses.get(p.id),g=A(p)?R(n,p)?`${B}\u2713${r}`:`${u}\u25CB${r}`:`${u}\xB7${r}`,k=d===n.currentIndex?`${E}\u25B8${r} `:" ";d===n.currentIndex&&($=c.length),c.push(` ${k}${g} ${H(b(j(p)),Math.max(10,s-16))}`),y!==void 0&&c.push(` ${u}${H(b(F(fn(f,p))),Math.max(10,s-10))}${r}`)}if(n.manifest.slots.some(d=>A(d)&&!C(d))){c.push("");for(const d of M("This page must be answered in Northlight. Leaving it here keeps it in your inbox; x on the list cancels it.",e-2))c.push(` ${I}${d}${r}`)}o&&c.push(""),c.push(` ${u}${O(e)}${r}`);const h=!n.manifest.slots.some(A)?` ${u}u/d${r} scroll ${u}q${r} dismiss`:` ${u}enter${r} review ${u}\u2191\u2193/jk${r} navigate ${u}u/d${r} scroll ${u}q${r} close`;return K(c,h,n,s,t,e+2,n.followSelection?$:void 0)}a(kn,"renderOverview");function un(n,s,t){const e=[...n],i=Math.max(0,Math.min(s,e.length)),c=G(n,t);let o=0;for(let $=0;$<c.length;$++)c[$].start<=i&&(o=$);const f=c.map(($,m)=>{if(m!==o)return e.slice($.start,$.end).join("");const h=e.slice($.start,i).join(""),d=i<$.end?e[i]:" ",p=i<$.end?e.slice(i+1,$.end).join(""):"";return`${h}${T}${d}${r}${p}`}),l=c[o];return i===l.end&&v(e.slice(l.start,l.end).join(""))>=t&&(f[o]=e.slice(l.start,l.end).join(""),f.splice(o+1,0,`${T} ${r}`)),f}a(un,"renderInputBuffer");function bn(n,s){const t=[...n];let e=Math.max(0,Math.min(s,t.length));for(;e>0&&/\s/.test(t[e-1]);)e--;for(;e>0&&!/\s/.test(t[e-1]);)e--;return e}a(bn,"wordLeftIndex");function On(n,s){const t=[...n];let e=Math.max(0,Math.min(s,t.length));for(;e<t.length&&/\s/.test(t[e]);)e++;for(;e<t.length&&!/\s/.test(t[e]);)e++;return e}a(On,"wordRightIndex");function G(n,s){const t=[...n],e=Math.max(1,s),i=[];let c=0,o=0;for(let f=0;f<t.length;f++){const l=t[f];if(l===`
2
+ `){i.push({start:c,end:f}),c=f+1,o=0;continue}const $=v(l);o+$>e?(i.push({start:c,end:f}),c=f,o=$):o+=$}return i.push({start:c,end:t.length}),i}a(G,"wrappedRowBounds");function yn(n,s,t,e){const i=G(n,e),c=Math.max(0,Math.min(s,[...n].length));let o=0;for(let l=0;l<i.length;l++)i[l].start<=c&&(o=l);const f=i[o+t];return f===void 0?c:Math.min(f.start+(c-i[o].start),f.end)}a(yn,"verticalCursor");function $n(n,s){return s.id===void 0?[]:n.responses.get(s.id)?.comments??[]}a($n,"responseComments");function W(n,s,t,e,i){return M(n,Math.max(10,s-e)).map((c,o)=>` ${o===0?t:" ".repeat(e)}${i}${c}${r}`)}a(W,"hangingWrap");function hn(n,s,t,e){if(s.id===void 0)return!1;const i=n.responses.get(s.id);return t==="option"?i?.selectedOptionIds?.includes(e)===!0:t==="card"?i?.selectedCardIds?.includes(e)===!0:t==="row"?i?.selectedRowIds?.includes(e)===!0:i?.selectedColumnIds?.includes(e)===!0}a(hn,"selected");function V(n,s){const t=n.slots[n.currentIndex],e=Math.min(Math.max(20,s-4),120),i=Math.max(20,s-4),c=`[${n.currentIndex+1}/${n.slots.length}]`,o=["",...W(b(n.manifest.title),e,`${w}${E}${c}${r} `,v(c)+2,u),` ${u}${O(e)}${r}`,"",...W(b(j(t)),e,"",0,w)],f=t.config.body;if(typeof f=="string"&&f.length>0){o.push("");for(const m of L(f,e,i))o.push(` ${m}`)}if(C(t)){if(t.kind==="chart"){const m=t.config,h=m.title??`${m.x} vs ${(m.y??[]).map(d=>d.label??d.field).join(", ")}`;o.push("",` ${u}chart \u2014 ${x(m.kind)}: ${x(h)}${r}`)}else if(t.kind==="table"){o.push("");for(const h of L(rn(t.config),e,i))o.push(` ${h}`);const m=t.config.label;m&&o.push(` ${u}${x(m)}${r}`)}else if(t.kind==="text"){const h=(t.id===void 0?void 0:n.responses.get(t.id))?.text??t.config.initialText;o.push("");for(const d of L(h,e,i))o.push(` ${d}`)}}else{o.push("");for(const m of M(`${x(t.kind)} \u2014 must be answered in Northlight; the terminal cannot submit this page`,Math.max(10,e-2)))o.push(` ${I}${m}${r}`);o.push(` ${u}q leaves it in your inbox; x on the list cancels it${r}`)}o.push("",` ${u}${O(e)}${r}`);let l;if(n.inputMode!==null){const m=n.inputMode.kind==="comment"?`Comment on ${Y(t,n.inputMode.anchor)}`:n.inputMode.kind==="text"?"Edit text":"Other response";o.push(` ${w}${m}${r}`);for(const h of un(n.inputMode.buffer,n.inputMode.cursor,Math.max(10,e-4)))o.push(` ${h}`);l=` ${u}enter save esc cancel${n.editorAvailable?" ^o editor":""}${r}`}else{on(t).forEach((h,d)=>{const p=d===n.selectedAction?`${E}\u25B8${r}`:" ";if(h.kind==="select"){const y=hn(n,t,h.group,h.id)?`${B}\u2713${r}`:" ",g=h.shortcut?`[${h.shortcut}] `:"",k=v(g);if(M(x(h.label),Math.max(10,e-6-k)).forEach((S,Q)=>o.push(Q===0?` ${p} [${y}] ${g}${S}`:`${" ".repeat(8+k)}${S}`)),h.description)for(const S of M(x(h.description),Math.max(10,e-6)))o.push(` ${u}${S}${r}`)}else{const y=h.kind==="freetext"?"r":h.kind==="edit"?"e":"a";M(x(h.label),Math.max(10,e-6)).forEach((g,k)=>o.push(k===0?` ${p} [${y}] ${g}`:` ${g}`))}});for(const h of $n(n,t))for(const d of M(`attached: ${Y(t,h.anchor)} \u2014 ${x(h.text)}`,Math.max(10,e-2)))o.push(` ${u}${d}${r}`);n.hint&&o.push(` ${I}${x(n.hint)}${r}`),l=` ${u}n/p next/previous \u2191\u2193/jk actions${tn(t).length>0?" c comment":""} u/d scroll \u2190\u2192/hl pan q close${r}`}const $=Math.max(0,...o.map(N));return{lines:o,footer:l,maxW:e,maxH:Math.max(0,$-Math.max(1,s))}}a(V,"itemLines");function In(n,s,t){if(n.slots[n.currentIndex]===void 0){n.scrollOffset=0,n.hscrollOffset=0,n.hscrollMax=0;return}const e=V(n,s);n.hscrollMax=e.maxH,n.hscrollOffset=Math.max(0,Math.min(n.hscrollOffset,n.hscrollMax)),n.scrollOffset=Math.max(0,Math.min(n.scrollOffset,Math.max(0,e.lines.length-q(t))))}a(In,"clampItemReviewScroll");function En(n,s){n.hscrollOffset=Math.max(0,Math.min(n.hscrollMax,n.hscrollOffset+s*ln))}a(En,"panItemReview");function Sn(n,s,t){const e=V(n,s),i=q(t),c=n.scrollOffset,o=e.lines.slice(c,c+i);for(;o.length<i;)o.push("");const f=n.hscrollOffset>0?o.map($=>cn($,n.hscrollOffset,s)):o,l=Math.min(s,e.maxW+2);return i<t&&f.push(J(e.footer,_(c,i,e.lines.length),e.maxW+2)),D(f.map($=>P($,s)),s,l)}a(Sn,"renderItemReview");function vn(n,s,t){const e=Math.min(Math.max(20,s-4),120),i=["",...W(`${b(n.manifest.title)} \u2014 Summary`,e,"",0,`${w}${E}`),` ${u}${O(e)}${r}`,""],c=z(n);for(const l of c.slots){const $=n.manifest.slots.find(d=>d.id===l.id),h=$!==void 0&&R(n,$)?`${B}\u2713${r}`:`${I}\u25CB${r}`;i.push(` ${h} ${w}${x($===void 0?l.kind:j($))}${r} ${u}${x(F(l))}${r}`)}const o=sn(n);if(!o){i.push("",` ${I}Cannot submit yet.${r}`);for(const l of n.slots.filter(A))R(n,l)||(i.push(` ${u}${l.id??l.kind} is unanswered${r}`),C(l)||i.push(` ${u}${l.id} must be answered in Northlight${r}`))}i.push("",` ${u}${O(e)}${r}`);const f=o?` ${u}enter${r} submit ${u}u/d${r} scroll ${u}q${r} back`:` ${u}u/d${r} scroll ${u}q${r} back`;return K(i,f,n,s,t,e+2)}a(vn,"renderFinal");function q(n){return n>=2?n-1:n}a(q,"bodyRows");function _(n,s,t){if(s<=0||t<=s)return{marks:"",full:""};const e=Math.min(t,n+s),i=`${n>0?"\u25B4":" "}${e<t?"\u25BE":" "}`;return{marks:`${u}${i}${r}`,full:`${u}${i} ${n+1}\u2013${e}/${t}${r}`}}a(_,"scrollBadge");function J(n,s,t){if(s.full==="")return n;const e=a(i=>t-N(n)-N(i)-2,"room");for(const i of[s.full,s.marks])if(e(i)>=2)return`${n}${" ".repeat(e(i))}${i}`;return` ${s.marks} ${n.trimStart()}`}a(J,"pinnedFooter");function K(n,s,t,e,i,c,o){const f=q(i),l=Math.max(0,n.length-f);t.scrollOffset=Math.max(0,Math.min(t.scrollOffset,l)),o!==void 0&&f>0&&(o<t.scrollOffset?t.scrollOffset=o:o>=t.scrollOffset+f&&(t.scrollOffset=Math.min(l,o-f+1)));const $=n.slice(t.scrollOffset,t.scrollOffset+f);for(;$.length<f;)$.push("");return f<i&&$.push(J(s,_(t.scrollOffset,f,n.length),c)),D($.map(m=>P(m,e)),e,Math.min(e,c))}a(K,"centeredWindow");export{In as clampItemReviewScroll,En as panItemReview,vn as renderFinal,un as renderInputBuffer,Sn as renderItemReview,kn as renderOverview,yn as verticalCursor,bn as wordLeftIndex,On as wordRightIndex,G as wrappedRowBounds};
@@ -1,3 +1,3 @@
1
- var V=Object.defineProperty;var w=(i,a)=>V(i,"name",{value:a,configurable:!0});import{DatabaseSync as W}from"node:sqlite";import{existsSync as Y,mkdirSync as D,mkdtempSync as J,renameSync as X,rmSync as P}from"node:fs";import{tmpdir as Q}from"node:os";import{join as A}from"node:path";import j from"pg";import{migrate as z,MIGRATIONS as I}from"./migrations.js";import{canvasDbPath as Z}from"./paths.js";import{canvasSearchPathOptions as ee}from"./schema-version.js";import{EXPORT_MARK_KEY as F,IMPORT_MARK_KEY as te,OBJECT_COLUMNS as ne,pgColumns as se,quote as m,TABLE_ORDER as C}from"./import-sqlite.js";const oe=5e3,re=new Set(["environments","relations","schemas","charges","charge_totals","limits","viewer_attachments","reconciler_leases","environment_repos","conversation_entries","conversation_parts","charge_chunks","profiles","profile_projects","profile_defaults","scope_settings","scope_bookkeeping","scope_marketplaces","scope_plugins","person_settings","model_accounts","model_account_cooldowns","model_api_keys","model_settings","model_sign_in_flows","env_vars","usage","grantee_usage","provider_receipts"]),ae={exposures:"node, object, channel",rules:"object, position",edges:"type, src, dst",unresolved_links:"src, name",revision_parents:"child, parent",canvas_meta:"key",runtime_state:"key",message_causes:"message_id",peer_ingested:"key",peer_ingested_destinations:"key",message_sequence:"id",messages:"seq",review_comment_events:"seq",audit_rows:"id",peer_outbox:"id",app_users:"uid"},ie={objects:"id",revisions:"rev_id",watches:"watch_id"},H=new Set(["daemon_owner","schema_version","schema_min_reader","ledger_migrated",te,F]);function ce(i){const a=J(A(Q(),"crtr-export-")),s=process.env.CRTR_HOME;process.env.CRTR_HOME=a,D(A(a,"runtime","nodes"),{recursive:!0});try{const o=new W(i);return o.exec("PRAGMA journal_mode = DELETE;"),o.exec("PRAGMA foreign_keys = ON;"),z(o),o.exec("DELETE FROM canvas_meta; DELETE FROM message_sequence;"),o}finally{s===void 0?delete process.env.CRTR_HOME:process.env.CRTR_HOME=s,P(a,{recursive:!0,force:!0})}}w(ce,"createEmptyDatabase");async function Re(i,a=()=>{}){const s=Z();if(Y(s))throw new Error(`${s} already exists; refusing to overwrite it`);D(A(s,".."),{recursive:!0,mode:448});const o=`${s}.exporting-${process.pid}`,u=Date.now(),t=new j.Client({connectionString:i,application_name:`crtr-export-${process.pid}`,options:ee(),types:{getTypeParser:w(((d,r)=>d===20?f=>Number(f):j.types.getTypeParser(d,r)),"getTypeParser")}});await t.connect();let l;try{if((await t.query("SELECT value FROM canvas_meta WHERE key = 'schema_version'").catch(()=>({rows:[]}))).rows.length===0)throw new Error("the Postgres database has no canvas schema; nothing to export");l=ce(o);const r=l;r.exec("PRAGMA foreign_keys = OFF;");const f=await se(t),L=[...f.keys()].filter(e=>!re.has(e)&&!C.includes(e));if(L.length>0)throw new Error(`export: Postgres tables with no export rule: ${L.join(", ")}`);await t.query("BEGIN READ ONLY ISOLATION LEVEL REPEATABLE READ");const E={};for(const[e,S]of Object.entries(ie)){const h=new Map;for(const _ of(await t.query(`SELECT ${S} AS id, seq FROM ${e}`)).rows)h.set(_.id,Number(_.seq));E[e]=h}const B=E.objects;let M=0;r.exec("BEGIN");for(const e of C){const S=Date.now(),h=new Set(r.prepare(`PRAGMA table_info(${m(e)})`).all().map(n=>n.name)),_=f.get(e);if(_===void 0)throw new Error(`export: Postgres has no table ${e}`);const G=_.some(n=>n.name==="seq"),O=_.filter(n=>h.has(n.name)),K=new Set(ne[e]??[]),g=e==="objects"||e==="revisions"||e==="watches"||e==="watch_subscriptions",$=O.map(n=>n.type==="jsonb"?`${m(n.name)}::text AS ${m(n.name)}`:m(n.name));e==="watches"&&$.push("watch_id AS _watch_id"),g&&$.push("seq AS _seq");const R=O.map(n=>m(n.name)),U=r.prepare(`INSERT INTO ${m(e)} (${g?["rowid",...R]:R}) VALUES (${(g?["?",...R]:R).map(()=>"?").join(", ")})`),q=G?"seq":ae[e],b=`export_${e}`;await t.query(`DECLARE ${b} NO SCROLL CURSOR FOR SELECT ${$.join(", ")} FROM ${m(e)}${e==="canvas_meta"?` WHERE key NOT IN (${[...H].map(n=>`'${n}'`).join(", ")})`:""}${q===void 0?"":` ORDER BY ${q}`}`);let T=0;for(;;){const n=await t.query(`FETCH ${oe} FROM ${b}`);if(n.rows.length===0)break;for(const k of n.rows){const N=g?[Number(k._seq)]:[];for(const c of O){let p=k[c.name];e==="revision_parents"&&(c.name==="child"||c.name==="parent")?p=x(E.revisions,"revision",e,c.name,p):e==="watch_subscriptions"&&c.name==="watch"?p=x(E.watches,"watch",e,c.name,p):K.has(c.name)?p=x(B,"object",e,c.name,p):e==="jobs"&&(c.name==="job_dir"||c.name==="log_path")&&(p=p??""),N.push(p??null)}U.run(...N)}T+=n.rows.length}await t.query(`CLOSE ${b}`),M+=T,a(`export ${e}: ${T} rows in ${Date.now()-S} ms`)}r.exec("COMMIT"),await t.query("COMMIT");const v=r.prepare("PRAGMA foreign_key_check").all();if(v.length>0)throw new Error(`export: ${v.length} foreign key violation(s) in the exported canvas.db, first: ${JSON.stringify(v[0])}`);await pe(t,r);const y=r.prepare("PRAGMA user_version").get().user_version;if(y!==I.length)throw new Error(`export: exported canvas.db is at user_version ${y}, expected ${I.length}`);return r.close(),l=void 0,await t.query("INSERT INTO canvas_meta (key, value) VALUES ($1, $2) ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value",[F,JSON.stringify({at:new Date().toISOString(),path:s})]),X(o,s),{path:s,rows:M,ms:Date.now()-u,userVersion:y}}catch(d){await t.query("ROLLBACK").catch(()=>{});try{l?.close()}catch{}for(const r of["","-wal","-shm","-journal"])P(`${o}${r}`,{force:!0});throw d}finally{await t.end().catch(()=>{})}}w(Re,"exportPostgresToSqlite");function x(i,a,s,o,u){if(u==null)return null;const t=i.get(u);if(t===void 0)throw new Error(`export: ${s}.${o} references ${a} ${String(u)}, which was not exported (`);return t}w(x,"mapId");async function pe(i,a){const s=[];for(const o of C){const u=Number((await i.query(`SELECT count(*) AS n FROM ${m(o)}`)).rows[0].n);let t=Number(a.prepare(`SELECT count(*) AS n FROM ${m(o)}`).get().n),l=u;o==="canvas_meta"&&(l=Number((await i.query("SELECT count(*) AS n FROM canvas_meta WHERE key <> ALL($1)",[[...H]])).rows[0].n),t=Number(a.prepare("SELECT count(*) AS n FROM canvas_meta").get().n)),l!==t&&s.push(`${o}: Postgres ${l} rows, SQLite ${t}`)}if(s.length>0)throw new Error(`Postgres \u2192 SQLite export verification failed:
1
+ var V=Object.defineProperty;var d=(i,a)=>V(i,"name",{value:a,configurable:!0});import{DatabaseSync as W}from"node:sqlite";import{existsSync as Y,mkdirSync as D,mkdtempSync as J,renameSync as X,rmSync as P}from"node:fs";import{tmpdir as Q}from"node:os";import{join as A}from"node:path";import j from"pg";import{migrate as z,MIGRATIONS as I}from"./migrations.js";import{canvasDbPath as Z}from"./paths.js";import{canvasSearchPathOptions as ee}from"./schema-version.js";import{EXPORT_MARK_KEY as F,IMPORT_MARK_KEY as te,OBJECT_COLUMNS as ne,pgColumns as se,quote as p,TABLE_ORDER as C}from"./import-sqlite.js";const re=5e3,oe=new Set(["environments","relations","schemas","charges","charge_totals","limits","viewer_attachments","reconciler_leases","environment_repos","conversation_entries","conversation_parts","charge_chunks","profiles","profile_projects","profile_defaults","scope_settings","scope_bookkeeping","scope_marketplaces","scope_plugins","person_settings","model_accounts","model_account_cooldowns","model_api_keys","model_settings","model_sign_in_flows","env_vars","import_runs","import_key_map","import_manifest","import_id_map","machine_links","machine_calls","machine_credentials","import_node_files","usage","grantee_usage","provider_receipts"]),ae={exposures:"node, object, channel",rules:"object, position",edges:"type, src, dst",unresolved_links:"src, name",revision_parents:"child, parent",canvas_meta:"key",runtime_state:"key",message_causes:"message_id",peer_ingested:"key",peer_ingested_destinations:"key",message_sequence:"id",messages:"seq",review_comment_events:"seq",audit_rows:"id",peer_outbox:"id",app_users:"uid"},ie={objects:"id",revisions:"rev_id",watches:"watch_id"},H=new Set(["daemon_owner","schema_version","schema_min_reader","ledger_migrated",te,F]);function ce(i){const a=J(A(Q(),"crtr-export-")),s=process.env.CRTR_HOME;process.env.CRTR_HOME=a,D(A(a,"runtime","nodes"),{recursive:!0});try{const r=new W(i);return r.exec("PRAGMA journal_mode = DELETE;"),r.exec("PRAGMA foreign_keys = ON;"),z(r),r.exec("DELETE FROM canvas_meta; DELETE FROM message_sequence;"),r}finally{s===void 0?delete process.env.CRTR_HOME:process.env.CRTR_HOME=s,P(a,{recursive:!0,force:!0})}}d(ce,"createEmptyDatabase");async function Re(i,a=()=>{}){const s=Z();if(Y(s))throw new Error(`${s} already exists; refusing to overwrite it`);D(A(s,".."),{recursive:!0,mode:448});const r=`${s}.exporting-${process.pid}`,u=Date.now(),t=new j.Client({connectionString:i,application_name:`crtr-export-${process.pid}`,options:ee(),types:{getTypeParser:d(((f,o)=>f===20?w=>Number(w):j.types.getTypeParser(f,o)),"getTypeParser")}});await t.connect();let _;try{if((await t.query("SELECT value FROM canvas_meta WHERE key = 'schema_version'").catch(()=>({rows:[]}))).rows.length===0)throw new Error("the Postgres database has no canvas schema; nothing to export");_=ce(r);const o=_;o.exec("PRAGMA foreign_keys = OFF;");const w=await se(t),L=[...w.keys()].filter(e=>!oe.has(e)&&!C.includes(e));if(L.length>0)throw new Error(`export: Postgres tables with no export rule: ${L.join(", ")}`);await t.query("BEGIN READ ONLY ISOLATION LEVEL REPEATABLE READ");const E={};for(const[e,S]of Object.entries(ie)){const h=new Map;for(const l of(await t.query(`SELECT ${S} AS id, seq FROM ${e}`)).rows)h.set(l.id,Number(l.seq));E[e]=h}const B=E.objects;let M=0;o.exec("BEGIN");for(const e of C){const S=Date.now(),h=new Set(o.prepare(`PRAGMA table_info(${p(e)})`).all().map(n=>n.name)),l=w.get(e);if(l===void 0)throw new Error(`export: Postgres has no table ${e}`);const G=l.some(n=>n.name==="seq"),O=l.filter(n=>h.has(n.name)),K=new Set(ne[e]??[]),g=e==="objects"||e==="revisions"||e==="watches"||e==="watch_subscriptions",$=O.map(n=>n.type==="jsonb"?`${p(n.name)}::text AS ${p(n.name)}`:p(n.name));e==="watches"&&$.push("watch_id AS _watch_id"),g&&$.push("seq AS _seq");const R=O.map(n=>p(n.name)),U=o.prepare(`INSERT INTO ${p(e)} (${g?["rowid",...R]:R}) VALUES (${(g?["?",...R]:R).map(()=>"?").join(", ")})`),k=G?"seq":ae[e],b=`export_${e}`;await t.query(`DECLARE ${b} NO SCROLL CURSOR FOR SELECT ${$.join(", ")} FROM ${p(e)}${e==="canvas_meta"?` WHERE key NOT IN (${[...H].map(n=>`'${n}'`).join(", ")})`:""}${k===void 0?"":` ORDER BY ${k}`}`);let T=0;for(;;){const n=await t.query(`FETCH ${re} FROM ${b}`);if(n.rows.length===0)break;for(const q of n.rows){const N=g?[Number(q._seq)]:[];for(const c of O){let m=q[c.name];e==="revision_parents"&&(c.name==="child"||c.name==="parent")?m=x(E.revisions,"revision",e,c.name,m):e==="watch_subscriptions"&&c.name==="watch"?m=x(E.watches,"watch",e,c.name,m):K.has(c.name)?m=x(B,"object",e,c.name,m):e==="jobs"&&(c.name==="job_dir"||c.name==="log_path")&&(m=m??""),N.push(m??null)}U.run(...N)}T+=n.rows.length}await t.query(`CLOSE ${b}`),M+=T,a(`export ${e}: ${T} rows in ${Date.now()-S} ms`)}o.exec("COMMIT"),await t.query("COMMIT");const y=o.prepare("PRAGMA foreign_key_check").all();if(y.length>0)throw new Error(`export: ${y.length} foreign key violation(s) in the exported canvas.db, first: ${JSON.stringify(y[0])}`);await me(t,o);const v=o.prepare("PRAGMA user_version").get().user_version;if(v!==I.length)throw new Error(`export: exported canvas.db is at user_version ${v}, expected ${I.length}`);return o.close(),_=void 0,await t.query("INSERT INTO canvas_meta (key, value) VALUES ($1, $2) ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value",[F,JSON.stringify({at:new Date().toISOString(),path:s})]),X(r,s),{path:s,rows:M,ms:Date.now()-u,userVersion:v}}catch(f){await t.query("ROLLBACK").catch(()=>{});try{_?.close()}catch{}for(const o of["","-wal","-shm","-journal"])P(`${r}${o}`,{force:!0});throw f}finally{await t.end().catch(()=>{})}}d(Re,"exportPostgresToSqlite");function x(i,a,s,r,u){if(u==null)return null;const t=i.get(u);if(t===void 0)throw new Error(`export: ${s}.${r} references ${a} ${String(u)}, which was not exported (`);return t}d(x,"mapId");async function me(i,a){const s=[];for(const r of C){const u=Number((await i.query(`SELECT count(*) AS n FROM ${p(r)}`)).rows[0].n);let t=Number(a.prepare(`SELECT count(*) AS n FROM ${p(r)}`).get().n),_=u;r==="canvas_meta"&&(_=Number((await i.query("SELECT count(*) AS n FROM canvas_meta WHERE key <> ALL($1)",[[...H]])).rows[0].n),t=Number(a.prepare("SELECT count(*) AS n FROM canvas_meta").get().n)),_!==t&&s.push(`${r}: Postgres ${_} rows, SQLite ${t}`)}if(s.length>0)throw new Error(`Postgres \u2192 SQLite export verification failed:
2
2
  ${s.join(`
3
- `)}`)}w(pe,"verify");export{Re as exportPostgresToSqlite};
3
+ `)}`)}d(me,"verify");export{Re as exportPostgresToSqlite};
@@ -54,6 +54,20 @@ export declare function inferenceEndpointOf(value: unknown): InferenceEndpoint |
54
54
  export declare function mirrorOnly(grant: Pick<GrantRecord, 'grantee'>): boolean;
55
55
  /** Every mirrored grant this machine acts on: `allGrants()` without the mirror-only ones. */
56
56
  export declare function localGrants(): Promise<GrantRecord[]>;
57
+ /** Every provider id a scope of an active grant's resolved set names: a
58
+ * `<id>:<group>` string whose id is not `crtr` (the runtime's own namespace). */
59
+ export declare function providerIdsFromGrants(grants: readonly GrantRecord[]): string[];
60
+ /** The provider ids an active grant of `person` names: the directory's provider bundles that person has, and so the only
61
+ * rows of their plugin list that a provider call installs on its own (`ensureProviderBundle`) rather than from a source. */
62
+ export declare function grantedProviderIds(person: string): Promise<Set<string>>;
63
+ /** Whether a `scope_plugins` row is a directory provider bundle rather than a plugin that has no source this process can read:
64
+ * it carries neither a marketplace nor a path (what `ensureProviderBundle` writes) AND its name is a provider an active grant of the
65
+ * person names. A source-less row for any other name (a git, local or HTTP-bundle install) is not one. */
66
+ export declare function isProviderBundleRow(row: {
67
+ name: string;
68
+ source_marketplace: string | null;
69
+ source_path: string | null;
70
+ }, granted: ReadonlySet<string>): boolean;
57
71
  /** Every mirrored grant, whatever its status. */
58
72
  export declare function allGrants(): Promise<GrantRecord[]>;
59
73
  /** Whether a grant's `expires_at` has passed. An unparsable time reads as expired (fail closed). */
@@ -1,4 +1,4 @@
1
- var T=Object.defineProperty;var r=(e,n)=>T(e,"name",{value:n,configurable:!0});import{createHash as N}from"node:crypto";import{parseScope as O}from"@north-light/identity";import{openDb as d,withCanvasWrite as j}from"../canvas/db.js";import{isUnsandboxedMachine as w}from"../runtime/unsandboxed.js";import{replaceWrittenBy as h,userSubject as A}from"../permissions/facts.js";import{GROUP_TYPE as b}from"../permissions/builtin.js";const R=["not_started","pending","ready","failed","unavailable"],k=["ultra","strong","medium","light"];function D(){const e=process.env.CRTR_RUNTIME;return e?{sql:" AND runtime = ?",args:[e]}:{sql:"",args:[]}}r(D,"runtimeScope");async function u(e,n,t=[]){const s=D();return await(await d()).prepare(`SELECT ${e} FROM grants WHERE ${n}${s.sql}`).all(...t,...s.args)}r(u,"grantRows");async function c(e,n,t=[]){return(await u(e,n,t))[0]}r(c,"grantRow");function l(e){if(!Array.isArray(e))throw new TypeError("grant scopes must be a list");return e.map(n=>{if(!n||typeof n!="object"||typeof n.scope!="string"||!["always","ask"].includes(n.mode))throw new TypeError("invalid grant scope entry");return{scope:n.scope,mode:n.mode}})}r(l,"entries");function C(e){if(!e||typeof e!="object"||Array.isArray(e))return null;const{state:n,message:t,next:s,step:i}=e;return typeof n!="string"||!R.includes(n)||t!==void 0&&typeof t!="string"||i!==void 0&&typeof i!="string"||typeof s!="string"||!s?null:{state:n,...t===void 0?{}:{message:t},next:s,...i===void 0?{}:{step:i}}}r(C,"dependencyOf");function m(e){if(!e||typeof e!="object"||Array.isArray(e))return{};const n={};for(const[t,s]of Object.entries(e)){const i=C(s);i&&(n[t]=i)}return n}r(m,"dependenciesOf");function I(e){if(!e||typeof e!="object"||Array.isArray(e))return null;const{base_url:n,rungs:t}=e;if(typeof n!="string"||!n||!t||typeof t!="object"||Array.isArray(t))return null;const s={};for(const i of k){const a=t[i];typeof a=="string"&&a&&(s[i]=a)}return Object.keys(s).length?{base_url:n.replace(/\/+$/,""),rungs:s}:null}r(I,"inferenceEndpointOf");function y(e){if(e.computed_rev!==e.rev||e.scopes_json===null||e.resolved_scopes_json===null)throw new TypeError("grant columns are not computed for this revision");return{id:e.id,runtime:e.runtime,sub:e.sub,grantee:e.grantee,rev:e.rev,scopes:l(JSON.parse(e.scopes_json)),resolved_scopes:l(JSON.parse(e.resolved_scopes_json)),spend_cap:e.spend_cap,expires_at:e.expires_at,status:e.status,revoked_at:e.revoked_at,approved_at:e.approved_at,payer:e.payer,dependencies:m(JSON.parse(e.dependencies_json??"{}")),run_notice_url:e.run_notice_url??null,inference_endpoint:e.inference_endpoint_json?I(JSON.parse(e.inference_endpoint_json)):null,synced_at:e.synced_at??""}}r(y,"record");function p(e){if(!e||e.status!=="active")return null;try{return M(y(e))}catch(n){if(n instanceof TypeError||n instanceof SyntaxError)return null;throw n}}r(p,"active");function g(e){return e.grantee!=="app:terminal"&&w()}r(g,"mirrorOnly");async function B(){return(await F()).filter(e=>!g(e))}r(B,"localGrants");async function F(){return(await u("*","true")).flatMap(e=>{try{return[y(e)]}catch(n){if(n instanceof TypeError||n instanceof SyntaxError)return[];throw n}})}r(F,"allGrants");function L(e){return e.expires_at!==null&&!(Date.parse(e.expires_at)>Date.now())}r(L,"grantExpired");function M(e){if(e.status!=="active"||L(e))return null;try{const n=e.resolved_scopes.map(({scope:t})=>O(t));return{...e,resolved:e.resolved_scopes.flatMap(({mode:t},s)=>t==="always"?[n[s]]:[])}}catch(n){if(n instanceof TypeError||n instanceof SyntaxError)return null;throw n}}r(M,"activeRecord");function v(e){return Array.isArray(e)?`[${e.map(v).join(",")}]`:e&&typeof e=="object"?`{${Object.keys(e).sort().filter(n=>e[n]!==void 0).map(n=>`${JSON.stringify(n)}:${v(e[n])}`).join(",")}}`:JSON.stringify(e)??"null"}r(v,"canonicalJson");function X(e){const n={rev:e.rev,resolved_scopes:e.resolved_scopes,spend_cap:e.spend_cap,payer:e.payer,dependencies:e.dependencies??{},...e.inference_endpoint?{inference_endpoint:e.inference_endpoint}:{}};return N("sha256").update(v(n)).digest("base64url").slice(0,22)}r(X,"grantVersion");async function Y(e){return!!await c("1","id = ?",[e])}r(Y,"hasGrant");async function z(e){return await c("rev, status","id = ?",[e])??null}r(z,"grantSyncState");async function K(e,n){return await c("rev, status","grantee = ? AND sub = ?",[e,n])??null}r(K,"grantSyncStateFor");async function G(e){const n=await c("*","id = ?",[e]);if(!n)return null;try{return y(n)}catch(t){if(t instanceof TypeError||t instanceof SyntaxError)return null;throw t}}r(G,"grantRecord");async function Q(e){const n=await c("*","id = ?",[e]);if(!n)return{standing:"inactive",sub:null,grantee:null,status:null,removal_done_at:null,record:null};const t={sub:n.sub,grantee:n.grantee,status:n.status,removal_done_at:n.removal_done_at??null};if(n.status!=="active")return{...t,standing:"inactive",record:null};if(n.computed_rev!==n.rev)return{...t,standing:"lagging",record:null};const s=p(n);return s===null?{...t,standing:"inactive",record:null}:{...t,standing:"active",record:s}}r(Q,"grantStanding");async function Z(e){return(await u("id",e)).map(n=>n.id)}r(Z,"grantIdsWhere");async function ee(e){return(e?(await G(e))?.payer:null)??"user"}r(ee,"payerOfGrant");async function H(e){return p(await c("*","id = ?",[e]))}r(H,"activeGrant");async function ne(e,n){return p(await c("*","grantee = ? AND sub = ?",[e,n]))}r(ne,"grantFor");async function re(e){if(g({grantee:e}))return null;const n=await u("*","grantee = ? AND status = 'active'",[e]);if(n.length>1)throw new Error(`multiple active grants for ${e}`);return p(n[0])}r(re,"activeGrantForApp");async function te(e){const n=await H(e);return n&&n.run_notice_url&&!g(n)?{url:n.run_notice_url,sub:n.sub,grantee:n.grantee}:null}r(te,"runNoticeTarget");async function se(){const e=await u("*","grantee = 'app:terminal' AND status = 'active'");if(e.length>1)throw new Error("multiple active terminal grants");return p(e[0])}r(se,"terminalGrant");async function ae(){return(await(await d()).prepare("SELECT value FROM runtime_state WHERE key = 'grants_source'").get())?.value??null}r(ae,"grantsSource");async function oe(e){return(await(await d()).prepare("SELECT value FROM runtime_state WHERE key = 'grants_source'").get())?.value===e}r(oe,"hasSyncedGrants");async function ie(e){return(await e.prepare("SELECT value FROM runtime_state WHERE key = 'grants_source'").get())?.value!=="local"?0:(await e.prepare("DELETE FROM runtime_state WHERE key = 'grants_source'").run(),Number((await e.prepare("DELETE FROM grants").run()).changes))}r(ie,"dropLocalGrantMirror");function ce(e){if(e===void 0)return[];if(!Array.isArray(e))throw new TypeError("invalid grant sync response");return e.map(n=>{const{team:t,members:s}=n??{};if(typeof t!="string"||!/^team:\S+$/.test(t)||!Array.isArray(s)||s.some(i=>typeof i!="string"||!/^user:[^\s*]+$/.test(i)))throw new TypeError("invalid grant sync response");return{team:t,members:s}})}r(ce,"teamsOf");async function ue(e,n,t,s=[]){const i=new Set;for(const a of e){if(i.has(a.id)||!a.id||!a.runtime||!a.sub||!a.grantee||!Number.isInteger(a.rev)||!["active","revoked","removed"].includes(a.status))throw new TypeError("invalid grant sync response");i.add(a.id),l(a.scopes),l(a.resolved_scopes)}await j(async a=>{let _=0;const f=[...i];_+=Number((await(f.length?a.prepareUncached(`DELETE FROM grants WHERE id NOT IN (${f.map(()=>"?").join(",")})`).run(...f):a.prepareUncached("DELETE FROM grants").run())).changes);const E=a.prepare(`INSERT INTO grants
1
+ var w=Object.defineProperty;var t=(e,r)=>w(e,"name",{value:r,configurable:!0});import{createHash as O}from"node:crypto";import{parseScope as N}from"@north-light/identity";import{openDb as d,withCanvasWrite as h}from"../canvas/db.js";import{isUnsandboxedMachine as j}from"../runtime/unsandboxed.js";import{replaceWrittenBy as A,userSubject as b}from"../permissions/facts.js";import{GROUP_TYPE as R}from"../permissions/builtin.js";const D=["not_started","pending","ready","failed","unavailable"],k=["ultra","strong","medium","light"];function I(){const e=process.env.CRTR_RUNTIME;return e?{sql:" AND runtime = ?",args:[e]}:{sql:"",args:[]}}t(I,"runtimeScope");async function u(e,r,n=[]){const s=I();return await(await d()).prepare(`SELECT ${e} FROM grants WHERE ${r}${s.sql}`).all(...n,...s.args)}t(u,"grantRows");async function c(e,r,n=[]){return(await u(e,r,n))[0]}t(c,"grantRow");function l(e){if(!Array.isArray(e))throw new TypeError("grant scopes must be a list");return e.map(r=>{if(!r||typeof r!="object"||typeof r.scope!="string"||!["always","ask"].includes(r.mode))throw new TypeError("invalid grant scope entry");return{scope:r.scope,mode:r.mode}})}t(l,"entries");function C(e){if(!e||typeof e!="object"||Array.isArray(e))return null;const{state:r,message:n,next:s,step:a}=e;return typeof r!="string"||!D.includes(r)||n!==void 0&&typeof n!="string"||a!==void 0&&typeof a!="string"||typeof s!="string"||!s?null:{state:r,...n===void 0?{}:{message:n},next:s,...a===void 0?{}:{step:a}}}t(C,"dependencyOf");function m(e){if(!e||typeof e!="object"||Array.isArray(e))return{};const r={};for(const[n,s]of Object.entries(e)){const a=C(s);a&&(r[n]=a)}return r}t(m,"dependenciesOf");function F(e){if(!e||typeof e!="object"||Array.isArray(e))return null;const{base_url:r,rungs:n}=e;if(typeof r!="string"||!r||!n||typeof n!="object"||Array.isArray(n))return null;const s={};for(const a of k){const o=n[a];typeof o=="string"&&o&&(s[a]=o)}return Object.keys(s).length?{base_url:r.replace(/\/+$/,""),rungs:s}:null}t(F,"inferenceEndpointOf");function _(e){if(e.computed_rev!==e.rev||e.scopes_json===null||e.resolved_scopes_json===null)throw new TypeError("grant columns are not computed for this revision");return{id:e.id,runtime:e.runtime,sub:e.sub,grantee:e.grantee,rev:e.rev,scopes:l(JSON.parse(e.scopes_json)),resolved_scopes:l(JSON.parse(e.resolved_scopes_json)),spend_cap:e.spend_cap,expires_at:e.expires_at,status:e.status,revoked_at:e.revoked_at,approved_at:e.approved_at,payer:e.payer,dependencies:m(JSON.parse(e.dependencies_json??"{}")),run_notice_url:e.run_notice_url??null,inference_endpoint:e.inference_endpoint_json?F(JSON.parse(e.inference_endpoint_json)):null,synced_at:e.synced_at??""}}t(_,"record");function p(e){if(!e||e.status!=="active")return null;try{return H(_(e))}catch(r){if(r instanceof TypeError||r instanceof SyntaxError)return null;throw r}}t(p,"active");function g(e){return e.grantee!=="app:terminal"&&j()}t(g,"mirrorOnly");async function Y(){return(await G()).filter(e=>!g(e))}t(Y,"localGrants");const E="[A-Za-z0-9_-]{1,64}",M=new RegExp(`^(${E}):(${E})$`);function L(e){const r=new Set;for(const n of e)if(n.status==="active")for(const{scope:s}of n.resolved_scopes){const a=M.exec(s);a&&a[1]!=="crtr"&&r.add(a[1])}return[...r].sort()}t(L,"providerIdsFromGrants");async function Z(e){const r=await u("*","sub = ? AND status = 'active'",[e]);return new Set(L(r.flatMap(n=>{try{return[_(n)]}catch(s){if(s instanceof TypeError||s instanceof SyntaxError)return[];throw s}})))}t(Z,"grantedProviderIds");function K(e,r){return e.source_marketplace===null&&e.source_path===null&&r.has(e.name)}t(K,"isProviderBundleRow");async function G(){return(await u("*","true")).flatMap(e=>{try{return[_(e)]}catch(r){if(r instanceof TypeError||r instanceof SyntaxError)return[];throw r}})}t(G,"allGrants");function $(e){return e.expires_at!==null&&!(Date.parse(e.expires_at)>Date.now())}t($,"grantExpired");function H(e){if(e.status!=="active"||$(e))return null;try{const r=e.resolved_scopes.map(({scope:n})=>N(n));return{...e,resolved:e.resolved_scopes.flatMap(({mode:n},s)=>n==="always"?[r[s]]:[])}}catch(r){if(r instanceof TypeError||r instanceof SyntaxError)return null;throw r}}t(H,"activeRecord");function v(e){return Array.isArray(e)?`[${e.map(v).join(",")}]`:e&&typeof e=="object"?`{${Object.keys(e).sort().filter(r=>e[r]!==void 0).map(r=>`${JSON.stringify(r)}:${v(e[r])}`).join(",")}}`:JSON.stringify(e)??"null"}t(v,"canonicalJson");function Q(e){const r={rev:e.rev,resolved_scopes:e.resolved_scopes,spend_cap:e.spend_cap,payer:e.payer,dependencies:e.dependencies??{},...e.inference_endpoint?{inference_endpoint:e.inference_endpoint}:{}};return O("sha256").update(v(r)).digest("base64url").slice(0,22)}t(Q,"grantVersion");async function ee(e){return!!await c("1","id = ?",[e])}t(ee,"hasGrant");async function re(e){return await c("rev, status","id = ?",[e])??null}t(re,"grantSyncState");async function ne(e,r){return await c("rev, status","grantee = ? AND sub = ?",[e,r])??null}t(ne,"grantSyncStateFor");async function U(e){const r=await c("*","id = ?",[e]);if(!r)return null;try{return _(r)}catch(n){if(n instanceof TypeError||n instanceof SyntaxError)return null;throw n}}t(U,"grantRecord");async function te(e){const r=await c("*","id = ?",[e]);if(!r)return{standing:"inactive",sub:null,grantee:null,status:null,removal_done_at:null,record:null};const n={sub:r.sub,grantee:r.grantee,status:r.status,removal_done_at:r.removal_done_at??null};if(r.status!=="active")return{...n,standing:"inactive",record:null};if(r.computed_rev!==r.rev)return{...n,standing:"lagging",record:null};const s=p(r);return s===null?{...n,standing:"inactive",record:null}:{...n,standing:"active",record:s}}t(te,"grantStanding");async function se(e){return(await u("id",e)).map(r=>r.id)}t(se,"grantIdsWhere");async function ae(e){return(e?(await U(e))?.payer:null)??"user"}t(ae,"payerOfGrant");async function W(e){return p(await c("*","id = ?",[e]))}t(W,"activeGrant");async function oe(e,r){return p(await c("*","grantee = ? AND sub = ?",[e,r]))}t(oe,"grantFor");async function ie(e){if(g({grantee:e}))return null;const r=await u("*","grantee = ? AND status = 'active'",[e]);if(r.length>1)throw new Error(`multiple active grants for ${e}`);return p(r[0])}t(ie,"activeGrantForApp");async function ce(e){const r=await W(e);return r&&r.run_notice_url&&!g(r)?{url:r.run_notice_url,sub:r.sub,grantee:r.grantee}:null}t(ce,"runNoticeTarget");async function ue(){const e=await u("*","grantee = 'app:terminal' AND status = 'active'");if(e.length>1)throw new Error("multiple active terminal grants");return p(e[0])}t(ue,"terminalGrant");async function pe(){return(await(await d()).prepare("SELECT value FROM runtime_state WHERE key = 'grants_source'").get())?.value??null}t(pe,"grantsSource");async function de(e){return(await(await d()).prepare("SELECT value FROM runtime_state WHERE key = 'grants_source'").get())?.value===e}t(de,"hasSyncedGrants");async function le(e){return(await e.prepare("SELECT value FROM runtime_state WHERE key = 'grants_source'").get())?.value!=="local"?0:(await e.prepare("DELETE FROM runtime_state WHERE key = 'grants_source'").run(),Number((await e.prepare("DELETE FROM grants").run()).changes))}t(le,"dropLocalGrantMirror");function _e(e){if(e===void 0)return[];if(!Array.isArray(e))throw new TypeError("invalid grant sync response");return e.map(r=>{const{team:n,members:s}=r??{};if(typeof n!="string"||!/^team:\S+$/.test(n)||!Array.isArray(s)||s.some(a=>typeof a!="string"||!/^user:[^\s*]+$/.test(a)))throw new TypeError("invalid grant sync response");return{team:n,members:s}})}t(_e,"teamsOf");async function fe(e,r,n,s=[]){const a=new Set;for(const o of e){if(a.has(o.id)||!o.id||!o.runtime||!o.sub||!o.grantee||!Number.isInteger(o.rev)||!["active","revoked","removed"].includes(o.status))throw new TypeError("invalid grant sync response");a.add(o.id),l(o.scopes),l(o.resolved_scopes)}await h(async o=>{let f=0;const y=[...a];f+=Number((await(y.length?o.prepareUncached(`DELETE FROM grants WHERE id NOT IN (${y.map(()=>"?").join(",")})`).run(...y):o.prepareUncached("DELETE FROM grants").run())).changes);const x=o.prepare(`INSERT INTO grants
2
2
  (id, runtime, sub, grantee, rev, scopes_json, resolved_scopes_json, spend_cap, expires_at,
3
3
  status, revoked_at, approved_at, payer, dependencies_json, run_notice_url, inference_endpoint_json, synced_at, computed_rev)
4
4
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
@@ -13,4 +13,4 @@ var T=Object.defineProperty;var r=(e,n)=>T(e,"name",{value:n,configurable:!0});i
13
13
  grants.run_notice_url, grants.inference_endpoint_json, grants.computed_rev)
14
14
  IS DISTINCT FROM (excluded.runtime, excluded.sub, excluded.grantee, excluded.rev, excluded.scopes_json, excluded.resolved_scopes_json,
15
15
  excluded.spend_cap, excluded.expires_at, excluded.status, excluded.revoked_at, excluded.approved_at, excluded.payer,
16
- excluded.dependencies_json, excluded.run_notice_url, excluded.inference_endpoint_json, excluded.computed_rev)`);for(const o of e)_+=Number((await E.run(o.id,o.runtime,o.sub,o.grantee,o.rev,JSON.stringify(o.scopes),JSON.stringify(o.resolved_scopes),o.spend_cap,o.expires_at,o.status,o.revoked_at,o.approved_at,o.payer,JSON.stringify(m(o.dependencies)),o.run_notice_url??null,o.inference_endpoint?JSON.stringify(o.inference_endpoint):null,o.synced_at,o.rev)).changes);await h(a,"directory",b,"member",s.flatMap(({team:o,members:x})=>x.map(S=>({resourceId:o,subject:A(S)})))),await a.prepare("INSERT INTO runtime_state (key, value) VALUES ('grants_source', ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value").run(t),_>0&&await a.prepare("INSERT INTO runtime_state (key, value) VALUES ('grants_generation', '1') ON CONFLICT(key) DO UPDATE SET value = CAST(CAST(runtime_state.value AS INTEGER) + 1 AS TEXT)").run()})}r(ue,"replaceGrants");async function pe(){const e=await(await d()).prepare("SELECT value FROM runtime_state WHERE key = 'grants_generation'").get();return e?Number(e.value):0}r(pe,"grantsGeneration");export{H as activeGrant,re as activeGrantForApp,M as activeRecord,F as allGrants,m as dependenciesOf,ie as dropLocalGrantMirror,L as grantExpired,ne as grantFor,Z as grantIdsWhere,G as grantRecord,Q as grantStanding,z as grantSyncState,K as grantSyncStateFor,X as grantVersion,pe as grantsGeneration,ae as grantsSource,Y as hasGrant,oe as hasSyncedGrants,I as inferenceEndpointOf,B as localGrants,g as mirrorOnly,ee as payerOfGrant,ue as replaceGrants,te as runNoticeTarget,ce as teamsOf,se as terminalGrant};
16
+ excluded.dependencies_json, excluded.run_notice_url, excluded.inference_endpoint_json, excluded.computed_rev)`);for(const i of e)f+=Number((await x.run(i.id,i.runtime,i.sub,i.grantee,i.rev,JSON.stringify(i.scopes),JSON.stringify(i.resolved_scopes),i.spend_cap,i.expires_at,i.status,i.revoked_at,i.approved_at,i.payer,JSON.stringify(m(i.dependencies)),i.run_notice_url??null,i.inference_endpoint?JSON.stringify(i.inference_endpoint):null,i.synced_at,i.rev)).changes);await A(o,"directory",R,"member",s.flatMap(({team:i,members:S})=>S.map(T=>({resourceId:i,subject:b(T)})))),await o.prepare("INSERT INTO runtime_state (key, value) VALUES ('grants_source', ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value").run(n),f>0&&await o.prepare("INSERT INTO runtime_state (key, value) VALUES ('grants_generation', '1') ON CONFLICT(key) DO UPDATE SET value = CAST(CAST(runtime_state.value AS INTEGER) + 1 AS TEXT)").run()})}t(fe,"replaceGrants");async function ye(){const e=await(await d()).prepare("SELECT value FROM runtime_state WHERE key = 'grants_generation'").get();return e?Number(e.value):0}t(ye,"grantsGeneration");export{W as activeGrant,ie as activeGrantForApp,H as activeRecord,G as allGrants,m as dependenciesOf,le as dropLocalGrantMirror,$ as grantExpired,oe as grantFor,se as grantIdsWhere,U as grantRecord,te as grantStanding,re as grantSyncState,ne as grantSyncStateFor,Q as grantVersion,Z as grantedProviderIds,ye as grantsGeneration,pe as grantsSource,ee as hasGrant,de as hasSyncedGrants,F as inferenceEndpointOf,K as isProviderBundleRow,Y as localGrants,g as mirrorOnly,ae as payerOfGrant,L as providerIdsFromGrants,fe as replaceGrants,ce as runNoticeTarget,_e as teamsOf,ue as terminalGrant};
@@ -1,6 +1,5 @@
1
1
  import { type ValidatedCommandManifest } from '../../api/command-manifest/manifest.js';
2
2
  import { type ProviderBlock } from '../command-plugins/bundle.js';
3
- import { type GrantRecord } from '../grants/mirror.js';
4
3
  import { ProviderError } from './errors.js';
5
4
  export type ProviderTransport = {
6
5
  kind: 'http';
@@ -22,9 +21,6 @@ export interface ResolvedProviderBundle {
22
21
  /** The installed plugin root; absent for a pinned major. */
23
22
  root?: string;
24
23
  }
25
- /** Every provider id a scope of an active grant's resolved set names: a
26
- * `<id>:<group>` string whose id is not `crtr` (the runtime's own namespace). */
27
- export declare function providerIdsFromGrants(grants: readonly GrantRecord[]): string[];
28
24
  /** Install attempts are owned by this daemon process. An installed bundle remains
29
25
  * usable after a failed refresh; only absent bundles expose the last failure.
30
26
  * A new daemon starts another grant-sync pass, so no duplicate on-disk ledger is
@@ -1,2 +1,2 @@
1
- var ue=Object.defineProperty;var a=(e,n)=>ue(e,"name",{value:n,configurable:!0});import{existsSync as g,readdirSync as W,readFileSync as S,rmSync as $,statSync as k}from"node:fs";import{join as d}from"node:path";import{validateCommandManifest as de}from"../../api/command-manifest/manifest.js";import{crtrHome as z}from"../canvas/paths.js";import{withCanvasWrite as G}from"../canvas/db.js";import{bundleVersion as ce,isInstalledGeneration as fe,recordCheckedGeneration as le}from"../command-plugins/bundle.js";import{readState as pe,updateConfig as me,updateState as ve}from"../config.js";import{directoryIssuer as q}from"../directory/issuer.js";import{emitEvent as m}from"../events/emit.js";import{describeOutboundFailure as he,outboundFetch as we,outboundRefusal as V,OutboundError as ge}from"../events/outbound.js";import{withExclusiveDirectoryLockAsync as Z}from"../exclusive-lock.js";import{atomicWriteText as x,ensureDir as T,pathExists as $e}from"../fs-utils.js";import{reconcilePackages as L}from"../graph/package-reconcile.js";import{readPluginManifest as C}from"../manifest.js";import{bundleSwapLockPath as J}from"../plugin-swap-lock.js";import{pluginsDir as ye,userScopeRoot as K}from"../scope.js";import{listInstalledPlugins as be}from"../resolver.js";import{localGrants as j}from"../grants/mirror.js";import{ProviderError as c}from"./errors.js";const A="[A-Za-z0-9_-]{1,64}",_e=new RegExp(`^(${A}):(${A})$`),Q=new RegExp(`^${A}$`),Pe=3e4,X=3e4;function Y(e){const n=new Set;for(const r of e)if(r.status==="active")for(const{scope:t}of r.resolved_scopes){const o=_e.exec(t);o&&o[1]!=="crtr"&&n.add(o[1])}return[...n].sort()}a(Y,"providerIdsFromGrants");function v(e){return d(ye("user"),e)}a(v,"pluginRoot");function R(){return d(z(),"providers")}a(R,"providersRoot");function ee(e,n){return d(R(),e,`v${n}`)}a(ee,"majorDir");function B(e){if(!Q.test(e))throw new c("not_found",`"${e}" is not a provider id`,{param:"provider"})}a(B,"assertId");function E(){const e=q();if(!e)throw new c("not_found","provider bundles need a directory-connected runtime");return e}a(E,"directoryBase");async function ne(e,n){const r=`${E()}/providers/${encodeURIComponent(e)}/plugin${n.version!==void 0?`?version=${n.version}`:""}`;let t;try{t=await we("bundle_fetch",r,{headers:{Accept:"application/x-tar",...n.ifNoneMatch!==void 0?{"If-None-Match":n.ifNoneMatch}:{}},signal:AbortSignal.timeout(Pe)})}catch(i){return{status:"failed",message:`could not reach ${r}: ${i instanceof Error?i.message:String(i)}`,cause:i}}if(t.status===304){if(n.ifNoneMatch===void 0){const i=`HTTP 304 from ${r} without a conditional request`;return{status:"failed",message:i,cause:V("bundle_fetch",r,i)}}return{status:304}}if(t.status===404||t.status===410)return await t.body?.cancel().catch(()=>{}),{status:t.status===404?404:410,message:`HTTP ${t.status} from ${r}`};if(t.status<200||t.status>=300){await t.body?.cancel().catch(()=>{});{const i=`HTTP ${t.status} from ${r}`;return{status:"failed",message:i,cause:V("bundle_fetch",r,i)}}}let o;try{o=new Uint8Array(await t.arrayBuffer())}catch(i){return{status:"failed",message:`reading ${r} failed: ${i instanceof Error?i.message:String(i)}`,cause:new ge("bundle_fetch",r,i)}}const s=t.headers.get("etag");return{status:200,raw:o,...s!==null?{etag:s}:{}}}a(ne,"fetchProviderBundle");const I=new WeakSet;class M extends Error{static{a(this,"BundleInvalid")}issues;constructor(n,r){super(n),this.issues=r}}function re(e,n,r){if(r.provider===void 0)return"the archive carries no provider block";if(r.provider.id!==e)return`the archive is provider "${r.provider.id}", not "${e}"`;if(n!==void 0&&r.provider.major!==n)return`the archive is major ${r.provider.major}, not ${n}`;const t=["hooks","kinds","page_components","memory_extensions"].filter(o=>r[o]!==void 0);if(t.length>0)return`a provider bundle may not carry ${t.join(", ")}`}a(re,"wrongBundle");function D(e,n,r){m({level:"error",event:"daemon.providers.bundle_invalid",fields:{provider:e,...n!==void 0?{major:n}:{},...r instanceof M?{issues:r.issues}:{}},error:r})}a(D,"logInvalid");function te(e,n,r){m({level:"warn",event:"daemon.providers.bundle_fetch_failed",fields:{provider:e,...n!==void 0?{major:n}:{},message:r instanceof Error?r.message:String(r),...he(r instanceof Error?r.cause:void 0)}})}a(te,"logFailure");async function oe(){return import("../../commands/pkg/plugin-manage.js")}a(oe,"pluginManage");const y=new Map,b=a(e=>`${z()}\0${e}`,"installKey");function Se(e){const n=e,r=n?.code??n?.cause?.code;return typeof r=="string"&&/^[A-Z][A-Z0-9_]{1,30}$/.test(r)?`${r} on provider install directory`:e instanceof c&&e.message.includes("HTTP 404")?"directory returned HTTP 404":e instanceof c&&e.message.includes("HTTP 410")?"directory returned HTTP 410":"provider bundle could not be installed"}a(Se,"publicInstallCause");function Ee(e){if(h(e)===void 0)return y.get(b(e))??{status:"installing"}}a(Ee,"providerInstallState");function Ie(e){return new c("provider_unavailable",`provider "${e}" unavailable: ${Ee(e)?.error??"bundle installation pending"}`)}a(Ie,"unavailableProviderError");function h(e){const n=C(v(e));if(!(n?.provider===void 0||n.transport?.kind!=="http"&&n.transport?.kind!=="machine")&&$e(d(v(e),"commands.json"))){try{if(k(d(v(e),"commands.json")).size===0)return}catch{return}return{provider:n.provider,transport:n.transport.kind==="machine"?{kind:"machine"}:{kind:"http",endpoint:n.transport.endpoint}}}}a(h,"installedManifest");async function N(e,n,r=!1){const t=K();h(e)===void 0&&y.set(b(e),{status:"installing"});try{return T(d(t,"tmp")),await Z(J(t,e),async()=>{const o=v(e),s=C(o);if(s!==null&&s.provider===void 0)throw new c("provider_unavailable",`plugin "${e}" is installed and is not the directory's provider bundle`);const i=h(e)!==void 0;if(i&&r)return"unchanged";const f=i&&n?pe("user").plugins[e]?.etag:void 0,u=await ne(e,f!==void 0?{ifNoneMatch:f}:{});if(u.status===304)return"unchanged";if(u.status===404||u.status===410)throw new c("not_found",`the directory serves no provider "${e}" (${u.message})`,{param:"provider"});if(u.status==="failed")throw new c("provider_unavailable",`provider "${e}" bundle fetch failed: ${u.message}`,{cause:u.cause});const l=ce(u.raw);if(i&&fe(e,"user",o,l))return await le("user",e,u.etag,{swapped:!1}),"unchanged";const{installPluginArchive:w}=await oe();try{await w(e,u.raw,{scope:"user",scopeRootPath:t,root:o,version:l,etag:u.etag,enable:!i,bundle:{endpoint:`${E()}/providers/${encodeURIComponent(e)}/plugin`},transport:{kind:"http",endpoint:E()},accept:a(p=>re(e,void 0,p),"accept")})}catch(p){D(e,void 0,p);const U=new c("not_found",`provider "${e}" served an invalid bundle: ${p instanceof Error?p.message:String(p)}`,{param:"provider"});throw I.add(U),U}try{await G(async p=>await L(p))}catch(p){m({level:"error",event:"daemon.providers.docs_reconcile_failed",fields:{provider:e},error:p})}return m({level:"info",event:"daemon.providers.bundle_installed",fields:{provider:e,version:l,replaced:i}}),y.delete(b(e)),i?"updated":"installed"},{timeoutMs:X,timeoutError:a(()=>new c("provider_unavailable",`timed out waiting for another process replacing plugin "${e}"`),"timeoutError")})}catch(o){throw h(e)===void 0&&y.set(b(e),{status:"install_failed",error:Se(o)}),o instanceof c?o:(m({level:"error",event:"daemon.providers.bundle_install_failed",fields:{provider:e},error:o}),Ie(e))}}a(N,"refreshInstalled");async function nn(e){B(e),h(e)===void 0&&await N(e,!1,!0)}a(nn,"ensureProviderBundle");async function rn(e,n){return B(e),n.version!==void 0&&h(e)?.provider.major!==n.version?O(e,n.version,n.conditional):N(e,n.conditional)}a(rn,"refreshProviderBundle");async function ae(e,n,r){const{validatePluginArchive:t}=await oe(),o=await t(r);if(o.bundle===void 0)throw new M(`provider "${e}" major ${n} bundle is invalid: ${o.issues.map(i=>i.message).join("; ")}`,o.issues);const s=re(e,n,o.bundle);if(s!==void 0)throw new M(`provider "${e}" major ${n} bundle is invalid: ${s}`,[]);if(o.bundle.transport===void 0)throw new M(`provider "${e}" major ${n} bundle declares no transport`,[]);return o.bundle}a(ae,"validateArchive");function Me(e){T(e),x(d(e,"retired"),`${new Date().toISOString()}
2
- `),$(d(e,"bundle.tar"),{force:!0}),$(d(e,"etag"),{force:!0})}a(Me,"retire");function ie(e,n){return new c("provider_version_retired",`provider "${e}" major ${n} is retired`,{param:"version"})}a(ie,"retiredError");async function O(e,n,r){const t=ee(e,n),o=d(t,"bundle.tar"),s=g(o),i=s&&r&&g(d(t,"etag"))?S(d(t,"etag"),"utf8"):void 0,f=await ne(e,{version:n,...i!==void 0?{ifNoneMatch:i}:{}});if(f.status===304)return"unchanged";if(f.status===410)throw Me(t),m({level:"info",event:"daemon.providers.major_retired",fields:{provider:e,major:n}}),ie(e,n);if(f.status===404)throw new c("not_found",`provider "${e}" never served major ${n}`,{param:"version"});if(f.status==="failed")throw new c("provider_unavailable",`provider "${e}" major ${n} bundle fetch failed: ${f.message}`,{cause:f.cause});try{await ae(e,n,f.raw)}catch(l){D(e,n,l);const w=new c(s?"provider_unavailable":"not_found",l instanceof Error?l.message:String(l),{param:"version"});throw I.add(w),w}T(t),$(d(t,"retired"),{force:!0});const u=s&&Buffer.from(f.raw).equals(S(o));return u||x(o,f.raw),f.etag!==void 0?x(d(t,"etag"),f.etag):$(d(t,"etag"),{force:!0}),u?"unchanged":s?"updated":"installed"}a(O,"refreshPinned");function ke(){const e=[],n=R();if(!g(n))return e;for(const r of W(n)){if(!Q.test(r))continue;let t;try{t=W(d(n,r))}catch{continue}for(const o of t){const s=/^v([1-9][0-9]*)$/.exec(o);s&&g(d(n,r,o,"bundle.tar"))&&e.push([r,Number(s[1])])}}return e}a(ke,"cachedMajors");function xe(e){const n=de(e,{transport:"http",reservedCoreNames:new Set});if(n.manifest===void 0)throw new c("not_found","the provider bundle's commands.json no longer validates");return n.manifest}a(xe,"readCommands");const _=new Map,H=new Map;function Te(e,n){const r=k(n),t=`${r.ino}:${r.mtimeMs}:${r.size}`,o=H.get(e);if(o?.stamp===t)return o.commands;const s=xe(JSON.parse(S(n,"utf8")));return H.set(e,{stamp:t,commands:s}),s}a(Te,"installedCommands");async function tn(e,n){B(e);const r=h(e);if(n===void 0||r?.provider.major===n){if(r===void 0)throw new c("not_found",`provider "${e}" has no installed bundle`,{param:"provider"});const l=v(e),w=Te(e,d(l,"commands.json"));return{id:e,major:r.provider.major,provider:r.provider,commands:w,transport:r.transport,source:"installed",root:l}}if(!Number.isInteger(n)||n<1)throw new c("invalid_request","version must be an integer of at least 1",{param:"version"});const t=ee(e,n);if(g(d(t,"retired")))throw ie(e,n);const o=d(t,"bundle.tar");g(o)||await O(e,n,!1);const s=`${e}/${n}`,i=k(o),f=`${i.mtimeMs}:${i.size}`;let u=_.get(s)?.stamp===f?_.get(s).bundle:void 0;if(u===void 0){try{u=await ae(e,n,new Uint8Array(S(o)))}catch(l){throw D(e,n,l),new c("not_found",`provider "${e}" has no valid bundle for major ${n}`,{param:"version"})}_.set(s,{stamp:f,bundle:u})}return{id:e,major:n,provider:u.provider,commands:u.commands,transport:u.transport.kind==="machine"?{kind:"machine"}:{kind:"http",endpoint:u.transport.endpoint},source:"pinned"}}a(tn,"resolveProviderBundle");let P=null,F=!1;function Ce(e,n){return e?.provider?.id===n&&e.bundle?.endpoint===`${E()}/providers/${encodeURIComponent(n)}/plugin`}a(Ce,"isDirectoryBundle");async function se(e){return Y(await j()).includes(e)}a(se,"grantNames");async function je(e){await Z(J(K(),e),async()=>{if(!(await se(e)||!Ce(C(v(e)),e))){$(v(e),{recursive:!0,force:!0}),await me("user",n=>{delete n.plugins[e]}),await ve("user",n=>{delete n.plugins[e]}),$(d(R(),e),{recursive:!0,force:!0}),H.delete(e),y.delete(b(e));for(const n of[..._.keys()])n.startsWith(`${e}/`)&&_.delete(n);try{await G(async n=>await L(n))}catch(n){m({level:"error",event:"daemon.providers.docs_reconcile_failed",fields:{provider:e},error:n})}m({level:"info",event:"daemon.providers.bundle_uninstalled",fields:{provider:e}})}},{timeoutMs:X,timeoutError:a(()=>new c("provider_unavailable",`timed out waiting for another process replacing plugin "${e}"`),"timeoutError")})}a(je,"uninstallProvider");async function Ae(){if(!(await j()).every(e=>e.status!=="active")){for(const e of be("user"))if(!(e.manifest.provider===void 0||await se(e.name)))try{await je(e.name)}catch(n){m({level:"error",event:"daemon.providers.bundle_uninstall_failed",fields:{provider:e.name},error:n})}}}a(Ae,"uninstallDisconnected");async function Re(){if(!q())return;const e=Y(await j());for(const n of e)try{await N(n,!0)}catch(r){r instanceof Object&&I.has(r)||te(n,void 0,r)}await Ae();for(const[n,r]of ke())try{await O(n,r,!0)}catch(t){if(t instanceof c&&t.code==="provider_version_retired"||t instanceof Object&&I.has(t))continue;te(n,r,t)}}a(Re,"syncOnce");function on(){return P!==null?(F=!0,P):(P=(async()=>{try{do F=!1,await Re();while(F)}finally{P=null}})(),P)}a(on,"syncProviderBundles");export{nn as ensureProviderBundle,Y as providerIdsFromGrants,Ee as providerInstallState,rn as refreshProviderBundle,tn as resolveProviderBundle,on as syncProviderBundles,Ie as unavailableProviderError};
1
+ var se=Object.defineProperty;var o=(e,n)=>se(e,"name",{value:n,configurable:!0});import{existsSync as g,readdirSync as U,readFileSync as S,rmSync as $,statSync as E}from"node:fs";import{join as d}from"node:path";import{validateCommandManifest as ue}from"../../api/command-manifest/manifest.js";import{crtrHome as W}from"../canvas/paths.js";import{withCanvasWrite as z}from"../canvas/db.js";import{bundleVersion as de,isInstalledGeneration as ce,recordCheckedGeneration as fe}from"../command-plugins/bundle.js";import{readState as le,updateConfig as pe,updateState as me}from"../config.js";import{directoryIssuer as G}from"../directory/issuer.js";import{emitEvent as m}from"../events/emit.js";import{describeOutboundFailure as ve,outboundFetch as he,outboundRefusal as q,OutboundError as we}from"../events/outbound.js";import{withExclusiveDirectoryLockAsync as Z}from"../exclusive-lock.js";import{atomicWriteText as T,ensureDir as x,pathExists as ge}from"../fs-utils.js";import{reconcilePackages as L}from"../graph/package-reconcile.js";import{readPluginManifest as j}from"../manifest.js";import{bundleSwapLockPath as V}from"../plugin-swap-lock.js";import{pluginsDir as $e,userScopeRoot as J}from"../scope.js";import{listInstalledPlugins as ye}from"../resolver.js";import{localGrants as A,providerIdsFromGrants as K}from"../grants/mirror.js";import{ProviderError as c}from"./errors.js";const be="[A-Za-z0-9_-]{1,64}",Q=new RegExp(`^${be}$`),_e=3e4,X=3e4;function v(e){return d($e("user"),e)}o(v,"pluginRoot");function C(){return d(W(),"providers")}o(C,"providersRoot");function Y(e,n){return d(C(),e,`v${n}`)}o(Y,"majorDir");function B(e){if(!Q.test(e))throw new c("not_found",`"${e}" is not a provider id`,{param:"provider"})}o(B,"assertId");function I(){const e=G();if(!e)throw new c("not_found","provider bundles need a directory-connected runtime");return e}o(I,"directoryBase");async function ee(e,n){const r=`${I()}/providers/${encodeURIComponent(e)}/plugin${n.version!==void 0?`?version=${n.version}`:""}`;let t;try{t=await he("bundle_fetch",r,{headers:{Accept:"application/x-tar",...n.ifNoneMatch!==void 0?{"If-None-Match":n.ifNoneMatch}:{}},signal:AbortSignal.timeout(_e)})}catch(i){return{status:"failed",message:`could not reach ${r}: ${i instanceof Error?i.message:String(i)}`,cause:i}}if(t.status===304){if(n.ifNoneMatch===void 0){const i=`HTTP 304 from ${r} without a conditional request`;return{status:"failed",message:i,cause:q("bundle_fetch",r,i)}}return{status:304}}if(t.status===404||t.status===410)return await t.body?.cancel().catch(()=>{}),{status:t.status===404?404:410,message:`HTTP ${t.status} from ${r}`};if(t.status<200||t.status>=300){await t.body?.cancel().catch(()=>{});{const i=`HTTP ${t.status} from ${r}`;return{status:"failed",message:i,cause:q("bundle_fetch",r,i)}}}let a;try{a=new Uint8Array(await t.arrayBuffer())}catch(i){return{status:"failed",message:`reading ${r} failed: ${i instanceof Error?i.message:String(i)}`,cause:new we("bundle_fetch",r,i)}}const s=t.headers.get("etag");return{status:200,raw:a,...s!==null?{etag:s}:{}}}o(ee,"fetchProviderBundle");const M=new WeakSet;class k extends Error{static{o(this,"BundleInvalid")}issues;constructor(n,r){super(n),this.issues=r}}function ne(e,n,r){if(r.provider===void 0)return"the archive carries no provider block";if(r.provider.id!==e)return`the archive is provider "${r.provider.id}", not "${e}"`;if(n!==void 0&&r.provider.major!==n)return`the archive is major ${r.provider.major}, not ${n}`;const t=["hooks","kinds","page_components","memory_extensions"].filter(a=>r[a]!==void 0);if(t.length>0)return`a provider bundle may not carry ${t.join(", ")}`}o(ne,"wrongBundle");function N(e,n,r){m({level:"error",event:"daemon.providers.bundle_invalid",fields:{provider:e,...n!==void 0?{major:n}:{},...r instanceof k?{issues:r.issues}:{}},error:r})}o(N,"logInvalid");function re(e,n,r){m({level:"warn",event:"daemon.providers.bundle_fetch_failed",fields:{provider:e,...n!==void 0?{major:n}:{},message:r instanceof Error?r.message:String(r),...ve(r instanceof Error?r.cause:void 0)}})}o(re,"logFailure");async function te(){return import("../../commands/pkg/plugin-manage.js")}o(te,"pluginManage");const y=new Map,b=o(e=>`${W()}\0${e}`,"installKey");function Pe(e){const n=e,r=n?.code??n?.cause?.code;return typeof r=="string"&&/^[A-Z][A-Z0-9_]{1,30}$/.test(r)?`${r} on provider install directory`:e instanceof c&&e.message.includes("HTTP 404")?"directory returned HTTP 404":e instanceof c&&e.message.includes("HTTP 410")?"directory returned HTTP 410":"provider bundle could not be installed"}o(Pe,"publicInstallCause");function Se(e){if(h(e)===void 0)return y.get(b(e))??{status:"installing"}}o(Se,"providerInstallState");function Ie(e){return new c("provider_unavailable",`provider "${e}" unavailable: ${Se(e)?.error??"bundle installation pending"}`)}o(Ie,"unavailableProviderError");function h(e){const n=j(v(e));if(!(n?.provider===void 0||n.transport?.kind!=="http"&&n.transport?.kind!=="machine")&&ge(d(v(e),"commands.json"))){try{if(E(d(v(e),"commands.json")).size===0)return}catch{return}return{provider:n.provider,transport:n.transport.kind==="machine"?{kind:"machine"}:{kind:"http",endpoint:n.transport.endpoint}}}}o(h,"installedManifest");async function R(e,n,r=!1){const t=J();h(e)===void 0&&y.set(b(e),{status:"installing"});try{return x(d(t,"tmp")),await Z(V(t,e),async()=>{const a=v(e),s=j(a);if(s!==null&&s.provider===void 0)throw new c("provider_unavailable",`plugin "${e}" is installed and is not the directory's provider bundle`);const i=h(e)!==void 0;if(i&&r)return"unchanged";const f=i&&n?le("user").plugins[e]?.etag:void 0,u=await ee(e,f!==void 0?{ifNoneMatch:f}:{});if(u.status===304)return"unchanged";if(u.status===404||u.status===410)throw new c("not_found",`the directory serves no provider "${e}" (${u.message})`,{param:"provider"});if(u.status==="failed")throw new c("provider_unavailable",`provider "${e}" bundle fetch failed: ${u.message}`,{cause:u.cause});const l=de(u.raw);if(i&&ce(e,"user",a,l))return await fe("user",e,u.etag,{swapped:!1}),"unchanged";const{installPluginArchive:w}=await te();try{await w(e,u.raw,{scope:"user",scopeRootPath:t,root:a,version:l,etag:u.etag,enable:!i,bundle:{endpoint:`${I()}/providers/${encodeURIComponent(e)}/plugin`},transport:{kind:"http",endpoint:I()},accept:o(p=>ne(e,void 0,p),"accept")})}catch(p){N(e,void 0,p);const F=new c("not_found",`provider "${e}" served an invalid bundle: ${p instanceof Error?p.message:String(p)}`,{param:"provider"});throw M.add(F),F}try{await z(async p=>await L(p))}catch(p){m({level:"error",event:"daemon.providers.docs_reconcile_failed",fields:{provider:e},error:p})}return m({level:"info",event:"daemon.providers.bundle_installed",fields:{provider:e,version:l,replaced:i}}),y.delete(b(e)),i?"updated":"installed"},{timeoutMs:X,timeoutError:o(()=>new c("provider_unavailable",`timed out waiting for another process replacing plugin "${e}"`),"timeoutError")})}catch(a){throw h(e)===void 0&&y.set(b(e),{status:"install_failed",error:Pe(a)}),a instanceof c?a:(m({level:"error",event:"daemon.providers.bundle_install_failed",fields:{provider:e},error:a}),Ie(e))}}o(R,"refreshInstalled");async function en(e){B(e),h(e)===void 0&&await R(e,!1,!0)}o(en,"ensureProviderBundle");async function nn(e,n){return B(e),n.version!==void 0&&h(e)?.provider.major!==n.version?D(e,n.version,n.conditional):R(e,n.conditional)}o(nn,"refreshProviderBundle");async function oe(e,n,r){const{validatePluginArchive:t}=await te(),a=await t(r);if(a.bundle===void 0)throw new k(`provider "${e}" major ${n} bundle is invalid: ${a.issues.map(i=>i.message).join("; ")}`,a.issues);const s=ne(e,n,a.bundle);if(s!==void 0)throw new k(`provider "${e}" major ${n} bundle is invalid: ${s}`,[]);if(a.bundle.transport===void 0)throw new k(`provider "${e}" major ${n} bundle declares no transport`,[]);return a.bundle}o(oe,"validateArchive");function Me(e){x(e),T(d(e,"retired"),`${new Date().toISOString()}
2
+ `),$(d(e,"bundle.tar"),{force:!0}),$(d(e,"etag"),{force:!0})}o(Me,"retire");function ae(e,n){return new c("provider_version_retired",`provider "${e}" major ${n} is retired`,{param:"version"})}o(ae,"retiredError");async function D(e,n,r){const t=Y(e,n),a=d(t,"bundle.tar"),s=g(a),i=s&&r&&g(d(t,"etag"))?S(d(t,"etag"),"utf8"):void 0,f=await ee(e,{version:n,...i!==void 0?{ifNoneMatch:i}:{}});if(f.status===304)return"unchanged";if(f.status===410)throw Me(t),m({level:"info",event:"daemon.providers.major_retired",fields:{provider:e,major:n}}),ae(e,n);if(f.status===404)throw new c("not_found",`provider "${e}" never served major ${n}`,{param:"version"});if(f.status==="failed")throw new c("provider_unavailable",`provider "${e}" major ${n} bundle fetch failed: ${f.message}`,{cause:f.cause});try{await oe(e,n,f.raw)}catch(l){N(e,n,l);const w=new c(s?"provider_unavailable":"not_found",l instanceof Error?l.message:String(l),{param:"version"});throw M.add(w),w}x(t),$(d(t,"retired"),{force:!0});const u=s&&Buffer.from(f.raw).equals(S(a));return u||T(a,f.raw),f.etag!==void 0?T(d(t,"etag"),f.etag):$(d(t,"etag"),{force:!0}),u?"unchanged":s?"updated":"installed"}o(D,"refreshPinned");function ke(){const e=[],n=C();if(!g(n))return e;for(const r of U(n)){if(!Q.test(r))continue;let t;try{t=U(d(n,r))}catch{continue}for(const a of t){const s=/^v([1-9][0-9]*)$/.exec(a);s&&g(d(n,r,a,"bundle.tar"))&&e.push([r,Number(s[1])])}}return e}o(ke,"cachedMajors");function Ee(e){const n=ue(e,{transport:"http",reservedCoreNames:new Set});if(n.manifest===void 0)throw new c("not_found","the provider bundle's commands.json no longer validates");return n.manifest}o(Ee,"readCommands");const _=new Map,H=new Map;function Te(e,n){const r=E(n),t=`${r.ino}:${r.mtimeMs}:${r.size}`,a=H.get(e);if(a?.stamp===t)return a.commands;const s=Ee(JSON.parse(S(n,"utf8")));return H.set(e,{stamp:t,commands:s}),s}o(Te,"installedCommands");async function rn(e,n){B(e);const r=h(e);if(n===void 0||r?.provider.major===n){if(r===void 0)throw new c("not_found",`provider "${e}" has no installed bundle`,{param:"provider"});const l=v(e),w=Te(e,d(l,"commands.json"));return{id:e,major:r.provider.major,provider:r.provider,commands:w,transport:r.transport,source:"installed",root:l}}if(!Number.isInteger(n)||n<1)throw new c("invalid_request","version must be an integer of at least 1",{param:"version"});const t=Y(e,n);if(g(d(t,"retired")))throw ae(e,n);const a=d(t,"bundle.tar");g(a)||await D(e,n,!1);const s=`${e}/${n}`,i=E(a),f=`${i.mtimeMs}:${i.size}`;let u=_.get(s)?.stamp===f?_.get(s).bundle:void 0;if(u===void 0){try{u=await oe(e,n,new Uint8Array(S(a)))}catch(l){throw N(e,n,l),new c("not_found",`provider "${e}" has no valid bundle for major ${n}`,{param:"version"})}_.set(s,{stamp:f,bundle:u})}return{id:e,major:n,provider:u.provider,commands:u.commands,transport:u.transport.kind==="machine"?{kind:"machine"}:{kind:"http",endpoint:u.transport.endpoint},source:"pinned"}}o(rn,"resolveProviderBundle");let P=null,O=!1;function xe(e,n){return e?.provider?.id===n&&e.bundle?.endpoint===`${I()}/providers/${encodeURIComponent(n)}/plugin`}o(xe,"isDirectoryBundle");async function ie(e){return K(await A()).includes(e)}o(ie,"grantNames");async function je(e){await Z(V(J(),e),async()=>{if(!(await ie(e)||!xe(j(v(e)),e))){$(v(e),{recursive:!0,force:!0}),await pe("user",n=>{delete n.plugins[e]}),await me("user",n=>{delete n.plugins[e]}),$(d(C(),e),{recursive:!0,force:!0}),H.delete(e),y.delete(b(e));for(const n of[..._.keys()])n.startsWith(`${e}/`)&&_.delete(n);try{await z(async n=>await L(n))}catch(n){m({level:"error",event:"daemon.providers.docs_reconcile_failed",fields:{provider:e},error:n})}m({level:"info",event:"daemon.providers.bundle_uninstalled",fields:{provider:e}})}},{timeoutMs:X,timeoutError:o(()=>new c("provider_unavailable",`timed out waiting for another process replacing plugin "${e}"`),"timeoutError")})}o(je,"uninstallProvider");async function Ae(){if(!(await A()).every(e=>e.status!=="active")){for(const e of ye("user"))if(!(e.manifest.provider===void 0||await ie(e.name)))try{await je(e.name)}catch(n){m({level:"error",event:"daemon.providers.bundle_uninstall_failed",fields:{provider:e.name},error:n})}}}o(Ae,"uninstallDisconnected");async function Ce(){if(!G())return;const e=K(await A());for(const n of e)try{await R(n,!0)}catch(r){r instanceof Object&&M.has(r)||re(n,void 0,r)}await Ae();for(const[n,r]of ke())try{await D(n,r,!0)}catch(t){if(t instanceof c&&t.code==="provider_version_retired"||t instanceof Object&&M.has(t))continue;re(n,r,t)}}o(Ce,"syncOnce");function tn(){return P!==null?(O=!0,P):(P=(async()=>{try{do O=!1,await Ce();while(O)}finally{P=null}})(),P)}o(tn,"syncProviderBundles");export{en as ensureProviderBundle,Se as providerInstallState,nn as refreshProviderBundle,rn as resolveProviderBundle,tn as syncProviderBundles,Ie as unavailableProviderError};
@@ -8,6 +8,8 @@ export interface PluginInstall {
8
8
  url: string | null;
9
9
  ref: string | null;
10
10
  } | null;
11
+ /** A directory provider bundle (`isProviderBundleRow`): a provider call installs it into this process's own plugin directory, so there is nothing to fetch. */
12
+ providerBundle: boolean;
11
13
  }
12
14
  /** The directory holding this person's materialized plugins as `<dir>/<plugin name>`; undefined when none were materialized. */
13
15
  export declare function materializedPluginsDir(person: string): string | undefined;
@@ -1 +1 @@
1
- var z=Object.defineProperty;var u=(e,n)=>z(e,"name",{value:n,configurable:!0});import{cpSync as S,existsSync as h,mkdirSync as b,renameSync as M,rmSync as w}from"node:fs";import{tmpdir as A}from"node:os";import{isAbsolute as $,join as l,relative as P,resolve as v}from"node:path";import{CrtrError as c}from"../errors.js";import{gitAsync as x}from"../git.js";import{readMarketplaceManifest as D}from"../manifest.js";const m=new Map,d=new Map;function q(e){const n=m.get(e);if(n!==void 0&&"failure"in n)throw n.failure;return n?.dir}u(q,"materializedPluginsDir");function y(e){return l(A(),"crtr-plugin-cache",e.replace(/[^A-Za-z0-9._-]/g,"_"))}u(y,"cacheRoot");async function _(e,n,o,r){const t=await x(["clone","--depth","1",...n?["--branch",n]:[],e,o]);if(t.status!==0)throw new c("plugin_install_failed",`${r}: git clone ${e} failed: ${t.stderr.trim()}`)}u(_,"cloneInto");async function R(e,n){const o=new Map;b(l(e,"plugins"),{recursive:!0});for(const r of n){if(r.sourcePath!==null)throw new c("plugin_source_unreachable",`${r.name}: its source is ${r.sourcePath} on the machine that registered it, which the control plane cannot read`);const t=r.marketplace;if(t===null)throw new c("plugin_source_unreachable",`${r.name}: it names neither a marketplace nor a path`);if(t.url===null||t.url==="")throw new c("plugin_source_unreachable",`${r.name}: its marketplace ${t.name} has no url`);const s=`${t.url}\0${t.ref??""}`;let a=o.get(s);a===void 0&&(a=l(e,"marketplaces",String(o.size)),await _(t.url,t.ref,a,`${r.name}: marketplace ${t.name}`),o.set(s,a));const i=D(a)?.plugins.find(k=>k.name===r.name);if(i===void 0)throw new c("plugin_install_failed",`${r.name}: marketplace ${t.name} (${t.url}) does not list it`);const f=l(e,"plugins",r.name);if($(i.source)||/^[a-z][a-z0-9+.-]*:|^git@/i.test(i.source)||i.source.endsWith(".git")){await _(i.source,null,f,r.name);continue}const p=v(a,i.source),g=P(a,p);if(g.startsWith("..")||$(g))throw new c("plugin_install_failed",`${r.name}: its source ${i.source} leaves the marketplace`);if(!h(p))throw new c("plugin_install_failed",`${r.name}: its source ${i.source} is not in marketplace ${t.name}`);S(p,f,{recursive:!0})}w(l(e,"marketplaces"),{recursive:!0,force:!0})}u(R,"installAll");async function B(e,n,o){const r=m.get(e);if(r?.revision===n&&!("failure"in r))return;const t=l(y(e),String(n)),s=`${e}\0${n}`;let a=d.get(s);a===void 0&&(a=(async()=>{if(!h(l(t,"plugins"))){const i=l(y(e),`${n}.${process.pid}.${Date.now()}`);try{await R(i,o);try{M(i,t)}catch(f){if(!h(l(t,"plugins")))throw f}}finally{w(i,{recursive:!0,force:!0})}}return l(t,"plugins")})().finally(()=>d.delete(s)),d.set(s,a));try{m.set(e,{revision:n,dir:await a})}catch(i){if(!(i instanceof c))throw i;m.set(e,{revision:n,failure:i})}}u(B,"materializePlugins");export{B as materializePlugins,q as materializedPluginsDir};
1
+ var z=Object.defineProperty;var u=(e,r)=>z(e,"name",{value:r,configurable:!0});import{cpSync as S,existsSync as d,mkdirSync as b,renameSync as M,rmSync as w}from"node:fs";import{tmpdir as v}from"node:os";import{isAbsolute as $,join as l,relative as A,resolve as P}from"node:path";import{CrtrError as c}from"../errors.js";import{gitAsync as x}from"../git.js";import{readMarketplaceManifest as D}from"../manifest.js";const m=new Map,h=new Map;function Z(e){const r=m.get(e);if(r!==void 0&&"failure"in r)throw r.failure;return r?.dir}u(Z,"materializedPluginsDir");function y(e){return l(v(),"crtr-plugin-cache",e.replace(/[^A-Za-z0-9._-]/g,"_"))}u(y,"cacheRoot");async function _(e,r,o,n){const t=await x(["clone","--depth","1",...r?["--branch",r]:[],e,o]);if(t.status!==0)throw new c("plugin_install_failed",`${n}: git clone ${e} failed: ${t.stderr.trim()}`)}u(_,"cloneInto");async function R(e,r){const o=new Map;b(l(e,"plugins"),{recursive:!0});for(const n of r){if(n.sourcePath!==null)throw new c("plugin_source_unreachable",`${n.name}: its source is ${n.sourcePath} on the machine that registered it, which the control plane cannot read`);const t=n.marketplace;if(n.providerBundle)continue;if(t===null)throw new c("plugin_source_unreachable",`${n.name}: it names neither a marketplace nor a path`);if(t.url===null||t.url==="")throw new c("plugin_source_unreachable",`${n.name}: its marketplace ${t.name} has no url`);const s=`${t.url}\0${t.ref??""}`;let a=o.get(s);a===void 0&&(a=l(e,"marketplaces",String(o.size)),await _(t.url,t.ref,a,`${n.name}: marketplace ${t.name}`),o.set(s,a));const i=D(a)?.plugins.find(k=>k.name===n.name);if(i===void 0)throw new c("plugin_install_failed",`${n.name}: marketplace ${t.name} (${t.url}) does not list it`);const f=l(e,"plugins",n.name);if($(i.source)||/^[a-z][a-z0-9+.-]*:|^git@/i.test(i.source)||i.source.endsWith(".git")){await _(i.source,null,f,n.name);continue}const p=P(a,i.source),g=A(a,p);if(g.startsWith("..")||$(g))throw new c("plugin_install_failed",`${n.name}: its source ${i.source} leaves the marketplace`);if(!d(p))throw new c("plugin_install_failed",`${n.name}: its source ${i.source} is not in marketplace ${t.name}`);S(p,f,{recursive:!0})}w(l(e,"marketplaces"),{recursive:!0,force:!0})}u(R,"installAll");async function q(e,r,o){const n=m.get(e);if(n?.revision===r&&!("failure"in n))return;const t=l(y(e),String(r)),s=`${e}\0${r}`;let a=h.get(s);a===void 0&&(a=(async()=>{if(!d(l(t,"plugins"))){const i=l(y(e),`${r}.${process.pid}.${Date.now()}`);try{await R(i,o);try{M(i,t)}catch(f){if(!d(l(t,"plugins")))throw f}}finally{w(i,{recursive:!0,force:!0})}}return l(t,"plugins")})().finally(()=>h.delete(s)),h.set(s,a));try{m.set(e,{revision:r,dir:await a})}catch(i){if(!(i instanceof c))throw i;m.set(e,{revision:r,failure:i})}}u(q,"materializePlugins");export{q as materializePlugins,Z as materializedPluginsDir};
@@ -1,6 +1,6 @@
1
- var O=Object.defineProperty;var _=(a,n)=>O(a,"name",{value:n,configurable:!0});import{openDb as S,withCanvasWrite as N}from"../canvas/db.js";import{isCloudRole as R}from"../canvas/person-state.js";import{materializePlugins as T}from"./materialized-plugins.js";import{revisionOf as y,setPersonSnapshot as D,setSettingsReloader as v,setSettingsTransport as L,SettingsRevisionConflictError as C}from"./person-store.js";const s=_(a=>typeof a=="string"?a:void 0,"s");async function w(a,n){const o=await a.prepare("SELECT * FROM profiles WHERE person = ? AND deleting_since IS NULL ORDER BY created_at, id").all(n),f=await a.prepare("SELECT p.profile_id, p.path FROM profile_projects p JOIN profiles f ON f.id = p.profile_id WHERE f.person = ? ORDER BY p.profile_id, p.position").all(n),r=new Map;for(const e of f)(r.get(e.profile_id)??r.set(e.profile_id,[]).get(e.profile_id)).push({path:e.path});const t=o.map(e=>{const l=JSON.parse(String(e.metadata_json)),p=JSON.parse(String(e.delivery_limits_json));return{profileId:String(e.id),manifest:{schema_version:1,name:String(e.name),projects:r.get(String(e.id))??[],paused_at:s(e.paused_at)??null,...s(e.default_kind)===void 0?{}:{default_kind:s(e.default_kind)},...s(e.grantee)===void 0?{}:{grantee:s(e.grantee)},...s(e.environment_id)===void 0?{}:{environment_id:s(e.environment_id)},...Object.keys(p).length===0?{}:{delivery_limits:p},...Object.keys(l).length===0?{}:{metadata:l},created_at:String(e.created_at),last_used_at:s(e.last_used_at)??null}}}),i=Object.fromEntries((await a.prepare("SELECT directory, profile_id FROM profile_defaults WHERE person = ?").all(n)).map(e=>[e.directory,e.profile_id])),u={};for(const e of await a.prepare("SELECT owner_key, raw_json FROM scope_settings WHERE person = ?").all(n))u[e.owner_key]=JSON.parse(e.raw_json);const m={},E=_(e=>m[e]??={marketplaces:{},plugins:{},activeCanvas:null},"stateOf");for(const e of await a.prepare("SELECT owner_key, last_self_check, active_canvas FROM scope_bookkeeping WHERE person = ?").all(n)){const l=E(e.owner_key);e.last_self_check!==null&&(l.last_self_check=e.last_self_check),l.activeCanvas=e.active_canvas}for(const e of await a.prepare("SELECT * FROM scope_marketplaces WHERE person = ? ORDER BY owner_key, name").all(n)){const l=String(e.owner_key),p=String(e.name);if(E(l).marketplaces[p]=s(e.last_updated)===void 0?{}:{last_updated:s(e.last_updated)},s(e.url)!==void 0){const d=u[l]??={},c=d.marketplaces??={};c[p]={url:s(e.url),ref:s(e.ref)??"",installed_at:s(e.installed_at)??""}}}for(const e of await a.prepare("SELECT * FROM scope_plugins WHERE person = ? ORDER BY owner_key, name").all(n)){const l=String(e.owner_key),p=String(e.name);E(l).plugins[p]={...s(e.last_updated)===void 0?{}:{last_updated:s(e.last_updated)},...s(e.etag)===void 0?{}:{etag:s(e.etag)},...s(e.checked_at)===void 0?{}:{checked_at:s(e.checked_at)}};const d=u[l]??={},c=d.plugins??={};c[p]={enabled:Number(e.enabled)===1,...s(e.source_marketplace)===void 0?{}:{source_marketplace:s(e.source_marketplace)},...s(e.version)===void 0?{}:{version:s(e.version)}}}return{profiles:t,defaults:i,settings:u,state:m}}_(w,"readSnapshotFromRows");async function g(a){const n=await S(),o=await w(n,a);return R()&&await I(n,a),D(a,o),o}_(g,"loadPersonSettings");async function I(a,n){const o=await a.prepare(`SELECT p.name, p.version, p.source_path, p.source_marketplace, m.url, m.ref FROM scope_plugins p
1
+ var O=Object.defineProperty;var _=(a,t)=>O(a,"name",{value:t,configurable:!0});import{openDb as S,withCanvasWrite as N}from"../canvas/db.js";import{isCloudRole as R}from"../canvas/person-state.js";import{grantedProviderIds as T,isProviderBundleRow as y}from"../grants/mirror.js";import{materializePlugins as D}from"./materialized-plugins.js";import{revisionOf as v,setPersonSnapshot as L,setSettingsReloader as C,setSettingsTransport as h,SettingsRevisionConflictError as I}from"./person-store.js";const s=_(a=>typeof a=="string"?a:void 0,"s");async function w(a,t){const o=await a.prepare("SELECT * FROM profiles WHERE person = ? AND deleting_since IS NULL ORDER BY created_at, id").all(t),u=await a.prepare("SELECT p.profile_id, p.path FROM profile_projects p JOIN profiles f ON f.id = p.profile_id WHERE f.person = ? ORDER BY p.profile_id, p.position").all(t),n=new Map;for(const e of u)(n.get(e.profile_id)??n.set(e.profile_id,[]).get(e.profile_id)).push({path:e.path});const r=o.map(e=>{const l=JSON.parse(String(e.metadata_json)),p=JSON.parse(String(e.delivery_limits_json));return{profileId:String(e.id),manifest:{schema_version:1,name:String(e.name),projects:n.get(String(e.id))??[],paused_at:s(e.paused_at)??null,...s(e.default_kind)===void 0?{}:{default_kind:s(e.default_kind)},...s(e.grantee)===void 0?{}:{grantee:s(e.grantee)},...s(e.environment_id)===void 0?{}:{environment_id:s(e.environment_id)},...Object.keys(p).length===0?{}:{delivery_limits:p},...Object.keys(l).length===0?{}:{metadata:l},created_at:String(e.created_at),last_used_at:s(e.last_used_at)??null}}}),i=Object.fromEntries((await a.prepare("SELECT directory, profile_id FROM profile_defaults WHERE person = ?").all(t)).map(e=>[e.directory,e.profile_id])),f={};for(const e of await a.prepare("SELECT owner_key, raw_json FROM scope_settings WHERE person = ?").all(t))f[e.owner_key]=JSON.parse(e.raw_json);const m={},E=_(e=>m[e]??={marketplaces:{},plugins:{},activeCanvas:null},"stateOf");for(const e of await a.prepare("SELECT owner_key, last_self_check, active_canvas FROM scope_bookkeeping WHERE person = ?").all(t)){const l=E(e.owner_key);e.last_self_check!==null&&(l.last_self_check=e.last_self_check),l.activeCanvas=e.active_canvas}for(const e of await a.prepare("SELECT * FROM scope_marketplaces WHERE person = ? ORDER BY owner_key, name").all(t)){const l=String(e.owner_key),p=String(e.name);if(E(l).marketplaces[p]=s(e.last_updated)===void 0?{}:{last_updated:s(e.last_updated)},s(e.url)!==void 0){const d=f[l]??={},c=d.marketplaces??={};c[p]={url:s(e.url),ref:s(e.ref)??"",installed_at:s(e.installed_at)??""}}}for(const e of await a.prepare("SELECT * FROM scope_plugins WHERE person = ? ORDER BY owner_key, name").all(t)){const l=String(e.owner_key),p=String(e.name);E(l).plugins[p]={...s(e.last_updated)===void 0?{}:{last_updated:s(e.last_updated)},...s(e.etag)===void 0?{}:{etag:s(e.etag)},...s(e.checked_at)===void 0?{}:{checked_at:s(e.checked_at)}};const d=f[l]??={},c=d.plugins??={};c[p]={enabled:Number(e.enabled)===1,...s(e.source_marketplace)===void 0?{}:{source_marketplace:s(e.source_marketplace)},...s(e.version)===void 0?{}:{version:s(e.version)}}}return{profiles:r,defaults:i,settings:f,state:m}}_(w,"readSnapshotFromRows");async function g(a){const t=await S(),o=await w(t,a);return R()&&await j(t,a),L(a,o),o}_(g,"loadPersonSettings");async function j(a,t){const o=await a.prepare(`SELECT p.name, p.version, p.source_path, p.source_marketplace, m.url, m.ref FROM scope_plugins p
2
2
  LEFT JOIN scope_marketplaces m ON m.person = p.person AND m.owner_key = p.owner_key AND m.name = p.source_marketplace
3
- WHERE p.person = ? AND p.owner_key = 'user' AND p.enabled = 1 ORDER BY p.name`).all(n),f=await a.prepare("SELECT plugins_revision FROM person_settings WHERE person = ?").get(n),r=o.map(t=>({name:String(t.name),version:s(t.version)??null,sourcePath:s(t.source_path)??null,marketplace:s(t.source_marketplace)===void 0?null:{name:s(t.source_marketplace),url:s(t.url)??null,ref:s(t.ref)??null}}));await T(n,f===void 0?0:Number(f.plugins_revision),r)}_(I,"materializePersonPlugins");async function h(a,n,o){return(await w(a,n)).settings[o]}_(h,"currentRaw");async function j(a,n,o){let f=!1;for(const r of o)if(r.op==="settings.replace"){const t=await h(a,n,r.owner);if(r.ifRevision!==y(t))throw new C;const{plugins:i,marketplaces:u,...m}=r.raw,E=JSON.stringify(m);await a.prepare("INSERT INTO scope_settings (person, owner_key, raw_json) VALUES (?, ?, ?) ON CONFLICT (person, owner_key) DO UPDATE SET raw_json = EXCLUDED.raw_json").run(n,r.owner,E);const e=i??{},l=u??{},p=JSON.stringify([t?.plugins,t?.marketplaces]);p!==JSON.stringify([i,u])&&!(p==="[null,null]"&&i===void 0&&u===void 0)&&(f=!0),await a.prepare("DELETE FROM scope_plugins WHERE person = ? AND owner_key = ? AND NOT (name IN (SELECT jsonb_array_elements_text(?::jsonb)))").run(n,r.owner,JSON.stringify(Object.keys(e)));for(const[d,c]of Object.entries(e))await a.prepare(`INSERT INTO scope_plugins (person, owner_key, name, enabled, source_marketplace, version) VALUES (?, ?, ?, ?, ?, ?)
4
- ON CONFLICT (person, owner_key, name) DO UPDATE SET enabled = EXCLUDED.enabled, source_marketplace = EXCLUDED.source_marketplace, version = EXCLUDED.version`).run(n,r.owner,d,c.enabled===!1?0:1,c.source_marketplace??null,c.version??null);await a.prepare("DELETE FROM scope_marketplaces WHERE person = ? AND owner_key = ? AND NOT (name IN (SELECT jsonb_array_elements_text(?::jsonb)))").run(n,r.owner,JSON.stringify(Object.keys(l)));for(const[d,c]of Object.entries(l))await a.prepare(`INSERT INTO scope_marketplaces (person, owner_key, name, url, ref, installed_at) VALUES (?, ?, ?, ?, ?, ?)
5
- ON CONFLICT (person, owner_key, name) DO UPDATE SET url = EXCLUDED.url, ref = EXCLUDED.ref, installed_at = EXCLUDED.installed_at`).run(n,r.owner,d,c.url??null,c.ref??null,c.installed_at??null)}else if(r.op==="state.write"){await a.prepare(`INSERT INTO scope_bookkeeping (person, owner_key, last_self_check, active_canvas) VALUES (?, ?, ?, ?)
6
- ON CONFLICT (person, owner_key) DO UPDATE SET last_self_check = EXCLUDED.last_self_check, active_canvas = EXCLUDED.active_canvas`).run(n,r.owner,r.state.last_self_check??null,r.state.activeCanvas??null);for(const[t,i]of Object.entries(r.state.marketplaces))await a.prepare("UPDATE scope_marketplaces SET last_updated = ? WHERE person = ? AND owner_key = ? AND name = ?").run(i.last_updated??null,n,r.owner,t);for(const[t,i]of Object.entries(r.state.plugins))await a.prepare("UPDATE scope_plugins SET last_updated = ?, etag = ?, checked_at = ? WHERE person = ? AND owner_key = ? AND name = ?").run(i.last_updated??null,i.etag??null,i.checked_at??null,n,r.owner,t)}else if(r.op==="profile.insert"){const t=r.profile.manifest;await a.prepare("INSERT INTO profiles (id, person, name, created_at, last_used_at, paused_at, default_kind, grantee, metadata_json, delivery_limits_json, environment_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)").run(r.profile.profileId,n,t.name,t.created_at,t.last_used_at,t.paused_at,t.default_kind??null,t.grantee??null,JSON.stringify(t.metadata??{}),JSON.stringify(t.delivery_limits??{}),t.environment_id??null),await k(a,r.profile)}else if(r.op==="profile.update"){const t=r.profile.manifest;if((await a.prepare("UPDATE profiles SET name = ?, last_used_at = ?, paused_at = ?, default_kind = ?, grantee = ?, metadata_json = ?, delivery_limits_json = ?, environment_id = ? WHERE id = ? AND person = ?").run(t.name,t.last_used_at,t.paused_at,t.default_kind??null,t.grantee??null,JSON.stringify(t.metadata??{}),JSON.stringify(t.delivery_limits??{}),t.environment_id??null,r.profile.profileId,n)).changes!==1)throw new Error(`no stored profile ${r.profile.profileId}`);await k(a,r.profile)}else if(r.op==="profile.delete"){const t=`profile:${r.profileId}`;for(const i of["scope_settings","scope_bookkeeping","scope_marketplaces","scope_plugins"])await a.prepare(`DELETE FROM ${i} WHERE person = ? AND owner_key = ?`).run(n,t);await a.prepare("DELETE FROM profiles WHERE id = ? AND person = ?").run(r.profileId,n)}else{await a.prepare("DELETE FROM profile_defaults WHERE person = ?").run(n);for(const[t,i]of Object.entries(r.defaults))await a.prepare("INSERT INTO profile_defaults (person, directory, profile_id) VALUES (?, ?, ?)").run(n,t,i)}f&&await a.prepare("INSERT INTO person_settings (person, plugins_revision) VALUES (?, 1) ON CONFLICT (person) DO UPDATE SET plugins_revision = person_settings.plugins_revision + 1").run(n),await a.prepare("SELECT pg_notify('crtr_signal', ?)").get(`settings:${n}`)}_(j,"applyOpsToRows");async function k(a,n){await a.prepare("DELETE FROM profile_projects WHERE profile_id = ?").run(n.profileId);let o=0;for(const f of n.manifest.projects)await a.prepare("INSERT INTO profile_projects (profile_id, path, position) VALUES (?, ?, ?)").run(n.profileId,f.path,o++)}_(k,"replaceProjects");const A=_(async(a,n)=>(await N(o=>j(o,a,n)),await g(a)),"rowsTransport");L(A),v(g);export{j as applyOpsToRows,g as loadPersonSettings,w as readSnapshotFromRows,A as rowsTransport};
3
+ WHERE p.person = ? AND p.owner_key = 'user' AND p.enabled = 1 ORDER BY p.name`).all(t),u=await a.prepare("SELECT plugins_revision FROM person_settings WHERE person = ?").get(t),n=await T(t),r=o.map(i=>({name:String(i.name),version:s(i.version)??null,sourcePath:s(i.source_path)??null,marketplace:s(i.source_marketplace)===void 0?null:{name:s(i.source_marketplace),url:s(i.url)??null,ref:s(i.ref)??null},providerBundle:y({name:String(i.name),source_marketplace:s(i.source_marketplace)??null,source_path:s(i.source_path)??null},n)}));await D(t,u===void 0?0:Number(u.plugins_revision),r)}_(j,"materializePersonPlugins");async function A(a,t,o){return(await w(a,t)).settings[o]}_(A,"currentRaw");async function U(a,t,o){let u=!1;for(const n of o)if(n.op==="settings.replace"){const r=await A(a,t,n.owner);if(n.ifRevision!==v(r))throw new I;const{plugins:i,marketplaces:f,...m}=n.raw,E=JSON.stringify(m);await a.prepare("INSERT INTO scope_settings (person, owner_key, raw_json) VALUES (?, ?, ?) ON CONFLICT (person, owner_key) DO UPDATE SET raw_json = EXCLUDED.raw_json").run(t,n.owner,E);const e=i??{},l=f??{},p=JSON.stringify([r?.plugins,r?.marketplaces]);p!==JSON.stringify([i,f])&&!(p==="[null,null]"&&i===void 0&&f===void 0)&&(u=!0),await a.prepare("DELETE FROM scope_plugins WHERE person = ? AND owner_key = ? AND NOT (name IN (SELECT jsonb_array_elements_text(?::jsonb)))").run(t,n.owner,JSON.stringify(Object.keys(e)));for(const[d,c]of Object.entries(e))await a.prepare(`INSERT INTO scope_plugins (person, owner_key, name, enabled, source_marketplace, version) VALUES (?, ?, ?, ?, ?, ?)
4
+ ON CONFLICT (person, owner_key, name) DO UPDATE SET enabled = EXCLUDED.enabled, source_marketplace = EXCLUDED.source_marketplace, version = EXCLUDED.version`).run(t,n.owner,d,c.enabled===!1?0:1,c.source_marketplace??null,c.version??null);await a.prepare("DELETE FROM scope_marketplaces WHERE person = ? AND owner_key = ? AND NOT (name IN (SELECT jsonb_array_elements_text(?::jsonb)))").run(t,n.owner,JSON.stringify(Object.keys(l)));for(const[d,c]of Object.entries(l))await a.prepare(`INSERT INTO scope_marketplaces (person, owner_key, name, url, ref, installed_at) VALUES (?, ?, ?, ?, ?, ?)
5
+ ON CONFLICT (person, owner_key, name) DO UPDATE SET url = EXCLUDED.url, ref = EXCLUDED.ref, installed_at = EXCLUDED.installed_at`).run(t,n.owner,d,c.url??null,c.ref??null,c.installed_at??null)}else if(n.op==="state.write"){await a.prepare(`INSERT INTO scope_bookkeeping (person, owner_key, last_self_check, active_canvas) VALUES (?, ?, ?, ?)
6
+ ON CONFLICT (person, owner_key) DO UPDATE SET last_self_check = EXCLUDED.last_self_check, active_canvas = EXCLUDED.active_canvas`).run(t,n.owner,n.state.last_self_check??null,n.state.activeCanvas??null);for(const[r,i]of Object.entries(n.state.marketplaces))await a.prepare("UPDATE scope_marketplaces SET last_updated = ? WHERE person = ? AND owner_key = ? AND name = ?").run(i.last_updated??null,t,n.owner,r);for(const[r,i]of Object.entries(n.state.plugins))await a.prepare("UPDATE scope_plugins SET last_updated = ?, etag = ?, checked_at = ? WHERE person = ? AND owner_key = ? AND name = ?").run(i.last_updated??null,i.etag??null,i.checked_at??null,t,n.owner,r)}else if(n.op==="profile.insert"){const r=n.profile.manifest;await a.prepare("INSERT INTO profiles (id, person, name, created_at, last_used_at, paused_at, default_kind, grantee, metadata_json, delivery_limits_json, environment_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)").run(n.profile.profileId,t,r.name,r.created_at,r.last_used_at,r.paused_at,r.default_kind??null,r.grantee??null,JSON.stringify(r.metadata??{}),JSON.stringify(r.delivery_limits??{}),r.environment_id??null),await k(a,n.profile)}else if(n.op==="profile.update"){const r=n.profile.manifest;if((await a.prepare("UPDATE profiles SET name = ?, last_used_at = ?, paused_at = ?, default_kind = ?, grantee = ?, metadata_json = ?, delivery_limits_json = ?, environment_id = ? WHERE id = ? AND person = ?").run(r.name,r.last_used_at,r.paused_at,r.default_kind??null,r.grantee??null,JSON.stringify(r.metadata??{}),JSON.stringify(r.delivery_limits??{}),r.environment_id??null,n.profile.profileId,t)).changes!==1)throw new Error(`no stored profile ${n.profile.profileId}`);await k(a,n.profile)}else if(n.op==="profile.delete"){const r=`profile:${n.profileId}`;for(const i of["scope_settings","scope_bookkeeping","scope_marketplaces","scope_plugins"])await a.prepare(`DELETE FROM ${i} WHERE person = ? AND owner_key = ?`).run(t,r);await a.prepare("DELETE FROM profiles WHERE id = ? AND person = ?").run(n.profileId,t)}else{await a.prepare("DELETE FROM profile_defaults WHERE person = ?").run(t);for(const[r,i]of Object.entries(n.defaults))await a.prepare("INSERT INTO profile_defaults (person, directory, profile_id) VALUES (?, ?, ?)").run(t,r,i)}u&&await a.prepare("INSERT INTO person_settings (person, plugins_revision) VALUES (?, 1) ON CONFLICT (person) DO UPDATE SET plugins_revision = person_settings.plugins_revision + 1").run(t),await a.prepare("SELECT pg_notify('crtr_signal', ?)").get(`settings:${t}`)}_(U,"applyOpsToRows");async function k(a,t){await a.prepare("DELETE FROM profile_projects WHERE profile_id = ?").run(t.profileId);let o=0;for(const u of t.manifest.projects)await a.prepare("INSERT INTO profile_projects (profile_id, path, position) VALUES (?, ?, ?)").run(t.profileId,u.path,o++)}_(k,"replaceProjects");const F=_(async(a,t)=>(await N(o=>U(o,a,t)),await g(a)),"rowsTransport");h(F),C(g);export{U as applyOpsToRows,g as loadPersonSettings,w as readSnapshotFromRows,F as rowsTransport};
@@ -1,2 +1,2 @@
1
- var w=Object.defineProperty;var l=(r,n)=>w(r,"name",{value:n,configurable:!0});import{ApiError as s}from"../../../api/errors.js";import{getNode as f}from"../../../core/canvas/canvas.js";import{openDb as d}from"../../../core/canvas/db.js";import{environmentOfNode as h,recipeOf as g}from"../environment-of.js";import{mintInstallationToken as E}from"./git-credential.js";async function k(r,n){const i=await d(),u=n===null?["user"]:["user",`profile:${n}`],t=await i.prepare(`SELECT owner_key, name, version, source_marketplace, source_path FROM scope_plugins
2
- WHERE person = ? AND enabled = 1 AND owner_key IN (${u.map(()=>"?").join(", ")}) ORDER BY name`).all(r,...u),o=new Map;for(const e of t)(!o.has(e.name)||e.owner_key!=="user")&&o.set(e.name,e);const a=[];for(const e of o.values()){if(e.source_path!==null){a.push({name:e.name,source:{path:e.source_path},version:e.version});continue}if(e.source_marketplace===null)throw new s(500,"plugin_source_missing",`plugin ${e.name} names neither a marketplace nor a path`);const c=await i.prepare("SELECT url, ref FROM scope_marketplaces WHERE person = ? AND owner_key = ? AND name = ?").get(r,e.owner_key,e.source_marketplace);a.push({name:e.name,source:{marketplace:{url:c?.url??null,ref:c?.ref??null}},version:e.version})}return a}l(k,"plugins");async function b(r){const{principal:n}=r;if(n.class!=="broker")throw new s(403,"broker_only","the start bundle is read with a node token");const i=r.params.execution_id??"",u=await d(),t=await u.prepare("SELECT node_id, state, launch_json FROM executions WHERE execution_id = ?").get(i);if(t===void 0||t.node_id!==n.node)throw new s(403,"not_your_execution",`execution ${i} is not an execution of this node`);if(t.state!=="launching"&&t.state!=="running")throw new s(403,"execution_ended",`execution ${i} is ${t.state}`);if(t.launch_json===null)throw new s(409,"launch_missing",`execution ${i} carries no launch recipe`);const o=await f(n.node);if(o===null)throw new s(404,"node_not_found",`no node ${n.node}`);const a=await u.prepare("SELECT plugins_revision FROM person_settings WHERE person = ?").get(n.sub),e=await h(o.profile_id??null),c=await g(e),p=c?.repositories.map(m=>m.github_full_name)??[],_=p.length===0?null:(await E(n.sub,p)).author;return{status:200,body:{launch:JSON.parse(t.launch_json),node:{id:o.node_id,app:n.app,cwd:o.cwd,profile:o.profile_id??null,isolated:o.isolated===!0,environment_id:e},recipe:c,git_identity:_,plugins_revision:a===void 0?0:Number(a.plugins_revision),plugins:await k(n.sub,o.profile_id??null)}}}l(b,"handleStart");const S=[{method:"GET",pattern:"/v1/executions/:execution_id/start",handler:b}];export{S as executionStartRoutes};
1
+ var w=Object.defineProperty;var p=(r,n)=>w(r,"name",{value:n,configurable:!0});import{ApiError as s}from"../../../api/errors.js";import{getNode as f}from"../../../core/canvas/canvas.js";import{openDb as d}from"../../../core/canvas/db.js";import{grantedProviderIds as h,isProviderBundleRow as g}from"../../../core/grants/mirror.js";import{environmentOfNode as E,recipeOf as k}from"../environment-of.js";import{mintInstallationToken as b}from"./git-credential.js";async function v(r,n){const i=await d(),u=n===null?["user"]:["user",`profile:${n}`],t=await i.prepare(`SELECT owner_key, name, version, source_marketplace, source_path FROM scope_plugins
2
+ WHERE person = ? AND enabled = 1 AND owner_key IN (${u.map(()=>"?").join(", ")}) ORDER BY name`).all(r,...u),o=new Map;for(const e of t)(!o.has(e.name)||e.owner_key!=="user")&&o.set(e.name,e);const a=[],l=await h(r);for(const e of o.values()){if(g(e,l))continue;if(e.source_path!==null){a.push({name:e.name,source:{path:e.source_path},version:e.version});continue}if(e.source_marketplace===null)throw new s(500,"plugin_source_missing",`plugin ${e.name} names neither a marketplace nor a path`);const c=await i.prepare("SELECT url, ref FROM scope_marketplaces WHERE person = ? AND owner_key = ? AND name = ?").get(r,e.owner_key,e.source_marketplace);a.push({name:e.name,source:{marketplace:{url:c?.url??null,ref:c?.ref??null}},version:e.version})}return a}p(v,"plugins");async function x(r){const{principal:n}=r;if(n.class!=="broker")throw new s(403,"broker_only","the start bundle is read with a node token");const i=r.params.execution_id??"",u=await d(),t=await u.prepare("SELECT node_id, state, launch_json FROM executions WHERE execution_id = ?").get(i);if(t===void 0||t.node_id!==n.node)throw new s(403,"not_your_execution",`execution ${i} is not an execution of this node`);if(t.state!=="launching"&&t.state!=="running")throw new s(403,"execution_ended",`execution ${i} is ${t.state}`);if(t.launch_json===null)throw new s(409,"launch_missing",`execution ${i} carries no launch recipe`);const o=await f(n.node);if(o===null)throw new s(404,"node_not_found",`no node ${n.node}`);const a=await u.prepare("SELECT plugins_revision FROM person_settings WHERE person = ?").get(n.sub),l=await E(o.profile_id??null),e=await k(l),c=e?.repositories.map(_=>_.github_full_name)??[],m=c.length===0?null:(await b(n.sub,c)).author;return{status:200,body:{launch:JSON.parse(t.launch_json),node:{id:o.node_id,app:n.app,cwd:o.cwd,profile:o.profile_id??null,isolated:o.isolated===!0,environment_id:l},recipe:e,git_identity:m,plugins_revision:a===void 0?0:Number(a.plugins_revision),plugins:await v(n.sub,o.profile_id??null)}}}p(x,"handleStart");const A=[{method:"GET",pattern:"/v1/executions/:execution_id/start",handler:x}];export{A as executionStartRoutes};
@@ -1 +1 @@
1
- var I=Object.defineProperty;var s=(e,r)=>I(e,"name",{value:r,configurable:!0});import{randomUUID as R}from"node:crypto";import{cloudPersonScope as $}from"../../../core/runs/operations.js";import{directoryIssuer as O}from"../../../core/directory/issuer.js";import{activeGrantForApp as q,grantFor as G,localGrants as _}from"../../../core/grants/mirror.js";import{getNode as N}from"../../../core/canvas/canvas.js";import{scopeAllowed as T}from"../../../core/scopes.js";import{ProviderError as f,providerErrorBody as F,scopeMissing as x}from"../../../core/providers/errors.js";import{callProvider as j,providerTools as k}from"../../../core/providers/call.js";import{providerIdsFromGrants as C,providerInstallState as B}from"../../../core/providers/bundles.js";import{providerRootClaims as D,resolveProviderRef as M}from"../../../core/providers/roots.js";import{listInstalledPlugins as U}from"../../../core/resolver.js";import{listAudit as z,writeProviderAudit as L}from"../../../core/providers/audit.js";import{emitEvent as g}from"../../../core/events/emit.js";import{resolvedScopes as y}from"../router.js";const _e=new Set(["provider.tools","provider.call","audit.list"]);async function V(e){if(e.class==="forwarded"){if(e.actor.kind==="person")return{class:"person",sub:null,grantee:null,client_id:null,resolved:null,nodeScopes:null,run_id:null,node_id:null,origin:{node:null,run:null,machine:e.via}};const r=await G(e.actor.app,e.sub);if(!r)throw new f("grant_revoked",`app ${e.actor.app} has no active grant`);return{class:"app",sub:e.sub,grantee:e.actor.app,client_id:null,resolved:r.resolved,nodeScopes:e.actor.node_scopes,run_id:null,node_id:null,origin:{node:e.actor.node,run:e.actor.place?.run??null,machine:e.via}}}if(e.class==="machine")throw new f("permission_error","this operation is not available to the caller");if(e.class==="person"&&e.sub!==void 0)throw new f("permission_error","provider calls are made by the install's person, a node or an app");if(e.class==="person")return{class:"person",sub:null,grantee:null,client_id:null,resolved:null,nodeScopes:null,run_id:null,node_id:null};if(e.class==="app"){const r=await q(e.app);if(!r)throw new f("grant_revoked",`app ${e.app} has no active grant`);return{class:"app",sub:r.sub,grantee:e.app,client_id:null,resolved:r.resolved,nodeScopes:e.scopes,run_id:null,node_id:null}}return e.class==="cron"?{class:"app",sub:e.sub,grantee:e.app,client_id:null,resolved:await y(e),nodeScopes:e.scopes,run_id:e.origin_run,node_id:null}:{class:"broker",sub:e.sub,grantee:e.app,client_id:e.client_id,resolved:await y(e),nodeScopes:e.scopes,run_id:e.run??(await N(e.node))?.run_id??null,node_id:e.node}}s(V,"callerFromRequest");function ge(e){return{class:"app",sub:e.sub,grantee:e.grantee,client_id:e.client_id,resolved:e.resolved,nodeScopes:null,run_id:null,node_id:null}}s(ge,"callerFromApp");const Z=/^([A-Za-z0-9_-]{1,64}):([A-Za-z0-9_-]{1,64})$/;function W(e){const r=Z.exec(e);return r!==null&&r[1]!=="crtr"}s(W,"isProviderScope");const S=new WeakMap;function J(e){let r=S.get(e);return r===void 0&&(r=new Set(e),S.set(e,r)),r}s(J,"heldSet");function A(e,r){return e===null?!0:W(r)?J(e).has(r):T(e,r)}s(A,"holds");function P(e,r){return e.class==="person"?!0:A(e.resolved??[],r)&&A(e.nodeScopes,r)}s(P,"callerAllows");function ye(e,r,t){const o=`${r}:${t}`;if(!P(e,o))throw x(o)}s(ye,"requireGroup");function X(){return!!O()}s(X,"providerOperationsEnabled");const H=/^[A-Za-z0-9_-]{1,64}$/;function m(e,r){return new f("invalid_request",e,{param:r})}s(m,"invalid");function E(e){if(typeof e!="string"||!H.test(e)||e==="crtr")throw m("provider must be a provider id","provider");return e}s(E,"parseProvider");function K(e){if(!e||typeof e!="object"||Array.isArray(e))throw new f("invalid_request","a JSON object is required");const r=e,t=E(r.provider),o=r.tool;if(typeof o!="string"||!o.trim())throw m("tool must be a non-empty string","tool");const i=r.arguments??{};if(!i||typeof i!="object"||Array.isArray(i))throw m("arguments must be an object","arguments");const d=r.version;if(d!==void 0&&(typeof d!="number"||!Number.isInteger(d)||d<1))throw m("version must be an integer >= 1","version");const a=r.approval;if(a!==void 0&&(typeof a!="string"||!a))throw m("approval must be a question id","approval");const l=r.machine;if(l!==void 0&&(typeof l!="string"||!l.trim()))throw m("machine must be a machine id","machine");return{provider:t,tool:o.trim(),arguments:i,...d===void 0?{}:{version:d},...a===void 0?{}:{approval:a},...l===void 0?{}:{machine:l.trim()}}}s(K,"parseCallInput");async function Q(e,r=new URLSearchParams){const t=s((u,v,w)=>{const c=r.get(u);if(c===null)return v;if(!/^\d+$/.test(c)||Number(c)>w||Number(c)<1)throw m(`invalid ${u}`,u);return Number(c)},"numeric"),o=r.has("after")?t("after",0,Number.MAX_SAFE_INTEGER):void 0,i=t("limit",100,1e3),d=Object.fromEntries(["operation","outcome","run_id","since","until"].filter(u=>r.has(u)).map(u=>[u,r.get(u)])),a=$(e.sub),l=a===void 0?{}:{sub:a};return{status:200,body:await z(e.class==="person"?null:e.grantee,{after:o,limit:i,...l,...d})}}s(Q,"auditList");async function Y(e,r,t){let o=r;const i=performance.now(),d=U("user"),{skipped:a}=D(d),l=new Set(d.filter(n=>n.enabled&&n.manifest.provider?.id===n.name&&!a.has(n.name)).map(n=>n.name)),u=C(await _()).filter(n=>!a.has(n)),v=[...new Set([...l,...u])].sort();if(o!==void 0){const n=M(o);if(n===null)throw new f("not_found",`provider "${o}" is not available`);o=n}if(o!==void 0&&!v.includes(o))throw new f("not_found",`provider "${o}" is not available`);const w=o===void 0?v:[o],c=e.class==="person"?(await _()).flatMap(n=>n.status==="active"?n.resolved_scopes.map(({scope:p})=>p):[]):e.resolved??[],h=await Promise.all(w.map(async n=>{if(l.has(n))return k(e,n);if(!c.some(b=>b.startsWith(`${n}:`)&&P(e,b)))return null;const p=B(n);return{provider:n,display_name:n,status:p.status,...p.error?{error:p.error}:{},groups:[],commands:[]}})).then(n=>n.filter(p=>p!==null));if(g({level:"debug",event:"api.provider.tools",duration_ms:performance.now()-i,fields:{caller:e.class,provider:o??null,providers:h.length}}),o!==void 0){if(h.length===0)throw new f("not_found",`provider "${o}" is not available`);return{status:200,body:h[0]}}return{status:200,body:{providers:h}}}s(Y,"providerTools");async function ee(e,r,t){return j(e,r,t)}s(ee,"providerCall");async function re(e,r,t,o){if(!X())throw new f("not_found","provider operations need a directory-connected runtime");if(e==="audit.list")return await Q(r,t.query);if(e==="provider.tools")return Y(r,t.params.provider===void 0?void 0:E(t.params.provider),o);if(e==="provider.call"){let i;try{i=K(t.body)}catch(d){const a=t.body&&typeof t.body=="object"&&!Array.isArray(t.body)?t.body:{};throw await L({request_id:o.requestId,caller:r,provider:typeof a.provider=="string"?a.provider:"",tool:typeof a.tool=="string"?a.tool:"",outcome:`refused:${d instanceof f?d.code:"invalid_request"}`}),d}return ee(r,i,o)}throw new f("not_found","unknown operation")}s(re,"handleProviderOperation");function oe(e){const r=new AbortController,t=s(()=>{e.writableFinished||r.abort()},"abort");return e.on("close",t),{signal:r.signal,release:s(()=>e.off("close",t),"release")}}s(oe,"abortOnClose");function te(e,r,t,o){const i=F(e,r,t,o??void 0);return i.status>=500&&!(e instanceof f)&&g({event:"api.provider.failed",level:"error",error:e instanceof Error?e:new Error(String(e)),fields:{request_id:r}}),i}s(te,"providerFailure");const Se=[{method:"GET",pattern:"/v1/audit",name:"audit.list"},{method:"GET",pattern:"/v1/providers/tools",name:"provider.tools"},{method:"GET",pattern:"/v1/providers/:provider/tools",name:"provider.tools"},{method:"POST",pattern:"/v1/provider/call",name:"provider.call"}].map(({method:e,pattern:r,name:t})=>({method:e,pattern:r,handler:s(async o=>{const i=R(),{signal:d,release:a}=oe(o.res);let l=null;try{return l=await V(o.principal),{...await re(t,l,{params:o.params,body:o.body,query:new URL(o.req.url??"/","http://runtime.local").searchParams},{requestId:i,signal:d,...o.causedBy===void 0?{}:{causedBy:o.causedBy}}),headers:{"Request-Id":i}}}catch(u){return te(u,i,!1,l?.run_id)}finally{a()}},"handler")}));export{_e as PROVIDER_OPERATIONS,oe as abortOnClose,P as callerAllows,ge as callerFromApp,V as callerFromRequest,re as handleProviderOperation,K as parseCallInput,E as parseProvider,te as providerFailure,X as providerOperationsEnabled,Se as providerRoutes,ye as requireGroup};
1
+ var I=Object.defineProperty;var s=(e,r)=>I(e,"name",{value:r,configurable:!0});import{randomUUID as R}from"node:crypto";import{cloudPersonScope as $}from"../../../core/runs/operations.js";import{directoryIssuer as O}from"../../../core/directory/issuer.js";import{activeGrantForApp as q,grantFor as G,localGrants as _,providerIdsFromGrants as N}from"../../../core/grants/mirror.js";import{getNode as T}from"../../../core/canvas/canvas.js";import{scopeAllowed as F}from"../../../core/scopes.js";import{ProviderError as f,providerErrorBody as x,scopeMissing as j}from"../../../core/providers/errors.js";import{callProvider as k,providerTools as C}from"../../../core/providers/call.js";import{providerInstallState as B}from"../../../core/providers/bundles.js";import{providerRootClaims as D,resolveProviderRef as M}from"../../../core/providers/roots.js";import{listInstalledPlugins as U}from"../../../core/resolver.js";import{listAudit as z,writeProviderAudit as L}from"../../../core/providers/audit.js";import{emitEvent as g}from"../../../core/events/emit.js";import{resolvedScopes as y}from"../router.js";const _e=new Set(["provider.tools","provider.call","audit.list"]);async function V(e){if(e.class==="forwarded"){if(e.actor.kind==="person")return{class:"person",sub:null,grantee:null,client_id:null,resolved:null,nodeScopes:null,run_id:null,node_id:null,origin:{node:null,run:null,machine:e.via}};const r=await G(e.actor.app,e.sub);if(!r)throw new f("grant_revoked",`app ${e.actor.app} has no active grant`);return{class:"app",sub:e.sub,grantee:e.actor.app,client_id:null,resolved:r.resolved,nodeScopes:e.actor.node_scopes,run_id:null,node_id:null,origin:{node:e.actor.node,run:e.actor.place?.run??null,machine:e.via}}}if(e.class==="machine")throw new f("permission_error","this operation is not available to the caller");if(e.class==="person"&&e.sub!==void 0)throw new f("permission_error","provider calls are made by the install's person, a node or an app");if(e.class==="person")return{class:"person",sub:null,grantee:null,client_id:null,resolved:null,nodeScopes:null,run_id:null,node_id:null};if(e.class==="app"){const r=await q(e.app);if(!r)throw new f("grant_revoked",`app ${e.app} has no active grant`);return{class:"app",sub:r.sub,grantee:e.app,client_id:null,resolved:r.resolved,nodeScopes:e.scopes,run_id:null,node_id:null}}return e.class==="cron"?{class:"app",sub:e.sub,grantee:e.app,client_id:null,resolved:await y(e),nodeScopes:e.scopes,run_id:e.origin_run,node_id:null}:{class:"broker",sub:e.sub,grantee:e.app,client_id:e.client_id,resolved:await y(e),nodeScopes:e.scopes,run_id:e.run??(await T(e.node))?.run_id??null,node_id:e.node}}s(V,"callerFromRequest");function ge(e){return{class:"app",sub:e.sub,grantee:e.grantee,client_id:e.client_id,resolved:e.resolved,nodeScopes:null,run_id:null,node_id:null}}s(ge,"callerFromApp");const Z=/^([A-Za-z0-9_-]{1,64}):([A-Za-z0-9_-]{1,64})$/;function W(e){const r=Z.exec(e);return r!==null&&r[1]!=="crtr"}s(W,"isProviderScope");const S=new WeakMap;function J(e){let r=S.get(e);return r===void 0&&(r=new Set(e),S.set(e,r)),r}s(J,"heldSet");function A(e,r){return e===null?!0:W(r)?J(e).has(r):F(e,r)}s(A,"holds");function P(e,r){return e.class==="person"?!0:A(e.resolved??[],r)&&A(e.nodeScopes,r)}s(P,"callerAllows");function ye(e,r,t){const o=`${r}:${t}`;if(!P(e,o))throw j(o)}s(ye,"requireGroup");function X(){return!!O()}s(X,"providerOperationsEnabled");const H=/^[A-Za-z0-9_-]{1,64}$/;function m(e,r){return new f("invalid_request",e,{param:r})}s(m,"invalid");function E(e){if(typeof e!="string"||!H.test(e)||e==="crtr")throw m("provider must be a provider id","provider");return e}s(E,"parseProvider");function K(e){if(!e||typeof e!="object"||Array.isArray(e))throw new f("invalid_request","a JSON object is required");const r=e,t=E(r.provider),o=r.tool;if(typeof o!="string"||!o.trim())throw m("tool must be a non-empty string","tool");const i=r.arguments??{};if(!i||typeof i!="object"||Array.isArray(i))throw m("arguments must be an object","arguments");const d=r.version;if(d!==void 0&&(typeof d!="number"||!Number.isInteger(d)||d<1))throw m("version must be an integer >= 1","version");const a=r.approval;if(a!==void 0&&(typeof a!="string"||!a))throw m("approval must be a question id","approval");const l=r.machine;if(l!==void 0&&(typeof l!="string"||!l.trim()))throw m("machine must be a machine id","machine");return{provider:t,tool:o.trim(),arguments:i,...d===void 0?{}:{version:d},...a===void 0?{}:{approval:a},...l===void 0?{}:{machine:l.trim()}}}s(K,"parseCallInput");async function Q(e,r=new URLSearchParams){const t=s((u,v,w)=>{const c=r.get(u);if(c===null)return v;if(!/^\d+$/.test(c)||Number(c)>w||Number(c)<1)throw m(`invalid ${u}`,u);return Number(c)},"numeric"),o=r.has("after")?t("after",0,Number.MAX_SAFE_INTEGER):void 0,i=t("limit",100,1e3),d=Object.fromEntries(["operation","outcome","run_id","since","until"].filter(u=>r.has(u)).map(u=>[u,r.get(u)])),a=$(e.sub),l=a===void 0?{}:{sub:a};return{status:200,body:await z(e.class==="person"?null:e.grantee,{after:o,limit:i,...l,...d})}}s(Q,"auditList");async function Y(e,r,t){let o=r;const i=performance.now(),d=U("user"),{skipped:a}=D(d),l=new Set(d.filter(n=>n.enabled&&n.manifest.provider?.id===n.name&&!a.has(n.name)).map(n=>n.name)),u=N(await _()).filter(n=>!a.has(n)),v=[...new Set([...l,...u])].sort();if(o!==void 0){const n=M(o);if(n===null)throw new f("not_found",`provider "${o}" is not available`);o=n}if(o!==void 0&&!v.includes(o))throw new f("not_found",`provider "${o}" is not available`);const w=o===void 0?v:[o],c=e.class==="person"?(await _()).flatMap(n=>n.status==="active"?n.resolved_scopes.map(({scope:p})=>p):[]):e.resolved??[],h=await Promise.all(w.map(async n=>{if(l.has(n))return C(e,n);if(!c.some(b=>b.startsWith(`${n}:`)&&P(e,b)))return null;const p=B(n);return{provider:n,display_name:n,status:p.status,...p.error?{error:p.error}:{},groups:[],commands:[]}})).then(n=>n.filter(p=>p!==null));if(g({level:"debug",event:"api.provider.tools",duration_ms:performance.now()-i,fields:{caller:e.class,provider:o??null,providers:h.length}}),o!==void 0){if(h.length===0)throw new f("not_found",`provider "${o}" is not available`);return{status:200,body:h[0]}}return{status:200,body:{providers:h}}}s(Y,"providerTools");async function ee(e,r,t){return k(e,r,t)}s(ee,"providerCall");async function re(e,r,t,o){if(!X())throw new f("not_found","provider operations need a directory-connected runtime");if(e==="audit.list")return await Q(r,t.query);if(e==="provider.tools")return Y(r,t.params.provider===void 0?void 0:E(t.params.provider),o);if(e==="provider.call"){let i;try{i=K(t.body)}catch(d){const a=t.body&&typeof t.body=="object"&&!Array.isArray(t.body)?t.body:{};throw await L({request_id:o.requestId,caller:r,provider:typeof a.provider=="string"?a.provider:"",tool:typeof a.tool=="string"?a.tool:"",outcome:`refused:${d instanceof f?d.code:"invalid_request"}`}),d}return ee(r,i,o)}throw new f("not_found","unknown operation")}s(re,"handleProviderOperation");function oe(e){const r=new AbortController,t=s(()=>{e.writableFinished||r.abort()},"abort");return e.on("close",t),{signal:r.signal,release:s(()=>e.off("close",t),"release")}}s(oe,"abortOnClose");function te(e,r,t,o){const i=x(e,r,t,o??void 0);return i.status>=500&&!(e instanceof f)&&g({event:"api.provider.failed",level:"error",error:e instanceof Error?e:new Error(String(e)),fields:{request_id:r}}),i}s(te,"providerFailure");const Se=[{method:"GET",pattern:"/v1/audit",name:"audit.list"},{method:"GET",pattern:"/v1/providers/tools",name:"provider.tools"},{method:"GET",pattern:"/v1/providers/:provider/tools",name:"provider.tools"},{method:"POST",pattern:"/v1/provider/call",name:"provider.call"}].map(({method:e,pattern:r,name:t})=>({method:e,pattern:r,handler:s(async o=>{const i=R(),{signal:d,release:a}=oe(o.res);let l=null;try{return l=await V(o.principal),{...await re(t,l,{params:o.params,body:o.body,query:new URL(o.req.url??"/","http://runtime.local").searchParams},{requestId:i,signal:d,...o.causedBy===void 0?{}:{causedBy:o.causedBy}}),headers:{"Request-Id":i}}}catch(u){return te(u,i,!1,l?.run_id)}finally{a()}},"handler")}));export{_e as PROVIDER_OPERATIONS,oe as abortOnClose,P as callerAllows,ge as callerFromApp,V as callerFromRequest,re as handleProviderOperation,K as parseCallInput,E as parseProvider,te as providerFailure,X as providerOperationsEnabled,Se as providerRoutes,ye as requireGroup};