@ouro.bot/cli 0.1.0-alpha.192 → 0.1.0-alpha.193

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,10 +1,16 @@
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.193",
6
+ "changes": [
7
+ "feat: Outlook visibility \u2014 orientation, obligations, changes, self-fix, memory decisions, route migration to /"
8
+ ]
9
+ },
4
10
  {
5
11
  "version": "0.1.0-alpha.192",
6
12
  "changes": [
7
- "refactor: canonical obligations — ActiveWorkFrame as single source of truth for prompt sections",
13
+ "refactor: canonical obligations \u2014 ActiveWorkFrame as single source of truth for prompt sections",
8
14
  "feat(mcp): dynamic server add/remove \u2014 agents can manage MCP servers without daemon restart"
9
15
  ]
10
16
  },
@@ -1198,4 +1204,4 @@
1198
1204
  ]
1199
1205
  }
1200
1206
  ]
1201
- }
1207
+ }
@@ -208,7 +208,7 @@ class OuroDaemon {
208
208
  daemon: "running",
209
209
  health: workers.every((worker) => worker.status === "running") ? "ok" : "warn",
210
210
  socketPath: this.socketPath,
211
- outlookUrl: `${this.outlookServer?.origin ?? "http://127.0.0.1:0"}/outlook`,
211
+ outlookUrl: this.outlookServer?.origin ?? "http://127.0.0.1:0",
212
212
  ...(0, runtime_metadata_1.getRuntimeMetadata)(),
213
213
  workerCount: workers.length,
214
214
  senseCount: senses.length,
@@ -196,6 +196,12 @@ async function startOutlookHttpServer(options = {}) {
196
196
  readAgentBridges: options.readAgentBridges ?? ((agentName) => (0, outlook_read_1.readBridgeInventory)(agentRoot(agentName))),
197
197
  readAgentMemory: options.readAgentMemory ?? ((agentName) => (0, outlook_read_1.readMemoryView)(agentRoot(agentName))),
198
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))),
199
205
  readAgentHabits: options.readAgentHabits ?? ((agentName) => (0, outlook_read_1.readHabitView)(agentRoot(agentName))),
200
206
  readDaemonHealth: options.readDaemonHealth ?? (() => (0, outlook_read_1.readDaemonHealthDeep)(options.healthPath)),
201
207
  readLogs: options.readLogs ?? (() => (0, outlook_read_1.readLogView)(options.logPath ?? null)),
@@ -208,14 +214,8 @@ async function startOutlookHttpServer(options = {}) {
208
214
  }) : null;
209
215
  /* v8 ignore stop */
