@chenbihao/pomasa-dashboard 1.0.0
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 +73 -0
- package/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/dist/assets/index-CDTmjyCs.css +1 -0
- package/dist/assets/index-CjZHJUM4.js +480 -0
- package/dist/cli.js +1045 -0
- package/dist/favicon.svg +1 -0
- package/dist/icons.svg +24 -0
- package/dist/index.html +14 -0
- package/package.json +88 -0
- package/templates/user_input_template.md +118 -0
- package/templates/user_input_template_zh.md +141 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,1045 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
5
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
6
|
+
}) : x)(function(x) {
|
|
7
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
8
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
9
|
+
});
|
|
10
|
+
var __esm = (fn, res) => function __init() {
|
|
11
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
12
|
+
};
|
|
13
|
+
var __export = (target, all) => {
|
|
14
|
+
for (var name in all)
|
|
15
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// server/utils/paths.ts
|
|
19
|
+
var paths_exports = {};
|
|
20
|
+
__export(paths_exports, {
|
|
21
|
+
assertPathInsideBase: () => assertPathInsideBase,
|
|
22
|
+
assertSafeMasName: () => assertSafeMasName,
|
|
23
|
+
assertSafeProjectName: () => assertSafeProjectName,
|
|
24
|
+
assertValidWorkdir: () => assertValidWorkdir
|
|
25
|
+
});
|
|
26
|
+
import path from "path";
|
|
27
|
+
function assertPathInsideBase(filePath, baseDir) {
|
|
28
|
+
const resolved = path.resolve(filePath);
|
|
29
|
+
const base = path.resolve(baseDir);
|
|
30
|
+
if (!(resolved === base || resolved.startsWith(base + path.sep))) {
|
|
31
|
+
throw new Error("Path traversal detected");
|
|
32
|
+
}
|
|
33
|
+
return resolved;
|
|
34
|
+
}
|
|
35
|
+
function assertSafeProjectName(name) {
|
|
36
|
+
if (!name || name.includes("..") || name.includes("/") || name.includes("\\")) {
|
|
37
|
+
throw new Error("Invalid project name");
|
|
38
|
+
}
|
|
39
|
+
return name;
|
|
40
|
+
}
|
|
41
|
+
function assertSafeMasName(name) {
|
|
42
|
+
if (!name || name.includes("..") || name.includes("/") || name.includes("\\")) {
|
|
43
|
+
throw new Error("MAS name contains invalid characters");
|
|
44
|
+
}
|
|
45
|
+
return name;
|
|
46
|
+
}
|
|
47
|
+
function assertValidWorkdir(workdir) {
|
|
48
|
+
if (!workdir) throw new Error("Missing workdir parameter");
|
|
49
|
+
if (!path.isAbsolute(workdir)) throw new Error("workdir must be an absolute path");
|
|
50
|
+
return workdir;
|
|
51
|
+
}
|
|
52
|
+
var init_paths = __esm({
|
|
53
|
+
"server/utils/paths.ts"() {
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// server/setup.ts
|
|
58
|
+
import express from "express";
|
|
59
|
+
import cors from "cors";
|
|
60
|
+
import path7 from "path";
|
|
61
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
62
|
+
import { createServer } from "http";
|
|
63
|
+
|
|
64
|
+
// server/websocket/terminal.ts
|
|
65
|
+
import { WebSocketServer, WebSocket } from "ws";
|
|
66
|
+
var ptyModule = null;
|
|
67
|
+
var ptyAvailable = false;
|
|
68
|
+
try {
|
|
69
|
+
ptyModule = await import("node-pty");
|
|
70
|
+
ptyAvailable = true;
|
|
71
|
+
} catch {
|
|
72
|
+
console.warn("[terminal] node-pty not available, falling back to child_process.spawn");
|
|
73
|
+
console.warn("[terminal] Interactive programs (vim, htop, etc.) and resize will not work");
|
|
74
|
+
}
|
|
75
|
+
function spawnWithPty(ws, cwd, cols, rows) {
|
|
76
|
+
const isWin = process.platform === "win32";
|
|
77
|
+
const shell = isWin ? "powershell.exe" : "bash";
|
|
78
|
+
const shellArgs = isWin ? [] : ["--login"];
|
|
79
|
+
const ptyProc = ptyModule.spawn(shell, shellArgs, {
|
|
80
|
+
name: "xterm-256color",
|
|
81
|
+
cols: cols || 80,
|
|
82
|
+
rows: rows || 24,
|
|
83
|
+
cwd,
|
|
84
|
+
env: process.env
|
|
85
|
+
});
|
|
86
|
+
ptyProc.onData((data) => {
|
|
87
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
88
|
+
ws.send(JSON.stringify({ type: "output", data }));
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
ptyProc.onExit(({ exitCode }) => {
|
|
92
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
93
|
+
ws.send(JSON.stringify({ type: "exit", code: exitCode }));
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
return {
|
|
97
|
+
write: (data) => ptyProc.write(data),
|
|
98
|
+
resize: (cols2, rows2) => ptyProc.resize(cols2, rows2),
|
|
99
|
+
kill: () => ptyProc.kill(),
|
|
100
|
+
pid: ptyProc.pid
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function spawnWithChildProcess(ws, cwd) {
|
|
104
|
+
const { spawn } = __require("child_process");
|
|
105
|
+
const isWin = process.platform === "win32";
|
|
106
|
+
const shell = isWin ? "powershell.exe" : "bash";
|
|
107
|
+
const shellArgs = isWin ? [] : ["--login"];
|
|
108
|
+
const child = spawn(shell, shellArgs, {
|
|
109
|
+
cwd,
|
|
110
|
+
env: process.env,
|
|
111
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
112
|
+
});
|
|
113
|
+
child.stdout?.on("data", (data) => {
|
|
114
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
115
|
+
ws.send(JSON.stringify({ type: "output", data: data.toString() }));
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
child.stderr?.on("data", (data) => {
|
|
119
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
120
|
+
ws.send(JSON.stringify({ type: "output", data: data.toString() }));
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
child.on("error", (err) => {
|
|
124
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
125
|
+
ws.send(JSON.stringify({ type: "output", data: `\r
|
|
126
|
+
[Error] ${err.message}\r
|
|
127
|
+
` }));
|
|
128
|
+
ws.send(JSON.stringify({ type: "exit", code: 1 }));
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
child.on("exit", (code) => {
|
|
132
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
133
|
+
ws.send(JSON.stringify({ type: "exit", code: code ?? 0 }));
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
return {
|
|
137
|
+
write: (data) => child.stdin?.write(data),
|
|
138
|
+
resize: (_cols, _rows) => {
|
|
139
|
+
},
|
|
140
|
+
kill: () => child.kill(),
|
|
141
|
+
pid: child.pid ?? -1
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
function spawnShell(ws, cwd, cols, rows) {
|
|
145
|
+
if (ptyAvailable && ptyModule) {
|
|
146
|
+
return spawnWithPty(ws, cwd, cols, rows);
|
|
147
|
+
}
|
|
148
|
+
return spawnWithChildProcess(ws, cwd);
|
|
149
|
+
}
|
|
150
|
+
function setupTerminalWebSocket(server) {
|
|
151
|
+
const wss = new WebSocketServer({ server, path: "/api/terminal" });
|
|
152
|
+
wss.on("connection", (ws) => {
|
|
153
|
+
let shellProcess = null;
|
|
154
|
+
ws.on("message", (data) => {
|
|
155
|
+
try {
|
|
156
|
+
const msg = JSON.parse(data.toString());
|
|
157
|
+
if (msg.type === "start") {
|
|
158
|
+
const cwd = msg.cwd || process.cwd();
|
|
159
|
+
const cols = msg.cols || 80;
|
|
160
|
+
const rows = msg.rows || 24;
|
|
161
|
+
shellProcess = spawnShell(ws, cwd, cols, rows);
|
|
162
|
+
ws.send(JSON.stringify({
|
|
163
|
+
type: "info",
|
|
164
|
+
pty: ptyAvailable,
|
|
165
|
+
pid: shellProcess.pid
|
|
166
|
+
}));
|
|
167
|
+
} else if (msg.type === "input" && shellProcess) {
|
|
168
|
+
shellProcess.write(msg.data);
|
|
169
|
+
} else if (msg.type === "resize" && shellProcess) {
|
|
170
|
+
shellProcess.resize(msg.cols, msg.rows);
|
|
171
|
+
} else if (msg.type === "kill" && shellProcess) {
|
|
172
|
+
shellProcess.kill();
|
|
173
|
+
}
|
|
174
|
+
} catch (err) {
|
|
175
|
+
console.error("WebSocket error:", err);
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
ws.on("close", () => {
|
|
179
|
+
if (shellProcess) {
|
|
180
|
+
shellProcess.kill();
|
|
181
|
+
shellProcess = null;
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
return wss;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// server/routes/projects.ts
|
|
189
|
+
init_paths();
|
|
190
|
+
import { Router as Router2 } from "express";
|
|
191
|
+
import path4 from "path";
|
|
192
|
+
import fs3 from "fs/promises";
|
|
193
|
+
|
|
194
|
+
// server/services/projectScanner.ts
|
|
195
|
+
import path2 from "path";
|
|
196
|
+
import fs from "fs/promises";
|
|
197
|
+
async function countExpectedAgents(projectPath) {
|
|
198
|
+
try {
|
|
199
|
+
const orchPath = path2.join(projectPath, "agents", "00.orchestrator.md");
|
|
200
|
+
const content = await fs.readFile(orchPath, "utf-8");
|
|
201
|
+
const match = content.match(/--stages\s+'([^']+)'/);
|
|
202
|
+
if (match) {
|
|
203
|
+
return match[1].split(",").filter((s) => s.trim()).length;
|
|
204
|
+
}
|
|
205
|
+
} catch {
|
|
206
|
+
}
|
|
207
|
+
return 0;
|
|
208
|
+
}
|
|
209
|
+
async function countAgentFiles(projectPath) {
|
|
210
|
+
try {
|
|
211
|
+
const agentsDir = path2.join(projectPath, "agents");
|
|
212
|
+
const entries = await fs.readdir(agentsDir);
|
|
213
|
+
return entries.filter((f) => f.endsWith(".md") && f !== "00.orchestrator.md").length;
|
|
214
|
+
} catch {
|
|
215
|
+
}
|
|
216
|
+
return 0;
|
|
217
|
+
}
|
|
218
|
+
async function detectPrepStages(projectPath) {
|
|
219
|
+
let s01done = false, s01ts = null;
|
|
220
|
+
try {
|
|
221
|
+
const stat = await fs.stat(path2.join(projectPath, "user_input_template.md"));
|
|
222
|
+
s01done = true;
|
|
223
|
+
s01ts = stat.mtime.toISOString();
|
|
224
|
+
} catch {
|
|
225
|
+
}
|
|
226
|
+
let blueprintState = "pending";
|
|
227
|
+
let blueprintTs = null;
|
|
228
|
+
let blueprintDetail;
|
|
229
|
+
let orchExists = false;
|
|
230
|
+
try {
|
|
231
|
+
const stat = await fs.stat(path2.join(projectPath, "agents", "00.orchestrator.md"));
|
|
232
|
+
orchExists = true;
|
|
233
|
+
blueprintTs = stat.mtime.toISOString();
|
|
234
|
+
const [actual, expected] = await Promise.all([
|
|
235
|
+
countAgentFiles(projectPath),
|
|
236
|
+
countExpectedAgents(projectPath)
|
|
237
|
+
]);
|
|
238
|
+
if (expected > 0) {
|
|
239
|
+
blueprintDetail = `${actual}/${expected}`;
|
|
240
|
+
blueprintState = actual >= expected ? "done" : "running";
|
|
241
|
+
} else {
|
|
242
|
+
blueprintState = actual > 0 ? "running" : "pending";
|
|
243
|
+
if (actual > 0) blueprintDetail = `${actual}`;
|
|
244
|
+
}
|
|
245
|
+
} catch {
|
|
246
|
+
}
|
|
247
|
+
let s04done = false, s04ts = null;
|
|
248
|
+
try {
|
|
249
|
+
const stat = await fs.stat(path2.join(projectPath, "README.md"));
|
|
250
|
+
s04done = true;
|
|
251
|
+
s04ts = stat.mtime.toISOString();
|
|
252
|
+
} catch {
|
|
253
|
+
}
|
|
254
|
+
return [
|
|
255
|
+
{ id: "0.1", labelKey: "pipeline.createTemplate", state: s01done ? "done" : "pending", ts: s01ts },
|
|
256
|
+
{
|
|
257
|
+
id: "0.2",
|
|
258
|
+
labelKey: blueprintState === "done" ? "pipeline.createBlueprintDone" : "pipeline.createBlueprint",
|
|
259
|
+
state: blueprintState === "done" ? "done" : orchExists ? "running" : "pending",
|
|
260
|
+
ts: blueprintTs,
|
|
261
|
+
detail: blueprintDetail
|
|
262
|
+
},
|
|
263
|
+
{ id: "0.3", labelKey: "pipeline.structureCreated", state: s04done ? "done" : "pending", ts: s04ts }
|
|
264
|
+
];
|
|
265
|
+
}
|
|
266
|
+
async function scanProject(name, projectPath, obsPath) {
|
|
267
|
+
let status = "unknown";
|
|
268
|
+
let orchStages = 0;
|
|
269
|
+
let orchCompleted = 0;
|
|
270
|
+
let lastUpdate = null;
|
|
271
|
+
let hasAlerts = false;
|
|
272
|
+
let instance = null;
|
|
273
|
+
let prepDetail;
|
|
274
|
+
let hasObservation = false;
|
|
275
|
+
const PREP_STEPS = 3;
|
|
276
|
+
let prepDone = 0;
|
|
277
|
+
try {
|
|
278
|
+
await fs.access(path2.join(projectPath, "user_input_template.md"));
|
|
279
|
+
prepDone++;
|
|
280
|
+
} catch {
|
|
281
|
+
}
|
|
282
|
+
let blueprintComplete = false;
|
|
283
|
+
try {
|
|
284
|
+
await fs.access(path2.join(projectPath, "agents", "00.orchestrator.md"));
|
|
285
|
+
const [actual, expected] = await Promise.all([
|
|
286
|
+
countAgentFiles(projectPath),
|
|
287
|
+
countExpectedAgents(projectPath)
|
|
288
|
+
]);
|
|
289
|
+
if (expected > 0) {
|
|
290
|
+
prepDetail = `${actual}/${expected}`;
|
|
291
|
+
blueprintComplete = actual >= expected;
|
|
292
|
+
} else {
|
|
293
|
+
blueprintComplete = actual > 0;
|
|
294
|
+
if (actual > 0) prepDetail = `${actual}`;
|
|
295
|
+
}
|
|
296
|
+
} catch {
|
|
297
|
+
}
|
|
298
|
+
if (blueprintComplete) prepDone++;
|
|
299
|
+
try {
|
|
300
|
+
await fs.access(path2.join(projectPath, "README.md"));
|
|
301
|
+
prepDone++;
|
|
302
|
+
} catch {
|
|
303
|
+
}
|
|
304
|
+
const prepAllDone = prepDone >= PREP_STEPS;
|
|
305
|
+
try {
|
|
306
|
+
await fs.access(obsPath);
|
|
307
|
+
hasObservation = true;
|
|
308
|
+
const obsEntries = await fs.readdir(obsPath, { withFileTypes: true });
|
|
309
|
+
const instanceDir = obsEntries.find((e) => e.isDirectory() && !e.name.startsWith("."));
|
|
310
|
+
if (instanceDir) {
|
|
311
|
+
instance = instanceDir.name;
|
|
312
|
+
const instancePath = path2.join(obsPath, instanceDir.name);
|
|
313
|
+
try {
|
|
314
|
+
const manifestContent = await fs.readFile(path2.join(instancePath, "run_manifest.json"), "utf-8");
|
|
315
|
+
const manifest = JSON.parse(manifestContent);
|
|
316
|
+
orchStages = manifest.stages.length;
|
|
317
|
+
const assignedDir = path2.join(instancePath, "00.orchestrator", "assigned");
|
|
318
|
+
try {
|
|
319
|
+
const assignedFiles = await fs.readdir(assignedDir);
|
|
320
|
+
for (const stage of manifest.stages) {
|
|
321
|
+
const statusFile = `${stage.id.replace(/^\d+\./, "")}.json`;
|
|
322
|
+
const matchFile = assignedFiles.find((f) => f === statusFile || f === `${stage.id}.json`);
|
|
323
|
+
if (matchFile) {
|
|
324
|
+
try {
|
|
325
|
+
const sc = await fs.readFile(path2.join(assignedDir, matchFile), "utf-8");
|
|
326
|
+
const sd = JSON.parse(sc);
|
|
327
|
+
if (sd.state === "done" || sd.state === "completed") orchCompleted++;
|
|
328
|
+
if (sd.state === "failed") status = "failed";
|
|
329
|
+
if (sd.ts > (lastUpdate ?? "")) lastUpdate = sd.ts;
|
|
330
|
+
} catch {
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
} catch {
|
|
335
|
+
}
|
|
336
|
+
try {
|
|
337
|
+
const runLog = path2.join(instancePath, "00.orchestrator", "run.jsonl");
|
|
338
|
+
const content = await fs.readFile(runLog, "utf-8");
|
|
339
|
+
for (const line of content.split("\n")) {
|
|
340
|
+
if (!line.trim()) continue;
|
|
341
|
+
try {
|
|
342
|
+
const entry = JSON.parse(line);
|
|
343
|
+
if (entry.level === "WARN" || entry.level === "ERROR") {
|
|
344
|
+
hasAlerts = true;
|
|
345
|
+
break;
|
|
346
|
+
}
|
|
347
|
+
} catch {
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
} catch {
|
|
351
|
+
}
|
|
352
|
+
} catch {
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
} catch {
|
|
356
|
+
}
|
|
357
|
+
let manifestExists = false;
|
|
358
|
+
try {
|
|
359
|
+
const obsEntries = await fs.readdir(obsPath, { withFileTypes: true });
|
|
360
|
+
const instanceDir = obsEntries.find((e) => e.isDirectory() && !e.name.startsWith("."));
|
|
361
|
+
if (instanceDir) {
|
|
362
|
+
await fs.access(path2.join(obsPath, instanceDir.name, "run_manifest.json"));
|
|
363
|
+
manifestExists = true;
|
|
364
|
+
}
|
|
365
|
+
} catch {
|
|
366
|
+
}
|
|
367
|
+
const totalStages = manifestExists ? 1 + orchStages : 1;
|
|
368
|
+
const completedStages = manifestExists ? 1 + orchCompleted : 0;
|
|
369
|
+
const progress = totalStages > 0 ? completedStages / totalStages : 0;
|
|
370
|
+
if (status === "failed") {
|
|
371
|
+
} else if (manifestExists && completedStages === totalStages && totalStages > 1) {
|
|
372
|
+
status = "completed";
|
|
373
|
+
} else if (manifestExists && orchCompleted > 0) {
|
|
374
|
+
status = "running";
|
|
375
|
+
} else if (manifestExists) {
|
|
376
|
+
status = "running";
|
|
377
|
+
} else if (prepAllDone) {
|
|
378
|
+
status = "ready";
|
|
379
|
+
} else if (prepDone > 0) {
|
|
380
|
+
status = "preparing";
|
|
381
|
+
} else {
|
|
382
|
+
status = "pending";
|
|
383
|
+
}
|
|
384
|
+
return {
|
|
385
|
+
name,
|
|
386
|
+
path: projectPath,
|
|
387
|
+
hasObservation,
|
|
388
|
+
status,
|
|
389
|
+
progress,
|
|
390
|
+
stages: totalStages,
|
|
391
|
+
stagesCompleted: completedStages,
|
|
392
|
+
lastUpdate,
|
|
393
|
+
hasAlerts,
|
|
394
|
+
instance,
|
|
395
|
+
prepDetail
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
async function findInstanceDir(workdir, projectName) {
|
|
399
|
+
const { assertValidWorkdir: assertValidWorkdir2, assertSafeProjectName: assertSafeProjectName2 } = await Promise.resolve().then(() => (init_paths(), paths_exports));
|
|
400
|
+
const validWorkdir = assertValidWorkdir2(workdir);
|
|
401
|
+
const safeName = assertSafeProjectName2(projectName);
|
|
402
|
+
const projectPath = path2.join(validWorkdir, safeName);
|
|
403
|
+
const obsPath = path2.join(projectPath, "_observation");
|
|
404
|
+
const obsEntries = await fs.readdir(obsPath, { withFileTypes: true });
|
|
405
|
+
const instanceDir = obsEntries.find((e) => e.isDirectory() && !e.name.startsWith("."));
|
|
406
|
+
if (!instanceDir) return null;
|
|
407
|
+
return {
|
|
408
|
+
projectPath,
|
|
409
|
+
obsPath,
|
|
410
|
+
instancePath: path2.join(obsPath, instanceDir.name),
|
|
411
|
+
instanceName: instanceDir.name
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// server/routes/filesystem.ts
|
|
416
|
+
init_paths();
|
|
417
|
+
import { Router } from "express";
|
|
418
|
+
import path3 from "path";
|
|
419
|
+
import fs2 from "fs/promises";
|
|
420
|
+
var router = Router();
|
|
421
|
+
async function buildFileTree(dirPath, maxDepth = 3, currentDepth = 0) {
|
|
422
|
+
if (currentDepth >= maxDepth) return [];
|
|
423
|
+
const entries = await fs2.readdir(dirPath, { withFileTypes: true });
|
|
424
|
+
const nodes = [];
|
|
425
|
+
const sortedEntries = entries.sort((a, b) => {
|
|
426
|
+
if (a.isDirectory() && !b.isDirectory()) return -1;
|
|
427
|
+
if (!a.isDirectory() && b.isDirectory()) return 1;
|
|
428
|
+
return a.name.localeCompare(b.name);
|
|
429
|
+
});
|
|
430
|
+
for (const entry of sortedEntries) {
|
|
431
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
432
|
+
const fullPath = path3.join(dirPath, entry.name);
|
|
433
|
+
const node = {
|
|
434
|
+
name: entry.name,
|
|
435
|
+
path: fullPath,
|
|
436
|
+
type: entry.isDirectory() ? "directory" : "file"
|
|
437
|
+
};
|
|
438
|
+
if (entry.isDirectory()) {
|
|
439
|
+
node.children = await buildFileTree(fullPath, maxDepth, currentDepth + 1);
|
|
440
|
+
}
|
|
441
|
+
nodes.push(node);
|
|
442
|
+
}
|
|
443
|
+
return nodes;
|
|
444
|
+
}
|
|
445
|
+
router.get("/tree", async (req, res) => {
|
|
446
|
+
const dirPath = req.query.path;
|
|
447
|
+
const depth = Math.min(Math.max(parseInt(req.query.depth) || 3, 1), 10);
|
|
448
|
+
if (!dirPath) {
|
|
449
|
+
return res.status(400).json({ error: "Missing path parameter" });
|
|
450
|
+
}
|
|
451
|
+
try {
|
|
452
|
+
const resolved = assertPathInsideBase(dirPath, dirPath);
|
|
453
|
+
const stat = await fs2.stat(resolved);
|
|
454
|
+
if (!stat.isDirectory()) {
|
|
455
|
+
return res.status(400).json({ error: "Path is not a directory" });
|
|
456
|
+
}
|
|
457
|
+
const tree = await buildFileTree(resolved, depth);
|
|
458
|
+
res.json({ tree });
|
|
459
|
+
} catch (err) {
|
|
460
|
+
const message = err instanceof Error ? err.message : "Failed to read directory";
|
|
461
|
+
res.status(400).json({ error: message });
|
|
462
|
+
}
|
|
463
|
+
});
|
|
464
|
+
router.get("/file", async (req, res) => {
|
|
465
|
+
const filePath = req.query.path;
|
|
466
|
+
if (!filePath) {
|
|
467
|
+
return res.status(400).json({ error: "Missing path parameter" });
|
|
468
|
+
}
|
|
469
|
+
try {
|
|
470
|
+
const resolved = assertPathInsideBase(filePath, filePath);
|
|
471
|
+
const content = await fs2.readFile(resolved, "utf-8");
|
|
472
|
+
res.json({ content });
|
|
473
|
+
} catch (err) {
|
|
474
|
+
const message = err instanceof Error ? err.message : "Failed to read file";
|
|
475
|
+
res.status(400).json({ error: message });
|
|
476
|
+
}
|
|
477
|
+
});
|
|
478
|
+
router.get("/changes", async (req, res) => {
|
|
479
|
+
const dirPath = req.query.path;
|
|
480
|
+
if (!dirPath) {
|
|
481
|
+
return res.status(400).json({ error: "Missing path parameter" });
|
|
482
|
+
}
|
|
483
|
+
try {
|
|
484
|
+
const resolved = assertPathInsideBase(dirPath, dirPath);
|
|
485
|
+
const entries = await fs2.readdir(resolved, { withFileTypes: true });
|
|
486
|
+
const fingerprints = {};
|
|
487
|
+
for (const entry of entries) {
|
|
488
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
489
|
+
try {
|
|
490
|
+
const stat = await fs2.stat(path3.join(resolved, entry.name));
|
|
491
|
+
fingerprints[entry.name] = stat.mtimeMs;
|
|
492
|
+
} catch {
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
res.json({ fingerprints });
|
|
496
|
+
} catch (err) {
|
|
497
|
+
const message = err instanceof Error ? err.message : "Failed to check changes";
|
|
498
|
+
res.status(400).json({ error: message });
|
|
499
|
+
}
|
|
500
|
+
});
|
|
501
|
+
router.get("/stat", async (req, res) => {
|
|
502
|
+
const filePath = req.query.path;
|
|
503
|
+
if (!filePath) {
|
|
504
|
+
return res.status(400).json({ error: "Missing path parameter" });
|
|
505
|
+
}
|
|
506
|
+
try {
|
|
507
|
+
const resolved = assertPathInsideBase(filePath, filePath);
|
|
508
|
+
const stat = await fs2.stat(resolved);
|
|
509
|
+
res.json({ mtimeMs: stat.mtimeMs, size: stat.size });
|
|
510
|
+
} catch (err) {
|
|
511
|
+
const message = err instanceof Error ? err.message : "Failed to stat file";
|
|
512
|
+
res.status(400).json({ error: message });
|
|
513
|
+
}
|
|
514
|
+
});
|
|
515
|
+
router.get("/dirs", async (req, res) => {
|
|
516
|
+
const dirPath = req.query.path;
|
|
517
|
+
if (!dirPath) {
|
|
518
|
+
return res.status(400).json({ error: "Missing path parameter" });
|
|
519
|
+
}
|
|
520
|
+
try {
|
|
521
|
+
const resolved = assertPathInsideBase(dirPath, dirPath);
|
|
522
|
+
const entries = await fs2.readdir(resolved, { withFileTypes: true });
|
|
523
|
+
const dirs = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => ({
|
|
524
|
+
name: e.name,
|
|
525
|
+
path: path3.join(resolved, e.name)
|
|
526
|
+
})).sort((a, b) => a.name.localeCompare(b.name));
|
|
527
|
+
res.json({ dirs });
|
|
528
|
+
} catch (err) {
|
|
529
|
+
const message = err instanceof Error ? err.message : "Failed to list directories";
|
|
530
|
+
res.status(400).json({ error: message });
|
|
531
|
+
}
|
|
532
|
+
});
|
|
533
|
+
var filesystem_default = router;
|
|
534
|
+
|
|
535
|
+
// server/routes/projects.ts
|
|
536
|
+
var router2 = Router2();
|
|
537
|
+
router2.get("/", async (req, res) => {
|
|
538
|
+
const workdir = req.query.workdir;
|
|
539
|
+
try {
|
|
540
|
+
const validWorkdir = assertValidWorkdir(workdir);
|
|
541
|
+
const entries = await fs3.readdir(validWorkdir, { withFileTypes: true });
|
|
542
|
+
const projects = [];
|
|
543
|
+
for (const entry of entries) {
|
|
544
|
+
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
545
|
+
const projectPath = path4.join(validWorkdir, entry.name);
|
|
546
|
+
const obsPath = path4.join(projectPath, "_observation");
|
|
547
|
+
const project = await scanProject(entry.name, projectPath, obsPath);
|
|
548
|
+
projects.push(project);
|
|
549
|
+
}
|
|
550
|
+
res.json({ projects });
|
|
551
|
+
} catch (err) {
|
|
552
|
+
const message = err instanceof Error ? err.message : "Failed to scan work directory";
|
|
553
|
+
res.status(400).json({ error: message });
|
|
554
|
+
}
|
|
555
|
+
});
|
|
556
|
+
router2.get("/:name/manifest", async (req, res) => {
|
|
557
|
+
const workdir = req.query.workdir;
|
|
558
|
+
try {
|
|
559
|
+
const validWorkdir = assertValidWorkdir(workdir);
|
|
560
|
+
const safeName = assertSafeProjectName(req.params.name);
|
|
561
|
+
const projectPath = path4.join(validWorkdir, safeName);
|
|
562
|
+
const prepStages = await detectPrepStages(projectPath);
|
|
563
|
+
try {
|
|
564
|
+
const info = await findInstanceDir(workdir, req.params.name);
|
|
565
|
+
if (!info) {
|
|
566
|
+
return res.json({ instance: null, created: null, stages: [], prepStages });
|
|
567
|
+
}
|
|
568
|
+
try {
|
|
569
|
+
const manifestPath = path4.join(info.instancePath, "run_manifest.json");
|
|
570
|
+
const content = await fs3.readFile(manifestPath, "utf-8");
|
|
571
|
+
const manifest = JSON.parse(content);
|
|
572
|
+
manifest.prepStages = prepStages;
|
|
573
|
+
res.json(manifest);
|
|
574
|
+
} catch {
|
|
575
|
+
res.json({ instance: info.instanceName, created: null, stages: [], prepStages });
|
|
576
|
+
}
|
|
577
|
+
} catch {
|
|
578
|
+
res.json({ instance: null, created: null, stages: [], prepStages });
|
|
579
|
+
}
|
|
580
|
+
} catch (err) {
|
|
581
|
+
const message = err instanceof Error ? err.message : "Failed to read manifest";
|
|
582
|
+
res.status(400).json({ error: message });
|
|
583
|
+
}
|
|
584
|
+
});
|
|
585
|
+
router2.get("/:name/status", async (req, res) => {
|
|
586
|
+
const workdir = req.query.workdir;
|
|
587
|
+
try {
|
|
588
|
+
assertSafeProjectName(req.params.name);
|
|
589
|
+
const info = await findInstanceDir(workdir, req.params.name);
|
|
590
|
+
if (!info) return res.status(404).json({ error: "No instance found" });
|
|
591
|
+
const agents = {};
|
|
592
|
+
const assignedDir = path4.join(info.instancePath, "00.orchestrator", "assigned");
|
|
593
|
+
try {
|
|
594
|
+
const assignedFiles = await fs3.readdir(assignedDir);
|
|
595
|
+
for (const file of assignedFiles) {
|
|
596
|
+
if (!file.endsWith(".json")) continue;
|
|
597
|
+
const key = file.replace(".json", "");
|
|
598
|
+
const content = await fs3.readFile(path4.join(assignedDir, file), "utf-8");
|
|
599
|
+
if (!agents[key]) agents[key] = { assigned: null, self: null };
|
|
600
|
+
agents[key].assigned = JSON.parse(content);
|
|
601
|
+
}
|
|
602
|
+
} catch {
|
|
603
|
+
}
|
|
604
|
+
const stageDirs = await fs3.readdir(info.instancePath, { withFileTypes: true });
|
|
605
|
+
for (const dir of stageDirs) {
|
|
606
|
+
if (!dir.isDirectory() || dir.name.startsWith(".") || dir.name === "00.orchestrator" || dir.name === "_fallback") continue;
|
|
607
|
+
const statusFile = path4.join(info.instancePath, dir.name, "status.json");
|
|
608
|
+
try {
|
|
609
|
+
const content = await fs3.readFile(statusFile, "utf-8");
|
|
610
|
+
if (!agents[dir.name]) agents[dir.name] = { assigned: null, self: null };
|
|
611
|
+
agents[dir.name].self = JSON.parse(content);
|
|
612
|
+
} catch {
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
res.json({ agents });
|
|
616
|
+
} catch (err) {
|
|
617
|
+
const message = err instanceof Error ? err.message : "Failed to read status";
|
|
618
|
+
res.status(400).json({ error: message });
|
|
619
|
+
}
|
|
620
|
+
});
|
|
621
|
+
router2.get("/:name/events", async (req, res) => {
|
|
622
|
+
const workdir = req.query.workdir;
|
|
623
|
+
try {
|
|
624
|
+
assertSafeProjectName(req.params.name);
|
|
625
|
+
const info = await findInstanceDir(workdir, req.params.name);
|
|
626
|
+
if (!info) return res.status(404).json({ error: "No instance found" });
|
|
627
|
+
const events = [];
|
|
628
|
+
const runLog = path4.join(info.instancePath, "00.orchestrator", "run.jsonl");
|
|
629
|
+
try {
|
|
630
|
+
const content = await fs3.readFile(runLog, "utf-8");
|
|
631
|
+
for (const line of content.split("\n")) {
|
|
632
|
+
if (line.trim()) {
|
|
633
|
+
try {
|
|
634
|
+
events.push(JSON.parse(line));
|
|
635
|
+
} catch {
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
} catch {
|
|
640
|
+
}
|
|
641
|
+
const stageDirs = await fs3.readdir(info.instancePath, { withFileTypes: true });
|
|
642
|
+
for (const dir of stageDirs) {
|
|
643
|
+
if (!dir.isDirectory() || dir.name.startsWith(".") || dir.name === "00.orchestrator") continue;
|
|
644
|
+
const logFile = path4.join(info.instancePath, dir.name, "_log.jsonl");
|
|
645
|
+
try {
|
|
646
|
+
const content = await fs3.readFile(logFile, "utf-8");
|
|
647
|
+
for (const line of content.split("\n")) {
|
|
648
|
+
if (line.trim()) {
|
|
649
|
+
try {
|
|
650
|
+
events.push(JSON.parse(line));
|
|
651
|
+
} catch {
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
} catch {
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
events.sort((a, b) => {
|
|
659
|
+
const tsA = a.ts || "";
|
|
660
|
+
const tsB = b.ts || "";
|
|
661
|
+
return tsA.localeCompare(tsB);
|
|
662
|
+
});
|
|
663
|
+
res.json({ events });
|
|
664
|
+
} catch (err) {
|
|
665
|
+
const message = err instanceof Error ? err.message : "Failed to read events";
|
|
666
|
+
res.status(400).json({ error: message });
|
|
667
|
+
}
|
|
668
|
+
});
|
|
669
|
+
router2.get("/:name/logs/:agentKey", async (req, res) => {
|
|
670
|
+
const workdir = req.query.workdir;
|
|
671
|
+
const agentKey = req.params.agentKey;
|
|
672
|
+
try {
|
|
673
|
+
assertSafeProjectName(req.params.name);
|
|
674
|
+
const info = await findInstanceDir(workdir, req.params.name);
|
|
675
|
+
if (!info) return res.status(404).json({ error: "No instance found" });
|
|
676
|
+
const logFile = path4.join(info.instancePath, agentKey, "_log.jsonl");
|
|
677
|
+
try {
|
|
678
|
+
const content = await fs3.readFile(logFile, "utf-8");
|
|
679
|
+
const lines = content.split("\n").filter((l) => l.trim());
|
|
680
|
+
const logs = lines.map((l) => {
|
|
681
|
+
try {
|
|
682
|
+
return JSON.parse(l);
|
|
683
|
+
} catch {
|
|
684
|
+
return null;
|
|
685
|
+
}
|
|
686
|
+
}).filter(Boolean);
|
|
687
|
+
res.json({ logs });
|
|
688
|
+
} catch {
|
|
689
|
+
res.json({ logs: [] });
|
|
690
|
+
}
|
|
691
|
+
} catch (err) {
|
|
692
|
+
const message = err instanceof Error ? err.message : "Failed to read agent log";
|
|
693
|
+
res.status(400).json({ error: message });
|
|
694
|
+
}
|
|
695
|
+
});
|
|
696
|
+
router2.get("/:name/agents", async (req, res) => {
|
|
697
|
+
const workdir = req.query.workdir;
|
|
698
|
+
try {
|
|
699
|
+
const validWorkdir = assertValidWorkdir(workdir);
|
|
700
|
+
const safeName = assertSafeProjectName(req.params.name);
|
|
701
|
+
const agentsDir = path4.join(validWorkdir, safeName, "agents");
|
|
702
|
+
const entries = await fs3.readdir(agentsDir);
|
|
703
|
+
const mdFiles = entries.filter((f) => f.endsWith(".md")).sort();
|
|
704
|
+
const agents = await Promise.all(
|
|
705
|
+
mdFiles.map(async (fileName) => {
|
|
706
|
+
const filePath = path4.join(agentsDir, fileName);
|
|
707
|
+
const content = await fs3.readFile(filePath, "utf-8");
|
|
708
|
+
const name = fileName.replace(".md", "");
|
|
709
|
+
return { name, fileName, path: filePath, content };
|
|
710
|
+
})
|
|
711
|
+
);
|
|
712
|
+
res.json({ agents });
|
|
713
|
+
} catch (err) {
|
|
714
|
+
const message = err instanceof Error ? err.message : "Failed to read agent blueprints";
|
|
715
|
+
res.status(400).json({ error: message });
|
|
716
|
+
}
|
|
717
|
+
});
|
|
718
|
+
router2.get("/:name/files/:section", async (req, res) => {
|
|
719
|
+
const workdir = req.query.workdir;
|
|
720
|
+
const section = req.params.section;
|
|
721
|
+
const allowedSections = ["references", "workspace", "wip", "agents", "scripts", "library", "wiki", "_output"];
|
|
722
|
+
if (!allowedSections.includes(section)) {
|
|
723
|
+
return res.status(400).json({ error: `Invalid section. Allowed: ${allowedSections.join(", ")}` });
|
|
724
|
+
}
|
|
725
|
+
try {
|
|
726
|
+
const validWorkdir = assertValidWorkdir(workdir);
|
|
727
|
+
const safeName = assertSafeProjectName(req.params.name);
|
|
728
|
+
const sectionPath = path4.join(validWorkdir, safeName, section);
|
|
729
|
+
try {
|
|
730
|
+
const stat = await fs3.stat(sectionPath);
|
|
731
|
+
if (!stat.isDirectory()) {
|
|
732
|
+
return res.json({ tree: [] });
|
|
733
|
+
}
|
|
734
|
+
} catch {
|
|
735
|
+
return res.json({ tree: [] });
|
|
736
|
+
}
|
|
737
|
+
const tree = await buildFileTree(sectionPath, 5);
|
|
738
|
+
res.json({ tree });
|
|
739
|
+
} catch (err) {
|
|
740
|
+
const message = err instanceof Error ? err.message : "Failed to read project files";
|
|
741
|
+
res.status(400).json({ error: message });
|
|
742
|
+
}
|
|
743
|
+
});
|
|
744
|
+
router2.get("/:name/config", async (req, res) => {
|
|
745
|
+
const workdir = req.query.workdir;
|
|
746
|
+
try {
|
|
747
|
+
const validWorkdir = assertValidWorkdir(workdir);
|
|
748
|
+
const safeName = assertSafeProjectName(req.params.name);
|
|
749
|
+
const projectPath = path4.join(validWorkdir, safeName);
|
|
750
|
+
const readFile = async (fileName) => {
|
|
751
|
+
try {
|
|
752
|
+
return await fs3.readFile(path4.join(projectPath, fileName), "utf-8");
|
|
753
|
+
} catch {
|
|
754
|
+
return null;
|
|
755
|
+
}
|
|
756
|
+
};
|
|
757
|
+
const [config, readme, template] = await Promise.all([
|
|
758
|
+
readFile("config.yml"),
|
|
759
|
+
readFile("README.md"),
|
|
760
|
+
readFile("user_input_template.md")
|
|
761
|
+
]);
|
|
762
|
+
res.json({ config, readme, template });
|
|
763
|
+
} catch (err) {
|
|
764
|
+
const message = err instanceof Error ? err.message : "Failed to read project config";
|
|
765
|
+
res.status(400).json({ error: message });
|
|
766
|
+
}
|
|
767
|
+
});
|
|
768
|
+
var projects_default = router2;
|
|
769
|
+
|
|
770
|
+
// server/routes/framework.ts
|
|
771
|
+
import { Router as Router3 } from "express";
|
|
772
|
+
import path5 from "path";
|
|
773
|
+
import fs4 from "fs/promises";
|
|
774
|
+
var router3 = Router3();
|
|
775
|
+
async function findSkillsDir() {
|
|
776
|
+
let dir = process.cwd();
|
|
777
|
+
for (let i = 0; i < 5; i++) {
|
|
778
|
+
const candidate = path5.join(dir, "skills", "pomasa");
|
|
779
|
+
try {
|
|
780
|
+
await fs4.access(candidate);
|
|
781
|
+
return candidate;
|
|
782
|
+
} catch {
|
|
783
|
+
}
|
|
784
|
+
const parent = path5.dirname(dir);
|
|
785
|
+
if (parent === dir) break;
|
|
786
|
+
dir = parent;
|
|
787
|
+
}
|
|
788
|
+
return null;
|
|
789
|
+
}
|
|
790
|
+
router3.get("/patterns", async (_req, res) => {
|
|
791
|
+
const skillsDir = await findSkillsDir();
|
|
792
|
+
if (!skillsDir) {
|
|
793
|
+
return res.json({ patterns: [] });
|
|
794
|
+
}
|
|
795
|
+
const skillsPath = path5.join(skillsDir, "pattern-catalog");
|
|
796
|
+
try {
|
|
797
|
+
const entries = await fs4.readdir(skillsPath, { withFileTypes: true });
|
|
798
|
+
const patterns = [];
|
|
799
|
+
for (const entry of entries) {
|
|
800
|
+
if (!entry.isFile() || !entry.name.endsWith(".md") || entry.name === "README.md") continue;
|
|
801
|
+
const match = entry.name.match(/^(COR|STR|BHV|QUA)-(\d+)-(.+)\.md$/);
|
|
802
|
+
if (!match) continue;
|
|
803
|
+
const [, prefix, num, nameSlug] = match;
|
|
804
|
+
const filePath = path5.join(skillsPath, entry.name);
|
|
805
|
+
const content = await fs4.readFile(filePath, "utf-8");
|
|
806
|
+
let necessity = "Optional";
|
|
807
|
+
if (content.includes("**Necessity**: Required")) necessity = "Required";
|
|
808
|
+
else if (content.includes("**Necessity**: Recommended")) necessity = "Recommended";
|
|
809
|
+
const problemMatch = content.match(/## Problem\s*\n\n(.+?)(?:\n\n|\n##)/s);
|
|
810
|
+
const description = problemMatch ? problemMatch[1].replace(/\n/g, " ").trim().slice(0, 100) : "";
|
|
811
|
+
patterns.push({
|
|
812
|
+
id: `${prefix}-${num.padStart(2, "0")}`,
|
|
813
|
+
name: nameSlug.replace(/-/g, " "),
|
|
814
|
+
category: prefix,
|
|
815
|
+
necessity,
|
|
816
|
+
description
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
res.json({ patterns });
|
|
820
|
+
} catch (err) {
|
|
821
|
+
console.error("Failed to load patterns:", err);
|
|
822
|
+
res.json({ patterns: [] });
|
|
823
|
+
}
|
|
824
|
+
});
|
|
825
|
+
router3.get("/generator", async (_req, res) => {
|
|
826
|
+
const skillsDir = await findSkillsDir();
|
|
827
|
+
if (!skillsDir) {
|
|
828
|
+
return res.status(404).json({ error: "POMASA skills not found. Run this command from within a POMASA project directory." });
|
|
829
|
+
}
|
|
830
|
+
const generatorPath = path5.join(skillsDir, "SKILL.md");
|
|
831
|
+
try {
|
|
832
|
+
const content = await fs4.readFile(generatorPath, "utf-8");
|
|
833
|
+
res.json({ content });
|
|
834
|
+
} catch {
|
|
835
|
+
res.status(404).json({ error: "Generator not found" });
|
|
836
|
+
}
|
|
837
|
+
});
|
|
838
|
+
router3.get("/template", async (_req, res) => {
|
|
839
|
+
const skillsDir = await findSkillsDir();
|
|
840
|
+
if (!skillsDir) {
|
|
841
|
+
return res.status(404).json({ error: "POMASA skills not found. Run this command from within a POMASA project directory." });
|
|
842
|
+
}
|
|
843
|
+
const templatePath = path5.join(skillsDir, "user_input_template.md");
|
|
844
|
+
const templatePathZh = path5.join(skillsDir, "user_input_template_zh.md");
|
|
845
|
+
try {
|
|
846
|
+
let content;
|
|
847
|
+
try {
|
|
848
|
+
content = await fs4.readFile(templatePath, "utf-8");
|
|
849
|
+
} catch {
|
|
850
|
+
content = await fs4.readFile(templatePathZh, "utf-8");
|
|
851
|
+
}
|
|
852
|
+
res.json({ content });
|
|
853
|
+
} catch {
|
|
854
|
+
res.status(404).json({ error: "Template not found" });
|
|
855
|
+
}
|
|
856
|
+
});
|
|
857
|
+
var framework_default = router3;
|
|
858
|
+
|
|
859
|
+
// server/routes/mas.ts
|
|
860
|
+
init_paths();
|
|
861
|
+
import { Router as Router4 } from "express";
|
|
862
|
+
import path6 from "path";
|
|
863
|
+
import fs5 from "fs/promises";
|
|
864
|
+
import { fileURLToPath } from "url";
|
|
865
|
+
var __dirname = path6.dirname(fileURLToPath(import.meta.url));
|
|
866
|
+
var router4 = Router4();
|
|
867
|
+
function sendSse(res, data) {
|
|
868
|
+
res.write(`data: ${JSON.stringify(data)}
|
|
869
|
+
|
|
870
|
+
`);
|
|
871
|
+
}
|
|
872
|
+
router4.post("/create", async (req, res) => {
|
|
873
|
+
const { targetDir, masName, userInput, selectedPatterns, language } = req.body;
|
|
874
|
+
res.setHeader("Content-Type", "text/event-stream");
|
|
875
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
876
|
+
res.setHeader("Connection", "keep-alive");
|
|
877
|
+
try {
|
|
878
|
+
if (!targetDir || !masName) {
|
|
879
|
+
sendSse(res, { type: "error", content: "Missing targetDir or masName" });
|
|
880
|
+
sendSse(res, { type: "done", code: 1 });
|
|
881
|
+
return res.end();
|
|
882
|
+
}
|
|
883
|
+
assertSafeMasName(masName);
|
|
884
|
+
assertPathInsideBase(targetDir, targetDir);
|
|
885
|
+
const masPath = path6.join(targetDir, masName);
|
|
886
|
+
try {
|
|
887
|
+
await fs5.access(masPath);
|
|
888
|
+
sendSse(res, { type: "error", content: "Directory already exists" });
|
|
889
|
+
sendSse(res, { type: "done", code: 1 });
|
|
890
|
+
return res.end();
|
|
891
|
+
} catch {
|
|
892
|
+
}
|
|
893
|
+
await fs5.mkdir(masPath, { recursive: true });
|
|
894
|
+
const templateFile = language === "zh" ? "user_input_template_zh.md" : "user_input_template.md";
|
|
895
|
+
const templatePath = path6.resolve(__dirname, "../../templates", templateFile);
|
|
896
|
+
let template = await fs5.readFile(templatePath, "utf-8");
|
|
897
|
+
const ui = userInput;
|
|
898
|
+
const isZhLang = language === "zh";
|
|
899
|
+
const formats = ui.deliverableFormats || "Markdown";
|
|
900
|
+
const deliverableBlock = isZhLang ? [
|
|
901
|
+
`- [x] Markdown\uFF08\u59CB\u7EC8\u751F\u6210\uFF09`,
|
|
902
|
+
`- [${formats.includes("pdf") ? "x" : " "}] PDF\uFF08\u63A8\u8350\uFF0C\u4FBF\u4E8E\u5206\u53D1\uFF09`,
|
|
903
|
+
`- [${formats.includes("docx") ? "x" : " "}] DOCX\uFF08\u63A8\u8350\uFF0C\u4FBF\u4E8E\u7F16\u8F91\uFF09`,
|
|
904
|
+
`- [${formats.includes("wiki") ? "x" : " "}] Wiki\uFF08\u6301\u4E45\u5316\u7684 Obsidian \u77E5\u8BC6\u56FE\u8C31\uFF0C\u7528\u4E8E\u8DE8\u6B21\u8FD0\u884C\u7684\u7814\u7A76\u79EF\u7D2F\uFF09`
|
|
905
|
+
].join("\n") : [
|
|
906
|
+
`- [x] Markdown (always generated)`,
|
|
907
|
+
`- [${formats.includes("pdf") ? "x" : " "}] PDF (recommended, for distribution)`,
|
|
908
|
+
`- [${formats.includes("docx") ? "x" : " "}] DOCX (recommended, for editing)`,
|
|
909
|
+
`- [${formats.includes("wiki") ? "x" : " "}] Wiki (persistent Obsidian knowledge graph, for compounding research across runs)`
|
|
910
|
+
].join("\n");
|
|
911
|
+
const ql = ui.qualityLevel || "standard";
|
|
912
|
+
const qualityBlock = isZhLang ? [
|
|
913
|
+
`- [${ql === "simple" ? "x" : " "}] \u7B80\u5355\uFF08Simple\uFF09\uFF1A\u4EC5\u91C7\u7528\u5FC5\u9700\u7684\u6A21\u5F0F\uFF0C\u4E0D\u8FDB\u884C\u989D\u5916\u7684\u8D28\u91CF\u68C0\u67E5`,
|
|
914
|
+
`- [${ql === "standard" ? "x" : " "}] \u6807\u51C6\uFF08Standard\uFF0C\u9ED8\u8BA4\uFF09\uFF1A\u91C7\u7528 QUA-01 \u5D4C\u5165\u5F0F\u8D28\u91CF\u6807\u51C6 + BHV-05 \u57FA\u4E8E\u4E8B\u5B9E\u7684\u7F51\u7EDC\u7814\u7A76`,
|
|
915
|
+
`- [${ql === "strict" ? "x" : " "}] \u4E25\u683C\uFF08Strict\uFF09\uFF1A\u91C7\u7528 QUA-01 + QUA-02 \u591A\u5C42\u8D28\u91CF\u4FDD\u8BC1 + BHV-05 \u57FA\u4E8E\u4E8B\u5B9E\u7684\u7F51\u7EDC\u7814\u7A76`
|
|
916
|
+
].join("\n") : [
|
|
917
|
+
`- [${ql === "simple" ? "x" : " "}] Simple: Only adopt required patterns, no additional quality checks`,
|
|
918
|
+
`- [${ql === "standard" ? "x" : " "}] Standard (default): Adopt QUA-01 Embedded Quality Standards + BHV-05 Grounded Web Research`,
|
|
919
|
+
`- [${ql === "strict" ? "x" : " "}] Strict: Adopt QUA-01 + QUA-02 Multi-Layer Quality Assurance + BHV-05 Grounded Web Research`
|
|
920
|
+
].join("\n");
|
|
921
|
+
const ol = ui.observabilityLevel || "normal";
|
|
922
|
+
const obsBlock = isZhLang ? [
|
|
923
|
+
`- [${ol === "none" ? "x" : " "}] none\uFF1A\u4E0D\u4EA7\u751F\u6267\u884C\u65E5\u5FD7\uFF08\u8282\u7701 token\uFF09\uFF1B\u7F16\u6392\u8005\u4ECD\u4EC5\u8BB0\u5F55\u9A8C\u6536\u5224\u5B9A`,
|
|
924
|
+
`- [${ol === "minimal" ? "x" : " "}] minimal\uFF1A\u53EA\u8BB0\u5F55\u9519\u8BEF\uFF08ERROR\uFF09`,
|
|
925
|
+
`- [${ol === "normal" ? "x" : " "}] normal\uFF08\u9ED8\u8BA4\uFF09\uFF1A\u8BB0\u5F55\u9519\u8BEF + \u8B66\u544A\uFF08\u542B agent \u7684\u964D\u7EA7\u3001\u7F29\u8303\u56F4\u3001\u56F0\u96BE\uFF09`,
|
|
926
|
+
`- [${ol === "detailed" ? "x" : " "}] detailed\uFF1A\u8BB0\u5F55\u5168\u90E8\uFF08\u542B\u5168\u94FE\u8DEF INFO \u91CC\u7A0B\u7891\uFF09`
|
|
927
|
+
].join("\n") : [
|
|
928
|
+
`- [${ol === "none" ? "x" : " "}] none: No execution logs (saves tokens); the orchestrator still records acceptance verdicts only`,
|
|
929
|
+
`- [${ol === "minimal" ? "x" : " "}] minimal: Log errors only`,
|
|
930
|
+
`- [${ol === "normal" ? "x" : " "}] normal (default): Log errors + warnings (including agent degradations, scope reductions, and difficulties)`,
|
|
931
|
+
`- [${ol === "detailed" ? "x" : " "}] detailed: Log everything (including INFO milestones across the full chain)`
|
|
932
|
+
].join("\n");
|
|
933
|
+
const refLines = (ui.existingReferences || "").split("\n").filter((l) => l.trim());
|
|
934
|
+
const refBlock = refLines.length > 0 ? refLines.map((l) => `- ${l.trim()}`).join("\n") : "- None";
|
|
935
|
+
const replacements = {
|
|
936
|
+
"{{BLUEPRINT_LANGUAGE}}": ui.blueprintLanguage || "Chinese",
|
|
937
|
+
"{{REPORT_LANGUAGE}}": ui.reportLanguage || "Chinese",
|
|
938
|
+
"{{PROJECT_IDENTIFIER}}": ui.projectIdentifier || masName,
|
|
939
|
+
"{{RESEARCH_TOPIC}}": ui.researchTopic || "",
|
|
940
|
+
"{{INITIAL_IDEAS}}": ui.initialIdeas || "",
|
|
941
|
+
"{{DATA_SOURCES}}": ui.dataSources || "",
|
|
942
|
+
"{{EXISTING_REFERENCES}}": refBlock,
|
|
943
|
+
"{{ANALYSIS_METHODS}}": ui.analysisMethods || "",
|
|
944
|
+
"{{REPORT_FORMAT}}": ui.reportFormat || "",
|
|
945
|
+
"{{REPORT_STRUCTURE}}": ui.reportStructure || "",
|
|
946
|
+
"{{DELIVERABLE_FORMATS}}": deliverableBlock,
|
|
947
|
+
"{{QUALITY_LEVEL}}": qualityBlock,
|
|
948
|
+
"{{OBSERVABILITY_LEVEL}}": obsBlock,
|
|
949
|
+
"{{PATTERN_OVERRIDES}}": ui.patternOverrides || "None",
|
|
950
|
+
"{{OTHER_REQUIREMENTS}}": ui.otherRequirements || "None",
|
|
951
|
+
"{{SELECTED_PATTERNS}}": selectedPatterns.join(", ")
|
|
952
|
+
};
|
|
953
|
+
for (const [placeholder, value] of Object.entries(replacements)) {
|
|
954
|
+
template = template.replaceAll(placeholder, value);
|
|
955
|
+
}
|
|
956
|
+
await fs5.writeFile(path6.join(masPath, "user_input_template.md"), template);
|
|
957
|
+
sendSse(res, { type: "output", content: "Created user_input_template.md\n" });
|
|
958
|
+
const dirs = ["agents", "references", "workspace", "_observation"];
|
|
959
|
+
for (const dir of dirs) {
|
|
960
|
+
await fs5.mkdir(path6.join(masPath, dir), { recursive: true });
|
|
961
|
+
sendSse(res, { type: "output", content: `Created directory: ${dir}/
|
|
962
|
+
` });
|
|
963
|
+
}
|
|
964
|
+
sendSse(res, { type: "output", content: "\n--- Completed ---\n" });
|
|
965
|
+
sendSse(res, { type: "done", code: 0, masPath });
|
|
966
|
+
res.end();
|
|
967
|
+
} catch (err) {
|
|
968
|
+
const message = err instanceof Error ? err.message : "Unknown error during creation";
|
|
969
|
+
sendSse(res, { type: "error", content: message });
|
|
970
|
+
sendSse(res, { type: "done", code: 1 });
|
|
971
|
+
res.end();
|
|
972
|
+
}
|
|
973
|
+
});
|
|
974
|
+
var mas_default = router4;
|
|
975
|
+
|
|
976
|
+
// server/setup.ts
|
|
977
|
+
var __dirname2 = path7.dirname(fileURLToPath2(import.meta.url));
|
|
978
|
+
function createApp() {
|
|
979
|
+
const app = express();
|
|
980
|
+
const server = createServer(app);
|
|
981
|
+
app.use(cors());
|
|
982
|
+
app.use(express.json({ limit: "10mb" }));
|
|
983
|
+
app.use("/api/projects", projects_default);
|
|
984
|
+
app.use("/api/framework", framework_default);
|
|
985
|
+
app.use("/api/fs", filesystem_default);
|
|
986
|
+
app.use("/api/mas", mas_default);
|
|
987
|
+
setupTerminalWebSocket(server);
|
|
988
|
+
const distDir = path7.resolve(__dirname2, "../dist");
|
|
989
|
+
app.use(express.static(distDir));
|
|
990
|
+
app.use("/api", (_req, res) => {
|
|
991
|
+
res.status(404).json({ error: "Not found" });
|
|
992
|
+
});
|
|
993
|
+
app.get("/{*splat}", (_req, res) => {
|
|
994
|
+
res.sendFile(path7.join(distDir, "index.html"));
|
|
995
|
+
});
|
|
996
|
+
return { app, server };
|
|
997
|
+
}
|
|
998
|
+
function startServer(port2, maxRetries = 10) {
|
|
999
|
+
const { server } = createApp();
|
|
1000
|
+
return new Promise((resolve, reject) => {
|
|
1001
|
+
server.on("error", (err) => {
|
|
1002
|
+
if (err.code === "EADDRINUSE" && maxRetries > 0) {
|
|
1003
|
+
console.log(`Port ${port2} in use, trying ${port2 + 1}...`);
|
|
1004
|
+
server.removeAllListeners("error");
|
|
1005
|
+
startServer(port2 + 1, maxRetries - 1).then(resolve).catch(reject);
|
|
1006
|
+
} else {
|
|
1007
|
+
reject(err);
|
|
1008
|
+
}
|
|
1009
|
+
});
|
|
1010
|
+
server.listen(port2, () => {
|
|
1011
|
+
console.log(`POMASA Dashboard running at http://localhost:${port2}`);
|
|
1012
|
+
console.log(`API at http://localhost:${port2}/api`);
|
|
1013
|
+
console.log(`WebSocket terminal at ws://localhost:${port2}/api/terminal`);
|
|
1014
|
+
resolve(server);
|
|
1015
|
+
});
|
|
1016
|
+
});
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
// server/cli.ts
|
|
1020
|
+
process.on("uncaughtException", (err) => {
|
|
1021
|
+
console.error("[uncaughtException]", err);
|
|
1022
|
+
});
|
|
1023
|
+
process.on("unhandledRejection", (reason) => {
|
|
1024
|
+
console.error("[unhandledRejection]", reason);
|
|
1025
|
+
});
|
|
1026
|
+
var port = 3001;
|
|
1027
|
+
var portArg = process.argv.indexOf("--port");
|
|
1028
|
+
if (portArg !== -1 && process.argv[portArg + 1]) {
|
|
1029
|
+
port = parseInt(process.argv[portArg + 1], 10);
|
|
1030
|
+
} else if (process.env.PORT) {
|
|
1031
|
+
port = parseInt(process.env.PORT, 10);
|
|
1032
|
+
}
|
|
1033
|
+
startServer(port).then(async () => {
|
|
1034
|
+
const url = `http://localhost:${port}`;
|
|
1035
|
+
try {
|
|
1036
|
+
const open = (await import("open")).default;
|
|
1037
|
+
await open(url);
|
|
1038
|
+
console.log(`Opened ${url} in browser`);
|
|
1039
|
+
} catch {
|
|
1040
|
+
console.log(`Please open ${url} in your browser`);
|
|
1041
|
+
}
|
|
1042
|
+
}).catch((err) => {
|
|
1043
|
+
console.error("Failed to start server:", err);
|
|
1044
|
+
process.exit(1);
|
|
1045
|
+
});
|