@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 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(`${spinner} ${BOLD}waiting for agents${RESET} ${DIM}(${elapsedSec}s)${RESET}`);
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 statusColor = worker.status === "running" ? GREEN
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
- let output = "";
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(`${spinner} ${BOLD}waiting for daemon${RESET} ${DIM}(${elapsedSec}s)${RESET}`);
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 output;
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
- function createSseBroadcaster() {
43
- let nextId = 1;
44
- const clients = new Set();
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
- /* v8 ignore start — default hook wiring, tested via integration */
187
- const agentRoot = (agentName) => {
188
- const base = bundlesRoot ?? "";
189
- return path.join(base, `${agentName}.ouro`);
190
- };
191
- const hooks = {
192
- readAgentSessions: options.readAgentSessions ?? ((agentName) => (0, outlook_read_1.readSessionInventory)(agentName, bundlesRoot ? { bundlesRoot } : undefined)),
193
- readAgentTranscript: options.readAgentTranscript ?? ((agentName, friendId, channel, key) => (0, outlook_read_1.readSessionTranscript)(agentName, friendId, channel, key, bundlesRoot ? { bundlesRoot } : undefined)),
194
- readAgentCoding: options.readAgentCoding ?? ((agentName) => (0, outlook_read_1.readCodingDeep)(agentRoot(agentName))),
195
- readAgentAttention: options.readAgentAttention ?? ((agentName) => (0, outlook_read_1.readAttentionView)(agentName, bundlesRoot ? { bundlesRoot } : undefined)),
196
- readAgentBridges: options.readAgentBridges ?? ((agentName) => (0, outlook_read_1.readBridgeInventory)(agentRoot(agentName))),
197
- readAgentMemory: options.readAgentMemory ?? ((agentName) => (0, outlook_read_1.readMemoryView)(agentRoot(agentName))),
198
- readAgentFriends: options.readAgentFriends ?? ((agentName) => (0, outlook_read_1.readFriendView)(agentName, bundlesRoot ? { bundlesRoot } : undefined)),
199
- readAgentContinuity: options.readAgentContinuity ?? ((agentName) => (0, outlook_read_1.readOutlookContinuity)(agentRoot(agentName), agentName)),
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));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.325",
3
+ "version": "0.1.0-alpha.327",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",