210
216
  const server = http.createServer((request, response) => {
211
- const pathname = normalizePath(request.url);
217
+ let pathname = normalizePath(request.url);
212
218
  const origin = `http://${host}:${server.address().port}`;
213
- if (pathname === "/outlook/api/events") {
214
- response.writeHead(200, { "content-type": "text/event-stream", "cache-control": "no-cache", "connection": "keep-alive", "access-control-allow-origin": "*" });
215
- response.write(":ok\n\n");
216
- sse.add(response);
217
- return;
218
- }
219
219
  /* v8 ignore start — SPA static asset serving */
220
220
  // Serve built SPA static assets: /assets/*
221
221
  if (pathname.startsWith("/assets/")) {
@@ -235,23 +235,39 @@ async function startOutlookHttpServer(options = {}) {
235
235
  response.end();
236
236
  return;
237
237
  }
238
- if (pathname === "/outlook/api/machine") {
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") {
239
255
  const machine = readMachineState();
240
256
  const machineView = readMachineView?.({ origin, machine });
241
257
  writeJson(response, 200, machineView ?? machine);
242
258
  return;
243
259
  }
244
- if (pathname === "/outlook/api/machine/health") {
260
+ if (pathname === "/api/machine/health") {
245
261
  const health = hooks.readDaemonHealth();
246
262
  writeJson(response, 200, health ?? { status: "unavailable" });
247
263
  return;
248
264
  }
249
- if (pathname === "/outlook/api/machine/logs") {
265
+ if (pathname === "/api/machine/logs") {
250
266
  writeJson(response, 200, hooks.readLogs());
251
267
  return;
252
268
  }
253
- // Agent-level endpoints: /outlook/api/agents/:agent[/:surface[/:params...]]
254
- const agentMatch = /^\/outlook\/api\/agents\/([^/]+)(?:\/(.+))?$/.exec(pathname);
269
+ // Agent-level endpoints: /api/agents/:agent[/:surface[/:params...]]
270
+ const agentMatch = /^\/api\/agents\/([^/]+)(?:\/(.+))?$/.exec(pathname);
255
271
  if (agentMatch) {
256
272
  const agent = decodeURIComponent(agentMatch[1]);
257
273
  const surface = agentMatch[2] ?? null;
@@ -306,6 +322,30 @@ async function startOutlookHttpServer(options = {}) {
306
322
  writeJson(response, 200, hooks.readAgentFriends(agent));
307
323
  return;
308
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
+ }
309
349
  /* v8 ignore start — desk prefs write + reads */
310
350
  if (surface === "dismiss-obligation" && request.method === "POST") {
311
351
  let body = "";
@@ -48,6 +48,11 @@ exports.readHabitView = readHabitView;
48
48
  exports.readNeedsMeView = readNeedsMeView;
49
49
  exports.readDeskPrefs = readDeskPrefs;
50
50
  exports.readOutlookContinuity = readOutlookContinuity;
51
+ exports.readOrientationView = readOrientationView;
52
+ exports.readObligationDetailView = readObligationDetailView;
53
+ exports.readChangesView = readChangesView;
54
+ exports.readSelfFixView = readSelfFixView;
55
+ exports.readMemoryDecisionView = readMemoryDecisionView;
51
56
  const fs = __importStar(require("fs"));
52
57
  const path = __importStar(require("path"));
53
58
  const runtime_1 = require("../../nerves/runtime");
@@ -1329,3 +1334,286 @@ function readOutlookContinuity(agentRoot, agentName) {
1329
1334
  },
1330
1335
  };
1331
1336
  }
1337
+ // ---------------------------------------------------------------------------
1338
+ // Orientation reader — daemon-side assembly of "where am I?"
1339
+ // ---------------------------------------------------------------------------
1340
+ function readOrientationView(agentRoot, agentName) {
1341
+ // Read obligations for primary selection
1342
+ let obligations = [];
1343
+ try {
1344
+ obligations = (0, obligations_1.readObligations)(agentRoot);
1345
+ }
1346
+ catch {
1347
+ obligations = [];
1348
+ }
1349
+ const openObligations = obligations.filter(obligations_1.isOpenObligation);
1350
+ // Select primary obligation (most advanced status, then most recent)
1351
+ const statusPriority = {
1352
+ returning: 0,
1353
+ collaborating: 1,
1354
+ in_progress: 2,
1355
+ delegated: 3,
1356
+ accepted: 4,
1357
+ pending: 5,
1358
+ };
1359
+ const sorted = [...openObligations].sort((a, b) => {
1360
+ const sp = (statusPriority[a.status] ?? 99) - (statusPriority[b.status] ?? 99);
1361
+ if (sp !== 0)
1362
+ return sp;
1363
+ const aMs = new Date(a.updatedAt ?? a.createdAt).getTime();
1364
+ const bMs = new Date(b.updatedAt ?? b.createdAt).getTime();
1365
+ return bMs - aMs;
1366
+ });
1367
+ const primary = sorted[0] ?? null;
1368
+ // Read session activity for current session and others
1369
+ let sessions = [];
1370
+ try {
1371
+ sessions = (0, session_activity_1.listSessionActivity)({
1372
+ sessionsDir: path.join(agentRoot, "state", "sessions"),
1373
+ friendsDir: path.join(agentRoot, "friends"),
1374
+ agentName,
1375
+ });
1376
+ }
1377
+ catch {
1378
+ sessions = [];
1379
+ }
1380
+ const sortedSessions = [...sessions].sort((a, b) => b.lastActivityMs - a.lastActivityMs);
1381
+ const currentSession = sortedSessions.length > 0
1382
+ ? {
1383
+ friendId: sortedSessions[0].friendId,
1384
+ channel: sortedSessions[0].channel,
1385
+ key: sortedSessions[0].key,
1386
+ lastActivityAt: sortedSessions[0].lastActivityAt,
1387
+ }
1388
+ : null;
1389
+ const otherActiveSessions = sortedSessions.slice(1).map((s) => ({
1390
+ friendId: s.friendId,
1391
+ friendName: s.friendName,
1392
+ channel: s.channel,
1393
+ key: s.key,
1394
+ lastActivityAt: s.lastActivityAt,
1395
+ }));
1396
+ // Derive center of gravity summary
1397
+ const parts = [];
1398
+ if (primary)
1399
+ parts.push(primary.content);
1400
+ if (openObligations.length > 1)
1401
+ parts.push(`${openObligations.length} open obligations`);
1402
+ if (sessions.length > 0)
1403
+ parts.push(`${sessions.length} active sessions`);
1404
+ const centerOfGravity = parts.length > 0 ? parts.join(" | ") : "idle";
1405
+ const primaryObligation = primary
1406
+ ? {
1407
+ id: primary.id,
1408
+ content: primary.content,
1409
+ status: primary.status,
1410
+ nextAction: primary.nextAction ?? null,
1411
+ waitingOn: primary.meaning?.waitingOn?.detail ?? null,
1412
+ }
1413
+ : null;
1414
+ (0, runtime_1.emitNervesEvent)({
1415
+ component: "heart",
1416
+ event: "heart.outlook_orientation_read",
1417
+ message: `outlook orientation: ${openObligations.length} obligations, ${sessions.length} sessions`,
1418
+ meta: { obligationCount: openObligations.length, sessionCount: sessions.length, primaryId: primary?.id ?? null },
1419
+ });
1420
+ return {
1421
+ currentSession,
1422
+ centerOfGravity,
1423
+ primaryObligation,
1424
+ resumeHandle: null,
1425
+ otherActiveSessions,
1426
+ rawState: null,
1427
+ };
1428
+ }
1429
+ // ---------------------------------------------------------------------------
1430
+ // Obligation detail reader — richer view with primary selection context
1431
+ // ---------------------------------------------------------------------------
1432
+ function readObligationDetailView(agentRoot) {
1433
+ let obligations = [];
1434
+ try {
1435
+ obligations = (0, obligations_1.readObligations)(agentRoot);
1436
+ }
1437
+ catch {
1438
+ obligations = [];
1439
+ }
1440
+ const openObligations = obligations.filter(obligations_1.isOpenObligation);
1441
+ // Select primary (same logic as orientation)
1442
+ const statusPriority = {
1443
+ returning: 0,
1444
+ collaborating: 1,
1445
+ in_progress: 2,
1446
+ delegated: 3,
1447
+ accepted: 4,
1448
+ pending: 5,
1449
+ };
1450
+ const sorted = [...openObligations].sort((a, b) => {
1451
+ const sp = (statusPriority[a.status] ?? 99) - (statusPriority[b.status] ?? 99);
1452
+ if (sp !== 0)
1453
+ return sp;
1454
+ const aMs = new Date(a.updatedAt ?? a.createdAt).getTime();
1455
+ const bMs = new Date(b.updatedAt ?? b.createdAt).getTime();
1456
+ return bMs - aMs;
1457
+ });
1458
+ const primary = sorted[0] ?? null;
1459
+ const items = openObligations.map((ob) => ({
1460
+ id: ob.id,
1461
+ status: ob.status,
1462
+ content: ob.content,
1463
+ updatedAt: ob.updatedAt ?? ob.createdAt,
1464
+ nextAction: ob.nextAction ?? null,
1465
+ origin: ob.origin ?? null,
1466
+ currentSurface: ob.currentSurface ? { kind: ob.currentSurface.kind, label: ob.currentSurface.label } : null,
1467
+ meaning: ob.meaning ? { waitingOn: ob.meaning.waitingOn?.detail ?? null } : null,
1468
+ isPrimary: primary ? ob.id === primary.id : false,
1469
+ }));
1470
+ let primarySelectionReason = null;
1471
+ if (primary) {
1472
+ if (primary.status !== "pending") {
1473
+ primarySelectionReason = `most advanced status: ${primary.status}`;
1474
+ }
1475
+ else {
1476
+ primarySelectionReason = "most recent pending";
1477
+ }
1478
+ }
1479
+ (0, runtime_1.emitNervesEvent)({
1480
+ component: "heart",
1481
+ event: "heart.outlook_obligations_read",
1482
+ message: `outlook obligations: ${openObligations.length} open`,
1483
+ meta: { openCount: openObligations.length, primaryId: primary?.id ?? null },
1484
+ });
1485
+ return {
1486
+ openCount: openObligations.length,
1487
+ primaryId: primary?.id ?? null,
1488
+ primarySelectionReason,
1489
+ items,
1490
+ };
1491
+ }
1492
+ // ---------------------------------------------------------------------------
1493
+ // Changes reader — cross-session drift via snapshot comparison
1494
+ // ---------------------------------------------------------------------------
1495
+ const active_work_1 = require("../active-work");
1496
+ function readChangesView(agentRoot) {
1497
+ const snapshotPath = path.join(agentRoot, "state", "outlook", "active-work-snapshot.json");
1498
+ // Read prior snapshot
1499
+ let previous = null;
1500
+ try {
1501
+ const raw = fs.readFileSync(snapshotPath, "utf-8");
1502
+ previous = JSON.parse(raw);
1503
+ if (!previous.obligationSnapshots || !previous.codingSnapshots)
1504
+ previous = null;
1505
+ }
1506
+ catch {
1507
+ previous = null;
1508
+ }
1509
+ // Build current snapshot from raw state
1510
+ let obligations = [];
1511
+ try {
1512
+ obligations = (0, obligations_1.readObligations)(agentRoot);
1513
+ }
1514
+ catch {
1515
+ obligations = [];
1516
+ }
1517
+ const openObligations = obligations.filter(obligations_1.isOpenObligation);
1518
+ const current = {
1519
+ obligationSnapshots: openObligations.map((ob) => ({
1520
+ id: ob.id,
1521
+ status: ob.status,
1522
+ artifact: ob.currentArtifact?.trim() || null,
1523
+ nextAction: ob.nextAction?.trim() || null,
1524
+ })),
1525
+ codingSnapshots: [],
1526
+ timestamp: new Date().toISOString(),
1527
+ };
1528
+ // Persist current as the new snapshot
1529
+ try {
1530
+ fs.mkdirSync(path.dirname(snapshotPath), { recursive: true });
1531
+ fs.writeFileSync(snapshotPath, JSON.stringify(current, null, 2) + "\n", "utf-8");
1532
+ }
1533
+ catch {
1534
+ // Best effort
1535
+ }
1536
+ if (!previous) {
1537
+ return { changeCount: 0, items: [], snapshotAge: null, formatted: "" };
1538
+ }
1539
+ const changes = (0, active_work_1.detectActiveWorkChanges)(previous, current);
1540
+ const formatted = (0, active_work_1.formatActiveWorkChanges)(changes);
1541
+ (0, runtime_1.emitNervesEvent)({
1542
+ component: "heart",
1543
+ event: "heart.outlook_changes_read",
1544
+ message: `outlook changes: ${changes.length} detected`,
1545
+ meta: { changeCount: changes.length, snapshotAge: previous.timestamp },
1546
+ });
1547
+ return {
1548
+ changeCount: changes.length,
1549
+ items: changes.map((c) => ({ kind: c.kind, id: c.id, from: c.from, to: c.to, summary: c.summary })),
1550
+ snapshotAge: previous.timestamp,
1551
+ formatted,
1552
+ };
1553
+ }
1554
+ // ---------------------------------------------------------------------------
1555
+ // Self-fix workflow reader — derived from task board and coding state
1556
+ // ---------------------------------------------------------------------------
1557
+ function readSelfFixView(agentRoot) {
1558
+ // Derive self-fix state from task scanner
1559
+ let tasks = [];
1560
+ try {
1561
+ const scanned = (0, scanner_1.scanTasks)(path.join(agentRoot, "tasks"));
1562
+ tasks = scanned.tasks.map((t) => ({ name: t.name, title: t.title, status: t.status }));
1563
+ }
1564
+ catch {
1565
+ tasks = [];
1566
+ }
1567
+ // Look for self-fix-related tasks
1568
+ const selfFixTasks = tasks.filter((t) => t.title.toLowerCase().includes("fix") || t.title.toLowerCase().includes("self-fix"));
1569
+ if (selfFixTasks.length === 0) {
1570
+ return { active: false, currentStep: null, steps: [] };
1571
+ }
1572
+ const steps = selfFixTasks.map((t) => ({
1573
+ label: t.title,
1574
+ status: t.status === "done" ? "done" : t.status === "processing" ? "active" : "pending",
1575
+ detail: `task ${t.name}: ${t.status}`,
1576
+ }));
1577
+ const activeStep = steps.find((s) => s.status === "active");
1578
+ (0, runtime_1.emitNervesEvent)({
1579
+ component: "heart",
1580
+ event: "heart.outlook_selffix_read",
1581
+ message: `outlook self-fix: ${selfFixTasks.length} tasks`,
1582
+ meta: { taskCount: selfFixTasks.length, active: !!activeStep },
1583
+ });
1584
+ return {
1585
+ active: !!activeStep,
1586
+ currentStep: activeStep?.label ?? null,
1587
+ steps,
1588
+ };
1589
+ }
1590
+ // ---------------------------------------------------------------------------
1591
+ // Memory decisions reader — append-only JSONL log
1592
+ // ---------------------------------------------------------------------------
1593
+ function readMemoryDecisionView(agentRoot, limit = 50) {
1594
+ const logPath = path.join(agentRoot, "state", "outlook", "memory-decisions.jsonl");
1595
+ let lines = [];
1596
+ try {
1597
+ const raw = fs.readFileSync(logPath, "utf-8");
1598
+ lines = raw.split("\n").filter((l) => l.trim().length > 0);
1599
+ }
1600
+ catch {
1601
+ return { totalCount: 0, items: [] };
1602
+ }
1603
+ const items = [];
1604
+ for (const line of lines) {
1605
+ try {
1606
+ const parsed = JSON.parse(line);
1607
+ if (parsed.kind && parsed.decision && parsed.timestamp) {
1608
+ items.push(parsed);
1609
+ }
1610
+ }
1611
+ catch {
1612
+ // Skip malformed lines
1613
+ }
1614
+ }
1615
+ // Reverse chronological
1616
+ items.reverse();
1617
+ const limited = items.slice(0, limit);
1618
+ return { totalCount: items.length, items: limited };
1619
+ }