@ouro.bot/cli 0.1.0-alpha.325 → 0.1.0-alpha.327
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/changelog.json +13 -0
- package/dist/heart/daemon/cli-exec.js +4 -0
- package/dist/heart/daemon/startup-tui.js +44 -34
- package/dist/heart/outlook/outlook-http-hooks.js +64 -0
- package/dist/heart/outlook/outlook-http-response.js +7 -0
- package/dist/heart/outlook/outlook-http-routes.js +232 -0
- package/dist/heart/outlook/outlook-http-static.js +99 -0
- package/dist/heart/outlook/outlook-http-transport.js +116 -0
- package/dist/heart/outlook/outlook-http.js +17 -357
- package/dist/nerves/coverage/file-completeness.js +7 -0
- package/package.json +1 -1
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.327",
|
|
6
|
+
"changes": [
|
|
7
|
+
"fix(daemon): make startup stability polling respect stdout TTY capability so captured `ouro up` output is plain append-only text without raw ANSI cursor-control or color escapes, while interactive terminals keep in-place progress rendering."
|
|
8
|
+
]
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"version": "0.1.0-alpha.326",
|
|
12
|
+
"changes": [
|
|
13
|
+
"refactor(outlook): split the Outlook HTTP server boundary into focused transport, static/path, default-hook, route-dispatch, and JSON-response helpers while preserving the public `startOutlookHttpServer()` API and existing HTTP behavior.",
|
|
14
|
+
"test(outlook): add direct seam coverage for Outlook HTTP helper modules, route/static/SSE/default-hook behavior, and nerves file-completeness exemptions for the new helper-module pattern."
|
|
15
|
+
]
|
|
16
|
+
},
|
|
4
17
|
{
|
|
5
18
|
"version": "0.1.0-alpha.325",
|
|
6
19
|
"changes": [
|
|
@@ -167,6 +167,8 @@ async function ensureDaemonRunning(deps) {
|
|
|
167
167
|
daemonPid: runtimeResult.startedPid ?? null,
|
|
168
168
|
/* v8 ignore next -- thin wrapper: raw process.stdout.write for ANSI cursor control @preserve */
|
|
169
169
|
writeRaw: (text) => process.stdout.write(text),
|
|
170
|
+
/* v8 ignore next -- thin wrapper: real stdout TTY detection injected for captured-output safety @preserve */
|
|
171
|
+
isTTY: process.stdout.isTTY === true,
|
|
170
172
|
/* v8 ignore next -- thin wrapper: real Date.now() injected for testability @preserve */
|
|
171
173
|
now: () => Date.now(),
|
|
172
174
|
/* v8 ignore next -- thin wrapper: real setTimeout injected for testability @preserve */
|
|
@@ -205,6 +207,8 @@ async function ensureDaemonRunning(deps) {
|
|
|
205
207
|
daemonPid: lastPid,
|
|
206
208
|
/* v8 ignore next -- thin wrapper: raw process.stdout.write for ANSI cursor control @preserve */
|
|
207
209
|
writeRaw: (text) => process.stdout.write(text),
|
|
210
|
+
/* v8 ignore next -- thin wrapper: real stdout TTY detection injected for captured-output safety @preserve */
|
|
211
|
+
isTTY: process.stdout.isTTY === true,
|
|
208
212
|
/* v8 ignore next -- thin wrapper: real Date.now() injected for testability @preserve */
|
|
209
213
|
now: () => Date.now(),
|
|
210
214
|
/* v8 ignore next -- thin wrapper: real setTimeout injected for testability @preserve */
|
|
@@ -68,66 +68,57 @@ function assessStability(payload, now) {
|
|
|
68
68
|
* Build an ANSI string for in-place terminal display during polling.
|
|
69
69
|
* Uses cursor-up and line-clear escapes to overwrite previous output.
|
|
70
70
|
*/
|
|
71
|
-
function renderStartupProgress(payload, elapsed, prevLineCount = 0) {
|
|
71
|
+
function renderStartupProgress(payload, elapsed, prevLineCount = 0, options = {}) {
|
|
72
|
+
const isTTY = options.isTTY ?? true;
|
|
72
73
|
const frameIndex = Math.floor(elapsed / 100) % SPINNER_FRAMES.length;
|
|
73
74
|
const spinner = SPINNER_FRAMES[frameIndex];
|
|
74
75
|
const lines = [];
|
|
75
76
|
const elapsedSec = (elapsed / 1000).toFixed(1);
|
|
76
|
-
lines.push(
|
|
77
|
+
lines.push(isTTY
|
|
78
|
+
? `${spinner} ${BOLD}waiting for agents${RESET} ${DIM}(${elapsedSec}s)${RESET}`
|
|
79
|
+
: `${spinner} waiting for agents (${elapsedSec}s)`);
|
|
77
80
|
for (const worker of payload.workers) {
|
|
78
|
-
const
|
|
79
|
-
: worker.status === "crashed" ? RED
|
|
80
|
-
: YELLOW;
|
|
81
|
-
const statusText = `${statusColor}${worker.status}${RESET}`;
|
|
81
|
+
const statusText = isTTY ? colorStatus(worker.status) : worker.status;
|
|
82
82
|
lines.push(` ${worker.agent}/${worker.worker}: ${statusText}`);
|
|
83
83
|
}
|
|
84
|
-
|
|
85
|
-
if (prevLineCount > 0) {
|
|
86
|
-
output += `\x1b[${prevLineCount}A`;
|
|
87
|
-
}
|
|
88
|
-
for (const line of lines) {
|
|
89
|
-
output += `\x1b[2K${line}\n`;
|
|
90
|
-
}
|
|
91
|
-
return output;
|
|
84
|
+
return renderStartupLines(lines, prevLineCount, isTTY);
|
|
92
85
|
}
|
|
93
86
|
/**
|
|
94
87
|
* Render a pre-socket status line showing what the daemon is doing.
|
|
95
88
|
*/
|
|
96
|
-
function renderWaitingForDaemon(elapsed, latestEvent, prevLineCount = 0) {
|
|
89
|
+
function renderWaitingForDaemon(elapsed, latestEvent, prevLineCount = 0, options = {}) {
|
|
90
|
+
const isTTY = options.isTTY ?? true;
|
|
97
91
|
const elapsedSec = (elapsed / 1000).toFixed(1);
|
|
98
92
|
const frameIndex = Math.floor(elapsed / 100) % SPINNER_FRAMES.length;
|
|
99
93
|
const spinner = SPINNER_FRAMES[frameIndex];
|
|
100
94
|
const lines = [];
|
|
101
|
-
lines.push(
|
|
95
|
+
lines.push(isTTY
|
|
96
|
+
? `${spinner} ${BOLD}waiting for daemon${RESET} ${DIM}(${elapsedSec}s)${RESET}`
|
|
97
|
+
: `${spinner} waiting for daemon (${elapsedSec}s)`);
|
|
102
98
|
if (latestEvent) {
|
|
103
|
-
lines.push(` ${DIM}${latestEvent}${RESET}`);
|
|
104
|
-
}
|
|
105
|
-
let output = "";
|
|
106
|
-
if (prevLineCount > 0) {
|
|
107
|
-
output += `\x1b[${prevLineCount}A`;
|
|
108
|
-
}
|
|
109
|
-
for (const line of lines) {
|
|
110
|
-
output += `\x1b[2K${line}\n`;
|
|
99
|
+
lines.push(isTTY ? ` ${DIM}${latestEvent}${RESET}` : ` ${latestEvent}`);
|
|
111
100
|
}
|
|
112
|
-
return
|
|
101
|
+
return renderStartupLines(lines, prevLineCount, isTTY);
|
|
113
102
|
}
|
|
114
103
|
/**
|
|
115
104
|
* Render the final summary after all agents have resolved.
|
|
116
105
|
*/
|
|
117
|
-
function renderFinalSummary(result) {
|
|
106
|
+
function renderFinalSummary(result, isTTY) {
|
|
118
107
|
const lines = [];
|
|
119
108
|
for (const agent of result.stable) {
|
|
120
|
-
lines.push(` ${GREEN}\u2713${RESET} ${agent}: ${GREEN}stable${RESET}`);
|
|
109
|
+
lines.push(isTTY ? ` ${GREEN}\u2713${RESET} ${agent}: ${GREEN}stable${RESET}` : ` \u2713 ${agent}: stable`);
|
|
121
110
|
}
|
|
122
111
|
for (const d of result.degraded) {
|
|
123
|
-
lines.push(` ${RED}\u2717${RESET} ${d.agent}: ${RED}degraded${RESET}`);
|
|
112
|
+
lines.push(isTTY ? ` ${RED}\u2717${RESET} ${d.agent}: ${RED}degraded${RESET}` : ` \u2717 ${d.agent}: degraded`);
|
|
124
113
|
if (d.errorReason !== "unknown error") {
|
|
125
|
-
lines.push(` ${DIM}error: ${d.errorReason}${RESET}`);
|
|
114
|
+
lines.push(isTTY ? ` ${DIM}error: ${d.errorReason}${RESET}` : ` error: ${d.errorReason}`);
|
|
126
115
|
}
|
|
127
116
|
if (d.fixHint !== "check daemon logs") {
|
|
128
|
-
lines.push(` ${DIM}fix: ${d.fixHint}${RESET}`);
|
|
117
|
+
lines.push(isTTY ? ` ${DIM}fix: ${d.fixHint}${RESET}` : ` fix: ${d.fixHint}`);
|
|
129
118
|
}
|
|
130
119
|
}
|
|
120
|
+
if (!isTTY)
|
|
121
|
+
return lines.join("\n") + "\n";
|
|
131
122
|
return lines.map((line) => `\x1b[2K${line}`).join("\n") + "\n";
|
|
132
123
|
}
|
|
133
124
|
// ── Polling loop ──
|
|
@@ -141,6 +132,7 @@ function renderFinalSummary(result) {
|
|
|
141
132
|
async function pollDaemonStartup(deps) {
|
|
142
133
|
const startTime = deps.now();
|
|
143
134
|
let prevLineCount = 0;
|
|
135
|
+
const isTTY = deps.isTTY ?? true;
|
|
144
136
|
const isAlive = deps.isProcessAlive ?? defaultIsProcessAlive;
|
|
145
137
|
(0, runtime_1.emitNervesEvent)({
|
|
146
138
|
component: "daemon",
|
|
@@ -169,7 +161,7 @@ async function pollDaemonStartup(deps) {
|
|
|
169
161
|
meta: { pid: deps.daemonPid, lastEvent: latestEvent },
|
|
170
162
|
});
|
|
171
163
|
// Clear the waiting line
|
|
172
|
-
if (prevLineCount > 0) {
|
|
164
|
+
if (isTTY && prevLineCount > 0) {
|
|
173
165
|
let clear = `\x1b[${prevLineCount}A`;
|
|
174
166
|
for (let i = 0; i < prevLineCount; i++)
|
|
175
167
|
clear += `\x1b[2K\n`;
|
|
@@ -182,12 +174,12 @@ async function pollDaemonStartup(deps) {
|
|
|
182
174
|
}
|
|
183
175
|
// Show what the daemon is doing from its log
|
|
184
176
|
const latestEvent = deps.readLatestDaemonEvent?.() ?? null;
|
|
185
|
-
const output = renderWaitingForDaemon(elapsed, latestEvent, prevLineCount);
|
|
177
|
+
const output = renderWaitingForDaemon(elapsed, latestEvent, prevLineCount, { isTTY });
|
|
186
178
|
deps.writeRaw(output);
|
|
187
179
|
prevLineCount = latestEvent ? 2 : 1;
|
|
188
180
|
}
|
|
189
181
|
if (payload) {
|
|
190
|
-
const output = renderStartupProgress(payload, elapsed, prevLineCount);
|
|
182
|
+
const output = renderStartupProgress(payload, elapsed, prevLineCount, { isTTY });
|
|
191
183
|
deps.writeRaw(output);
|
|
192
184
|
prevLineCount = payload.workers.length + 1;
|
|
193
185
|
const assessment = assessStability(payload, now);
|
|
@@ -196,7 +188,7 @@ async function pollDaemonStartup(deps) {
|
|
|
196
188
|
stable: assessment.stable,
|
|
197
189
|
degraded: assessment.degraded,
|
|
198
190
|
};
|
|
199
|
-
const summary = renderFinalSummary(result);
|
|
191
|
+
const summary = renderFinalSummary(result, isTTY);
|
|
200
192
|
deps.writeRaw(summary);
|
|
201
193
|
(0, runtime_1.emitNervesEvent)({
|
|
202
194
|
component: "daemon",
|
|
@@ -214,6 +206,24 @@ async function pollDaemonStartup(deps) {
|
|
|
214
206
|
await deps.sleep(POLL_INTERVAL_MS);
|
|
215
207
|
}
|
|
216
208
|
}
|
|
209
|
+
function colorStatus(status) {
|
|
210
|
+
const statusColor = status === "running" ? GREEN
|
|
211
|
+
: status === "crashed" ? RED
|
|
212
|
+
: YELLOW;
|
|
213
|
+
return `${statusColor}${status}${RESET}`;
|
|
214
|
+
}
|
|
215
|
+
function renderStartupLines(lines, prevLineCount, isTTY) {
|
|
216
|
+
if (!isTTY)
|
|
217
|
+
return lines.join("\n") + "\n";
|
|
218
|
+
let output = "";
|
|
219
|
+
if (prevLineCount > 0) {
|
|
220
|
+
output += `\x1b[${prevLineCount}A`;
|
|
221
|
+
}
|
|
222
|
+
for (const line of lines) {
|
|
223
|
+
output += `\x1b[2K${line}\n`;
|
|
224
|
+
}
|
|
225
|
+
return output;
|
|
226
|
+
}
|
|
217
227
|
/* v8 ignore start -- process liveness check: uses real process.kill(0), tested via deployment @preserve */
|
|
218
228
|
function defaultIsProcessAlive(pid) {
|
|
219
229
|
try {
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.createOutlookHttpReadHooks = createOutlookHttpReadHooks;
|
|
37
|
+
const path = __importStar(require("path"));
|
|
38
|
+
const outlook_read_1 = require("./outlook-read");
|
|
39
|
+
function createOutlookHttpReadHooks(options) {
|
|
40
|
+
const bundlesRoot = options.bundlesRoot;
|
|
41
|
+
const readOptions = bundlesRoot ? { bundlesRoot } : undefined;
|
|
42
|
+
const agentRoot = (agentName) => path.join(bundlesRoot ?? "", `${agentName}.ouro`);
|
|
43
|
+
return {
|
|
44
|
+
agentRoot,
|
|
45
|
+
readAgentSessions: options.readAgentSessions ?? ((agentName) => (0, outlook_read_1.readSessionInventory)(agentName, readOptions)),
|
|
46
|
+
readAgentTranscript: options.readAgentTranscript ?? ((agentName, friendId, channel, key) => (0, outlook_read_1.readSessionTranscript)(agentName, friendId, channel, key, readOptions)),
|
|
47
|
+
readAgentCoding: options.readAgentCoding ?? ((agentName) => (0, outlook_read_1.readCodingDeep)(agentRoot(agentName))),
|
|
48
|
+
readAgentAttention: options.readAgentAttention ?? ((agentName) => (0, outlook_read_1.readAttentionView)(agentName, readOptions)),
|
|
49
|
+
readAgentBridges: options.readAgentBridges ?? ((agentName) => (0, outlook_read_1.readBridgeInventory)(agentRoot(agentName))),
|
|
50
|
+
readAgentMemory: options.readAgentMemory ?? ((agentName) => (0, outlook_read_1.readMemoryView)(agentRoot(agentName))),
|
|
51
|
+
readAgentFriends: options.readAgentFriends ?? ((agentName) => (0, outlook_read_1.readFriendView)(agentName, readOptions)),
|
|
52
|
+
readAgentContinuity: options.readAgentContinuity ?? ((agentName) => (0, outlook_read_1.readOutlookContinuity)(agentRoot(agentName), agentName)),
|
|
53
|
+
readAgentOrientation: options.readAgentOrientation ?? ((agentName) => (0, outlook_read_1.readOrientationView)(agentRoot(agentName), agentName)),
|
|
54
|
+
readAgentObligations: options.readAgentObligations ?? ((agentName) => (0, outlook_read_1.readObligationDetailView)(agentRoot(agentName))),
|
|
55
|
+
readAgentChanges: options.readAgentChanges ?? ((agentName) => (0, outlook_read_1.readChangesView)(agentRoot(agentName))),
|
|
56
|
+
readAgentSelfFix: options.readAgentSelfFix ?? ((agentName) => (0, outlook_read_1.readSelfFixView)(agentRoot(agentName))),
|
|
57
|
+
readAgentMemoryDecisions: options.readAgentMemoryDecisions ?? ((agentName) => (0, outlook_read_1.readMemoryDecisionView)(agentRoot(agentName))),
|
|
58
|
+
readAgentHabits: options.readAgentHabits ?? ((agentName) => (0, outlook_read_1.readHabitView)(agentRoot(agentName))),
|
|
59
|
+
readDaemonHealth: options.readDaemonHealth ?? (() => (0, outlook_read_1.readDaemonHealthDeep)(options.healthPath)),
|
|
60
|
+
readLogs: options.readLogs ?? (() => (0, outlook_read_1.readLogView)(options.logPath ?? null)),
|
|
61
|
+
readDeskPrefs: (agentName) => (0, outlook_read_1.readDeskPrefs)(agentRoot(agentName)),
|
|
62
|
+
readNeedsMe: (agentName) => (0, outlook_read_1.readNeedsMeView)(agentName, readOptions),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.writeJson = writeJson;
|
|
4
|
+
function writeJson(response, statusCode, payload) {
|
|
5
|
+
response.writeHead(statusCode, { "content-type": "application/json; charset=utf-8" });
|
|
6
|
+
response.end(`${JSON.stringify(payload, null, 2)}\n`);
|
|
7
|
+
}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.createOutlookHttpRequestHandler = createOutlookHttpRequestHandler;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const outlook_http_response_1 = require("./outlook-http-response");
|
|
40
|
+
const outlook_http_static_1 = require("./outlook-http-static");
|
|
41
|
+
function createOutlookHttpRequestHandler(options) {
|
|
42
|
+
const staticFiles = options.staticFiles ?? { resolveSpaDistDir: outlook_http_static_1.resolveSpaDistDir, serveStaticFile: outlook_http_static_1.serveStaticFile };
|
|
43
|
+
return (request, response) => {
|
|
44
|
+
let pathname = (0, outlook_http_static_1.normalizeOutlookRequestPath)(request.url);
|
|
45
|
+
const origin = `http://${options.host}:${options.getPort()}`;
|
|
46
|
+
if (pathname.startsWith("/assets/")) {
|
|
47
|
+
const spaDir = staticFiles.resolveSpaDistDir();
|
|
48
|
+
if (spaDir) {
|
|
49
|
+
const assetPath = path.join(spaDir, pathname);
|
|
50
|
+
if (staticFiles.serveStaticFile(response, assetPath))
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
(0, outlook_http_response_1.writeJson)(response, 404, { ok: false, error: "asset not found" });
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (pathname === "/outlook") {
|
|
57
|
+
response.writeHead(301, { location: "/" });
|
|
58
|
+
response.end();
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
pathname = (0, outlook_http_static_1.normalizeLegacyOutlookApiPath)(pathname);
|
|
62
|
+
if (pathname === "/api/events") {
|
|
63
|
+
response.writeHead(200, { "content-type": "text/event-stream", "cache-control": "no-cache", "connection": "keep-alive", "access-control-allow-origin": "*" });
|
|
64
|
+
response.write(":ok\n\n");
|
|
65
|
+
options.sse.add(response);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (pathname === "/api/machine") {
|
|
69
|
+
const machine = options.readMachineState();
|
|
70
|
+
const machineView = options.readMachineView?.({ origin, machine });
|
|
71
|
+
(0, outlook_http_response_1.writeJson)(response, 200, machineView ?? machine);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (pathname === "/api/machine/health") {
|
|
75
|
+
const health = options.hooks.readDaemonHealth();
|
|
76
|
+
(0, outlook_http_response_1.writeJson)(response, 200, health ?? { status: "unavailable" });
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (pathname === "/api/machine/logs") {
|
|
80
|
+
(0, outlook_http_response_1.writeJson)(response, 200, options.hooks.readLogs());
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const agentMatch = /^\/api\/agents\/([^/]+)(?:\/(.+))?$/.exec(pathname);
|
|
84
|
+
if (agentMatch) {
|
|
85
|
+
handleAgentRoute(request, response, {
|
|
86
|
+
agent: decodeURIComponent(agentMatch[1]),
|
|
87
|
+
surface: agentMatch[2] ?? null,
|
|
88
|
+
options,
|
|
89
|
+
});
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const spaDir = staticFiles.resolveSpaDistDir();
|
|
93
|
+
if (spaDir) {
|
|
94
|
+
if (staticFiles.serveStaticFile(response, path.join(spaDir, "index.html")))
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
(0, outlook_http_response_1.writeJson)(response, 404, { ok: false, error: `not found: ${pathname}` });
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function handleAgentRoute(request, response, context) {
|
|
101
|
+
const { agent, surface, options } = context;
|
|
102
|
+
if (!surface) {
|
|
103
|
+
const view = options.readAgentView?.(agent);
|
|
104
|
+
if (view) {
|
|
105
|
+
(0, outlook_http_response_1.writeJson)(response, 200, view);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const state = options.readAgentState(agent);
|
|
109
|
+
if (!state) {
|
|
110
|
+
(0, outlook_http_response_1.writeJson)(response, 404, { ok: false, error: `unknown agent: ${agent}` });
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
(0, outlook_http_response_1.writeJson)(response, 200, state);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (surface === "sessions") {
|
|
117
|
+
(0, outlook_http_response_1.writeJson)(response, 200, options.hooks.readAgentSessions(agent));
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
const transcriptMatch = /^sessions\/([^/]+)\/([^/]+)\/([^/]+)$/.exec(surface);
|
|
121
|
+
if (transcriptMatch) {
|
|
122
|
+
const friendId = decodeURIComponent(transcriptMatch[1]);
|
|
123
|
+
const channel = decodeURIComponent(transcriptMatch[2]);
|
|
124
|
+
const key = decodeURIComponent(transcriptMatch[3]);
|
|
125
|
+
const transcript = options.hooks.readAgentTranscript(agent, friendId, channel, key);
|
|
126
|
+
if (!transcript) {
|
|
127
|
+
(0, outlook_http_response_1.writeJson)(response, 404, { ok: false, error: "session not found" });
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
(0, outlook_http_response_1.writeJson)(response, 200, transcript);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
if (surface === "coding") {
|
|
134
|
+
(0, outlook_http_response_1.writeJson)(response, 200, options.hooks.readAgentCoding(agent));
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
if (surface === "attention") {
|
|
138
|
+
(0, outlook_http_response_1.writeJson)(response, 200, options.hooks.readAgentAttention(agent));
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
if (surface === "bridges") {
|
|
142
|
+
(0, outlook_http_response_1.writeJson)(response, 200, options.hooks.readAgentBridges(agent));
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
if (surface === "memory") {
|
|
146
|
+
(0, outlook_http_response_1.writeJson)(response, 200, options.hooks.readAgentMemory(agent));
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (surface === "friends") {
|
|
150
|
+
(0, outlook_http_response_1.writeJson)(response, 200, options.hooks.readAgentFriends(agent));
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (surface === "continuity") {
|
|
154
|
+
(0, outlook_http_response_1.writeJson)(response, 200, options.hooks.readAgentContinuity(agent));
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (surface === "orientation") {
|
|
158
|
+
(0, outlook_http_response_1.writeJson)(response, 200, options.hooks.readAgentOrientation(agent));
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (surface === "obligations") {
|
|
162
|
+
(0, outlook_http_response_1.writeJson)(response, 200, options.hooks.readAgentObligations(agent));
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if (surface === "changes") {
|
|
166
|
+
(0, outlook_http_response_1.writeJson)(response, 200, options.hooks.readAgentChanges(agent));
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
if (surface === "self-fix") {
|
|
170
|
+
(0, outlook_http_response_1.writeJson)(response, 200, options.hooks.readAgentSelfFix(agent));
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
if (surface === "memory-decisions") {
|
|
174
|
+
(0, outlook_http_response_1.writeJson)(response, 200, options.hooks.readAgentMemoryDecisions(agent));
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
if (surface === "dismiss-obligation" && request.method === "POST") {
|
|
178
|
+
handleDismissObligation(request, response, options.hooks.agentRoot(agent));
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
if (surface === "desk-prefs") {
|
|
182
|
+
(0, outlook_http_response_1.writeJson)(response, 200, options.hooks.readDeskPrefs(agent));
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
if (surface === "needs-me") {
|
|
186
|
+
(0, outlook_http_response_1.writeJson)(response, 200, options.hooks.readNeedsMe(agent));
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
if (surface === "habits") {
|
|
190
|
+
(0, outlook_http_response_1.writeJson)(response, 200, options.hooks.readAgentHabits(agent));
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
if (surface === "inner-transcript") {
|
|
194
|
+
const transcript = options.hooks.readAgentTranscript(agent, "self", "inner", "dialog");
|
|
195
|
+
(0, outlook_http_response_1.writeJson)(response, 200, transcript ?? { messageCount: 0, messages: [] });
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
(0, outlook_http_response_1.writeJson)(response, 404, { ok: false, error: `unknown agent surface: ${surface}` });
|
|
199
|
+
}
|
|
200
|
+
function handleDismissObligation(request, response, agentRoot) {
|
|
201
|
+
let body = "";
|
|
202
|
+
request.on("data", (chunk) => {
|
|
203
|
+
body += chunk;
|
|
204
|
+
});
|
|
205
|
+
request.on("end", () => {
|
|
206
|
+
try {
|
|
207
|
+
const { obligationId } = JSON.parse(body);
|
|
208
|
+
if (!obligationId) {
|
|
209
|
+
(0, outlook_http_response_1.writeJson)(response, 400, { ok: false, error: "obligationId required" });
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
const prefsPath = path.join(agentRoot, "state", "outlook-prefs.json");
|
|
213
|
+
let prefs = {};
|
|
214
|
+
try {
|
|
215
|
+
prefs = JSON.parse(fs.readFileSync(prefsPath, "utf-8"));
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
// Missing or malformed prefs start from a clean preference object.
|
|
219
|
+
}
|
|
220
|
+
const dismissed = Array.isArray(prefs.dismissedObligations) ? prefs.dismissedObligations : [];
|
|
221
|
+
if (!dismissed.includes(obligationId))
|
|
222
|
+
dismissed.push(obligationId);
|
|
223
|
+
prefs.dismissedObligations = dismissed;
|
|
224
|
+
fs.mkdirSync(path.dirname(prefsPath), { recursive: true });
|
|
225
|
+
fs.writeFileSync(prefsPath, `${JSON.stringify(prefs, null, 2)}\n`, "utf-8");
|
|
226
|
+
(0, outlook_http_response_1.writeJson)(response, 200, { ok: true, dismissed: dismissed.length });
|
|
227
|
+
}
|
|
228
|
+
catch (error) {
|
|
229
|
+
(0, outlook_http_response_1.writeJson)(response, 500, { ok: false, error: String(error) });
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.normalizeOutlookRequestPath = normalizeOutlookRequestPath;
|
|
37
|
+
exports.normalizeLegacyOutlookApiPath = normalizeLegacyOutlookApiPath;
|
|
38
|
+
exports.resolveSpaDistDir = resolveSpaDistDir;
|
|
39
|
+
exports.serveStaticFile = serveStaticFile;
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
const MIME_TYPES = {
|
|
43
|
+
".html": "text/html",
|
|
44
|
+
".js": "application/javascript",
|
|
45
|
+
".css": "text/css",
|
|
46
|
+
".json": "application/json",
|
|
47
|
+
".svg": "image/svg+xml",
|
|
48
|
+
".png": "image/png",
|
|
49
|
+
".ico": "image/x-icon",
|
|
50
|
+
".woff2": "font/woff2",
|
|
51
|
+
};
|
|
52
|
+
function normalizeOutlookRequestPath(urlValue = "/") {
|
|
53
|
+
const parsed = new URL(urlValue, "http://127.0.0.1");
|
|
54
|
+
const normalizedPath = parsed.pathname.replace(/\/+$/, "");
|
|
55
|
+
if (normalizedPath.length === 0)
|
|
56
|
+
return "/";
|
|
57
|
+
return normalizedPath;
|
|
58
|
+
}
|
|
59
|
+
function normalizeLegacyOutlookApiPath(pathname) {
|
|
60
|
+
if (pathname.startsWith("/outlook/api/"))
|
|
61
|
+
return pathname.slice("/outlook".length);
|
|
62
|
+
if (pathname === "/outlook/api")
|
|
63
|
+
return "/api";
|
|
64
|
+
return pathname;
|
|
65
|
+
}
|
|
66
|
+
function defaultSpaDistCandidates() {
|
|
67
|
+
return [
|
|
68
|
+
path.resolve(__dirname, "..", "..", "..", "packages", "outlook-ui", "dist"),
|
|
69
|
+
path.resolve(__dirname, "..", "..", "packages", "outlook-ui", "dist"),
|
|
70
|
+
path.resolve(__dirname, "..", "..", "..", "..", "packages", "outlook-ui", "dist"),
|
|
71
|
+
path.resolve(__dirname, "..", "..", "outlook-ui"),
|
|
72
|
+
path.resolve(__dirname, "..", "outlook-ui"),
|
|
73
|
+
];
|
|
74
|
+
}
|
|
75
|
+
function resolveSpaDistDir(candidates = defaultSpaDistCandidates()) {
|
|
76
|
+
for (const candidate of candidates) {
|
|
77
|
+
if (fs.existsSync(path.join(candidate, "index.html")))
|
|
78
|
+
return candidate;
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
function serveStaticFile(response, filePath) {
|
|
83
|
+
try {
|
|
84
|
+
if (!fs.existsSync(filePath))
|
|
85
|
+
return false;
|
|
86
|
+
const ext = path.extname(filePath);
|
|
87
|
+
const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
|
|
88
|
+
const content = fs.readFileSync(filePath);
|
|
89
|
+
response.writeHead(200, {
|
|
90
|
+
"content-type": contentType,
|
|
91
|
+
"cache-control": ext === ".html" ? "no-cache" : "public, max-age=31536000, immutable",
|
|
92
|
+
});
|
|
93
|
+
response.end(content);
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.createSseBroadcaster = createSseBroadcaster;
|
|
37
|
+
exports.createStateChangedBroadcast = createStateChangedBroadcast;
|
|
38
|
+
exports.createBundleWatcher = createBundleWatcher;
|
|
39
|
+
const fs = __importStar(require("fs"));
|
|
40
|
+
const DEFAULT_BUNDLE_WATCHER_DEPS = {
|
|
41
|
+
existsSync: fs.existsSync,
|
|
42
|
+
watch: fs.watch,
|
|
43
|
+
setTimeout,
|
|
44
|
+
clearTimeout,
|
|
45
|
+
};
|
|
46
|
+
function createSseBroadcaster() {
|
|
47
|
+
let nextId = 1;
|
|
48
|
+
const clients = new Set();
|
|
49
|
+
function add(response) {
|
|
50
|
+
const client = { id: nextId++, response };
|
|
51
|
+
clients.add(client);
|
|
52
|
+
response.on("close", () => clients.delete(client));
|
|
53
|
+
return client;
|
|
54
|
+
}
|
|
55
|
+
function broadcast(event, data = {}) {
|
|
56
|
+
const payload = `event: ${event}\ndata: ${JSON.stringify(data)}\n\n`;
|
|
57
|
+
for (const client of clients) {
|
|
58
|
+
try {
|
|
59
|
+
client.response.write(payload);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
clients.delete(client);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function disconnectAll() {
|
|
67
|
+
for (const client of clients) {
|
|
68
|
+
try {
|
|
69
|
+
client.response.end();
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
// The client may already have closed between the loop snapshot and end.
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
clients.clear();
|
|
76
|
+
}
|
|
77
|
+
return { add, broadcast, disconnectAll };
|
|
78
|
+
}
|
|
79
|
+
function createStateChangedBroadcast(sse) {
|
|
80
|
+
return () => {
|
|
81
|
+
sse.broadcast("state-changed", { at: new Date().toISOString() });
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function createBundleWatcher(bundlesRoot, onChange, deps = DEFAULT_BUNDLE_WATCHER_DEPS) {
|
|
85
|
+
const watchers = [];
|
|
86
|
+
let debounceTimer = null;
|
|
87
|
+
const debounceMs = 500;
|
|
88
|
+
function debouncedOnChange() {
|
|
89
|
+
if (debounceTimer)
|
|
90
|
+
deps.clearTimeout(debounceTimer);
|
|
91
|
+
debounceTimer = deps.setTimeout(onChange, debounceMs);
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
if (deps.existsSync(bundlesRoot)) {
|
|
95
|
+
watchers.push(deps.watch(bundlesRoot, { recursive: true }, debouncedOnChange));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
// Watching is best-effort; manual broadcasts still keep Outlook usable.
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
stop() {
|
|
103
|
+
if (debounceTimer)
|
|
104
|
+
deps.clearTimeout(debounceTimer);
|
|
105
|
+
for (const watcher of watchers) {
|
|
106
|
+
try {
|
|
107
|
+
watcher.close();
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
// Already closed.
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
watchers.length = 0;
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
}
|
|
@@ -34,140 +34,12 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.startOutlookHttpServer = startOutlookHttpServer;
|
|
37
|
-
const fs = __importStar(require("fs"));
|
|
38
37
|
const http = __importStar(require("http"));
|
|
39
|
-
const path = __importStar(require("path"));
|
|
40
38
|
const runtime_1 = require("../../nerves/runtime");
|
|
41
39
|
const outlook_read_1 = require("./outlook-read");
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
function add(response) {
|
|
46
|
-
const client = { id: nextId++, response };
|
|
47
|
-
clients.add(client);
|
|
48
|
-
response.on("close", () => clients.delete(client));
|
|
49
|
-
return client;
|
|
50
|
-
}
|
|
51
|
-
function broadcast(event, data = {}) {
|
|
52
|
-
const payload = `event: ${event}\ndata: ${JSON.stringify(data)}\n\n`;
|
|
53
|
-
for (const client of clients) {
|
|
54
|
-
try {
|
|
55
|
-
client.response.write(payload);
|
|
56
|
-
/* v8 ignore start */
|
|
57
|
-
}
|
|
58
|
-
catch {
|
|
59
|
-
clients.delete(client);
|
|
60
|
-
}
|
|
61
|
-
/* v8 ignore stop */
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
function disconnectAll() {
|
|
65
|
-
for (const client of clients) {
|
|
66
|
-
try {
|
|
67
|
-
client.response.end();
|
|
68
|
-
/* v8 ignore start */
|
|
69
|
-
}
|
|
70
|
-
catch {
|
|
71
|
-
/* already closed */
|
|
72
|
-
}
|
|
73
|
-
/* v8 ignore stop */
|
|
74
|
-
}
|
|
75
|
-
clients.clear();
|
|
76
|
-
}
|
|
77
|
-
return { add, broadcast, disconnectAll };
|
|
78
|
-
}
|
|
79
|
-
/* v8 ignore start — filesystem watcher, tested via integration */
|
|
80
|
-
function createBundleWatcher(bundlesRoot, onChange) {
|
|
81
|
-
const watchers = [];
|
|
82
|
-
let debounceTimer = null;
|
|
83
|
-
const DEBOUNCE_MS = 500;
|
|
84
|
-
function debouncedOnChange() {
|
|
85
|
-
if (debounceTimer)
|
|
86
|
-
clearTimeout(debounceTimer);
|
|
87
|
-
debounceTimer = setTimeout(onChange, DEBOUNCE_MS);
|
|
88
|
-
}
|
|
89
|
-
try {
|
|
90
|
-
if (fs.existsSync(bundlesRoot)) {
|
|
91
|
-
const watcher = fs.watch(bundlesRoot, { recursive: true }, debouncedOnChange);
|
|
92
|
-
watchers.push(watcher);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
catch {
|
|
96
|
-
// watch not available — SSE will rely on manual broadcast
|
|
97
|
-
}
|
|
98
|
-
return {
|
|
99
|
-
stop() {
|
|
100
|
-
if (debounceTimer)
|
|
101
|
-
clearTimeout(debounceTimer);
|
|
102
|
-
for (const w of watchers)
|
|
103
|
-
try {
|
|
104
|
-
w.close();
|
|
105
|
-
}
|
|
106
|
-
catch { /* ignore */ }
|
|
107
|
-
watchers.length = 0;
|
|
108
|
-
},
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
/* v8 ignore stop */
|
|
112
|
-
function writeJson(response, statusCode, payload) {
|
|
113
|
-
response.writeHead(statusCode, { "content-type": "application/json; charset=utf-8" });
|
|
114
|
-
response.end(`${JSON.stringify(payload, null, 2)}\n`);
|
|
115
|
-
}
|
|
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
|
-
}
|
|
162
|
-
}
|
|
163
|
-
/* v8 ignore stop */
|
|
164
|
-
function normalizePath(urlValue = "/") {
|
|
165
|
-
const parsed = new URL(urlValue, "http://127.0.0.1");
|
|
166
|
-
const normalizedPath = parsed.pathname.replace(/\/+$/, "");
|
|
167
|
-
if (normalizedPath.length === 0)
|
|
168
|
-
return "/";
|
|
169
|
-
return normalizedPath;
|
|
170
|
-
}
|
|
40
|
+
const outlook_http_hooks_1 = require("./outlook-http-hooks");
|
|
41
|
+
const outlook_http_routes_1 = require("./outlook-http-routes");
|
|
42
|
+
const outlook_http_transport_1 = require("./outlook-http-transport");
|
|
171
43
|
async function startOutlookHttpServer(options = {}) {
|
|
172
44
|
const host = options.host ?? "127.0.0.1";
|
|
173
45
|
const port = options.port ?? 0;
|
|
@@ -175,238 +47,26 @@ async function startOutlookHttpServer(options = {}) {
|
|
|
175
47
|
const opts = bundlesRoot ? { bundlesRoot } : undefined;
|
|
176
48
|
const readMachineState = options.readMachineState ?? (() => (0, outlook_read_1.readOutlookMachineState)(opts));
|
|
177
49
|
const readMachineView = options.readMachineView;
|
|
178
|
-
/* v8 ignore start */
|
|
179
50
|
const readAgentState = options.readAgentState ?? ((agentName) => {
|
|
180
51
|
if (opts)
|
|
181
52
|
return (0, outlook_read_1.readOutlookAgentState)(agentName, opts);
|
|
182
53
|
return (0, outlook_read_1.readOutlookAgentState)(agentName);
|
|
183
54
|
});
|
|
184
|
-
/* v8 ignore stop */
|
|
185
55
|
const readAgentView = options.readAgentView;
|
|
186
|
-
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
readAgentOrientation: options.readAgentOrientation ?? ((agentName) => (0, outlook_read_1.readOrientationView)(agentRoot(agentName), agentName)),
|
|
201
|
-
readAgentObligations: options.readAgentObligations ?? ((agentName) => (0, outlook_read_1.readObligationDetailView)(agentRoot(agentName))),
|
|
202
|
-
readAgentChanges: options.readAgentChanges ?? ((agentName) => (0, outlook_read_1.readChangesView)(agentRoot(agentName))),
|
|
203
|
-
readAgentSelfFix: options.readAgentSelfFix ?? ((agentName) => (0, outlook_read_1.readSelfFixView)(agentRoot(agentName))),
|
|
204
|
-
readAgentMemoryDecisions: options.readAgentMemoryDecisions ?? ((agentName) => (0, outlook_read_1.readMemoryDecisionView)(agentRoot(agentName))),
|
|
205
|
-
readAgentHabits: options.readAgentHabits ?? ((agentName) => (0, outlook_read_1.readHabitView)(agentRoot(agentName))),
|
|
206
|
-
readDaemonHealth: options.readDaemonHealth ?? (() => (0, outlook_read_1.readDaemonHealthDeep)(options.healthPath)),
|
|
207
|
-
readLogs: options.readLogs ?? (() => (0, outlook_read_1.readLogView)(options.logPath ?? null)),
|
|
208
|
-
};
|
|
209
|
-
/* v8 ignore stop */
|
|
210
|
-
const sse = createSseBroadcaster();
|
|
211
|
-
/* v8 ignore start — watcher callback fires on filesystem changes */
|
|
212
|
-
const bundleWatcher = bundlesRoot ? createBundleWatcher(bundlesRoot, () => {
|
|
213
|
-
sse.broadcast("state-changed", { at: new Date().toISOString() });
|
|
214
|
-
}) : null;
|
|
215
|
-
/* v8 ignore stop */
|
|
216
|
-
const server = http.createServer((request, response) => {
|
|
217
|
-
let pathname = normalizePath(request.url);
|
|
218
|
-
const origin = `http://${host}:${server.address().port}`;
|
|
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
|
|
233
|
-
if (pathname === "/outlook") {
|
|
234
|
-
response.writeHead(301, { location: "/" });
|
|
235
|
-
response.end();
|
|
236
|
-
return;
|
|
237
|
-
}
|
|
238
|
-
// Compatibility alias: /outlook/api/* → /api/*
|
|
239
|
-
/* v8 ignore start -- legacy compat path: tested via integration @preserve */
|
|
240
|
-
if (pathname.startsWith("/outlook/api/")) {
|
|
241
|
-
pathname = pathname.slice("/outlook".length);
|
|
242
|
-
}
|
|
243
|
-
else if (pathname === "/outlook/api") {
|
|
244
|
-
pathname = "/api";
|
|
245
|
-
}
|
|
246
|
-
/* v8 ignore stop */
|
|
247
|
-
// SSE event stream
|
|
248
|
-
if (pathname === "/api/events") {
|
|
249
|
-
response.writeHead(200, { "content-type": "text/event-stream", "cache-control": "no-cache", "connection": "keep-alive", "access-control-allow-origin": "*" });
|
|
250
|
-
response.write(":ok\n\n");
|
|
251
|
-
sse.add(response);
|
|
252
|
-
return;
|
|
253
|
-
}
|
|
254
|
-
if (pathname === "/api/machine") {
|
|
255
|
-
const machine = readMachineState();
|
|
256
|
-
const machineView = readMachineView?.({ origin, machine });
|
|
257
|
-
writeJson(response, 200, machineView ?? machine);
|
|
258
|
-
return;
|
|
259
|
-
}
|
|
260
|
-
if (pathname === "/api/machine/health") {
|
|
261
|
-
const health = hooks.readDaemonHealth();
|
|
262
|
-
writeJson(response, 200, health ?? { status: "unavailable" });
|
|
263
|
-
return;
|
|
264
|
-
}
|
|
265
|
-
if (pathname === "/api/machine/logs") {
|
|
266
|
-
writeJson(response, 200, hooks.readLogs());
|
|
267
|
-
return;
|
|
268
|
-
}
|
|
269
|
-
// Agent-level endpoints: /api/agents/:agent[/:surface[/:params...]]
|
|
270
|
-
const agentMatch = /^\/api\/agents\/([^/]+)(?:\/(.+))?$/.exec(pathname);
|
|
271
|
-
if (agentMatch) {
|
|
272
|
-
const agent = decodeURIComponent(agentMatch[1]);
|
|
273
|
-
const surface = agentMatch[2] ?? null;
|
|
274
|
-
if (!surface) {
|
|
275
|
-
const view = readAgentView?.(agent);
|
|
276
|
-
if (view) {
|
|
277
|
-
writeJson(response, 200, view);
|
|
278
|
-
return;
|
|
279
|
-
}
|
|
280
|
-
const state = readAgentState(agent);
|
|
281
|
-
if (!state) {
|
|
282
|
-
writeJson(response, 404, { ok: false, error: `unknown agent: ${agent}` });
|
|
283
|
-
return;
|
|
284
|
-
}
|
|
285
|
-
writeJson(response, 200, state);
|
|
286
|
-
return;
|
|
287
|
-
}
|
|
288
|
-
if (surface === "sessions") {
|
|
289
|
-
writeJson(response, 200, hooks.readAgentSessions(agent));
|
|
290
|
-
return;
|
|
291
|
-
}
|
|
292
|
-
const transcriptMatch = /^sessions\/([^/]+)\/([^/]+)\/([^/]+)$/.exec(surface);
|
|
293
|
-
if (transcriptMatch) {
|
|
294
|
-
const friendId = decodeURIComponent(transcriptMatch[1]);
|
|
295
|
-
const channel = decodeURIComponent(transcriptMatch[2]);
|
|
296
|
-
const key = decodeURIComponent(transcriptMatch[3]);
|
|
297
|
-
const transcript = hooks.readAgentTranscript(agent, friendId, channel, key);
|
|
298
|
-
if (!transcript) {
|
|
299
|
-
writeJson(response, 404, { ok: false, error: "session not found" });
|
|
300
|
-
return;
|
|
301
|
-
}
|
|
302
|
-
writeJson(response, 200, transcript);
|
|
303
|
-
return;
|
|
304
|
-
}
|
|
305
|
-
if (surface === "coding") {
|
|
306
|
-
writeJson(response, 200, hooks.readAgentCoding(agent));
|
|
307
|
-
return;
|
|
308
|
-
}
|
|
309
|
-
if (surface === "attention") {
|
|
310
|
-
writeJson(response, 200, hooks.readAgentAttention(agent));
|
|
311
|
-
return;
|
|
312
|
-
}
|
|
313
|
-
if (surface === "bridges") {
|
|
314
|
-
writeJson(response, 200, hooks.readAgentBridges(agent));
|
|
315
|
-
return;
|
|
316
|
-
}
|
|
317
|
-
if (surface === "memory") {
|
|
318
|
-
writeJson(response, 200, hooks.readAgentMemory(agent));
|
|
319
|
-
return;
|
|
320
|
-
}
|
|
321
|
-
if (surface === "friends") {
|
|
322
|
-
writeJson(response, 200, hooks.readAgentFriends(agent));
|
|
323
|
-
return;
|
|
324
|
-
}
|
|
325
|
-
if (surface === "continuity") {
|
|
326
|
-
writeJson(response, 200, hooks.readAgentContinuity(agent));
|
|
327
|
-
return;
|
|
328
|
-
}
|
|
329
|
-
if (surface === "orientation") {
|
|
330
|
-
writeJson(response, 200, hooks.readAgentOrientation(agent));
|
|
331
|
-
return;
|
|
332
|
-
}
|
|
333
|
-
if (surface === "obligations") {
|
|
334
|
-
writeJson(response, 200, hooks.readAgentObligations(agent));
|
|
335
|
-
return;
|
|
336
|
-
}
|
|
337
|
-
if (surface === "changes") {
|
|
338
|
-
writeJson(response, 200, hooks.readAgentChanges(agent));
|
|
339
|
-
return;
|
|
340
|
-
}
|
|
341
|
-
if (surface === "self-fix") {
|
|
342
|
-
writeJson(response, 200, hooks.readAgentSelfFix(agent));
|
|
343
|
-
return;
|
|
344
|
-
}
|
|
345
|
-
if (surface === "memory-decisions") {
|
|
346
|
-
writeJson(response, 200, hooks.readAgentMemoryDecisions(agent));
|
|
347
|
-
return;
|
|
348
|
-
}
|
|
349
|
-
/* v8 ignore start — desk prefs write + reads */
|
|
350
|
-
if (surface === "dismiss-obligation" && request.method === "POST") {
|
|
351
|
-
let body = "";
|
|
352
|
-
request.on("data", (chunk) => { body += chunk; });
|
|
353
|
-
request.on("end", () => {
|
|
354
|
-
try {
|
|
355
|
-
const { obligationId } = JSON.parse(body);
|
|
356
|
-
if (!obligationId) {
|
|
357
|
-
writeJson(response, 400, { ok: false, error: "obligationId required" });
|
|
358
|
-
return;
|
|
359
|
-
}
|
|
360
|
-
const prefsPath = path.join(agentRoot(agent), "state", "outlook-prefs.json");
|
|
361
|
-
let prefs = {};
|
|
362
|
-
try {
|
|
363
|
-
prefs = JSON.parse(fs.readFileSync(prefsPath, "utf-8"));
|
|
364
|
-
}
|
|
365
|
-
catch { /* new file */ }
|
|
366
|
-
const dismissed = Array.isArray(prefs.dismissedObligations) ? prefs.dismissedObligations : [];
|
|
367
|
-
if (!dismissed.includes(obligationId))
|
|
368
|
-
dismissed.push(obligationId);
|
|
369
|
-
prefs.dismissedObligations = dismissed;
|
|
370
|
-
fs.mkdirSync(path.dirname(prefsPath), { recursive: true });
|
|
371
|
-
fs.writeFileSync(prefsPath, JSON.stringify(prefs, null, 2) + "\n", "utf-8");
|
|
372
|
-
writeJson(response, 200, { ok: true, dismissed: dismissed.length });
|
|
373
|
-
}
|
|
374
|
-
catch (error) {
|
|
375
|
-
writeJson(response, 500, { ok: false, error: String(error) });
|
|
376
|
-
}
|
|
377
|
-
});
|
|
378
|
-
return;
|
|
379
|
-
}
|
|
380
|
-
if (surface === "desk-prefs") {
|
|
381
|
-
writeJson(response, 200, (0, outlook_read_1.readDeskPrefs)(agentRoot(agent)));
|
|
382
|
-
return;
|
|
383
|
-
}
|
|
384
|
-
if (surface === "needs-me") {
|
|
385
|
-
writeJson(response, 200, (0, outlook_read_1.readNeedsMeView)(agent, opts));
|
|
386
|
-
return;
|
|
387
|
-
}
|
|
388
|
-
/* v8 ignore stop */
|
|
389
|
-
if (surface === "habits") {
|
|
390
|
-
writeJson(response, 200, hooks.readAgentHabits(agent));
|
|
391
|
-
return;
|
|
392
|
-
}
|
|
393
|
-
if (surface === "inner-transcript") {
|
|
394
|
-
const transcript = hooks.readAgentTranscript(agent, "self", "inner", "dialog");
|
|
395
|
-
writeJson(response, 200, transcript ?? { messageCount: 0, messages: [] });
|
|
396
|
-
return;
|
|
397
|
-
}
|
|
398
|
-
writeJson(response, 404, { ok: false, error: `unknown agent surface: ${surface}` });
|
|
399
|
-
return;
|
|
400
|
-
}
|
|
401
|
-
/* v8 ignore start — SPA fallback for client-side routing @preserve */
|
|
402
|
-
const spaDir = resolveSpaDistDir();
|
|
403
|
-
if (spaDir) {
|
|
404
|
-
if (serveStaticFile(response, path.join(spaDir, "index.html")))
|
|
405
|
-
return;
|
|
406
|
-
}
|
|
407
|
-
writeJson(response, 404, { ok: false, error: `not found: ${pathname}` });
|
|
408
|
-
/* v8 ignore stop */
|
|
409
|
-
});
|
|
56
|
+
const hooks = (0, outlook_http_hooks_1.createOutlookHttpReadHooks)(options);
|
|
57
|
+
const sse = (0, outlook_http_transport_1.createSseBroadcaster)();
|
|
58
|
+
const bundleWatcher = bundlesRoot ? (0, outlook_http_transport_1.createBundleWatcher)(bundlesRoot, (0, outlook_http_transport_1.createStateChangedBroadcast)(sse)) : null;
|
|
59
|
+
let server;
|
|
60
|
+
server = http.createServer((0, outlook_http_routes_1.createOutlookHttpRequestHandler)({
|
|
61
|
+
host,
|
|
62
|
+
getPort: () => server.address().port,
|
|
63
|
+
readMachineState,
|
|
64
|
+
readMachineView,
|
|
65
|
+
readAgentState,
|
|
66
|
+
readAgentView,
|
|
67
|
+
hooks,
|
|
68
|
+
sse,
|
|
69
|
+
}));
|
|
410
70
|
await new Promise((resolve, reject) => {
|
|
411
71
|
server.once("error", reject);
|
|
412
72
|
server.listen(port, host, () => resolve());
|
|
@@ -95,6 +95,13 @@ const DISPATCH_EXEMPT_PATTERNS = [
|
|
|
95
95
|
// consumed by server readers and the UI. Outlook read/render modules own
|
|
96
96
|
// the observability for these projections.
|
|
97
97
|
"heart/outlook/outlook-types",
|
|
98
|
+
// Outlook HTTP helper modules: route/static/transport/hook seams are
|
|
99
|
+
// dispatched by outlook-http.ts, whose server lifecycle owns observability.
|
|
100
|
+
"heart/outlook/outlook-http-transport",
|
|
101
|
+
"heart/outlook/outlook-http-static",
|
|
102
|
+
"heart/outlook/outlook-http-hooks",
|
|
103
|
+
"heart/outlook/outlook-http-routes",
|
|
104
|
+
"heart/outlook/outlook-http-response",
|
|
98
105
|
];
|
|
99
106
|
function isDispatchExempt(filePath) {
|
|
100
107
|
return DISPATCH_EXEMPT_PATTERNS.some((pattern) => filePath.includes(pattern));
|