@ouro.bot/cli 0.1.0-alpha.160 → 0.1.0-alpha.162
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 +1 -0
- package/changelog.json +13 -0
- package/dist/heart/daemon/daemon-entry.js +8 -0
- package/dist/heart/daemon/daemon.js +40 -29
- package/dist/heart/daemon/outlook-http.js +71 -9
- package/dist/outlook-ui/assets/index-CXQG0mVx.js +61 -0
- package/dist/outlook-ui/assets/index-Gq7EdfkO.css +1 -0
- package/dist/outlook-ui/index.html +15 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -221,3 +221,4 @@ See `skills/configure-dev-tools.md` for the full tool inventory and troubleshoot
|
|
|
221
221
|
## A Note To Future Maintainers
|
|
222
222
|
|
|
223
223
|
If you discover a doc that lies, fix it or remove it. Accuracy is a kindness. A future agent should not have to untangle a fossil record just to understand where their hands are.
|
|
224
|
+
# Production SPA serving
|
package/changelog.json
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
|
|
3
3
|
"versions": [
|
|
4
|
+
{
|
|
5
|
+
"version": "0.1.0-alpha.162",
|
|
6
|
+
"changes": [
|
|
7
|
+
"fix(daemon): handle EPIPE in detached daemon \u2014 suppress pipe errors when parent exits after ouro up"
|
|
8
|
+
]
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"version": "0.1.0-alpha.161",
|
|
12
|
+
"changes": [
|
|
13
|
+
"feat(outlook): serve React SPA at root (/), daemon serves static assets",
|
|
14
|
+
"fix(daemon): graceful EADDRINUSE handling for Outlook port"
|
|
15
|
+
]
|
|
16
|
+
},
|
|
4
17
|
{
|
|
5
18
|
"version": "0.1.0-alpha.160",
|
|
6
19
|
"changes": [
|
|
@@ -183,8 +183,16 @@ process.on("SIGTERM", () => {
|
|
|
183
183
|
}
|
|
184
184
|
void daemon.stop().then(() => process.exit(0));
|
|
185
185
|
});
|
|
186
|
+
// Suppress EPIPE on stdout/stderr — normal when detached daemon's parent exits
|
|
187
|
+
/* v8 ignore start -- EPIPE suppression: only fires when parent process exits @preserve */
|
|
188
|
+
process.stdout?.on?.("error", () => { });
|
|
189
|
+
process.stderr?.on?.("error", () => { });
|
|
190
|
+
/* v8 ignore stop */
|
|
186
191
|
/* v8 ignore start -- global exception handlers: genuinely untestable in vitest; exercised by real daemon crashes @preserve */
|
|
187
192
|
process.on("uncaughtException", (error) => {
|
|
193
|
+
// EPIPE is normal for detached daemon processes — parent closed the pipe
|
|
194
|
+
if (error.code === "EPIPE")
|
|
195
|
+
return;
|
|
188
196
|
(0, daemon_tombstone_1.writeDaemonTombstone)("uncaughtException", error);
|
|
189
197
|
(0, runtime_1.emitNervesEvent)({
|
|
190
198
|
level: "error",
|
|
@@ -295,35 +295,46 @@ class OuroDaemon {
|
|
|
295
295
|
await this.drainPendingSenseMessages();
|
|
296
296
|
/* v8 ignore start — Outlook server startup, tested via outlook-http.test.ts */
|
|
297
297
|
if (!this.outlookServer) {
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
298
|
+
try {
|
|
299
|
+
this.outlookServer = await (0, outlook_http_1.startOutlookHttpServer)({
|
|
300
|
+
host: "127.0.0.1",
|
|
301
|
+
port: outlook_types_1.OUTLOOK_DEFAULT_PORT,
|
|
302
|
+
readMachineState: () => (0, outlook_read_1.readOutlookMachineState)({ bundlesRoot: this.bundlesRoot }),
|
|
303
|
+
readMachineView: ({ machine }) => {
|
|
304
|
+
const overview = this.buildStatusPayload().overview;
|
|
305
|
+
return (0, outlook_view_1.buildOutlookMachineView)({
|
|
306
|
+
machine,
|
|
307
|
+
daemon: {
|
|
308
|
+
status: overview.daemon,
|
|
309
|
+
health: overview.health,
|
|
310
|
+
mode: overview.mode,
|
|
311
|
+
socketPath: overview.socketPath,
|
|
312
|
+
outlookUrl: overview.outlookUrl,
|
|
313
|
+
entryPath: overview.entryPath,
|
|
314
|
+
workerCount: overview.workerCount,
|
|
315
|
+
senseCount: overview.senseCount,
|
|
316
|
+
},
|
|
317
|
+
});
|
|
318
|
+
},
|
|
319
|
+
readAgentState: (agentName) => (0, outlook_read_1.readOutlookAgentState)(agentName, { bundlesRoot: this.bundlesRoot }),
|
|
320
|
+
readAgentView: (agentName) => {
|
|
321
|
+
const agent = (0, outlook_read_1.readOutlookAgentState)(agentName, { bundlesRoot: this.bundlesRoot });
|
|
322
|
+
return (0, outlook_view_1.buildOutlookAgentView)({
|
|
323
|
+
agent,
|
|
324
|
+
viewer: { kind: "human" },
|
|
325
|
+
});
|
|
326
|
+
},
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
catch (error) {
|
|
330
|
+
(0, runtime_1.emitNervesEvent)({
|
|
331
|
+
level: "warn",
|
|
332
|
+
component: "daemon",
|
|
333
|
+
event: "daemon.outlook_start_failed",
|
|
334
|
+
message: `Outlook server failed to start: ${String(error)}`,
|
|
335
|
+
meta: { port: outlook_types_1.OUTLOOK_DEFAULT_PORT },
|
|
336
|
+
});
|
|
337
|
+
}
|
|
327
338
|
}
|
|
328
339
|
/* v8 ignore stop */
|
|
329
340
|
if (fs.existsSync(this.socketPath)) {
|
|
@@ -39,7 +39,6 @@ const http = __importStar(require("http"));
|
|
|
39
39
|
const path = __importStar(require("path"));
|
|
40
40
|
const runtime_1 = require("../../nerves/runtime");
|
|
41
41
|
const outlook_read_1 = require("./outlook-read");
|
|
42
|
-
const outlook_render_1 = require("./outlook-render");
|
|
43
42
|
function createSseBroadcaster() {
|
|
44
43
|
let nextId = 1;
|
|
45
44
|
const clients = new Set();
|
|
@@ -114,10 +113,54 @@ function writeJson(response, statusCode, payload) {
|
|
|
114
113
|
response.writeHead(statusCode, { "content-type": "application/json; charset=utf-8" });
|
|
115
114
|
response.end(`${JSON.stringify(payload, null, 2)}\n`);
|
|
116
115
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
116
|
+
/* v8 ignore start — SPA static file serving infrastructure */
|
|
117
|
+
const MIME_TYPES = {
|
|
118
|
+
".html": "text/html",
|
|
119
|
+
".js": "application/javascript",
|
|
120
|
+
".css": "text/css",
|
|
121
|
+
".json": "application/json",
|
|
122
|
+
".svg": "image/svg+xml",
|
|
123
|
+
".png": "image/png",
|
|
124
|
+
".ico": "image/x-icon",
|
|
125
|
+
".woff2": "font/woff2",
|
|
126
|
+
};
|
|
127
|
+
function resolveSpaDistDir() {
|
|
128
|
+
// Look for the built SPA relative to this file's location
|
|
129
|
+
// In production: dist/heart/daemon/outlook-http.js -> ../../packages/outlook-ui/dist/
|
|
130
|
+
// In dev: packages/outlook-ui/dist/
|
|
131
|
+
const candidates = [
|
|
132
|
+
path.resolve(__dirname, "..", "..", "..", "packages", "outlook-ui", "dist"),
|
|
133
|
+
path.resolve(__dirname, "..", "..", "packages", "outlook-ui", "dist"),
|
|
134
|
+
path.resolve(__dirname, "..", "..", "..", "..", "packages", "outlook-ui", "dist"),
|
|
135
|
+
// npm-published layout: the SPA dist is copied to dist/outlook-ui/
|
|
136
|
+
path.resolve(__dirname, "..", "..", "outlook-ui"),
|
|
137
|
+
path.resolve(__dirname, "..", "outlook-ui"),
|
|
138
|
+
];
|
|
139
|
+
for (const candidate of candidates) {
|
|
140
|
+
if (fs.existsSync(path.join(candidate, "index.html")))
|
|
141
|
+
return candidate;
|
|
142
|
+
}
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
function serveStaticFile(response, filePath) {
|
|
146
|
+
try {
|
|
147
|
+
if (!fs.existsSync(filePath))
|
|
148
|
+
return false;
|
|
149
|
+
const ext = path.extname(filePath);
|
|
150
|
+
const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
|
|
151
|
+
const content = fs.readFileSync(filePath);
|
|
152
|
+
response.writeHead(200, {
|
|
153
|
+
"content-type": contentType,
|
|
154
|
+
"cache-control": ext === ".html" ? "no-cache" : "public, max-age=31536000, immutable",
|
|
155
|
+
});
|
|
156
|
+
response.end(content);
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
120
162
|
}
|
|
163
|
+
/* v8 ignore stop */
|
|
121
164
|
function normalizePath(urlValue = "/") {
|
|
122
165
|
const parsed = new URL(urlValue, "http://127.0.0.1");
|
|
123
166
|
const normalizedPath = parsed.pathname.replace(/\/+$/, "");
|
|
@@ -140,7 +183,6 @@ async function startOutlookHttpServer(options = {}) {
|
|
|
140
183
|
});
|
|
141
184
|
/* v8 ignore stop */
|
|
142
185
|
const readAgentView = options.readAgentView;
|
|
143
|
-
const renderApp = options.renderApp ?? outlook_render_1.renderOutlookApp;
|
|
144
186
|
/* v8 ignore start — default hook wiring, tested via integration */
|
|
145
187
|
const agentRoot = (agentName) => {
|
|
146
188
|
const base = bundlesRoot ?? "";
|
|
@@ -174,10 +216,23 @@ async function startOutlookHttpServer(options = {}) {
|
|
|
174
216
|
sse.add(response);
|
|
175
217
|
return;
|
|
176
218
|
}
|
|
219
|
+
/* v8 ignore start — SPA static asset serving */
|
|
220
|
+
// Serve built SPA static assets: /assets/*
|
|
221
|
+
if (pathname.startsWith("/assets/")) {
|
|
222
|
+
const spaDir = resolveSpaDistDir();
|
|
223
|
+
if (spaDir) {
|
|
224
|
+
const assetPath = path.join(spaDir, pathname);
|
|
225
|
+
if (serveStaticFile(response, assetPath))
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
writeJson(response, 404, { ok: false, error: "asset not found" });
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
/* v8 ignore stop */
|
|
232
|
+
// Legacy /outlook route — redirect to root
|
|
177
233
|
if (pathname === "/outlook") {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
writeHtml(response, renderApp({ origin, machine, machineView }));
|
|
234
|
+
response.writeHead(301, { location: "/" });
|
|
235
|
+
response.end();
|
|
181
236
|
return;
|
|
182
237
|
}
|
|
183
238
|
if (pathname === "/outlook/api/machine") {
|
|
@@ -273,7 +328,14 @@ async function startOutlookHttpServer(options = {}) {
|
|
|
273
328
|
writeJson(response, 404, { ok: false, error: `unknown agent surface: ${surface}` });
|
|
274
329
|
return;
|
|
275
330
|
}
|
|
276
|
-
|
|
331
|
+
/* v8 ignore start — SPA fallback for client-side routing */
|
|
332
|
+
const spaDir = resolveSpaDistDir();
|
|
333
|
+
if (spaDir) {
|
|
334
|
+
if (serveStaticFile(response, path.join(spaDir, "index.html")))
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
/* v8 ignore stop */
|
|
338
|
+
writeJson(response, 404, { ok: false, error: `not found: ${pathname}` });
|
|
277
339
|
});
|
|
278
340
|
await new Promise((resolve, reject) => {
|
|
279
341
|
server.once("error", reject);
|