@femtomc/mu-server 26.2.35 → 26.2.37
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/README.md +29 -1
- package/dist/api/events.d.ts +2 -0
- package/dist/api/events.js +45 -0
- package/dist/api/forum.js +2 -2
- package/dist/api/issues.js +5 -5
- package/dist/cli.js +3 -3
- package/dist/config.d.ts +99 -0
- package/dist/config.js +360 -0
- package/dist/control_plane.d.ts +5 -28
- package/dist/control_plane.js +33 -97
- package/dist/index.d.ts +6 -4
- package/dist/index.js +3 -2
- package/dist/server.d.ts +19 -5
- package/dist/server.js +207 -50
- package/package.json +6 -6
- package/public/assets/index-CxkevQNh.js +100 -0
- package/public/index.html +1 -1
- package/public/assets/index-0FGFtKeu.js +0 -85
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
(function(){const e=document.createElement("link").relList;if(e&&e.supports&&e.supports("modulepreload"))return;for(const n of document.querySelectorAll('link[rel="modulepreload"]'))c(n);new MutationObserver(n=>{for(const r of n)if(r.type==="childList")for(const d of r.addedNodes)d.tagName==="LINK"&&d.rel==="modulepreload"&&c(d)}).observe(document,{childList:!0,subtree:!0});function s(n){const r={};return n.integrity&&(r.integrity=n.integrity),n.referrerPolicy&&(r.referrerPolicy=n.referrerPolicy),n.crossOrigin==="use-credentials"?r.credentials="include":n.crossOrigin==="anonymous"?r.credentials="omit":r.credentials="same-origin",r}function c(n){if(n.ep)return;n.ep=!0;const r=s(n);fetch(n.href,r)}})();const C="";class S extends Error{constructor(e,s){super(s),this.status=e,this.name="ApiError"}}async function o(t,e){const s=await fetch(`${C}${t}`,{...e,headers:{"Content-Type":"application/json",...e==null?void 0:e.headers}});if(!s.ok){const c=await s.text();throw new S(s.status,c||`HTTP ${s.status}`)}return s.json()}const a={async getStatus(){return o("/api/status")},async listIssues(t){const e=new URLSearchParams;t!=null&&t.status&&e.set("status",t.status),t!=null&&t.tag&&e.set("tag",t.tag);const s=e.toString();return o(`/api/issues${s?`?${s}`:""}`)},async getIssue(t){return o(`/api/issues/${t}`)},async createIssue(t){return o("/api/issues",{method:"POST",body:JSON.stringify(t)})},async updateIssue(t,e){return o(`/api/issues/${t}`,{method:"PATCH",body:JSON.stringify(e)})},async claimIssue(t){return o(`/api/issues/${t}/claim`,{method:"POST",body:JSON.stringify({})})},async closeIssue(t,e){return o(`/api/issues/${t}/close`,{method:"POST",body:JSON.stringify({outcome:e})})},async getReadyIssues(t){const e=t?`?root=${encodeURIComponent(t)}`:"";return o(`/api/issues/ready${e}`)},async postMessage(t,e,s){return o("/api/forum/post",{method:"POST",body:JSON.stringify({topic:t,body:e,author:s})})},async readMessages(t,e){const s=new URLSearchParams({topic:t});return e&&s.set("limit",String(e)),o(`/api/forum/read?${s}`)},async listTopics(t,e){const s=new URLSearchParams;t&&s.set("prefix",t),e&&s.set("limit",String(e));const c=s.toString();return o(`/api/forum/topics${c?`?${c}`:""}`)},async getEvents(t){const e=new URLSearchParams;t!=null&&t.type&&e.set("type",t.type),t!=null&&t.source&&e.set("source",t.source),t!=null&&t.limit&&e.set("limit",String(t.limit));const s=e.toString();return o(`/api/events${s?`?${s}`:""}`)},async getEventsTail(t){const e=t?`?n=${t}`:"";return o(`/api/events/tail${e}`)}},w="mu-web:last_topic";function x(){return typeof localStorage<"u"?localStorage:null}function b(){try{const t=x();if(!t)return null;const e=String(t.getItem(w)??"").trim();return e.length>0?e:null}catch{return null}}function E(t){try{const e=x();if(!e)return;const s=t.trim();if(!s)return;e.setItem(w,s)}catch{}}const L=document.querySelector("#app");if(!L)throw new Error("missing #app");const T=L;T.innerHTML=`
|
|
2
|
+
<div class="container">
|
|
3
|
+
<h1>mu</h1>
|
|
4
|
+
<div class="row muted">
|
|
5
|
+
<span class="pill" data-testid="status-pill">Connecting...</span>
|
|
6
|
+
<button data-testid="refresh">Refresh</button>
|
|
7
|
+
<span class="muted" data-testid="repo-root"></span>
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<div class="grid">
|
|
11
|
+
<div class="card">
|
|
12
|
+
<h2>Issues</h2>
|
|
13
|
+
<div class="row">
|
|
14
|
+
<input data-testid="issue-title" placeholder="Issue title" />
|
|
15
|
+
<button data-testid="create-issue">Create</button>
|
|
16
|
+
</div>
|
|
17
|
+
<p class="muted">
|
|
18
|
+
All issues: <span data-testid="issues-count">0</span>
|
|
19
|
+
Ready leaves: <span data-testid="ready-count">0</span>
|
|
20
|
+
</p>
|
|
21
|
+
<div class="grid" style="grid-template-columns: 1fr; gap: 10px;">
|
|
22
|
+
<div>
|
|
23
|
+
<div class="muted">Issues</div>
|
|
24
|
+
<pre data-testid="issues-json">[]</pre>
|
|
25
|
+
</div>
|
|
26
|
+
<div>
|
|
27
|
+
<div class="muted">Ready Leaves</div>
|
|
28
|
+
<pre data-testid="ready-json">[]</pre>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<div class="card">
|
|
34
|
+
<h2>Forum</h2>
|
|
35
|
+
|
|
36
|
+
<div class="muted">Post</div>
|
|
37
|
+
<div class="row">
|
|
38
|
+
<input data-testid="forum-topic" placeholder="Topic (e.g. issue:mu-123)" />
|
|
39
|
+
<input data-testid="forum-author" placeholder="Author" value="worker" />
|
|
40
|
+
</div>
|
|
41
|
+
<textarea data-testid="forum-body" placeholder="Message body"></textarea>
|
|
42
|
+
<div class="row">
|
|
43
|
+
<button data-testid="forum-post">Post</button>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<div style="height: 10px;"></div>
|
|
47
|
+
|
|
48
|
+
<div class="muted">Read</div>
|
|
49
|
+
<div class="row">
|
|
50
|
+
<input data-testid="read-topic" placeholder="Topic to read" />
|
|
51
|
+
<button data-testid="forum-read">Read</button>
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
<div style="height: 10px;"></div>
|
|
55
|
+
|
|
56
|
+
<div class="muted">Topics</div>
|
|
57
|
+
<div class="row">
|
|
58
|
+
<input data-testid="topics-prefix" placeholder="Prefix (optional)" />
|
|
59
|
+
<button data-testid="topics-refresh">List</button>
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
<p class="muted">
|
|
63
|
+
Topics: <span data-testid="topics-count">0</span>
|
|
64
|
+
</p>
|
|
65
|
+
|
|
66
|
+
<div class="grid" style="grid-template-columns: 1fr; gap: 10px;">
|
|
67
|
+
<div>
|
|
68
|
+
<div class="muted">Topics</div>
|
|
69
|
+
<pre data-testid="topics-json">[]</pre>
|
|
70
|
+
</div>
|
|
71
|
+
<div>
|
|
72
|
+
<div class="muted">Messages</div>
|
|
73
|
+
<pre data-testid="messages-json">[]</pre>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
<div class="card" style="margin-top: 16px;">
|
|
80
|
+
<h2>Events</h2>
|
|
81
|
+
<div class="row">
|
|
82
|
+
<button data-testid="events-refresh">Refresh</button>
|
|
83
|
+
<label style="display: flex; align-items: center; gap: 4px;">
|
|
84
|
+
<input type="checkbox" data-testid="events-auto-refresh" />
|
|
85
|
+
Auto-refresh (5s)
|
|
86
|
+
</label>
|
|
87
|
+
</div>
|
|
88
|
+
<p class="muted">
|
|
89
|
+
Events: <span data-testid="events-count">0</span>
|
|
90
|
+
</p>
|
|
91
|
+
<pre data-testid="events-json">[]</pre>
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
<div class="card" style="margin-top: 16px;">
|
|
95
|
+
<div class="muted">Errors</div>
|
|
96
|
+
<pre data-testid="errors"></pre>
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
`;function i(t){const e=T.querySelector(`[data-testid="${t}"]`);if(!e)throw new Error(`missing [data-testid=${JSON.stringify(t)}]`);return e}const u=i("status-pill"),h=i("repo-root"),f=i("errors"),m=b();m&&(i("read-topic").value=m);function v(t){if(!t){f.textContent="";return}if(t instanceof S){f.textContent=`API Error (${t.status}): ${t.message}`;return}if(t instanceof Error){f.textContent=`${t.name}: ${t.message}
|
|
100
|
+
${t.stack??""}`.trim();return}f.textContent=String(t)}async function $(){try{const t=await a.getStatus();return u.textContent=`Connected to ${window.location.host}`,u.classList.add("success"),u.classList.remove("error"),h.textContent=t.repo_root||"",!0}catch(t){return u.textContent="Connection failed",u.classList.add("error"),u.classList.remove("success"),h.textContent="",v(t),!1}}async function g(){try{const t=await a.getEventsTail(50);i("events-count").textContent=String(t.length),i("events-json").textContent=JSON.stringify(t.map(e=>({time:new Date(e.ts_ms).toLocaleTimeString(),type:e.type,source:e.source,...e.issue_id?{issue_id:e.issue_id}:{},...Object.keys(e.payload??{}).length>0?{payload:e.payload}:{}})),null,2)}catch(t){i("events-json").textContent=`Error: ${t instanceof Error?t.message:String(t)}`}}async function l(t={}){try{v(null);const[e,s,c]=await Promise.all([a.listIssues(),a.getReadyIssues(),a.getStatus()]),n=i("topics-prefix").value.trim()||void 0,r=await a.listTopics(n);i("issues-count").textContent=String(e.length),i("ready-count").textContent=String(s.length),i("topics-count").textContent=String(r.length),i("issues-json").textContent=JSON.stringify(e,null,2),i("ready-json").textContent=JSON.stringify(s,null,2),i("topics-json").textContent=JSON.stringify(r,null,2);let d=t.readTopic??i("read-topic").value.trim();if(!d){const p=b();p&&(d=p,i("read-topic").value=p)}if(d){const p=await a.readMessages(d,50);i("messages-json").textContent=JSON.stringify(p,null,2)}else i("messages-json").textContent="[]";await g()}catch(e){v(e)}}i("refresh").addEventListener("click",()=>{l()});i("topics-refresh").addEventListener("click",()=>{l()});i("forum-read").addEventListener("click",()=>{const t=i("read-topic").value.trim();E(t),l({readTopic:t})});i("create-issue").addEventListener("click",()=>{const t=i("issue-title"),e=t.value.trim();if(!e){v(new Error("issue title required"));return}(async()=>(await a.createIssue({title:e,tags:["node:agent"]}),t.value="",await l()))()});i("forum-post").addEventListener("click",()=>{const t=i("forum-topic").value.trim(),e=i("forum-author").value.trim()||"system",s=i("forum-body").value;if(!t){v(new Error("topic required"));return}(async()=>(await a.postMessage(t,s,e),E(t),i("forum-body").value="",i("read-topic").value=t,await l({readTopic:t})))()});i("events-refresh").addEventListener("click",()=>{g()});let y=null;i("events-auto-refresh").addEventListener("change",t=>{t.target.checked?y=setInterval(()=>{g()},5e3):y&&(clearInterval(y),y=null)});(async()=>await $()&&await l())();
|
package/public/index.html
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>mu</title>
|
|
7
|
-
<script type="module" crossorigin src="/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/assets/index-CxkevQNh.js"></script>
|
|
8
8
|
<link rel="stylesheet" crossorigin href="/assets/index-D_8anM-D.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
(function(){const e=document.createElement("link").relList;if(e&&e.supports&&e.supports("modulepreload"))return;for(const r of document.querySelectorAll('link[rel="modulepreload"]'))a(r);new MutationObserver(r=>{for(const o of r)if(o.type==="childList")for(const c of o.addedNodes)c.tagName==="LINK"&&c.rel==="modulepreload"&&a(c)}).observe(document,{childList:!0,subtree:!0});function s(r){const o={};return r.integrity&&(o.integrity=r.integrity),r.referrerPolicy&&(o.referrerPolicy=r.referrerPolicy),r.crossOrigin==="use-credentials"?o.credentials="include":r.crossOrigin==="anonymous"?o.credentials="omit":o.credentials="same-origin",o}function a(r){if(r.ep)return;r.ep=!0;const o=s(r);fetch(r.href,o)}})();const T="";class g extends Error{constructor(e,s){super(s),this.status=e,this.name="ApiError"}}async function n(t,e){const s=await fetch(`${T}${t}`,{...e,headers:{"Content-Type":"application/json",...e==null?void 0:e.headers}});if(!s.ok){const a=await s.text();throw new g(s.status,a||`HTTP ${s.status}`)}return s.json()}const d={async getStatus(){return n("/api/status")},async listIssues(t){const e=new URLSearchParams;t!=null&&t.status&&e.set("status",t.status),t!=null&&t.tag&&e.set("tag",t.tag);const s=e.toString();return n(`/api/issues${s?`?${s}`:""}`)},async getIssue(t){return n(`/api/issues/${t}`)},async createIssue(t){return n("/api/issues",{method:"POST",body:JSON.stringify(t)})},async updateIssue(t,e){return n(`/api/issues/${t}`,{method:"PATCH",body:JSON.stringify(e)})},async claimIssue(t){return n(`/api/issues/${t}/claim`,{method:"POST",body:JSON.stringify({})})},async closeIssue(t,e){return n(`/api/issues/${t}/close`,{method:"POST",body:JSON.stringify({outcome:e})})},async getReadyIssues(t){const e=t?`?root=${encodeURIComponent(t)}`:"";return n(`/api/issues/ready${e}`)},async postMessage(t,e,s){return n("/api/forum/post",{method:"POST",body:JSON.stringify({topic:t,body:e,author:s})})},async readMessages(t,e){const s=new URLSearchParams({topic:t});return e&&s.set("limit",String(e)),n(`/api/forum/read?${s}`)},async listTopics(t,e){const s=new URLSearchParams;t&&s.set("prefix",t),e&&s.set("limit",String(e));const a=s.toString();return n(`/api/forum/topics${a?`?${a}`:""}`)}},h="mu-web:last_topic";function S(){return typeof localStorage<"u"?localStorage:null}function w(){try{const t=S();if(!t)return null;const e=String(t.getItem(h)??"").trim();return e.length>0?e:null}catch{return null}}function x(t){try{const e=S();if(!e)return;const s=t.trim();if(!s)return;e.setItem(h,s)}catch{}}const L=document.querySelector("#app");if(!L)throw new Error("missing #app");const C=L;C.innerHTML=`
|
|
2
|
-
<div class="container">
|
|
3
|
-
<h1>mu</h1>
|
|
4
|
-
<div class="row muted">
|
|
5
|
-
<span class="pill" data-testid="status-pill">Connecting...</span>
|
|
6
|
-
<button data-testid="refresh">Refresh</button>
|
|
7
|
-
<span class="muted" data-testid="repo-root"></span>
|
|
8
|
-
</div>
|
|
9
|
-
|
|
10
|
-
<div class="grid">
|
|
11
|
-
<div class="card">
|
|
12
|
-
<h2>Issues</h2>
|
|
13
|
-
<div class="row">
|
|
14
|
-
<input data-testid="issue-title" placeholder="Issue title" />
|
|
15
|
-
<button data-testid="create-issue">Create</button>
|
|
16
|
-
</div>
|
|
17
|
-
<p class="muted">
|
|
18
|
-
All issues: <span data-testid="issues-count">0</span>
|
|
19
|
-
Ready leaves: <span data-testid="ready-count">0</span>
|
|
20
|
-
</p>
|
|
21
|
-
<div class="grid" style="grid-template-columns: 1fr; gap: 10px;">
|
|
22
|
-
<div>
|
|
23
|
-
<div class="muted">Issues</div>
|
|
24
|
-
<pre data-testid="issues-json">[]</pre>
|
|
25
|
-
</div>
|
|
26
|
-
<div>
|
|
27
|
-
<div class="muted">Ready Leaves</div>
|
|
28
|
-
<pre data-testid="ready-json">[]</pre>
|
|
29
|
-
</div>
|
|
30
|
-
</div>
|
|
31
|
-
</div>
|
|
32
|
-
|
|
33
|
-
<div class="card">
|
|
34
|
-
<h2>Forum</h2>
|
|
35
|
-
|
|
36
|
-
<div class="muted">Post</div>
|
|
37
|
-
<div class="row">
|
|
38
|
-
<input data-testid="forum-topic" placeholder="Topic (e.g. issue:mu-123)" />
|
|
39
|
-
<input data-testid="forum-author" placeholder="Author" value="worker" />
|
|
40
|
-
</div>
|
|
41
|
-
<textarea data-testid="forum-body" placeholder="Message body"></textarea>
|
|
42
|
-
<div class="row">
|
|
43
|
-
<button data-testid="forum-post">Post</button>
|
|
44
|
-
</div>
|
|
45
|
-
|
|
46
|
-
<div style="height: 10px;"></div>
|
|
47
|
-
|
|
48
|
-
<div class="muted">Read</div>
|
|
49
|
-
<div class="row">
|
|
50
|
-
<input data-testid="read-topic" placeholder="Topic to read" />
|
|
51
|
-
<button data-testid="forum-read">Read</button>
|
|
52
|
-
</div>
|
|
53
|
-
|
|
54
|
-
<div style="height: 10px;"></div>
|
|
55
|
-
|
|
56
|
-
<div class="muted">Topics</div>
|
|
57
|
-
<div class="row">
|
|
58
|
-
<input data-testid="topics-prefix" placeholder="Prefix (optional)" />
|
|
59
|
-
<button data-testid="topics-refresh">List</button>
|
|
60
|
-
</div>
|
|
61
|
-
|
|
62
|
-
<p class="muted">
|
|
63
|
-
Topics: <span data-testid="topics-count">0</span>
|
|
64
|
-
</p>
|
|
65
|
-
|
|
66
|
-
<div class="grid" style="grid-template-columns: 1fr; gap: 10px;">
|
|
67
|
-
<div>
|
|
68
|
-
<div class="muted">Topics</div>
|
|
69
|
-
<pre data-testid="topics-json">[]</pre>
|
|
70
|
-
</div>
|
|
71
|
-
<div>
|
|
72
|
-
<div class="muted">Messages</div>
|
|
73
|
-
<pre data-testid="messages-json">[]</pre>
|
|
74
|
-
</div>
|
|
75
|
-
</div>
|
|
76
|
-
</div>
|
|
77
|
-
</div>
|
|
78
|
-
|
|
79
|
-
<div class="card" style="margin-top: 16px;">
|
|
80
|
-
<div class="muted">Errors</div>
|
|
81
|
-
<pre data-testid="errors"></pre>
|
|
82
|
-
</div>
|
|
83
|
-
</div>
|
|
84
|
-
`;function i(t){const e=C.querySelector(`[data-testid="${t}"]`);if(!e)throw new Error(`missing [data-testid=${JSON.stringify(t)}]`);return e}const u=i("status-pill"),y=i("repo-root"),v=i("errors"),m=w();m&&(i("read-topic").value=m);function f(t){if(!t){v.textContent="";return}if(t instanceof g){v.textContent=`API Error (${t.status}): ${t.message}`;return}if(t instanceof Error){v.textContent=`${t.name}: ${t.message}
|
|
85
|
-
${t.stack??""}`.trim();return}v.textContent=String(t)}async function b(){try{const t=await d.getStatus();return u.textContent=`Connected to ${window.location.host}`,u.classList.add("success"),u.classList.remove("error"),y.textContent=t.repo_root||"",!0}catch(t){return u.textContent="Connection failed",u.classList.add("error"),u.classList.remove("success"),y.textContent="",f(t),!1}}async function l(t={}){try{f(null);const[e,s,a]=await Promise.all([d.listIssues(),d.getReadyIssues(),d.getStatus()]),r=i("topics-prefix").value.trim()||void 0,o=await d.listTopics(r);i("issues-count").textContent=String(e.length),i("ready-count").textContent=String(s.length),i("topics-count").textContent=String(o.length),i("issues-json").textContent=JSON.stringify(e,null,2),i("ready-json").textContent=JSON.stringify(s,null,2),i("topics-json").textContent=JSON.stringify(o,null,2);let c=t.readTopic??i("read-topic").value.trim();if(!c){const p=w();p&&(c=p,i("read-topic").value=p)}if(c){const p=await d.readMessages(c,50);i("messages-json").textContent=JSON.stringify(p,null,2)}else i("messages-json").textContent="[]"}catch(e){f(e)}}i("refresh").addEventListener("click",()=>{l()});i("topics-refresh").addEventListener("click",()=>{l()});i("forum-read").addEventListener("click",()=>{const t=i("read-topic").value.trim();x(t),l({readTopic:t})});i("create-issue").addEventListener("click",()=>{const t=i("issue-title"),e=t.value.trim();if(!e){f(new Error("issue title required"));return}(async()=>(await d.createIssue({title:e,tags:["node:agent"]}),t.value="",await l()))()});i("forum-post").addEventListener("click",()=>{const t=i("forum-topic").value.trim(),e=i("forum-author").value.trim()||"system",s=i("forum-body").value;if(!t){f(new Error("topic required"));return}(async()=>(await d.postMessage(t,s,e),x(t),i("forum-body").value="",i("read-topic").value=t,await l({readTopic:t})))()});(async()=>await b()&&await l())();
|