@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 +13 -2
- package/dist/heart/daemon/daemon-cli.js +44 -1
- package/dist/heart/daemon/daemon.js +64 -18
- package/dist/heart/daemon/outlook-http.js +307 -0
- package/dist/heart/daemon/outlook-read.js +1281 -0
- package/dist/heart/daemon/outlook-render.js +1032 -0
- package/dist/heart/daemon/outlook-types.js +7 -0
- package/dist/heart/daemon/outlook-view.js +194 -0
- package/dist/heart/session-activity.js +5 -6
- package/dist/nerves/observation.js +20 -0
- package/package.json +1 -1
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
|
|
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
|
|
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) =>
|
|
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
|
|
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
|
+
}
|