@timqi/pier 0.0.19 → 0.0.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/config-sync.js +123 -0
- package/dist/agent/config.js +95 -3
- package/dist/agent/events.js +10 -1
- package/dist/agent/pi.js +50 -28
- package/dist/config-sync-fetch.js +84 -0
- package/dist/config-sync-task.js +59 -0
- package/dist/config-sync.js +179 -0
- package/dist/core/hub.js +10 -3
- package/dist/core/reply.js +12 -2
- package/dist/core/router.js +162 -37
- package/dist/db.js +6 -0
- package/dist/main.js +31 -1
- package/dist/tasks/agent.js +11 -22
- package/dist/tasks/callbacks.js +1 -1
- package/dist/tasks/definitions.js +25 -16
- package/dist/tasks/execution.js +11 -3
- package/dist/tasks/messages.js +57 -14
- package/dist/tasks/routes.js +58 -10
- package/dist/tasks/runs.js +0 -5
- package/dist/tasks/service.js +9 -4
- package/dist/tasks/store.js +45 -0
- package/dist/tasks/tool.js +8 -4
- package/dist/web/config-sync.js +91 -0
- package/dist/web/public/assets/activity-Cw0LRVNI.js +5 -0
- package/dist/web/public/assets/activity-Cw0LRVNI.js.br +0 -0
- package/dist/web/public/assets/activity-Cw0LRVNI.js.gz +0 -0
- package/dist/web/public/assets/{boards-DEMQ51m1.js → boards-DMVbV84c.js} +1 -1
- package/dist/web/public/assets/boards-DMVbV84c.js.br +0 -0
- package/dist/web/public/assets/boards-DMVbV84c.js.gz +0 -0
- package/dist/web/public/assets/{code-DjWvqL94.js → code-C86xKk2k.js} +1 -1
- package/dist/web/public/assets/{explorer-BTGQ6Syc.js → explorer-DTvsy5Rs.js} +1 -1
- package/dist/web/public/assets/explorer-DTvsy5Rs.js.br +0 -0
- package/dist/web/public/assets/explorer-DTvsy5Rs.js.gz +0 -0
- package/dist/web/public/assets/{ghostty-web-D8CLFi_l.js → ghostty-web-CvSZragH.js} +1 -1
- package/dist/web/public/assets/ghostty-web-CvSZragH.js.br +0 -0
- package/dist/web/public/assets/ghostty-web-CvSZragH.js.gz +0 -0
- package/dist/web/public/assets/index-DHxoYoeq.js +79 -0
- package/dist/web/public/assets/index-DHxoYoeq.js.br +0 -0
- package/dist/web/public/assets/index-DHxoYoeq.js.gz +0 -0
- package/dist/web/public/assets/{index-yNocr797.css → index-DuZ2vERd.css} +1 -1
- package/dist/web/public/assets/index-DuZ2vERd.css.br +0 -0
- package/dist/web/public/assets/index-DuZ2vERd.css.gz +0 -0
- package/dist/web/public/assets/runs-jY40JBP-.js +1 -0
- package/dist/web/public/assets/runs-jY40JBP-.js.br +0 -0
- package/dist/web/public/assets/runs-jY40JBP-.js.gz +0 -0
- package/dist/web/public/assets/settings-Dux7PIKF.js +6 -0
- package/dist/web/public/assets/settings-Dux7PIKF.js.br +0 -0
- package/dist/web/public/assets/settings-Dux7PIKF.js.gz +0 -0
- package/dist/web/public/assets/task-runs-DKvtBVxS.js +3 -0
- package/dist/web/public/assets/task-runs-DKvtBVxS.js.br +0 -0
- package/dist/web/public/assets/task-runs-DKvtBVxS.js.gz +0 -0
- package/dist/web/public/assets/tasks-CqfrOVIt.js +4 -0
- package/dist/web/public/assets/tasks-CqfrOVIt.js.br +0 -0
- package/dist/web/public/assets/tasks-CqfrOVIt.js.gz +0 -0
- package/dist/web/public/assets/{terminal-zLyOvGto.js → terminal-DL_0yEy7.js} +2 -2
- package/dist/web/public/assets/terminal-DL_0yEy7.js.br +0 -0
- package/dist/web/public/assets/terminal-DL_0yEy7.js.gz +0 -0
- package/dist/web/public/index.html +13 -2
- package/dist/web/public/index.html.br +0 -0
- package/dist/web/public/index.html.gz +0 -0
- package/dist/web/server.js +65 -60
- package/package.json +2 -2
- package/skills/pier-tasks/SKILL.md +14 -9
- package/dist/web/public/assets/activity-BsoWK6vt.js +0 -5
- package/dist/web/public/assets/activity-BsoWK6vt.js.br +0 -0
- package/dist/web/public/assets/activity-BsoWK6vt.js.gz +0 -0
- package/dist/web/public/assets/boards-DEMQ51m1.js.br +0 -0
- package/dist/web/public/assets/boards-DEMQ51m1.js.gz +0 -0
- package/dist/web/public/assets/explorer-BTGQ6Syc.js.br +0 -0
- package/dist/web/public/assets/explorer-BTGQ6Syc.js.gz +0 -0
- package/dist/web/public/assets/ghostty-web-D8CLFi_l.js.br +0 -0
- package/dist/web/public/assets/ghostty-web-D8CLFi_l.js.gz +0 -0
- package/dist/web/public/assets/index-BFzmvpGX.js +0 -78
- package/dist/web/public/assets/index-BFzmvpGX.js.br +0 -0
- package/dist/web/public/assets/index-BFzmvpGX.js.gz +0 -0
- package/dist/web/public/assets/index-yNocr797.css.br +0 -0
- package/dist/web/public/assets/index-yNocr797.css.gz +0 -0
- package/dist/web/public/assets/settings-DSTKXN2o.js +0 -6
- package/dist/web/public/assets/settings-DSTKXN2o.js.br +0 -0
- package/dist/web/public/assets/settings-DSTKXN2o.js.gz +0 -0
- package/dist/web/public/assets/tasks-Dy1SCAE_.js +0 -4
- package/dist/web/public/assets/tasks-Dy1SCAE_.js.br +0 -0
- package/dist/web/public/assets/tasks-Dy1SCAE_.js.gz +0 -0
- package/dist/web/public/assets/terminal-zLyOvGto.js.br +0 -0
- package/dist/web/public/assets/terminal-zLyOvGto.js.gz +0 -0
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import{F as e,I as t,U as n,V as r,g as i}from"./index-DHxoYoeq.js";var a=e=>e===null?`-`:new Date(e).toLocaleString(),o=t=>t.startedAt===null?`-`:e((t.finishedAt??Date.now())-t.startedAt),s=e=>e.state===`succeeded`&&e.matched===!1?`No match`:e.state,c=e=>e.trigger.type===`manual`?`Manual`:e.trigger.type===`cron`?`${e.trigger.expression} (${e.trigger.timezone})`:`Every ${e.trigger.intervalSeconds}s · ${e.trigger.mode}`,l=e=>e.action.type===`agent`?`Agent · ${e.action.session.mode}`:e.action.type===`bash`?`Bash`:e.action.type===`system`?`System`:`Task`;function u(e,n){let r=t(`div`,`grid max-w-4xl grid-cols-[6rem_minmax(0,1fr)] gap-x-5 gap-y-3 p-4 text-[13px] md:grid-cols-[9.375rem_minmax(0,1fr)]`),o=[[`Status`,e.archived?`Archived`:e.trigger.type===`manual`?`Manual`:e.enabled?`Enabled`:`Paused`],[`Trigger`,c(e)],[`Action`,l(e)],[`Timeout`,`${e.timeoutSeconds}s`],[`Next run`,a(e.nextRunAt)],[`Revision`,String(e.revision)],[`Created by`,e.createdBySessionId??e.creator],[`Callback`,e.callback.type===`session`?e.callback.sessionId:e.callback.type],[`Description`,e.description||`-`]];e.action.type===`agent`&&(o.push([`Session policy`,e.action.session.mode]),e.action.session.mode===`reuse`&&o.push([`Session`,e.action.session.sessionId]),`cwd`in e.action.session&&o.push([`Directory`,e.action.session.cwd]),e.action.launch?.model&&o.push([`Model`,`${e.action.launch.model.provider}/${e.action.launch.model.id}`]),e.action.launch?.thinking&&o.push([`Thinking`,e.action.launch.thinking]),o.push([`Prompt`,e.action.prompt])),e.action.type===`bash`&&o.push([`Directory`,e.action.cwd],[`Script`,e.action.script]),e.action.type===`task`&&o.push([`Target task`,e.action.taskId]),e.action.type===`system`&&o.push([`System action`,e.action.name]),e.trigger.type===`watch`&&o.push([`Probe`,e.trigger.script]);for(let[e,n]of o)r.append(t(`span`,`text-[11px] font-semibold uppercase text-neutral-400`,e),t(`pre`,`whitespace-pre-wrap break-words font-mono text-[12.5px]`,n));if(e.action.type===`agent`&&e.action.session.mode===`reuse`){let{sessionId:a}=e.action.session,o=i(`Open session`);o.onclick=()=>n(a),r.append(t(`span`,``,``),o)}return r}function d(e){return[e.pendingDecisionId?`Awaiting decision`:``,e.callbackState&&e.callbackState!==`delivered`?`Callback not delivered (${e.callbackState})`:``,e.groupCallbackState&&e.groupCallbackState!==`delivered`?`Group callback not delivered (${e.groupCallbackState})`:``].filter(Boolean).join(` · `)}function f(e,n,r,i){let c=t(`div`,`divide-y divide-neutral-100`);for(let e of n){let n=t(`button`,`grid w-full cursor-pointer grid-cols-[minmax(0,1fr)_auto] gap-x-3 gap-y-1 px-4 py-2.5 text-left text-[12.5px] hover:bg-neutral-50 md:grid-cols-[7.5rem_minmax(0,1fr)_7.5rem_6.25rem]`);n.setAttribute(`type`,`button`),n.append(t(`span`,`font-medium`,s(e)),t(`span`,`truncate text-neutral-500`,a(e.queuedAt)),t(`span`,`text-neutral-500`,e.triggerSource),t(`span`,`text-right text-neutral-500`,o(e))),n.onclick=()=>r(e.id),c.append(n)}n.length||c.append(t(`p`,`p-4 text-[13px] text-neutral-400`,`No runs yet.`)),e.replaceChildren(c),e.scrollTop=i.top,e.onscroll=()=>{i.top=e.scrollTop}}var p=e=>`Exit code: ${e.exitCode??`-`}\nstdout:\n${e.stdout}${e.stdoutTruncated?`
|
|
2
|
+
[stdout truncated]`:``}\nstderr:\n${e.stderr}${e.stderrTruncated?`
|
|
3
|
+
[stderr truncated]`:``}`;async function m(e,c,l,f,m){let g=String(Number(e.dataset.runRequest??0)+1);e.dataset.runRequest=g,e.onscroll=()=>{m.scrollTop=e.scrollTop};let _=i(`Runs`);_.className=`cursor-pointer text-neutral-500 hover:underline`,_.onclick=()=>{m.selectedId=null,m.scrollTop=0,l()};let v=t(`div`,`flex flex-wrap items-center gap-2 border-b border-neutral-200 px-4 py-2`);v.append(_,t(`span`,`min-w-0 truncate font-mono text-[12px] text-neutral-400`,c));let y=e.dataset.runId!==c;y&&(e.dataset.runId=c,e.replaceChildren(v,t(`p`,`p-4 text-[13px] text-neutral-500`,`Loading run...`)));let[b,x]=await Promise.all([r(`/api/task-runs/${c}`,`Could not load the run`),r(`/api/task-runs/${c}/messages`,`Could not load the run's messages`)]);if(!e.isConnected||e.dataset.runRequest!==g||m.selectedId!==c)return;if(!b.ok){m.drawn=``,e.replaceChildren(v,t(`p`,`p-4 text-[13px] text-red-600`,b.error));return}let S=b.value,C=x.ok?x.value:[],w=JSON.stringify([S,x]);if(!y&&m.drawn===w)return;if(m.drawn=w,S.targetSessionId){let e=i(`Open session`);e.classList.add(`ml-auto`),e.onclick=()=>f.openSession(S.targetSessionId),v.append(e)}if(S.state===`queued`||S.state===`running`){if(S.context.definition.action.type===`agent`){let e=i(`Steer`);e.onclick=()=>void h(n(`Steer run`,`/api/task-runs/${S.id}/steer`,{mode:`steer`,sourceSessionId:f.currentSessionId()},`Run control failed`),f),v.append(e)}let e=i(`Stop run`);e.onclick=()=>void f.mutate(`/api/task-runs/${S.id}/cancel`),v.append(e)}else if(S.context.definition.action.type===`agent`&&S.targetSessionId){let e=i(`Continue`);e.onclick=()=>void h(n(`Continue run`,`/api/task-runs/${S.id}/resume`,{sourceSessionId:f.currentSessionId()},`Run control failed`),f,!0),v.append(e)}let T=C.find(e=>e.id===S.pendingDecisionId);if(T&&T.toSessionId===f.currentSessionId()){let e=i(`Reply to decision`);e.onclick=()=>void h(n(`Reply to decision`,`/api/task-messages/${T.id}/reply`,{sourceSessionId:f.currentSessionId()},`Reply failed`),f),v.append(e)}let E=t(`div`,`min-w-0`);E.append(t(`div`,`flex flex-wrap items-center gap-x-5 gap-y-2 border-b border-neutral-100 px-4 py-3 text-[12.5px]`,t(`span`,`font-medium`,s(S)),t(`span`,`text-amber-700`,d(S)),t(`span`,`text-neutral-500`,a(S.queuedAt)),t(`span`,`text-neutral-500`,S.triggerSource),t(`span`,`text-neutral-500`,o(S))));let D=t(`div`,`flex flex-wrap items-center gap-3 border-b border-neutral-100 px-4 py-3 text-[12px]`),O=(e,t)=>{let n=i(e);n.classList.add(`max-w-full`,`break-all`,`!whitespace-normal`),n.onclick=t,D.append(n)};if(S.context.definition.kind===`task`&&O(`Task: ${S.context.definition.name}`,()=>f.openTask(S.taskId)),S.parentRunId&&O(`Parent: ${S.parentRunId}`,()=>f.openRun(S.parentRunId)),S.resumedFromRunId&&O(`Resumed from: ${S.resumedFromRunId}`,()=>f.openRun(S.resumedFromRunId)),S.result?.type===`task`){let e=S.result.runId;O(`Child result: ${e}`,()=>f.openRun(e))}S.groupId&&D.append(t(`span`,`break-all text-neutral-500`,`Group: ${S.groupId}`)),E.append(D);for(let e of[S.error,S.skipReason,S.callbackError])e&&E.append(t(`p`,`whitespace-pre-wrap break-words p-4 text-[13px] text-red-600`,e));if(S.result){let e=S.result,n=e.type===`agent`||e.type===`system`?e.text:e.type===`bash`?p(e):e.type===`task`?`Child run: ${e.runId}`:`Watch did not match.`;E.append(t(`h3`,`px-4 pt-3 text-[12px] font-medium`,`Result`),t(`pre`,`whitespace-pre-wrap break-words p-4 font-mono text-[12px]`,n||`No output.`))}S.probe&&E.append(t(`h3`,`px-4 pt-3 text-[12px] font-medium`,`Watch probe: ${S.matched===null?`not evaluated`:S.matched?`matched`:`not matched`}`),t(`pre`,`whitespace-pre-wrap break-words p-4 font-mono text-[12px]`,p(S.probe)));let k=t(`details`,`border-t border-neutral-200`);if(k.append(t(`summary`,`cursor-pointer px-4 py-3 text-[12px] text-neutral-500`,`Configuration snapshot (revision ${S.taskRevision})`),u(S.context.definition,f.openSession)),E.append(k),x.ok||E.append(t(`p`,`p-4 text-[13px] text-red-600`,x.error)),C.length){let e=t(`div`,`border-t border-neutral-200`);e.append(t(`div`,`px-4 py-2 text-[11px] font-semibold uppercase text-neutral-400`,`Messages`));for(let n of C){let r=t(`div`,`grid grid-cols-[auto_1fr] gap-3 border-t border-neutral-100 px-4 py-2 text-[12px] md:grid-cols-[6.25rem_6.25rem_minmax(0,1fr)]`);r.append(t(`span`,`font-medium`,n.kind),t(`span`,`text-neutral-500`,n.state),t(`span`,`col-span-2 whitespace-pre-wrap break-words md:col-span-1`,n.content)),n.error&&r.append(t(`span`,`col-span-2 break-words text-red-600 md:col-span-3`,n.error)),e.append(r)}E.append(e)}let A=t(`details`,`border-t border-neutral-200`);A.open=m.rawOpen,A.ontoggle=()=>{A.isConnected&&(m.rawOpen=A.open)},A.append(t(`summary`,`cursor-pointer px-4 py-3 text-[12px] text-neutral-500`,`Raw record`),t(`pre`,`whitespace-pre-wrap break-words px-4 pb-4 font-mono text-[12px]`,JSON.stringify(S,null,2))),E.append(A),e.replaceChildren(v,E),e.scrollTop=m.scrollTop,e.onscroll=()=>{m.scrollTop=e.scrollTop}}async function h(e,t,n=!1){let r=await e;if(r.sent){if(r.error)t.onError(r.error);else if(n&&r.response)try{let e=await r.response.json();t.openRun(e.id)}catch(e){t.onError(`Could not open continuation: ${String(e)}`)}else await t.reload()}}export{f as a,s as c,m as i,c as l,a as n,d as o,u as r,o as s,l as t};
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import{B as e,C as t,D as n,G as r,I as i,M as a,T as o,V as s,W as c,g as l,x as u,y as d,z as f}from"./index-DHxoYoeq.js";import{a as p,c as m,l as h,n as g,r as _,s as v,t as y}from"./task-runs-DKvtBVxS.js";function b(e,t){let n=e.map(e=>[`${e.title??`Untitled`} · ${e.cwd}`,e.id]);return t&&!n.some(([,e])=>e===t)&&n.push([`Current session · ${t}`,t]),n}function x(e,r){let a=document.createElement(`dialog`);a.className=`m-auto w-[38.75rem] max-w-[94vw] rounded-lg border border-neutral-200 p-0 shadow-xl backdrop:bg-black/20`;let o=document.createElement(`form`);o.className=`flex max-h-[88vh] flex-col`;let s=i(`span`,`mr-auto text-[12px] text-red-600`),c=i(`header`,`flex h-11 flex-none items-center border-b border-neutral-200 px-4 font-medium`,r?`Edit task`:`New task`),f=i(`div`,`grid min-h-0 flex-1 grid-cols-2 gap-3 overflow-y-auto p-4`),p=u(r?.name??``);p.required=!0;let m=u(r?.description??``),h=u(String(r?.timeoutSeconds??900),`number`);h.min=`1`,h.max=`86400`;let g=t([[`Manual`,`manual`],[`Scheduled`,`cron`],[`Watch`,`watch`]],r?.trigger.type??`manual`),_=t([[`Bash`,`bash`],[`Agent`,`agent`],[`Task`,`task`]],r?.action.type??`bash`),v=t([[`No callback`,`none`],[`Invoking session`,`origin`],[`Specific session`,`session`]],r?.callback.type??`none`),y=r?.callback.type===`session`?r.callback.sessionId:null,x=t(b(e.sessions(),y),y??``),C=d(`Callback session`,x);C.classList.toggle(`hidden`,v.value!==`session`),v.onchange=()=>C.classList.toggle(`hidden`,v.value!==`session`),f.append(d(`Name`,p),d(`Description`,m),d(`Trigger`,g),d(`Action`,_),d(`Callback`,v),C,d(`Timeout (seconds)`,h));let w=i(`div`,`col-span-2 grid grid-cols-2 gap-3 border-t border-neutral-200 pt-3`);f.append(w);let T=()=>{w.replaceChildren();let c,l,f,y,C,T;if(g.value===`cron`)c=u(r?.trigger.type===`cron`?r.trigger.expression:`0 9 * * *`),l=u(r?.trigger.type===`cron`?r.trigger.timezone:Intl.DateTimeFormat().resolvedOptions().timeZone),w.append(d(`Cron`,c),d(`Timezone`,l));else if(g.value===`watch`){f=n(r?.trigger.type===`watch`?r.trigger.script:``,4),T=u(r?.trigger.type===`watch`?r.trigger.cwd:e.sessions()[0]?.cwd??``),y=u(String(r?.trigger.type===`watch`?r.trigger.intervalSeconds:60),`number`),C=t([[`Continue after matches`,`repeat`],[`Stop after first match`,`once`]],r?.trigger.type===`watch`?r.trigger.mode:`repeat`);let i=d(`Probe script (0=match, 1=no match)`,f);i.classList.add(`col-span-2`),w.append(i,d(`Probe directory`,T),d(`Interval (seconds)`,y),d(`Mode`,C))}let E,D,O,k,A,j,M,N,P,F;if(_.value===`bash`){D=u(r?.action.type===`bash`?r.action.cwd:e.sessions()[0]?.cwd??``),E=n(r?.action.type===`bash`?r.action.script:``,6);let t=d(`Bash script`,E);t.classList.add(`col-span-2`),w.append(d(`Working directory`,D),i(`span`,``),t)}else if(_.value===`agent`){let i=e.sessions(),a=r?.action.type===`agent`?r.action:null,o=a?.session.mode===`reuse`?a.session.sessionId:``,s=b(i,o||null);O=t([[`Reuse session`,`reuse`],[`Fresh child per run`,`fresh`]],a?.session.mode===`reuse`?`reuse`:`fresh`),k=t(s,o||s[0]?.[1]||``);let c=a&&`cwd`in a.session?a.session.cwd??``:``;A=u(c||i[0]?.cwd||``),j=n(a?.prompt??``,6),M=t([[`Project default`,``],[`Off`,`off`],[`Low`,`low`],[`Medium`,`medium`],[`High`,`high`],[`Extra high`,`xhigh`]],a?.launch?.thinking??``),N=u(a?.launch?.model?.provider??``),P=u(a?.launch?.model?.id??``);let l=d(`Session`,k),f=d(`Child directory`,A),p=[d(`Thinking`,M),d(`Model provider (optional)`,N),d(`Model id (optional)`,P)],m=()=>{let e=O?.value===`reuse`;l.classList.toggle(`hidden`,!e),f.classList.toggle(`hidden`,e);for(let t of p)t.classList.toggle(`hidden`,e)};O.onchange=m,m();let h=d(`Prompt`,j);h.classList.add(`col-span-2`),w.append(d(`Session policy`,O),l,f,...p,h)}else F=t(e.tasks().filter(e=>e.id!==r?.id&&!e.archived).map(e=>[e.name,e.id]),r?.action.type===`task`?r.action.taskId:``),w.append(d(`Target task`,F));o.onsubmit=t=>{t.preventDefault();let n=g.value===`manual`?{type:`manual`}:g.value===`cron`?{type:`cron`,expression:c.value,timezone:l.value}:{type:`watch`,script:f.value,cwd:T.value,intervalSeconds:Number(y.value),mode:C.value},i=_.value===`agent`&&O.value!==`reuse`?{...M.value?{thinking:M.value}:{},...N.value.trim()&&P.value.trim()?{model:{provider:N.value.trim(),id:P.value.trim()}}:{}}:void 0,o=_.value===`bash`?{type:`bash`,cwd:D.value,script:E.value}:_.value===`agent`?{type:`agent`,prompt:j.value,session:O.value===`reuse`?{mode:`reuse`,sessionId:k.value}:{mode:`fresh`,cwd:A.value},...i?{launch:i}:{}}:{type:`task`,taskId:F.value},u=v.value===`origin`?{type:`origin`}:v.value===`session`?{type:`session`,sessionId:x.value}:{type:`none`},d={name:p.value,description:m.value,trigger:n,action:o,callback:u,timeoutSeconds:Number(h.value)};s.textContent=``,S(r?.id,d,a,s,e.onSaved)}};g.onchange=T,_.onchange=T,T();let E=l(`Cancel`);E.onclick=()=>a.close();let D=l(`Save`,!0);D.type=`submit`;let O=i(`footer`,`flex flex-none justify-end gap-2 border-t border-neutral-200 px-4 py-3`);O.append(s,E,D),o.append(c,f,O),a.append(o),a.addEventListener(`close`,()=>a.remove()),document.body.append(a),a.showModal(),p.focus()}async function S(e,t,n,i,a){let o=await r(e?`/api/tasks/${e}`:`/api/tasks`,t,e?`PATCH`:`POST`);if(!o.ok){i.textContent=(await o.json()).error??`Task save failed`;return}let s=await o.json(),c=`task`in s?s.task:s;n.close(),a(c.id)}function C(n,u,d,b,S,C,w){let T=[],E=[],D=null,O=`active`,k=`all`,A=``,j=`runs`,M={top:0},N=0,P=0,F=``,I=f(async()=>{let e=`${O}:${k}`,t=await s(`/api/tasks?state=${O}&kind=task`,`Failed to load tasks`);if(!t.ok)return F=``,L(t.error);if(e!==`${O}:${k}`)return;let n=JSON.stringify(t.value);e+n===F&&!D||(F=e+n,T=t.value,E=O===`archived`?await R():T,e===`${O}:${k}`&&(k!==`all`&&(T=T.filter(e=>e.trigger.type===k)),D?await G(D):U()))});function L(e){n.querySelector(`[role=alert]`)?.remove();let t=i(`p`,`flex-none p-4 text-[13px] text-red-600`,e);t.setAttribute(`role`,`alert`),n.append(t)}async function R(){let e=await s(`/api/tasks?state=active`,`Failed to load tasks`);return e.ok?e.value:E}let z={sessions:u,tasks:()=>E,onSaved:e=>{V(e),j=`definition`,w(e)}};function B(e,t){let n=i(`header`,`flex min-h-10 flex-none flex-wrap items-center gap-2 border-b border-neutral-200 px-4 py-2`);if(n.append(typeof e==`string`?i(`span`,`truncate font-medium max-md:hidden`,e):e),t.length){let e=i(`div`,`ml-auto flex flex-wrap items-center gap-2 max-md:w-full`);e.append(...t),n.append(e)}return n}function V(e){D=e,j=`runs`,M.top=0}function H(){D=null,N++,U()}function U(){let e=l(`New task`,!0);e.onclick=()=>void d().then(()=>x(z));let r=i(`div`,`flex flex-none flex-wrap items-center gap-3 border-b border-neutral-200 px-4 py-2`),a=(e,n,a,o)=>{let s=t(n,a);s.setAttribute(`aria-label`,e),s.classList.add(`!w-auto`,`pr-8`),s.onchange=()=>{o(s.value),P=0,I()},r.append(i(`label`,`flex items-center gap-2 text-[12px] text-neutral-500`,e,s))};a(`Status`,[[`Current`,`active`],[`Archived`,`archived`]],O,e=>{O=e});let o=i(`input`,`min-w-0 rounded border border-neutral-200 px-2 py-1 text-[13px]`);o.type=`search`,o.placeholder=`Search tasks`,o.setAttribute(`aria-label`,`Search tasks`),o.value=A,o.oninput=()=>{A=o.value,u()},r.append(o),a(`Trigger`,[[`All triggers`,`all`],[`Manual`,`manual`],[`Scheduled`,`cron`],[`Watching`,`watch`]],k,e=>{k=e});let s=document.createElement(`table`);s.className=`w-full table-fixed text-left text-[12.5px]`,s.innerHTML=`<thead class="sticky top-0 bg-neutral-50 text-[10.5px] uppercase text-neutral-400"><tr>
|
|
2
|
+
<th class="w-[42%] px-4 py-2 font-semibold md:w-[24%]">Name</th><th class="hidden w-[10%] px-2 py-2 font-semibold md:table-cell">Action</th>
|
|
3
|
+
<th class="hidden w-[23%] px-2 py-2 font-semibold md:table-cell">Trigger</th><th class="hidden w-[17%] px-2 py-2 font-semibold md:table-cell">Next</th>
|
|
4
|
+
<th class="px-2 py-2 font-semibold md:w-[14%]">Last result</th><th class="w-[6rem] px-2 py-2 font-semibold"></th></tr></thead>`;let c=document.createElement(`tbody`),u=()=>{let e=T.filter(e=>`${e.name} ${e.description??``}`.toLowerCase().includes(A.toLowerCase()));c.replaceChildren(...e.map(W)),f.classList.toggle(`hidden`,e.length>0)};s.append(c);let f=i(`p`,`p-4 text-[13px] text-neutral-500`,`No matching tasks.`),p=i(`div`,`min-h-0 flex-1 overflow-auto`,s,f);u(),p.onscroll=()=>{P=p.scrollTop},n.replaceChildren(B(`Tasks`,[e]),r,p),p.scrollTop=P}function W(e){let t=document.createElement(`tr`);t.className=`cursor-pointer border-b border-neutral-100 hover:bg-neutral-50`,t.onclick=()=>w(e.id);let n=e.archived?`Archived`:e.trigger.type===`manual`?`Manual`:e.enabled?`Enabled`:`Paused`,r=l(e.name);r.className=`block w-full cursor-pointer truncate text-left font-medium`,r.title=e.name,t.append(i(`td`,`truncate py-2.5 pl-4 pr-2`,r,i(`div`,`truncate text-[11px] text-neutral-400`,n),i(`div`,`truncate text-[11px] text-neutral-400 md:hidden`,h(e))));for(let n of[y(e),h(e),g(e.nextRunAt)]){let e=i(`td`,`hidden truncate px-2 py-2.5 md:table-cell`,n);e.title=n,t.append(e)}t.append(i(`td`,`px-2 py-2.5`,i(`div`,`break-words`,e.lastRun?m(e.lastRun):`-`),i(`div`,`text-[11px] text-neutral-400`,e.lastRun?v(e.lastRun):``)));let a=l(`Run now`);return a.disabled=e.archived,a.onclick=t=>{t.stopPropagation(),K(e.id)},t.append(i(`td`,`px-2 py-1 text-right`,a)),t}async function G(e){let t=++N,r=n.querySelector(`[data-task-detail]`),[a,c]=await Promise.all([s(`/api/tasks/${e}`,`Failed to load task`),s(`/api/tasks/${e}/runs`,`Failed to load the task's runs`)]);if(t!==N||D!==e)return;if(!a.ok)return L(a.error);if(!c.ok)return L(c.error);let u=a.value,f=c.value,m=l(`Tasks`);m.className=`cursor-pointer text-neutral-500 hover:underline`,m.onclick=()=>w();let h=i(`span`,`flex min-w-0 items-center gap-1.5`,m,i(`span`,`text-neutral-400`,`›`),i(`span`,`truncate font-medium`,u.name)),g=l(`Run now`,!0);g.disabled=u.archived,g.onclick=()=>void K(u.id);let v=l(u.enabled?`Pause schedule`:`Resume schedule`);v.disabled=u.archived||u.action.type===`system`||u.trigger.type===`manual`,v.onclick=()=>void q(`/api/tasks/${u.id}/${u.enabled?`pause`:`resume`}`);let y=l(`Edit`);y.disabled=u.archived||u.action.type===`system`,y.onclick=()=>void d().then(()=>x(z,u));let S=l(`Archive`);S.disabled=u.archived||u.action.type===`system`,S.onclick=()=>void q(`/api/tasks/${u.id}/archive`);let T=i(`div`,`flex flex-none gap-1 border-b border-neutral-200 px-4 py-2`),E=i(`div`,`min-h-0 flex-1 overflow-auto`);E.dataset.taskDetail=e;let O=()=>{T.replaceChildren(o(`Recent runs (${f.length})`,j===`runs`,()=>{j=`runs`,O()}),o(`Definition`,j===`definition`,()=>{j=`definition`,O()})),j===`runs`?p(E,f,e=>C({},e),M):(E.onscroll=null,E.replaceChildren(_(u,b)))},k=r?.scrollTop??0,A=l(`All runs`);A.onclick=()=>C({taskId:u.id}),n.replaceChildren(B(h,[g,...u.trigger.type===`manual`?[]:[v],y,S,A]),T,E),O(),E.scrollTop=k}async function K(t){try{let n=await r(`/api/tasks/${t}/run`,{sourceSessionId:S()});if(!n.ok)return L(await e(n,`Failed to run task`));C({},(await n.json()).runId)}catch(e){L(`Failed to run task: ${String(e)}`)}}async function q(e){let t=await c(e,`POST`,`Task update failed`);if(t)return L(t);await I()}let J=a(n,e=>{e&&e!==D&&V(e),e||H(),n.childElementCount||U(),I()});return Object.assign(J,{refresh(e){J.visible&&(!D||!e||D===e)&&I()}})}export{C as createTasksView};
|
|
Binary file
|
|
Binary file
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/ghostty-web-
|
|
2
|
-
import{B as e,G as t,H as n,I as r,M as i,a,d as o,i as s,n as c,w as l}from"./index-
|
|
1
|
+
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/ghostty-web-CvSZragH.js","assets/index-DHxoYoeq.js","assets/index-DuZ2vERd.css"])))=>i.map(i=>d[i]);
|
|
2
|
+
import{B as e,G as t,H as n,I as r,M as i,a,d as o,i as s,n as c,w as l}from"./index-DHxoYoeq.js";var u=`pier.terminalPrefs`,d={fontFamily:`monospace`,fontSize:14,cursorBlink:!0},f=()=>{try{let e=JSON.parse(localStorage.getItem(u)??`null`),t=typeof e?.fontSize==`number`?e.fontSize:d.fontSize;return{fontFamily:typeof e?.fontFamily==`string`&&e.fontFamily.trim()?e.fontFamily.slice(0,200):d.fontFamily,fontSize:Math.round(Math.max(10,Math.min(24,t))),cursorBlink:typeof e?.cursorBlink==`boolean`?e.cursorBlink:d.cursorBlink}}catch{return{...d}}},p=e=>localStorage.setItem(u,JSON.stringify(e)),m=null,h=()=>(m||=o(()=>import(`./ghostty-web-CvSZragH.js`).then(async e=>(await e.init(),e)),__vite__mapDeps([0,1,2])).catch(e=>{throw m=null,e}),m),g=()=>{let e=()=>void h().catch(e=>{console.warn(`terminal: emulator warm-up failed`,e)});`requestIdleCallback`in window?requestIdleCallback(e,{timeout:1e4}):setTimeout(e,3e3)},_={background:`#1a1b26`,foreground:`#a9b1d6`,cursor:`#c0caf5`};function v(e){let t=e.renderer,n=document.createElement(`canvas`).getContext(`2d`);if(!n||typeof t?.remeasureFont!=`function`){console.warn(`terminal: could not correct cell metrics — rows will render tight`);return}t.measureFont=()=>{let{fontSize:t,fontFamily:r}=e.options;n.font=`${t}px ${r}`;let i=n.measureText(`M`),a=i.fontBoundingBoxAscent||i.actualBoundingBoxAscent||t*.8,o=i.fontBoundingBoxDescent||i.actualBoundingBoxDescent||t*.25;return{width:Math.ceil(i.width),height:Math.ceil(a+o),baseline:Math.ceil(a)}},t.remeasureFont()}var y=`w-full rounded border border-neutral-300 px-2 py-1.5 font-mono text-[12px] outline-none focus:border-indigo-400`;function b(o,u,m,b,x,S){g();let C=``,w=null,T=null,E=null,D=!1,O=0,k=!1,A=f(),j=r(`div`,`ml-auto flex min-w-0 flex-none items-center gap-2`),M=r(`header`,`flex h-10 flex-none items-center gap-2 border-b border-neutral-200 bg-white px-3`),N=r(`div`,`min-h-0 flex-1 overflow-hidden p-1`);N.style.background=_.background,N.setAttribute(`data-owns-keyboard`,``),N.addEventListener(`keydown`,e=>{e.metaKey&&!e.defaultPrevented&&e.stopImmediatePropagation()},!0),N.addEventListener(`scroll`,()=>{N.scrollTop=0,N.scrollLeft=0}),N.addEventListener(`compositionend`,()=>{for(let e=N.childNodes.length-1;e>=0;e--){let t=N.childNodes[e];t&&t.nodeName!==`CANVAS`&&t.nodeName!==`TEXTAREA`&&t.remove()}}),o.append(M,N);let P=e=>{let t=r(`button`,`min-w-0 max-w-72 cursor-pointer truncate rounded-md bg-neutral-100 px-2 py-0.5 text-left font-mono text-[11.5px] text-neutral-600 hover:bg-neutral-200`,e);return t.type=`button`,t};function F(e,t){if(j.replaceChildren(),e&&j.append(r(`span`,`min-w-0 truncate text-[11.5px] text-neutral-400`,e)),t){let e=P(t.label);e.className=e.className.replace(`bg-neutral-100`,`bg-indigo-50 font-medium text-indigo-700 hover:bg-indigo-100`),e.onclick=t.run,j.append(e)}}function I(){w&&(w.options.fontFamily=A.fontFamily,w.options.fontSize=A.fontSize,w.options.cursorBlink=A.cursorBlink,requestAnimationFrame(H))}function L(){let i=document.createElement(`input`);i.type=`text`,i.placeholder=`tmux new -As "$(basename $PWD)"`,i.spellcheck=!1,i.autocomplete=`off`,i.autocapitalize=`off`,i.disabled=!0,i.className=`${y} disabled:bg-neutral-50`;let a=r(`span`,`block text-[11.5px] text-neutral-400`,`loading…`),o=(e,t)=>{a.isConnected?l(a,e,t):e===`failed`&&F(t)},s=``;(async()=>{try{s=(await n(`/api/settings`,`Could not load the startup command`)).terminalInitCommand??``,i.value=s,i.disabled=!1,o(`idle`,`Runs when a shell starts — Restart applies it here.`)}catch(e){o(`failed`,String(e))}})();let c=async()=>{let n=i.value.trim();if(!(i.disabled||n===s)){o(`saving`,`saving…`);try{let r=await t(`/api/settings`,{terminalInitCommand:n},`PUT`);if(!r.ok)return o(`failed`,await e(r,`Could not save the startup command`));s=(await r.json()).terminalInitCommand,i.value=s,o(`saved`,s?`Saved — the next shell runs it.`:`Cleared.`)}catch(e){o(`failed`,`Could not save the startup command: ${String(e)}`)}}};return i.onblur=()=>void c(),i.onkeydown=e=>{e.key===`Enter`&&c()},r(`div`,`space-y-1.5 border-t border-neutral-200 pt-2.5`,r(`div`,``,r(`span`,`text-[12px] font-semibold text-neutral-700`,`Startup command`),r(`span`,`block text-[11px] text-neutral-400`,`This Pier, every shell — not only this browser.`)),i,a)}function R(e){let t=document.createElement(`input`);t.type=`text`,t.value=A.fontFamily,t.setAttribute(`list`,`terminal-font-presets`),t.className=y;let n=document.createElement(`datalist`);n.id=`terminal-font-presets`;for(let e of[`monospace`,`ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace`,`JetBrains Mono, monospace`,`Cascadia Mono, monospace`,`Fira Code, monospace`]){let t=document.createElement(`option`);t.value=e,n.append(t)}let i=document.createElement(`input`);i.type=`number`,i.min=`10`,i.max=`24`,i.step=`1`,i.value=String(A.fontSize),i.className=`w-20 rounded border border-neutral-300 px-2 py-1.5 text-[12px] outline-none focus:border-indigo-400`;let o=document.createElement(`input`);o.type=`checkbox`,o.checked=A.cursorBlink,o.className=`h-4 w-4 accent-indigo-600`;let s=()=>{let e=t.value.trim(),n=Number(i.value);!e||!Number.isFinite(n)||(A={fontFamily:e.slice(0,200),fontSize:Math.round(Math.max(10,Math.min(24,n))),cursorBlink:o.checked},i.value=String(A.fontSize),p(A),I())};t.oninput=s,i.oninput=s,o.onchange=s;let c=r(`button`,`cursor-pointer text-[12px] text-neutral-500 hover:text-indigo-700`,`Reset`);c.type=`button`,c.onclick=()=>{A={...d},t.value=A.fontFamily,i.value=String(A.fontSize),o.checked=A.cursorBlink,p(A),I()},a(e,r(`div`,`w-72 space-y-3 px-3 py-2.5`,r(`div`,`flex items-center`,r(`span`,`text-[12px] font-semibold text-neutral-700`,`Terminal`),r(`span`,`ml-auto`,c)),r(`label`,`block space-y-1 text-[11.5px] text-neutral-500`,r(`span`,``,`Font family`),t,n),r(`label`,`flex items-center justify-between text-[11.5px] text-neutral-500`,r(`span`,``,`Font size`),i),r(`label`,`flex cursor-pointer items-center justify-between text-[11.5px] text-neutral-500`,r(`span`,``,`Blinking cursor`),o),L()))}function z(){let e=P(C||`Choose a folder…`);e.title=`Switch folder`,e.onclick=()=>c(e,u().map(e=>({path:e})),C||void 0,e=>e===C?void 0:b(e));let t=P(`Files`);t.title=`Browse this folder`,t.onclick=()=>x(C);let n=P(`Fit`);n.title=`Resize the shell to this window`,n.onclick=()=>{H(),w?.focus()};let i=P(`Restart`);i.title=`End this shell and start a new one`,i.onclick=()=>void V();let a=r(`button`,`icon-btn text-[15px]`,`⚙`);a.type=`button`,a.title=`Terminal settings`,a.setAttribute(`aria-label`,`Terminal settings`),a.onclick=()=>R(a);let o=r(`button`,`icon-btn`,`✕`);o.type=`button`,o.title=`Close Terminal`,o.setAttribute(`aria-label`,`Close Terminal`),o.onclick=S,M.replaceChildren(e,j,...C?[t]:[],n,i,a,o)}let B=()=>{w&&E?.readyState===WebSocket.OPEN&&E.send(JSON.stringify({t:`resize`,cols:w.cols,rows:w.rows}))};async function V(){if(E?.readyState!==WebSocket.OPEN){await W();return}window.confirm(`Restart the shell in ${C}? Anything running in it stops.`)&&(k=!0,F(`restarting…`),E.send(JSON.stringify({t:`restart`})))}function H(){!w||N.clientHeight===0||(T?.fit(),B())}window.addEventListener(`focus`,H),document.addEventListener(`visibilitychange`,()=>{document.hidden||H()});function U(){E?.close(),E=null,T=null,w?.dispose(),w=null,N.replaceChildren()}async function W(){let e=++O;U(),D=!1,k=!1,F(`connecting…`);let t;try{t=await h()}catch(e){F(`could not load the terminal: ${String(e)}`,{label:`Retry`,run:()=>void W()});return}if(e!==O)return;w=new t.Terminal({fontFamily:A.fontFamily,fontSize:A.fontSize,cursorBlink:A.cursorBlink,scrollback:2e3,theme:_}),w.open(N),v(w),T=new t.FitAddon,w.loadAddon(T),T.observeResize(),T.fit();let n=new WebSocket(`${location.protocol===`https:`?`wss`:`ws`}://${location.host}/api/terminal?cwd=${encodeURIComponent(C)}`);n.binaryType=`arraybuffer`,E=n,n.onopen=()=>{e===O&&(F(``),B(),w?.focus())},n.onmessage=t=>{if(e!==O)return;if(typeof t.data!=`string`){w?.write(new Uint8Array(t.data));return}let r;try{r=JSON.parse(t.data)}catch{D=!0,F(`invalid terminal response`,{label:`Reconnect`,run:()=>void W()}),n.close();return}if(r.t===`exit`){if(k){k=!1,W();return}D=!0,F(`shell exited (${r.code??`?`})`,{label:`Restart`,run:()=>void W()})}else r.t===`error`&&(D=!0,F(r.message??`terminal error`,{label:`Retry`,run:()=>void W()}))},n.onclose=()=>{e!==O||D||F(`disconnected`,{label:`Reconnect`,run:()=>void W()})},w.onData(e=>{n.readyState===WebSocket.OPEN&&n.send(JSON.stringify({t:`in`,d:e}))}),w.onResize(({cols:e,rows:t})=>{n.readyState===WebSocket.OPEN&&n.send(JSON.stringify({t:`resize`,cols:e,rows:t}))})}return i(o,e=>{let t=e?.startsWith(`/`)?e:C||m()||u()[0]||``;if(t===C&&w){z(),!D&&E&&E.readyState>WebSocket.OPEN?W():(H(),w.focus());return}if(C=t,z(),!C){F(`No project yet — pick a folder from the chip.`);return}W()},s)}export{b as createTerminalView};
|
|
Binary file
|
|
Binary file
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
which style.css pays for with the safe-area inset. -->
|
|
34
34
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
|
35
35
|
<title>Pier</title>
|
|
36
|
-
<script type="module" crossorigin src="/assets/index-
|
|
37
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
36
|
+
<script type="module" crossorigin src="/assets/index-DHxoYoeq.js"></script>
|
|
37
|
+
<link rel="stylesheet" crossorigin href="/assets/index-DuZ2vERd.css">
|
|
38
38
|
</head>
|
|
39
39
|
<!-- The document never scrolls: this is a fixed-viewport workbench, and every
|
|
40
40
|
scrollable region is an inner pane. h-dvh, not h-screen, because 100vh can
|
|
@@ -79,6 +79,16 @@
|
|
|
79
79
|
<span class="chev">▶</span>Console
|
|
80
80
|
</summary>
|
|
81
81
|
<ul class="pb-1 text-[13.5px]">
|
|
82
|
+
<li><button type="button" id="open-tasks"
|
|
83
|
+
class="flex w-full cursor-pointer items-center gap-2 py-1.5 pl-6 pr-3 text-left hover:bg-neutral-100">
|
|
84
|
+
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" class="h-3.5 w-3.5 flex-none text-neutral-400">
|
|
85
|
+
<path d="M6 3h8M6 8h8M6 13h8M2 3h1M2 8h1M2 13h1" />
|
|
86
|
+
</svg>Tasks</button></li>
|
|
87
|
+
<li><button type="button" id="open-runs"
|
|
88
|
+
class="flex w-full cursor-pointer items-center gap-2 py-1.5 pl-6 pr-3 text-left hover:bg-neutral-100">
|
|
89
|
+
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" class="h-3.5 w-3.5 flex-none text-neutral-400">
|
|
90
|
+
<path d="m5 3 7 5-7 5z" stroke-linejoin="round" />
|
|
91
|
+
</svg>Runs</button></li>
|
|
82
92
|
<li><button type="button" id="open-activity"
|
|
83
93
|
class="flex w-full cursor-pointer items-center gap-2 py-1.5 pl-6 pr-3 text-left hover:bg-neutral-100">
|
|
84
94
|
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" class="h-3.5 w-3.5 flex-none text-neutral-400">
|
|
@@ -139,6 +149,7 @@
|
|
|
139
149
|
class="hidden h-10 w-10 flex-none cursor-pointer items-center justify-center rounded-md text-[20px] leading-none text-neutral-500 active:bg-neutral-200">⋯</button>
|
|
140
150
|
</header>
|
|
141
151
|
<section id="activity-view" class="hidden min-h-0 flex-1 flex-col"></section>
|
|
152
|
+
<section id="runs-view" class="hidden min-h-0 flex-1 flex-col"></section>
|
|
142
153
|
<section id="tasks-view" class="hidden min-h-0 flex-1 flex-col"></section>
|
|
143
154
|
<section id="boards-view" class="hidden min-h-0 flex-1 flex-col"></section>
|
|
144
155
|
<section id="settings-view" class="hidden min-h-0 flex-1 flex-col"></section>
|
|
Binary file
|
|
Binary file
|
package/dist/web/server.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Web workbench backend: REST + SSE, a pure consumer of core.
|
|
2
2
|
// See docs/design/03-web-workbench.md for the route contract.
|
|
3
3
|
import { hostname } from "node:os";
|
|
4
|
+
import { randomUUID } from "node:crypto";
|
|
4
5
|
import { readFile, stat } from "node:fs/promises";
|
|
5
6
|
import { isAbsolute, join, relative } from "node:path";
|
|
6
7
|
import { fileURLToPath } from "node:url";
|
|
@@ -10,7 +11,7 @@ import { compress } from "hono/compress";
|
|
|
10
11
|
import { streamSSE } from "hono/streaming";
|
|
11
12
|
import { EventHub } from "../core/hub.js";
|
|
12
13
|
import { logger } from "../log.js";
|
|
13
|
-
import { Router } from "../core/router.js";
|
|
14
|
+
import { QueueOperationError, Router } from "../core/router.js";
|
|
14
15
|
import { registerConfigRoutes } from "./config.js";
|
|
15
16
|
import { registerExplorerRoutes } from "./explorer.js";
|
|
16
17
|
import { fileHeaders, MAX_FILE_BYTES, registerFsRoutes, scopedFile } from "./fs.js";
|
|
@@ -24,6 +25,17 @@ import {} from "./session-state.js";
|
|
|
24
25
|
import { registerInstanceRoutes } from "./instance.js";
|
|
25
26
|
import { registerProviderRoutes } from "./providers.js";
|
|
26
27
|
const log = logger("web");
|
|
28
|
+
async function queueResponse(action, status = 200) {
|
|
29
|
+
try {
|
|
30
|
+
return Response.json(await action(), { status });
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
const code = err instanceof QueueOperationError
|
|
34
|
+
? err.reason === "draining" ? 503 : err.reason === "missing" ? 404 : 409
|
|
35
|
+
: 404;
|
|
36
|
+
return Response.json({ error: String(err) }, { status: code });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
27
39
|
/** A transcript without the bytes nobody has asked to see yet. A step's `args`
|
|
28
40
|
* and `output` are ~90% of a long session's snapshot and sit inside a
|
|
29
41
|
* collapsed group; the client fetches one turn's worth when it is opened. */
|
|
@@ -46,25 +58,20 @@ const HEARTBEAT_MS = 15_000;
|
|
|
46
58
|
const SSE_HIGH_WATER = 4 * 1024 * 1024;
|
|
47
59
|
// Canonical base64 only: Buffer.from(.., "base64") happily "decodes" garbage.
|
|
48
60
|
const BASE64_RE = /^[A-Za-z0-9+/]+={0,2}$/;
|
|
49
|
-
/** The SSE frame for one event, built once and shared by every stream watching
|
|
50
|
-
* that session — three open tabs used to stringify the same delta three
|
|
51
|
-
* times. One slot is the whole cache: the hub fans an event out to its
|
|
52
|
-
* subscribers in one synchronous loop, so the reuse is always of the event
|
|
53
|
-
* just built, and the memo is keyed on identity so a miss only costs a
|
|
54
|
-
* rebuild. The hub stays transport-blind; this is the transport's business.
|
|
55
|
-
* Byte-identical to hono's `writeSSE`: JSON has no raw newline, so `data` is
|
|
56
|
-
* one line. */
|
|
57
|
-
let lastEvent = null;
|
|
58
|
-
let lastFrame = "";
|
|
59
|
-
const sseFrame = (e) => {
|
|
60
|
-
if (e !== lastEvent) {
|
|
61
|
-
lastEvent = e;
|
|
62
|
-
lastFrame = `data: ${JSON.stringify(e)}\nid: ${e.seq}\n\n`;
|
|
63
|
-
}
|
|
64
|
-
return lastFrame;
|
|
65
|
-
};
|
|
66
61
|
export function createServer({ factory, router, hub, sessions: state, config, providers, settings, catalog, names, onToolsChanged, validateCustomTools, secrets, onUnlocked, reload, updates, updater, backgroundRuns, activeBackgroundRunCounts, channelOf, }) {
|
|
67
62
|
const app = new Hono();
|
|
63
|
+
const epoch = randomUUID();
|
|
64
|
+
// One frame shared by synchronous fan-out to every watching tab. The epoch
|
|
65
|
+
// and wire encoding stay here; core only stamps transport-blind events.
|
|
66
|
+
let lastEvent = null;
|
|
67
|
+
let lastFrame = "";
|
|
68
|
+
const sseFrame = (e) => {
|
|
69
|
+
if (e !== lastEvent) {
|
|
70
|
+
lastEvent = e;
|
|
71
|
+
lastFrame = `data: ${JSON.stringify(e)}\nid: ${epoch}:${e.seq}\n\n`;
|
|
72
|
+
}
|
|
73
|
+
return lastFrame;
|
|
74
|
+
};
|
|
68
75
|
// A finished turn marks its session unread until some client reports it was
|
|
69
76
|
// seen (session selected + tab visible → POST read below). Server-side so
|
|
70
77
|
// every client shows the same attention state. Streaming → idle is the
|
|
@@ -246,16 +253,28 @@ export function createServer({ factory, router, hub, sessions: state, config, pr
|
|
|
246
253
|
guarded(app, "GET", "/api/sessions/:id/history", 404, async (c) => {
|
|
247
254
|
const id = c.req.param("id");
|
|
248
255
|
const session = await ensureLoadable(id);
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
256
|
+
// Async seam reads can straddle an event. Never label older content with a
|
|
257
|
+
// newer cursor, and never spin indefinitely if the session stays busy.
|
|
258
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
259
|
+
const lastSeq = hub.lastSeq(id);
|
|
260
|
+
const turns = (await session.history()).map(slim);
|
|
261
|
+
const queue = await session.pendingQueue();
|
|
262
|
+
if (hub.lastSeq(id) !== lastSeq)
|
|
263
|
+
continue;
|
|
264
|
+
return c.json({
|
|
265
|
+
turns, lastSeq, epoch,
|
|
266
|
+
model: session.model ?? null,
|
|
267
|
+
state: session.state,
|
|
268
|
+
context: session.contextUsage ?? null,
|
|
269
|
+
thinkingLevel: session.thinkingLevel,
|
|
270
|
+
queue,
|
|
271
|
+
queueRecovery: router.recoveryOf(id),
|
|
272
|
+
queueUncertain: router.queueUncertain(id),
|
|
273
|
+
backgroundRuns: backgroundRuns?.(id) ?? [],
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
log.warn(`snapshot for ${id} changed during all 3 reads`);
|
|
277
|
+
return c.json({ error: "Session changed while loading history; retry loading the session" }, 503);
|
|
259
278
|
});
|
|
260
279
|
// One turn's activity in full, for the group the user just opened. Indexed
|
|
261
280
|
// like the edit route below; the steps carry their own id and tool name so a
|
|
@@ -404,8 +423,8 @@ export function createServer({ factory, router, hub, sessions: state, config, pr
|
|
|
404
423
|
return c.json({ ok: true }, 202);
|
|
405
424
|
});
|
|
406
425
|
// Promote queued messages: "steer" delivers them into the running turn,
|
|
407
|
-
// "restart" aborts the turn and sends them as a fresh prompt.
|
|
408
|
-
//
|
|
426
|
+
// "restart" aborts the turn and sends them as a fresh prompt. Core owns
|
|
427
|
+
// exclusion and retains originals through the asynchronous handoff.
|
|
409
428
|
guarded(app, "POST", "/api/sessions/:id/queue/deliver", 404, async (c) => {
|
|
410
429
|
const id = c.req.param("id");
|
|
411
430
|
const body = await c.req.json().catch(() => null);
|
|
@@ -413,38 +432,20 @@ export function createServer({ factory, router, hub, sessions: state, config, pr
|
|
|
413
432
|
if (mode !== "steer" && mode !== "restart") {
|
|
414
433
|
return c.json({ error: "mode must be steer or restart" }, 400);
|
|
415
434
|
}
|
|
416
|
-
|
|
417
|
-
if (router.isDraining())
|
|
418
|
-
return c.json({ error: "Pier is restarting — try again in a moment" }, 503);
|
|
419
|
-
const session = await ensure(id);
|
|
420
|
-
const { steering, followUp } = await session.clearQueue();
|
|
421
|
-
const text = [...steering, ...followUp].join("\n").trim();
|
|
422
|
-
if (!text)
|
|
423
|
-
return c.json({ error: "queue is empty" }, 409);
|
|
424
|
-
if (mode === "restart")
|
|
425
|
-
await router.abort(id); // resolves once idle
|
|
426
|
-
// No sender here, unlike the other dispatches: the queued texts were
|
|
427
|
-
// headed when they were first dispatched — and in a shared session they
|
|
428
|
-
// are other speakers' words, which an operator header would claim.
|
|
429
|
-
await router.dispatch({
|
|
430
|
-
key: { channelId: "web", conversationId: id },
|
|
431
|
-
senderId: "web",
|
|
432
|
-
text,
|
|
433
|
-
mode: mode === "steer" ? "steer" : "auto",
|
|
434
|
-
});
|
|
435
|
-
return c.json({ delivered: text }, 202);
|
|
435
|
+
return queueResponse(async () => ({ submitted: await router.deliverQueue(id, mode) }), 202);
|
|
436
436
|
});
|
|
437
437
|
// Recall: drop all pending queued messages and hand them back (composer restore).
|
|
438
438
|
guarded(app, "POST", "/api/sessions/:id/queue/recall", 404, async (c) => {
|
|
439
439
|
const id = c.req.param("id");
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
if (steering.length || followUp.length)
|
|
445
|
-
router.forgetSender(id);
|
|
446
|
-
return c.json({ messages: [...steering, ...followUp] });
|
|
440
|
+
return queueResponse(async () => {
|
|
441
|
+
const { steering, followUp } = await router.recallQueue(id);
|
|
442
|
+
return { messages: [...steering, ...followUp] };
|
|
443
|
+
});
|
|
447
444
|
});
|
|
445
|
+
guarded(app, "POST", "/api/sessions/:id/queue/recovery/:batchId/ack", 404, async (c) => queueResponse(async () => {
|
|
446
|
+
router.acknowledgeRecovery(c.req.param("id"), c.req.param("batchId"));
|
|
447
|
+
return { ok: true };
|
|
448
|
+
}));
|
|
448
449
|
// Shrink the context on demand: Pi summarizes the older transcript away and
|
|
449
450
|
// the session continues from the summary. Refused while streaming, like the
|
|
450
451
|
// edit route above and for the same reason — Pi's own compaction aborts a
|
|
@@ -496,10 +497,14 @@ export function createServer({ factory, router, hub, sessions: state, config, pr
|
|
|
496
497
|
}));
|
|
497
498
|
app.get("/api/sessions/:id/events", (c) => {
|
|
498
499
|
const id = c.req.param("id");
|
|
499
|
-
const
|
|
500
|
-
|
|
501
|
-
|
|
500
|
+
const cursor = c.req.header("Last-Event-ID") ?? c.req.query("after") ?? "";
|
|
501
|
+
const match = /^([^:]+):(0|[1-9]\d*)$/.exec(cursor);
|
|
502
|
+
const lastId = match ? Number(match[2]) : NaN;
|
|
502
503
|
return streamSSE(c, async (stream) => {
|
|
504
|
+
if (match?.[1] !== epoch || !hub.covers(id, lastId)) {
|
|
505
|
+
await stream.writeSSE({ event: "reset", data: "snapshot required" });
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
503
508
|
let queued = 0; // frame chars written but not yet drained by the reader
|
|
504
509
|
const send = (frame) => {
|
|
505
510
|
if (stream.aborted || stream.closed)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@timqi/pier",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.21",
|
|
4
4
|
"description": "A self-hosted workspace for coding agents: web workbench and IM channels in front of Pi sessions",
|
|
5
5
|
"license": "AGPL-3.0-only",
|
|
6
6
|
"repository": "github:timqi/pier",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"vitest": "^4.1.11"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@earendil-works/pi-coding-agent": "^0.
|
|
47
|
+
"@earendil-works/pi-coding-agent": "^0.85.1",
|
|
48
48
|
"@hono/node-server": "^2.1.1",
|
|
49
49
|
"@larksuiteoapi/node-sdk": "^1.73.0",
|
|
50
50
|
"croner": "^10.0.1",
|
|
@@ -32,12 +32,18 @@ A summary's `triggerSource` is who fired that run (`agent` when you did, plus
|
|
|
32
32
|
`manual` / `cron` / `watch` / `task`); a definition's `trigger` is only its
|
|
33
33
|
schedule policy, where `manual` means on-demand — by a human or by you.
|
|
34
34
|
|
|
35
|
-
- `fresh`: clean context, requires `cwd
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
- `fresh`: clean context, requires `cwd` — how a child normally starts.
|
|
36
|
+
`reuse`: sends work to an existing session by id, continuing its history.
|
|
37
|
+
There is no way to copy your context into a child: what the child needs, you
|
|
38
|
+
write down.
|
|
39
|
+
- **The handoff is the prompt.** A child cannot see your discussion, so state:
|
|
40
|
+
the goal; the decisions *currently* in force (not the ones you argued out of);
|
|
41
|
+
the constraints the user added late; absolute paths; what "done" means and how
|
|
42
|
+
to verify it. Writing it is also the check — a decision you cannot state in a
|
|
43
|
+
sentence was not settled.
|
|
38
44
|
- The child inherits your current model unless `launch.model` is set.
|
|
39
45
|
Unknown models fail with the available list in the error. `launch` applies to
|
|
40
|
-
`fresh`
|
|
46
|
+
`fresh` only — a `reuse` session owns its own model and tools.
|
|
41
47
|
- The child has the same tools you do.
|
|
42
48
|
- Runs time out after 900s by default; set `timeoutSeconds` in the draft for
|
|
43
49
|
longer jobs.
|
|
@@ -65,9 +71,8 @@ group in core — you never track run ids across turns:
|
|
|
65
71
|
## Chains
|
|
66
72
|
|
|
67
73
|
Run step 1, end your turn, receive its callback, splice the needed part into
|
|
68
|
-
step 2's prompt. Children share nothing implicitly
|
|
69
|
-
|
|
70
|
-
expected output format. Branching and retries are your own logic between
|
|
74
|
+
step 2's prompt. Children share nothing implicitly — every prompt must be
|
|
75
|
+
self-contained: paths, acceptance criteria, expected output format. Branching and retries are your own logic between
|
|
71
76
|
turns.
|
|
72
77
|
|
|
73
78
|
## Choosing a model and thinking level
|
|
@@ -103,8 +108,8 @@ list in the error, so a stale guess costs one call.
|
|
|
103
108
|
|
|
104
109
|
`{"operation":"contact","reason":"decision","message":"..."}` returns a
|
|
105
110
|
receipt immediately. State what you are waiting for and **end your turn** —
|
|
106
|
-
never spin or poll. The answer
|
|
107
|
-
active; if your run already finished, Pier resumes your session with the
|
|
111
|
+
never spin or poll. The answer is steered into your current turn if your run is
|
|
112
|
+
still active; if your run already finished, Pier resumes your session with the
|
|
108
113
|
reply as the prompt. One open decision per run.
|
|
109
114
|
|
|
110
115
|
As a parent: the question arrives as a system input with a message id, steered
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import{F as e,I as t,K as n,M as r,R as i,T as a,V as o,z as s}from"./index-BFzmvpGX.js";var c=(e,t={})=>{let n=document.createElementNS(`http://www.w3.org/2000/svg`,e);for(let[e,r]of Object.entries(t))n.setAttribute(e,r);return n},l=t=>t===null?`-`:e(Date.now()-t),u={light:{edge:`#a3a3a3`,callback:`#0891b2`,message:`#d97706`,scheduler:{fill:`#f5f3ff`,stroke:`#a78bfa`,text:`#5b21b6`},console:{fill:`#eff6ff`,stroke:`#93c5fd`,text:`#1d4ed8`},task:{fill:`#fffbeb`,stroke:`#fbbf24`,text:`#92400e`},process:{fill:`#f5f5f5`,stroke:`#a3a3a3`,text:`#525252`,dash:`4 3`},streaming:{fill:`#ffffff`,stroke:`#10b981`,text:`#262626`},idle:{fill:`#fafafa`,stroke:`#d4d4d4`,text:`#737373`}},dark:{edge:`#7a7a7a`,callback:`#3fc3dd`,message:`#e0a13a`,scheduler:{fill:`#2b2440`,stroke:`#7d63c9`,text:`#c9b8f5`},console:{fill:`#1f2a3d`,stroke:`#4a7fbf`,text:`#a8c8f0`},task:{fill:`#3a2f1c`,stroke:`#b8862a`,text:`#f0d49a`},process:{fill:`#262626`,stroke:`#5c5c5c`,text:`#a3a3a3`,dash:`4 3`},streaming:{fill:`#22302a`,stroke:`#10b981`,text:`#e5e5e5`},idle:{fill:`#212121`,stroke:`#4a4a4a`,text:`#9a9a9a`}}},d=()=>u[document.documentElement.dataset.theme===`dark`?`dark`:`light`];function f(e,u,f){let p=`sessions`,m=`active`,h={sessions:[],runs:[],messages:[]},g=``,_=s(async()=>{let r=m,i=await o(`/api/activity?scope=${r}`,`Failed to load activity`);if(!i.ok){g=``,e.replaceChildren(t(`p`,`p-4 text-[13px] text-red-600`,i.error));return}if(r!==m)return;let a=JSON.stringify(i.value);if(a===g)return;g=a;let s=i.value;h={...s,sessions:s.sessions.map(e=>({...e,title:n(e.title)}))},v()});function v(){let n=t(`header`,`flex h-10 flex-none items-center gap-2 border-b border-neutral-200 px-4 max-md:hidden`,t(`span`,`font-medium`,`Activity`)),r=t(`div`,`tabstrip`);r.append(a(`Sessions`,p===`sessions`,()=>{p=`sessions`,v()}),a(`Dependencies`,p===`dependencies`,()=>{p=`dependencies`,v()}),a(`Tasks`,!1,()=>f()));let i=t(`div`,`ml-auto flex gap-1 max-md:ml-0 max-md:w-full`),o=e=>()=>{m=e,v(),_()};i.append(a(`Active`,m===`active`,o(`active`)),a(`Last 24h`,m===`recent`,o(`recent`))),r.append(i);let s=t(`div`,`min-h-0 flex-1 overflow-auto`);p===`sessions`?y(s):b(s),e.replaceChildren(n,r,s)}function y(e){let n=document.createElement(`table`);n.className=`w-full min-w-[38rem] table-fixed text-left text-[12.5px]`,n.innerHTML=`<thead class="bg-neutral-50 text-[10.5px] uppercase text-neutral-400"><tr>
|
|
2
|
-
<th class="w-[34%] px-4 py-2 font-semibold">Session</th>
|
|
3
|
-
<th class="w-[38%] px-2 py-2 font-semibold">Project</th>
|
|
4
|
-
<th class="w-[14%] px-2 py-2 font-semibold">State</th>
|
|
5
|
-
<th class="px-2 py-2 font-semibold">Duration</th></tr></thead>`;let r=document.createElement(`tbody`);for(let e of h.sessions){let n=document.createElement(`tr`);n.className=`cursor-pointer border-b border-neutral-100 hover:bg-neutral-50`,n.onclick=()=>u(e.id),n.append(t(`td`,`px-4 py-2.5`,t(`div`,`truncate font-medium`,e.title??i(e.cwd)),t(`div`,`truncate font-mono text-[11px] text-neutral-400`,e.id)),t(`td`,`truncate px-2 py-2.5 font-mono text-[11.5px]`,e.cwd||`-`),t(`td`,`px-2 py-2.5`,t(`span`,`mr-2 inline-block h-2 w-2 rounded-full ${e.state===`streaming`?`animate-pulse bg-green-500`:`bg-neutral-300`}`),e.state),t(`td`,`px-2 py-2.5 text-neutral-500`,l(e.stateSince))),r.append(n)}n.append(r),e.append(n),h.sessions.length||e.append(t(`p`,`p-4 text-[13px] text-neutral-400`,m===`active`?`No active sessions.`:`No sessions in the last 24 hours.`))}function b(e){let n=new Map,r=[],i=(e,t,r,i)=>{n.has(e)||n.set(e,{id:e,label:t,kind:r,state:i})};for(let e of h.sessions)i(e.id,e.title??e.id.slice(0,10),`session`,e.state);for(let e of h.runs){let t=e.invokedBySessionId??`scheduler`,n=e.targetSessionId??(e.context.definition.action.type===`bash`?`process:${e.id}`:`task:${e.id}`);i(t,t===`scheduler`?`Scheduler`:t.slice(0,10),t===`scheduler`?`scheduler`:`session`),i(n,e.targetSessionId?e.targetSessionId.slice(0,10):e.context.definition.action.type===`bash`?`Bash process`:`Task`,e.targetSessionId?`session`:e.context.definition.action.type===`bash`?`process`:`task`),r.push({from:t,to:n,run:e,kind:`invocation`}),e.callbackSessionId&&(i(e.callbackSessionId,e.callbackSessionId.slice(0,10),`session`),r.push({from:n,to:e.callbackSessionId,run:e,kind:`callback`}))}for(let e of h.messages){if(!e.fromSessionId||!e.toSessionId||e.fromSessionId===e.toSessionId)continue;let t=h.runs.find(t=>t.id===e.runId);if(t){for(let t of[e.fromSessionId,e.toSessionId])i(t,t===`console`?`Console`:t.slice(0,10),t===`console`?`console`:`session`);r.push({from:e.fromSessionId,to:e.toSessionId,run:t,kind:`message`})}}if(!n.size){e.append(t(`p`,`p-4 text-[13px] text-neutral-400`,m===`active`?`No active dependencies.`:`No dependencies in the last 24 hours.`));return}let a=[...n.values()],o=new Set(r.map(e=>e.to)),s=new Map,l=a.filter(e=>!o.has(e.id)).map(e=>e.id);l.length||l.push(a[0].id);for(let e of l)s.set(e,0);for(;l.length;){let e=l.shift();for(let t of r)t.from!==e||s.has(t.to)||(s.set(t.to,s.get(e)+1),l.push(t.to))}for(let e of a)s.has(e.id)||s.set(e.id,0);let p=new Map;for(let e of a){let t=s.get(e.id);p.set(t,[...p.get(t)??[],e])}let g=Math.max(...p.keys())+1,_=Math.max(...[...p.values()].map(e=>e.length)),v=r.some(e=>e.from!==e.to&&s.get(e.from)===s.get(e.to))?40:0,y=56+g*180+(g-1)*110+v,b=56+_*46+(_-1)*30,x=new Map;for(let[e,t]of p)t.forEach((t,n)=>x.set(t.id,{x:28+e*290+90,y:28+n*76+23}));let S=c(`svg`,{width:String(y),height:String(b),viewBox:`0 0 ${y} ${b}`,class:`block`}),C=c(`defs`),w=d();for(let[e,t]of[[`activity-arrow`,w.edge],[`activity-arrow-cb`,w.callback],[`activity-arrow-msg`,w.message]]){let n=c(`marker`,{id:e,viewBox:`0 0 10 10`,refX:`9`,refY:`5`,markerWidth:`6`,markerHeight:`6`,orient:`auto-start-reverse`});n.append(c(`path`,{d:`M 0 0 L 10 5 L 0 10 z`,fill:t})),C.append(n)}S.append(C);let T=new Set;for(let e of r){let t=`${e.from}→${e.to}:${e.kind}`;if(T.has(t))continue;T.add(t);let n=x.get(e.from),r=x.get(e.to),i=c(`path`,{d:n.x===r.x?`M ${n.x+90} ${n.y} C ${n.x+90+46} ${n.y}, ${r.x+90+46} ${r.y}, ${r.x+90} ${r.y}`:(()=>{let e=r.x>n.x,t=n.x+(e?90:-90),i=r.x+(e?-90:90),a=Math.max(36,Math.abs(i-t)*.45)*(e?1:-1);return`M ${t} ${n.y} C ${t+a} ${n.y}, ${i-a} ${r.y}, ${i} ${r.y}`})(),fill:`none`,stroke:e.kind===`callback`?w.callback:e.kind===`message`?w.message:w.edge,"stroke-width":`1.5`,"marker-end":e.kind===`callback`?`url(#activity-arrow-cb)`:e.kind===`message`?`url(#activity-arrow-msg)`:`url(#activity-arrow)`});e.kind===`callback`&&i.setAttribute(`stroke-dasharray`,`6 5`),e.kind===`message`&&i.setAttribute(`stroke-dasharray`,`2 5`),i.classList.add(`cursor-pointer`),i.onclick=()=>f(e.run.taskId),S.append(i)}let E=/[\u1100-\u11FF\u2E80-\uA4CF\uAC00-\uD7A3\uF900-\uFAFF\uFE30-\uFE4F\uFF00-\uFF60\uFFE0-\uFFE6]/,D=(e,t)=>{let n=0;for(let r=0;r<e.length;r++)if(n+=E.test(e[r])?2:1,n>t)return`${e.slice(0,r)}…`;return e};for(let e of a){let t=x.get(e.id),n=e.kind===`session`?e.state===`streaming`?w.streaming:w.idle:w[e.kind],r=c(`g`,{transform:`translate(${t.x},${t.y})`});e.kind===`session`&&r.classList.add(`cursor-pointer`),r.onclick=()=>{e.kind===`session`&&u(e.id)};let i=c(`rect`,{x:`-90`,y:`-23`,width:`180`,height:`46`,rx:`10`,fill:n.fill,stroke:n.stroke,"stroke-width":e.kind===`session`&&e.state===`streaming`?`1.5`:`1`});n.dash&&i.setAttribute(`stroke-dasharray`,n.dash),r.append(i);let a=-78;if(e.kind===`session`){let t=c(`circle`,{cx:`-75`,cy:`0`,r:`3.5`,fill:n.stroke});if(e.state===`streaming`){let e=c(`animate`,{attributeName:`opacity`,values:`1;0.3;1`,dur:`1.6s`,repeatCount:`indefinite`});t.append(e)}r.append(t),a=-64}let o=c(`text`,{x:String(a),"text-anchor":`start`,"dominant-baseline":`middle`,"font-size":`11.5`,"font-weight":e.kind===`session`?`500`:`400`,fill:n.text});o.textContent=D(e.label,e.kind===`session`?22:25);let s=c(`title`);s.textContent=e.label===e.id?e.id:`${e.label}\n${e.id}`,r.append(o,s),S.append(r)}let O=e=>t(`span`,`inline-block h-2 w-2 rounded-full ${e}`);e.append(t(`div`,`w-max p-4`,S),t(`div`,`flex flex-wrap gap-x-5 gap-y-1 border-t border-neutral-200 px-4 py-2 text-[11px] text-neutral-500`,t(`span`,``,`Solid: task invocation`),t(`span`,`text-cyan-700`,`Dashed: callback`),t(`span`,`text-amber-700`,`Dotted: supervisor/control`),t(`span`,`inline-flex items-center gap-1.5`,O(`bg-emerald-500`),`streaming`),t(`span`,`inline-flex items-center gap-1.5`,O(`bg-neutral-300`),`idle`)))}let x=r(e,e=>{e===`dependencies`?p=`dependencies`:e===`sessions`&&(p=`sessions`),_()},()=>{e.replaceChildren(),h={sessions:[],runs:[],messages:[]},g=``});return window.addEventListener(`pier:theme`,()=>{x.visible&&v()}),Object.assign(x,{refresh(){x.visible&&_()}})}export{f as createActivityView};
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|