@femtomc/mu-server 26.2.73 → 26.2.74
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 +54 -66
- package/dist/api/control_plane.js +56 -0
- package/dist/api/cron.js +2 -23
- package/dist/api/heartbeats.js +1 -66
- package/dist/api/identities.js +3 -2
- package/dist/api/runs.js +0 -83
- package/dist/api/session_flash.d.ts +60 -0
- package/dist/api/session_flash.js +326 -0
- package/dist/api/session_turn.d.ts +38 -0
- package/dist/api/session_turn.js +423 -0
- package/dist/config.d.ts +9 -4
- package/dist/config.js +24 -24
- package/dist/control_plane.d.ts +2 -16
- package/dist/control_plane.js +57 -83
- package/dist/control_plane_adapter_registry.d.ts +19 -0
- package/dist/control_plane_adapter_registry.js +74 -0
- package/dist/control_plane_contract.d.ts +1 -7
- package/dist/control_plane_run_queue_coordinator.d.ts +1 -7
- package/dist/control_plane_run_queue_coordinator.js +1 -62
- package/dist/control_plane_telegram_generation.js +1 -0
- package/dist/control_plane_wake_delivery.js +1 -0
- package/dist/cron_programs.d.ts +21 -35
- package/dist/cron_programs.js +32 -113
- package/dist/cron_request.d.ts +0 -6
- package/dist/cron_request.js +0 -41
- package/dist/heartbeat_programs.d.ts +20 -35
- package/dist/heartbeat_programs.js +26 -122
- package/dist/index.d.ts +2 -2
- package/dist/outbound_delivery_router.d.ts +12 -0
- package/dist/outbound_delivery_router.js +29 -0
- package/dist/run_supervisor.d.ts +1 -16
- package/dist/run_supervisor.js +0 -70
- package/dist/server.d.ts +0 -5
- package/dist/server.js +95 -127
- package/dist/server_program_orchestration.d.ts +4 -19
- package/dist/server_program_orchestration.js +49 -200
- package/dist/server_routing.d.ts +0 -9
- package/dist/server_routing.js +19 -654
- package/dist/server_runtime.js +0 -1
- package/dist/server_types.d.ts +0 -2
- package/dist/server_types.js +0 -7
- package/package.json +6 -9
- package/dist/api/context.d.ts +0 -5
- package/dist/api/context.js +0 -1147
- package/dist/api/forum.d.ts +0 -2
- package/dist/api/forum.js +0 -75
- package/dist/api/issues.d.ts +0 -2
- package/dist/api/issues.js +0 -173
- package/public/assets/index-CxkevQNh.js +0 -100
- package/public/assets/index-D_8anM-D.css +0 -1
- package/public/index.html +0 -14
package/dist/api/forum.d.ts
DELETED
package/dist/api/forum.js
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { DEFAULT_FORUM_TOPICS_LIMIT, ForumStoreValidationError, normalizeForumPrefix, normalizeForumReadLimit, normalizeForumTopic, normalizeForumTopicsLimit, } from "@femtomc/mu-forum";
|
|
2
|
-
async function readJsonBody(request) {
|
|
3
|
-
let body;
|
|
4
|
-
try {
|
|
5
|
-
body = await request.json();
|
|
6
|
-
}
|
|
7
|
-
catch {
|
|
8
|
-
throw new ForumStoreValidationError("invalid json body");
|
|
9
|
-
}
|
|
10
|
-
if (!body || typeof body !== "object" || Array.isArray(body)) {
|
|
11
|
-
throw new ForumStoreValidationError("json body must be an object");
|
|
12
|
-
}
|
|
13
|
-
return body;
|
|
14
|
-
}
|
|
15
|
-
function errorResponse(status, message) {
|
|
16
|
-
return new Response(JSON.stringify({ error: message }), {
|
|
17
|
-
status,
|
|
18
|
-
headers: { "Content-Type": "application/json" },
|
|
19
|
-
});
|
|
20
|
-
}
|
|
21
|
-
function mapForumRouteError(error) {
|
|
22
|
-
if (error instanceof ForumStoreValidationError) {
|
|
23
|
-
return errorResponse(400, error.message);
|
|
24
|
-
}
|
|
25
|
-
if (error instanceof Error && error.name === "ZodError") {
|
|
26
|
-
return errorResponse(400, error.message);
|
|
27
|
-
}
|
|
28
|
-
console.error("Forum API error:", error);
|
|
29
|
-
return errorResponse(500, error instanceof Error ? error.message : "Internal server error");
|
|
30
|
-
}
|
|
31
|
-
export async function forumRoutes(request, context) {
|
|
32
|
-
const url = new URL(request.url);
|
|
33
|
-
const path = url.pathname.replace("/api/forum", "") || "/";
|
|
34
|
-
const method = request.method;
|
|
35
|
-
try {
|
|
36
|
-
// List topics - GET /api/forum/topics
|
|
37
|
-
if (path === "/topics" && method === "GET") {
|
|
38
|
-
const prefix = normalizeForumPrefix(url.searchParams.get("prefix"));
|
|
39
|
-
const limit = normalizeForumTopicsLimit(url.searchParams.get("limit"), {
|
|
40
|
-
defaultLimit: DEFAULT_FORUM_TOPICS_LIMIT,
|
|
41
|
-
});
|
|
42
|
-
const topics = await context.forumStore.topics(prefix, { limit });
|
|
43
|
-
return Response.json(topics);
|
|
44
|
-
}
|
|
45
|
-
// Read messages - GET /api/forum/read
|
|
46
|
-
if (path === "/read" && method === "GET") {
|
|
47
|
-
const topic = normalizeForumTopic(url.searchParams.get("topic"));
|
|
48
|
-
const limit = normalizeForumReadLimit(url.searchParams.get("limit"));
|
|
49
|
-
const messages = await context.forumStore.read(topic, limit);
|
|
50
|
-
return Response.json(messages);
|
|
51
|
-
}
|
|
52
|
-
// Post message - POST /api/forum/post
|
|
53
|
-
if (path === "/post" && method === "POST") {
|
|
54
|
-
const body = await readJsonBody(request);
|
|
55
|
-
const topic = normalizeForumTopic(body.topic);
|
|
56
|
-
if (typeof body.body !== "string" || body.body.trim().length === 0) {
|
|
57
|
-
return errorResponse(400, "body is required");
|
|
58
|
-
}
|
|
59
|
-
const messageBody = body.body;
|
|
60
|
-
let author = "system";
|
|
61
|
-
if (body.author != null) {
|
|
62
|
-
if (typeof body.author !== "string" || body.author.trim().length === 0) {
|
|
63
|
-
return errorResponse(400, "author must be a non-empty string when provided");
|
|
64
|
-
}
|
|
65
|
-
author = body.author.trim();
|
|
66
|
-
}
|
|
67
|
-
const message = await context.forumStore.post(topic, messageBody, author);
|
|
68
|
-
return Response.json(message, { status: 201 });
|
|
69
|
-
}
|
|
70
|
-
return new Response("Not Found", { status: 404 });
|
|
71
|
-
}
|
|
72
|
-
catch (error) {
|
|
73
|
-
return mapForumRouteError(error);
|
|
74
|
-
}
|
|
75
|
-
}
|
package/dist/api/issues.d.ts
DELETED
package/dist/api/issues.js
DELETED
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
import { DEFAULT_ISSUE_QUERY_LIMIT, ISSUE_STATUS_VALUES, IssueStoreNotFoundError, IssueStoreValidationError, normalizeIssueContainsFilter, normalizeIssueQueryLimit, } from "@femtomc/mu-issue";
|
|
2
|
-
const ISSUE_STATUS_SET = new Set(ISSUE_STATUS_VALUES);
|
|
3
|
-
function normalizeIssueId(value) {
|
|
4
|
-
try {
|
|
5
|
-
return decodeURIComponent(value).trim();
|
|
6
|
-
}
|
|
7
|
-
catch (cause) {
|
|
8
|
-
throw new IssueStoreValidationError("invalid issue id encoding", { cause });
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
function normalizeIssueStatusFilter(value) {
|
|
12
|
-
if (value == null) {
|
|
13
|
-
return undefined;
|
|
14
|
-
}
|
|
15
|
-
const normalized = value.trim();
|
|
16
|
-
if (normalized.length === 0) {
|
|
17
|
-
return undefined;
|
|
18
|
-
}
|
|
19
|
-
if (!ISSUE_STATUS_SET.has(normalized)) {
|
|
20
|
-
throw new IssueStoreValidationError(`invalid issue status filter: ${normalized}`);
|
|
21
|
-
}
|
|
22
|
-
return normalized;
|
|
23
|
-
}
|
|
24
|
-
async function readJsonBody(request) {
|
|
25
|
-
let body;
|
|
26
|
-
try {
|
|
27
|
-
body = await request.json();
|
|
28
|
-
}
|
|
29
|
-
catch {
|
|
30
|
-
throw new IssueStoreValidationError("invalid json body");
|
|
31
|
-
}
|
|
32
|
-
if (!body || typeof body !== "object" || Array.isArray(body)) {
|
|
33
|
-
throw new IssueStoreValidationError("json body must be an object");
|
|
34
|
-
}
|
|
35
|
-
return body;
|
|
36
|
-
}
|
|
37
|
-
function errorResponse(status, message) {
|
|
38
|
-
return new Response(JSON.stringify({ error: message }), {
|
|
39
|
-
status,
|
|
40
|
-
headers: { "Content-Type": "application/json" },
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
function mapIssueRouteError(error) {
|
|
44
|
-
if (error instanceof IssueStoreNotFoundError) {
|
|
45
|
-
return errorResponse(404, error.message);
|
|
46
|
-
}
|
|
47
|
-
if (error instanceof IssueStoreValidationError) {
|
|
48
|
-
return errorResponse(400, error.message);
|
|
49
|
-
}
|
|
50
|
-
if (error instanceof Error && error.name === "ZodError") {
|
|
51
|
-
return errorResponse(400, error.message);
|
|
52
|
-
}
|
|
53
|
-
console.error("Issue API error:", error);
|
|
54
|
-
return errorResponse(500, error instanceof Error ? error.message : "Internal server error");
|
|
55
|
-
}
|
|
56
|
-
export async function issueRoutes(request, context) {
|
|
57
|
-
const url = new URL(request.url);
|
|
58
|
-
const path = url.pathname.replace("/api/issues", "") || "/";
|
|
59
|
-
const method = request.method;
|
|
60
|
-
try {
|
|
61
|
-
// List issues - GET /api/issues
|
|
62
|
-
if (path === "/" && method === "GET") {
|
|
63
|
-
const status = normalizeIssueStatusFilter(url.searchParams.get("status"));
|
|
64
|
-
const tag = url.searchParams.get("tag")?.trim() || undefined;
|
|
65
|
-
const contains = normalizeIssueContainsFilter(url.searchParams.get("contains"));
|
|
66
|
-
const limit = normalizeIssueQueryLimit(url.searchParams.get("limit"), {
|
|
67
|
-
defaultLimit: DEFAULT_ISSUE_QUERY_LIMIT,
|
|
68
|
-
});
|
|
69
|
-
const issues = await context.issueStore.list({ status, tag, contains, limit: limit ?? undefined });
|
|
70
|
-
return Response.json(issues);
|
|
71
|
-
}
|
|
72
|
-
// Get ready issues - GET /api/issues/ready
|
|
73
|
-
if (path === "/ready" && method === "GET") {
|
|
74
|
-
const root = url.searchParams.get("root")?.trim() || undefined;
|
|
75
|
-
const contains = normalizeIssueContainsFilter(url.searchParams.get("contains"));
|
|
76
|
-
const limit = normalizeIssueQueryLimit(url.searchParams.get("limit"), {
|
|
77
|
-
defaultLimit: DEFAULT_ISSUE_QUERY_LIMIT,
|
|
78
|
-
});
|
|
79
|
-
const issues = await context.issueStore.ready(root, { contains, limit: limit ?? undefined });
|
|
80
|
-
return Response.json(issues);
|
|
81
|
-
}
|
|
82
|
-
// Get single issue - GET /api/issues/:id
|
|
83
|
-
if (path.startsWith("/") && method === "GET") {
|
|
84
|
-
const id = normalizeIssueId(path.slice(1));
|
|
85
|
-
if (id.length === 0) {
|
|
86
|
-
return errorResponse(400, "issue id is required");
|
|
87
|
-
}
|
|
88
|
-
const issue = await context.issueStore.get(id);
|
|
89
|
-
if (!issue) {
|
|
90
|
-
return errorResponse(404, "issue not found");
|
|
91
|
-
}
|
|
92
|
-
return Response.json(issue);
|
|
93
|
-
}
|
|
94
|
-
// Create issue - POST /api/issues
|
|
95
|
-
if (path === "/" && method === "POST") {
|
|
96
|
-
const body = await readJsonBody(request);
|
|
97
|
-
const title = typeof body.title === "string" ? body.title.trim() : "";
|
|
98
|
-
if (!title) {
|
|
99
|
-
return errorResponse(400, "title is required");
|
|
100
|
-
}
|
|
101
|
-
const issueBody = body.body == null ? undefined : typeof body.body === "string" ? body.body : undefined;
|
|
102
|
-
if (body.body != null && issueBody == null) {
|
|
103
|
-
return errorResponse(400, "body must be a string when provided");
|
|
104
|
-
}
|
|
105
|
-
let tags;
|
|
106
|
-
if (body.tags != null) {
|
|
107
|
-
if (!Array.isArray(body.tags) || !body.tags.every((tag) => typeof tag === "string")) {
|
|
108
|
-
return errorResponse(400, "tags must be a string[] when provided");
|
|
109
|
-
}
|
|
110
|
-
tags = body.tags.map((tag) => tag.trim());
|
|
111
|
-
}
|
|
112
|
-
let priority;
|
|
113
|
-
if (body.priority != null) {
|
|
114
|
-
if (typeof body.priority !== "number" ||
|
|
115
|
-
!Number.isFinite(body.priority) ||
|
|
116
|
-
!Number.isInteger(body.priority)) {
|
|
117
|
-
return errorResponse(400, "priority must be an integer when provided");
|
|
118
|
-
}
|
|
119
|
-
priority = body.priority;
|
|
120
|
-
}
|
|
121
|
-
const issue = await context.issueStore.create(title, {
|
|
122
|
-
body: issueBody,
|
|
123
|
-
tags,
|
|
124
|
-
priority,
|
|
125
|
-
});
|
|
126
|
-
return Response.json(issue, { status: 201 });
|
|
127
|
-
}
|
|
128
|
-
// Update issue - PATCH /api/issues/:id
|
|
129
|
-
if (path.startsWith("/") && method === "PATCH") {
|
|
130
|
-
const id = normalizeIssueId(path.slice(1));
|
|
131
|
-
if (id.length === 0) {
|
|
132
|
-
return errorResponse(400, "issue id is required");
|
|
133
|
-
}
|
|
134
|
-
const body = await readJsonBody(request);
|
|
135
|
-
const issue = await context.issueStore.update(id, body);
|
|
136
|
-
return Response.json(issue);
|
|
137
|
-
}
|
|
138
|
-
// Close issue - POST /api/issues/:id/close
|
|
139
|
-
if (path.endsWith("/close") && method === "POST") {
|
|
140
|
-
const id = normalizeIssueId(path.slice(1, -"/close".length));
|
|
141
|
-
if (id.length === 0) {
|
|
142
|
-
return errorResponse(400, "issue id is required");
|
|
143
|
-
}
|
|
144
|
-
const body = await readJsonBody(request);
|
|
145
|
-
const outcome = typeof body.outcome === "string" ? body.outcome.trim() : "";
|
|
146
|
-
if (!outcome) {
|
|
147
|
-
return errorResponse(400, "outcome is required");
|
|
148
|
-
}
|
|
149
|
-
const issue = await context.issueStore.close(id, outcome);
|
|
150
|
-
return Response.json(issue);
|
|
151
|
-
}
|
|
152
|
-
// Claim issue - POST /api/issues/:id/claim
|
|
153
|
-
if (path.endsWith("/claim") && method === "POST") {
|
|
154
|
-
const id = normalizeIssueId(path.slice(1, -"/claim".length));
|
|
155
|
-
if (id.length === 0) {
|
|
156
|
-
return errorResponse(400, "issue id is required");
|
|
157
|
-
}
|
|
158
|
-
const success = await context.issueStore.claim(id);
|
|
159
|
-
if (!success) {
|
|
160
|
-
return errorResponse(409, "failed to claim issue");
|
|
161
|
-
}
|
|
162
|
-
const issue = await context.issueStore.get(id);
|
|
163
|
-
if (!issue) {
|
|
164
|
-
return errorResponse(404, "issue not found");
|
|
165
|
-
}
|
|
166
|
-
return Response.json(issue);
|
|
167
|
-
}
|
|
168
|
-
return new Response("Not Found", { status: 404 });
|
|
169
|
-
}
|
|
170
|
-
catch (error) {
|
|
171
|
-
return mapIssueRouteError(error);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
@@ -1,100 +0,0 @@
|
|
|
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())();
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
:root{color-scheme:light;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;line-height:1.4}body{margin:0;padding:24px;background:#0b0f14;color:#e8edf2}a{color:inherit}.container{max-width:980px;margin:0 auto}h1{font-size:20px;font-weight:700;margin:0 0 12px}.muted{color:#a6b4c2}.grid{display:grid;grid-template-columns:1fr;gap:16px;margin-top:16px}@media (min-width: 900px){.grid{grid-template-columns:1fr 1fr}}.card{border:1px solid #1f2a35;background:#0e141b;border-radius:10px;padding:14px}.row{display:flex;flex-wrap:wrap;gap:8px;align-items:center}input,textarea{width:100%;box-sizing:border-box;background:#0b0f14;color:#e8edf2;border:1px solid #233041;border-radius:8px;padding:10px}textarea{min-height:90px;resize:vertical}button{cursor:pointer;border:1px solid #2b3a4c;background:#111a24;color:#e8edf2;border-radius:8px;padding:10px 12px}button:hover{background:#132030}pre{margin:0;padding:10px;background:#0b0f14;border:1px solid #233041;border-radius:8px;overflow:auto}.pill{display:inline-block;font-size:12px;padding:3px 8px;border-radius:999px;border:1px solid #2b3a4c;color:#a6b4c2}.pill.success{background:#0d3b16;border-color:#1e5c2f;color:#5cb85c}.pill.error{background:#3b0d16;border-color:#5c1e2f;color:#d9534f}
|
package/public/index.html
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
<!doctype html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8" />
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
-
<title>mu</title>
|
|
7
|
-
<script type="module" crossorigin src="/assets/index-CxkevQNh.js"></script>
|
|
8
|
-
<link rel="stylesheet" crossorigin href="/assets/index-D_8anM-D.css">
|
|
9
|
-
</head>
|
|
10
|
-
<body>
|
|
11
|
-
<div id="app"></div>
|
|
12
|
-
</body>
|
|
13
|
-
</html>
|
|
14
|
-
|