@ouro.bot/cli 0.1.0-alpha.159 → 0.1.0-alpha.160

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,17 @@
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.160",
6
+ "changes": [
7
+ "feat(outlook): total inspectability expansion \u2014 14 API endpoints, session x-ray, obligation chain tracing, coding deep inspection, attention/pending queue, bridge inventory, habit triage, memory/journal, friend economics, SSE live updates",
8
+ "feat(outlook): React SPA with Catalyst UI \u2014 sidebar layout, 7-tab agent inspector, chat bubble transcripts with mechanism-tool awareness, hash URL routing",
9
+ "feat(outlook): agent desk customization \u2014 carrying block, pinned constellations, tab ordering, starred friends, status line, closure memory, needs-me urgency queue",
10
+ "feat(outlook): nerves observation layer \u2014 shared typed readers, eliminates bespoke type mirrors",
11
+ "fix(auth): ouro auth for openai-codex always refreshes token, provider verification uses correct endpoint",
12
+ "fix(daemon): use OUTLOOK_DEFAULT_PORT (6876) for Outlook server"
13
+ ]
14
+ },
4
15
  {
5
16
  "version": "0.1.0-alpha.159",
6
17
  "changes": [
@@ -14,12 +25,12 @@
14
25
  {
15
26
  "version": "0.1.0-alpha.158",
16
27
  "changes": [
17
- "Task scanner v2: explicit identity via kind: task field. Scanner only parses files that declare themselves as task cards — doing docs, planning docs, and artifacts silently skipped. Eliminates 184 false parse errors on real bundles.",
28
+ "Task scanner v2: explicit identity via kind: task field. Scanner only parses files that declare themselves as task cards \u2014 doing docs, planning docs, and artifacts silently skipped. Eliminates 184 false parse errors on real bundles.",
18
29
  "Typed issue model: every scanner issue has a code, description, proposed fix, confidence (safe/needs_review), and category (live/migration). Replaces flat parseErrors/invalidFilenames arrays.",
19
30
  "Board health line: compact board shows health: clean or health: 1 live, 10 migration. Live vs migration split prevents cleanup noise from looking like breakage.",
20
31
  "Fix command: ouro task fix (dry-run), ouro task fix --safe (apply deterministic fixes), ouro task fix <id> (inspect/apply individual issues). Currently auto-fixes schema-missing-kind.",
21
32
  "Cancelled status: new terminal state reachable from any active status. Auto-archives with work directory, hidden from active board view.",
22
- "Derived child_tasks: computed at scan time from parent_task links. child_tasks removed from authored schema — no more hand-maintained stale arrays.",
33
+ "Derived child_tasks: computed at scan time from parent_task links. child_tasks removed from authored schema \u2014 no more hand-maintained stale arrays.",
23
34
  "Work directory awareness: same-stem directories detected and listed on TaskFile (hasWorkDir, workDirFiles). Scanner never descends into them.",
24
35
  "Collection root clutter detection: non-task support docs at collection root summarized as one aggregated migration issue per collection.",
25
36
  "Root-only scanning: flat directory reads replace recursive walks. Faster and correct."
@@ -100,6 +100,7 @@ function parseStatusPayload(data) {
100
100
  daemon: stringField(overview.daemon) ?? "unknown",
101
101
  health: stringField(overview.health) ?? "unknown",
102
102
  socketPath: stringField(overview.socketPath) ?? "unknown",
103
+ outlookUrl: stringField(overview.outlookUrl) ?? "unavailable",
103
104
  version: stringField(overview.version) ?? "unknown",
104
105
  lastUpdated: stringField(overview.lastUpdated) ?? "unknown",
105
106
  repoRoot: stringField(overview.repoRoot) ?? "unknown",
@@ -173,7 +174,9 @@ function humanizeSenseName(sense, label) {
173
174
  }
174
175
  function formatTable(headers, rows) {
175
176
  const widths = headers.map((header, index) => Math.max(header.length, ...rows.map((row) => row[index].length)));
176
- const renderRow = (row) => `| ${row.map((cell, index) => cell.padEnd(widths[index])).join(" | ")} |`;
177
+ const renderRow = (row) => `| ${row.map((cell, index) => (index === row.length - 1
178
+ ? cell
179
+ : cell.padEnd(widths[index]))).join(" | ")} |`;
177
180
  const divider = `|-${widths.map((width) => "-".repeat(width)).join("-|-")}-|`;
178
181
  return [
179
182
  renderRow(headers),
@@ -190,6 +193,7 @@ function formatDaemonStatusOutput(response, fallback) {
190
193
  ["Socket", payload.overview.socketPath],
191
194
  ["Version", payload.overview.version],
192
195
  ["Last Updated", payload.overview.lastUpdated],
196
+ ["Outlook", payload.overview.outlookUrl],
193
197
  ["Entry Path", payload.overview.entryPath],
194
198
  ["Mode", payload.overview.mode],
195
199
  ["Workers", String(payload.overview.workerCount)],
@@ -289,6 +293,7 @@ function usage() {
289
293
  " ouro [up]",
290
294
  " ouro dev [--repo-path <path>] [--clone [--clone-path <path>]]",
291
295
  " ouro stop|down|status|logs|hatch",
296
+ " ouro outlook [--json]",
292
297
  " ouro -v|--version",
293
298
  " ouro config model --agent <name> <model-name>",
294
299
  " ouro config models --agent <name>",
@@ -340,6 +345,7 @@ function buildStoppedStatusPayload(socketPath) {
340
345
  daemon: "stopped",
341
346
  health: "warn",
342
347
  socketPath,
348
+ outlookUrl: "unavailable",
343
349
  version: metadata.version,
344
350
  lastUpdated: metadata.lastUpdated,
345
351
  repoRoot: metadata.repoRoot,
@@ -1126,6 +1132,8 @@ function parseOuroCommand(args) {
1126
1132
  return { kind: "daemon.status" };
1127
1133
  if (head === "logs")
1128
1134
  return { kind: "daemon.logs" };
1135
+ if (head === "outlook")
1136
+ return { kind: "outlook", ...(args.includes("--json") ? { json: true } : {}) };
1129
1137
  if (head === "hatch")
1130
1138
  return parseHatchCommand(args.slice(1));
1131
1139
  if (head === "auth")
@@ -2513,6 +2521,41 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
2513
2521
  deps.tailLogs();
2514
2522
  return "";
2515
2523
  }
2524
+ if (command.kind === "outlook") {
2525
+ let status;
2526
+ try {
2527
+ status = await deps.sendCommand(deps.socketPath, { kind: "daemon.status" });
2528
+ /* v8 ignore start — error path: daemon not running */
2529
+ }
2530
+ catch {
2531
+ const message = "daemon unavailable — start with `ouro up` first";
2532
+ deps.writeStdout(message);
2533
+ return message;
2534
+ }
2535
+ /* v8 ignore stop */
2536
+ const payload = parseStatusPayload(status.data);
2537
+ /* v8 ignore start -- ?? branch: outlookUrl always present in test fixtures */
2538
+ const outlookUrl = payload?.overview.outlookUrl ?? "unavailable";
2539
+ /* v8 ignore stop */
2540
+ if (!command.json) {
2541
+ deps.writeStdout(outlookUrl);
2542
+ return outlookUrl;
2543
+ }
2544
+ /* v8 ignore start — error path: outlook URL not available */
2545
+ if (outlookUrl === "unavailable") {
2546
+ deps.writeStdout(outlookUrl);
2547
+ return outlookUrl;
2548
+ }
2549
+ /* v8 ignore stop */
2550
+ /* v8 ignore start -- ?? branch: tests always inject fetchImpl */
2551
+ const fetchImpl = deps.fetchImpl ?? fetch;
2552
+ /* v8 ignore stop */
2553
+ const response = await fetchImpl(`${outlookUrl}/api/machine`);
2554
+ const data = await response.json();
2555
+ const text = JSON.stringify(data, null, 2);
2556
+ deps.writeStdout(text);
2557
+ return text;
2558
+ }
2516
2559
  // ── hook: handle Claude Code lifecycle hooks ──
2517
2560
  /* v8 ignore start -- hook handler: reads real stdin, sends to real daemon @preserve */
2518
2561
  if (command.kind === "hook") {
@@ -55,6 +55,10 @@ const pending_1 = require("../../mind/pending");
55
55
  const agent_service_1 = require("./agent-service");
56
56
  const channel_1 = require("../../mind/friends/channel");
57
57
  const mcp_manager_1 = require("../../repertoire/mcp-manager");
58
+ const outlook_http_1 = require("./outlook-http");
59
+ const outlook_types_1 = require("./outlook-types");
60
+ const outlook_read_1 = require("./outlook-read");
61
+ const outlook_view_1 = require("./outlook-view");
58
62
  const PIDFILE_PATH = path.join(os.homedir(), ".ouro-cli", "daemon.pids");
59
63
  /**
60
64
  * Kill all ouro processes from the previous daemon instance using the pidfile.
@@ -180,6 +184,7 @@ class OuroDaemon {
180
184
  bundlesRoot;
181
185
  mode;
182
186
  server = null;
187
+ outlookServer = null;
183
188
  constructor(options) {
184
189
  this.socketPath = options.socketPath;
185
190
  this.processManager = options.processManager;
@@ -190,6 +195,27 @@ class OuroDaemon {
190
195
  this.bundlesRoot = options.bundlesRoot ?? (0, identity_1.getAgentBundlesRoot)();
191
196
  this.mode = options.mode ?? "production";
192
197
  }
198
+ buildStatusPayload() {
199
+ const snapshots = this.processManager.listAgentSnapshots();
200
+ const workers = buildWorkerRows(snapshots);
201
+ const senses = this.senseManager?.listSenseRows() ?? [];
202
+ const repoRoot = (0, identity_1.getRepoRoot)();
203
+ return {
204
+ overview: {
205
+ daemon: "running",
206
+ health: workers.every((worker) => worker.status === "running") ? "ok" : "warn",
207
+ socketPath: this.socketPath,
208
+ outlookUrl: `${this.outlookServer?.origin ?? "http://127.0.0.1:0"}/outlook`,
209
+ ...(0, runtime_metadata_1.getRuntimeMetadata)(),
210
+ workerCount: workers.length,
211
+ senseCount: senses.length,
212
+ entryPath: path.join(repoRoot, "dist", "heart", "daemon", "daemon-entry.js"),
213
+ mode: (0, runtime_mode_1.detectRuntimeMode)(repoRoot),
214
+ },
215
+ workers,
216
+ senses,
217
+ };
218
+ }
193
219
  async start() {
194
220
  if (this.server)
195
221
  return;
@@ -267,6 +293,39 @@ class OuroDaemon {
267
293
  await this.scheduler.reconcile?.();
268
294
  await this.drainPendingBundleMessages();
269
295
  await this.drainPendingSenseMessages();
296
+ /* v8 ignore start — Outlook server startup, tested via outlook-http.test.ts */
297
+ if (!this.outlookServer) {
298
+ this.outlookServer = await (0, outlook_http_1.startOutlookHttpServer)({
299
+ host: "127.0.0.1",
300
+ port: outlook_types_1.OUTLOOK_DEFAULT_PORT,
301
+ readMachineState: () => (0, outlook_read_1.readOutlookMachineState)({ bundlesRoot: this.bundlesRoot }),
302
+ readMachineView: ({ machine }) => {
303
+ const overview = this.buildStatusPayload().overview;
304
+ return (0, outlook_view_1.buildOutlookMachineView)({
305
+ machine,
306
+ daemon: {
307
+ status: overview.daemon,
308
+ health: overview.health,
309
+ mode: overview.mode,
310
+ socketPath: overview.socketPath,
311
+ outlookUrl: overview.outlookUrl,
312
+ entryPath: overview.entryPath,
313
+ workerCount: overview.workerCount,
314
+ senseCount: overview.senseCount,
315
+ },
316
+ });
317
+ },
318
+ readAgentState: (agentName) => (0, outlook_read_1.readOutlookAgentState)(agentName, { bundlesRoot: this.bundlesRoot }),
319
+ readAgentView: (agentName) => {
320
+ const agent = (0, outlook_read_1.readOutlookAgentState)(agentName, { bundlesRoot: this.bundlesRoot });
321
+ return (0, outlook_view_1.buildOutlookAgentView)({
322
+ agent,
323
+ viewer: { kind: "human" },
324
+ });
325
+ },
326
+ });
327
+ }
328
+ /* v8 ignore stop */
270
329
  if (fs.existsSync(this.socketPath)) {
271
330
  fs.unlinkSync(this.socketPath);
272
331
  }
@@ -486,6 +545,10 @@ class OuroDaemon {
486
545
  });
487
546
  this.server = null;
488
547
  }
548
+ if (this.outlookServer) {
549
+ await this.outlookServer.stop();
550
+ this.outlookServer = null;
551
+ }
489
552
  if (fs.existsSync(this.socketPath)) {
490
553
  fs.unlinkSync(this.socketPath);
491
554
  }
@@ -539,24 +602,7 @@ class OuroDaemon {
539
602
  await this.stop();
540
603
  return { ok: true, message: "daemon stopped" };
541
604
  case "daemon.status": {
542
- const snapshots = this.processManager.listAgentSnapshots();
543
- const workers = buildWorkerRows(snapshots);
544
- const senses = this.senseManager?.listSenseRows() ?? [];
545
- const repoRoot = (0, identity_1.getRepoRoot)();
546
- const data = {
547
- overview: {
548
- daemon: "running",
549
- health: workers.every((worker) => worker.status === "running") ? "ok" : "warn",
550
- socketPath: this.socketPath,
551
- ...(0, runtime_metadata_1.getRuntimeMetadata)(),
552
- workerCount: workers.length,
553
- senseCount: senses.length,
554
- entryPath: path.join(repoRoot, "dist", "heart", "daemon", "daemon-entry.js"),
555
- mode: (0, runtime_mode_1.detectRuntimeMode)(repoRoot),
556
- },
557
- workers,
558
- senses,
559
- };
605
+ const data = this.buildStatusPayload();
560
606
  return {
561
607
  ok: true,
562
608
  summary: formatStatusSummary(data),
@@ -0,0 +1,307 @@
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.startOutlookHttpServer = startOutlookHttpServer;
37
+ const fs = __importStar(require("fs"));
38
+ const http = __importStar(require("http"));
39
+ const path = __importStar(require("path"));
40
+ const runtime_1 = require("../../nerves/runtime");
41
+ const outlook_read_1 = require("./outlook-read");
42
+ const outlook_render_1 = require("./outlook-render");
43
+ function createSseBroadcaster() {
44
+ let nextId = 1;
45
+ const clients = new Set();
46
+ function add(response) {
47
+ const client = { id: nextId++, response };
48
+ clients.add(client);
49
+ response.on("close", () => clients.delete(client));
50
+ return client;
51
+ }
52
+ function broadcast(event, data = {}) {
53
+ const payload = `event: ${event}\ndata: ${JSON.stringify(data)}\n\n`;
54
+ for (const client of clients) {
55
+ try {
56
+ client.response.write(payload);
57
+ /* v8 ignore start */
58
+ }
59
+ catch {
60
+ clients.delete(client);
61
+ }
62
+ /* v8 ignore stop */
63
+ }
64
+ }
65
+ function disconnectAll() {
66
+ for (const client of clients) {
67
+ try {
68
+ client.response.end();
69
+ /* v8 ignore start */
70
+ }
71
+ catch {
72
+ /* already closed */
73
+ }
74
+ /* v8 ignore stop */
75
+ }
76
+ clients.clear();
77
+ }
78
+ return { add, broadcast, disconnectAll };
79
+ }
80
+ /* v8 ignore start — filesystem watcher, tested via integration */
81
+ function createBundleWatcher(bundlesRoot, onChange) {
82
+ const watchers = [];
83
+ let debounceTimer = null;
84
+ const DEBOUNCE_MS = 500;
85
+ function debouncedOnChange() {
86
+ if (debounceTimer)
87
+ clearTimeout(debounceTimer);
88
+ debounceTimer = setTimeout(onChange, DEBOUNCE_MS);
89
+ }
90
+ try {
91
+ if (fs.existsSync(bundlesRoot)) {
92
+ const watcher = fs.watch(bundlesRoot, { recursive: true }, debouncedOnChange);
93
+ watchers.push(watcher);
94
+ }
95
+ }
96
+ catch {
97
+ // watch not available — SSE will rely on manual broadcast
98
+ }
99
+ return {
100
+ stop() {
101
+ if (debounceTimer)
102
+ clearTimeout(debounceTimer);
103
+ for (const w of watchers)
104
+ try {
105
+ w.close();
106
+ }
107
+ catch { /* ignore */ }
108
+ watchers.length = 0;
109
+ },
110
+ };
111
+ }
112
+ /* v8 ignore stop */
113
+ function writeJson(response, statusCode, payload) {
114
+ response.writeHead(statusCode, { "content-type": "application/json; charset=utf-8" });
115
+ response.end(`${JSON.stringify(payload, null, 2)}\n`);
116
+ }
117
+ function writeHtml(response, html) {
118
+ response.writeHead(200, { "content-type": "text/html; charset=utf-8" });
119
+ response.end(html);
120
+ }
121
+ function normalizePath(urlValue = "/") {
122
+ const parsed = new URL(urlValue, "http://127.0.0.1");
123
+ const normalizedPath = parsed.pathname.replace(/\/+$/, "");
124
+ if (normalizedPath.length === 0)
125
+ return "/";
126
+ return normalizedPath;
127
+ }
128
+ async function startOutlookHttpServer(options = {}) {
129
+ const host = options.host ?? "127.0.0.1";
130
+ const port = options.port ?? 0;
131
+ const bundlesRoot = options.bundlesRoot;
132
+ const opts = bundlesRoot ? { bundlesRoot } : undefined;
133
+ const readMachineState = options.readMachineState ?? (() => (0, outlook_read_1.readOutlookMachineState)(opts));
134
+ const readMachineView = options.readMachineView;
135
+ /* v8 ignore start */
136
+ const readAgentState = options.readAgentState ?? ((agentName) => {
137
+ if (opts)
138
+ return (0, outlook_read_1.readOutlookAgentState)(agentName, opts);
139
+ return (0, outlook_read_1.readOutlookAgentState)(agentName);
140
+ });
141
+ /* v8 ignore stop */
142
+ const readAgentView = options.readAgentView;
143
+ const renderApp = options.renderApp ?? outlook_render_1.renderOutlookApp;
144
+ /* v8 ignore start — default hook wiring, tested via integration */
145
+ const agentRoot = (agentName) => {
146
+ const base = bundlesRoot ?? "";
147
+ return path.join(base, `${agentName}.ouro`);
148
+ };
149
+ const hooks = {
150
+ readAgentSessions: options.readAgentSessions ?? ((agentName) => (0, outlook_read_1.readSessionInventory)(agentName, bundlesRoot ? { bundlesRoot } : undefined)),
151
+ readAgentTranscript: options.readAgentTranscript ?? ((agentName, friendId, channel, key) => (0, outlook_read_1.readSessionTranscript)(agentName, friendId, channel, key, bundlesRoot ? { bundlesRoot } : undefined)),
152
+ readAgentCoding: options.readAgentCoding ?? ((agentName) => (0, outlook_read_1.readCodingDeep)(agentRoot(agentName))),
153
+ readAgentAttention: options.readAgentAttention ?? ((agentName) => (0, outlook_read_1.readAttentionView)(agentName, bundlesRoot ? { bundlesRoot } : undefined)),
154
+ readAgentBridges: options.readAgentBridges ?? ((agentName) => (0, outlook_read_1.readBridgeInventory)(agentRoot(agentName))),
155
+ readAgentMemory: options.readAgentMemory ?? ((agentName) => (0, outlook_read_1.readMemoryView)(agentRoot(agentName))),
156
+ readAgentFriends: options.readAgentFriends ?? ((agentName) => (0, outlook_read_1.readFriendView)(agentName, bundlesRoot ? { bundlesRoot } : undefined)),
157
+ readAgentHabits: options.readAgentHabits ?? ((agentName) => (0, outlook_read_1.readHabitView)(agentRoot(agentName))),
158
+ readDaemonHealth: options.readDaemonHealth ?? (() => (0, outlook_read_1.readDaemonHealthDeep)(options.healthPath)),
159
+ readLogs: options.readLogs ?? (() => (0, outlook_read_1.readLogView)(options.logPath ?? null)),
160
+ };
161
+ /* v8 ignore stop */
162
+ const sse = createSseBroadcaster();
163
+ /* v8 ignore start — watcher callback fires on filesystem changes */
164
+ const bundleWatcher = bundlesRoot ? createBundleWatcher(bundlesRoot, () => {
165
+ sse.broadcast("state-changed", { at: new Date().toISOString() });
166
+ }) : null;
167
+ /* v8 ignore stop */
168
+ const server = http.createServer((request, response) => {
169
+ const pathname = normalizePath(request.url);
170
+ const origin = `http://${host}:${server.address().port}`;
171
+ if (pathname === "/outlook/api/events") {
172
+ response.writeHead(200, { "content-type": "text/event-stream", "cache-control": "no-cache", "connection": "keep-alive", "access-control-allow-origin": "*" });
173
+ response.write(":ok\n\n");
174
+ sse.add(response);
175
+ return;
176
+ }
177
+ if (pathname === "/outlook") {
178
+ const machine = readMachineState();
179
+ const machineView = readMachineView?.({ origin, machine });
180
+ writeHtml(response, renderApp({ origin, machine, machineView }));
181
+ return;
182
+ }
183
+ if (pathname === "/outlook/api/machine") {
184
+ const machine = readMachineState();
185
+ const machineView = readMachineView?.({ origin, machine });
186
+ writeJson(response, 200, machineView ?? machine);
187
+ return;
188
+ }
189
+ if (pathname === "/outlook/api/machine/health") {
190
+ const health = hooks.readDaemonHealth();
191
+ writeJson(response, 200, health ?? { status: "unavailable" });
192
+ return;
193
+ }
194
+ if (pathname === "/outlook/api/machine/logs") {
195
+ writeJson(response, 200, hooks.readLogs());
196
+ return;
197
+ }
198
+ // Agent-level endpoints: /outlook/api/agents/:agent[/:surface[/:params...]]
199
+ const agentMatch = /^\/outlook\/api\/agents\/([^/]+)(?:\/(.+))?$/.exec(pathname);
200
+ if (agentMatch) {
201
+ const agent = decodeURIComponent(agentMatch[1]);
202
+ const surface = agentMatch[2] ?? null;
203
+ if (!surface) {
204
+ const view = readAgentView?.(agent);
205
+ if (view) {
206
+ writeJson(response, 200, view);
207
+ return;
208
+ }
209
+ const state = readAgentState(agent);
210
+ if (!state) {
211
+ writeJson(response, 404, { ok: false, error: `unknown agent: ${agent}` });
212
+ return;
213
+ }
214
+ writeJson(response, 200, state);
215
+ return;
216
+ }
217
+ if (surface === "sessions") {
218
+ writeJson(response, 200, hooks.readAgentSessions(agent));
219
+ return;
220
+ }
221
+ const transcriptMatch = /^sessions\/([^/]+)\/([^/]+)\/([^/]+)$/.exec(surface);
222
+ if (transcriptMatch) {
223
+ const friendId = decodeURIComponent(transcriptMatch[1]);
224
+ const channel = decodeURIComponent(transcriptMatch[2]);
225
+ const key = decodeURIComponent(transcriptMatch[3]);
226
+ const transcript = hooks.readAgentTranscript(agent, friendId, channel, key);
227
+ if (!transcript) {
228
+ writeJson(response, 404, { ok: false, error: "session not found" });
229
+ return;
230
+ }
231
+ writeJson(response, 200, transcript);
232
+ return;
233
+ }
234
+ if (surface === "coding") {
235
+ writeJson(response, 200, hooks.readAgentCoding(agent));
236
+ return;
237
+ }
238
+ if (surface === "attention") {
239
+ writeJson(response, 200, hooks.readAgentAttention(agent));
240
+ return;
241
+ }
242
+ if (surface === "bridges") {
243
+ writeJson(response, 200, hooks.readAgentBridges(agent));
244
+ return;
245
+ }
246
+ if (surface === "memory") {
247
+ writeJson(response, 200, hooks.readAgentMemory(agent));
248
+ return;
249
+ }
250
+ if (surface === "friends") {
251
+ writeJson(response, 200, hooks.readAgentFriends(agent));
252
+ return;
253
+ }
254
+ /* v8 ignore start — desk prefs + needs-me use direct reads */
255
+ if (surface === "desk-prefs") {
256
+ writeJson(response, 200, (0, outlook_read_1.readDeskPrefs)(agentRoot(agent)));
257
+ return;
258
+ }
259
+ if (surface === "needs-me") {
260
+ writeJson(response, 200, (0, outlook_read_1.readNeedsMeView)(agent, opts));
261
+ return;
262
+ }
263
+ /* v8 ignore stop */
264
+ if (surface === "habits") {
265
+ writeJson(response, 200, hooks.readAgentHabits(agent));
266
+ return;
267
+ }
268
+ if (surface === "inner-transcript") {
269
+ const transcript = hooks.readAgentTranscript(agent, "self", "inner", "dialog");
270
+ writeJson(response, 200, transcript ?? { messageCount: 0, messages: [] });
271
+ return;
272
+ }
273
+ writeJson(response, 404, { ok: false, error: `unknown agent surface: ${surface}` });
274
+ return;
275
+ }
276
+ writeJson(response, 404, { ok: false, error: `unknown outlook path: ${pathname}` });
277
+ });
278
+ await new Promise((resolve, reject) => {
279
+ server.once("error", reject);
280
+ server.listen(port, host, () => resolve());
281
+ });
282
+ const address = server.address();
283
+ const origin = `http://${host}:${address.port}`;
284
+ (0, runtime_1.emitNervesEvent)({
285
+ component: "daemon",
286
+ event: "daemon.outlook_http_started",
287
+ message: "started Outlook HTTP server",
288
+ meta: { origin },
289
+ });
290
+ return {
291
+ origin,
292
+ broadcast(event, data) {
293
+ sse.broadcast(event, data);
294
+ },
295
+ async stop() {
296
+ bundleWatcher?.stop();
297
+ sse.disconnectAll();
298
+ await new Promise((resolve) => server.close(() => resolve()));
299
+ (0, runtime_1.emitNervesEvent)({
300
+ component: "daemon",
301
+ event: "daemon.outlook_http_stopped",
302
+ message: "stopped Outlook HTTP server",
303
+ meta: { origin },
304
+ });
305
+ },
306
+ };
307
+ }