@ouro.bot/cli 0.1.0-alpha.184 → 0.1.0-alpha.186

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.
Files changed (38) hide show
  1. package/README.md +1 -1
  2. package/changelog.json +19 -2
  3. package/dist/heart/active-work.js +19 -10
  4. package/dist/heart/agent-relationships.js +161 -0
  5. package/dist/heart/cares.js +174 -0
  6. package/dist/heart/daemon/daemon-cli.js +48 -16
  7. package/dist/heart/daemon/hatch-flow.js +1 -1
  8. package/dist/heart/daemon/hatch-specialist.js +3 -3
  9. package/dist/heart/daemon/outlook-read.js +40 -0
  10. package/dist/heart/daemon/specialist-prompt.js +2 -2
  11. package/dist/heart/intentions.js +168 -0
  12. package/dist/heart/obligations.js +23 -0
  13. package/dist/heart/presence.js +185 -0
  14. package/dist/heart/tempo.js +93 -0
  15. package/dist/heart/temporal-view.js +41 -0
  16. package/dist/heart/wake-packet.js +226 -0
  17. package/dist/mind/episodes.js +116 -0
  18. package/dist/mind/prompt.js +10 -1
  19. package/dist/repertoire/coding/context-pack.js +25 -2
  20. package/dist/repertoire/tools-base.js +240 -0
  21. package/dist/senses/cli.js +3 -1
  22. package/dist/senses/pipeline.js +88 -0
  23. package/package.json +2 -2
  24. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/agent.json +0 -0
  25. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/SOUL.md +0 -0
  26. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/basilisk.md +0 -0
  27. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jafar.md +0 -0
  28. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jormungandr.md +0 -0
  29. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/kaa.md +0 -0
  30. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/medusa.md +0 -0
  31. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/monty.md +0 -0
  32. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/nagini.md +0 -0
  33. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/ouroboros.md +0 -0
  34. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/python.md +0 -0
  35. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/quetzalcoatl.md +0 -0
  36. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/sir-hiss.md +0 -0
  37. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-serpent.md +0 -0
  38. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-snake.md +0 -0
@@ -62,6 +62,11 @@ const obligations_1 = require("../mind/obligations");
62
62
  const progress_story_1 = require("../heart/progress-story");
63
63
  const cross_chat_delivery_1 = require("../heart/cross-chat-delivery");
64
64
  const obligations_2 = require("../heart/obligations");
65
+ const episodes_1 = require("../mind/episodes");
66
+ const cares_1 = require("../heart/cares");
67
+ const agent_relationships_1 = require("../heart/agent-relationships");
68
+ const presence_1 = require("../heart/presence");
69
+ const intentions_1 = require("../heart/intentions");
65
70
  // Tracks which file paths have been read via read_file in this session.
66
71
  // edit_file requires a file to be read first (must-read-first guard).
67
72
  exports.editFileReadTracker = new Set();
@@ -1498,6 +1503,241 @@ exports.baseToolDefinitions = [
1498
1503
  requiredCapability: "reasoning-effort",
1499
1504
  summaryKeys: ["level"],
1500
1505
  },
1506
+ // ── Continuity tools ──────────────────────────────────────────────
1507
+ {
1508
+ tool: {
1509
+ type: "function",
1510
+ function: {
1511
+ name: "query_episodes",
1512
+ description: "Query recent episodes from my continuity memory. Returns timestamped records of significant events (obligation shifts, coding milestones, bridge events, care events, turning points).",
1513
+ parameters: {
1514
+ type: "object",
1515
+ properties: {
1516
+ limit: { type: "number", description: "Maximum episodes to return (default 20)" },
1517
+ kind: { type: "string", description: "Filter by episode kind: obligation_shift, coding_milestone, bridge_event, care_event, tempo_shift, turning_point" },
1518
+ since: { type: "string", description: "ISO timestamp — only return episodes after this time" },
1519
+ },
1520
+ },
1521
+ },
1522
+ },
1523
+ handler: (a) => {
1524
+ const agentRoot = (0, identity_1.getAgentRoot)();
1525
+ const options = {};
1526
+ if (a.limit)
1527
+ options.limit = parseInt(a.limit, 10);
1528
+ if (a.kind)
1529
+ options.kinds = [a.kind];
1530
+ if (a.since)
1531
+ options.since = a.since;
1532
+ const episodes = (0, episodes_1.readRecentEpisodes)(agentRoot, options);
1533
+ (0, runtime_1.emitNervesEvent)({ component: "repertoire", event: "repertoire.query_episodes", message: `queried ${episodes.length} episodes`, meta: { count: episodes.length } });
1534
+ return JSON.stringify(episodes, null, 2);
1535
+ },
1536
+ },
1537
+ {
1538
+ tool: {
1539
+ type: "function",
1540
+ function: {
1541
+ name: "capture_episode",
1542
+ description: "Record a turning point or significant moment. This is my tool for saying 'that was important — keep it.' Nearly frictionless: only summary and whyItMattered required.",
1543
+ parameters: {
1544
+ type: "object",
1545
+ properties: {
1546
+ summary: { type: "string", description: "What happened" },
1547
+ whyItMattered: { type: "string", description: "Why this was significant" },
1548
+ kind: { type: "string", description: "Episode kind (default: turning_point)" },
1549
+ salience: { type: "string", description: "low, medium, high, or critical (default: medium)" },
1550
+ },
1551
+ required: ["summary", "whyItMattered"],
1552
+ },
1553
+ },
1554
+ },
1555
+ handler: (a) => {
1556
+ const agentRoot = (0, identity_1.getAgentRoot)();
1557
+ const episode = (0, episodes_1.emitEpisode)(agentRoot, {
1558
+ kind: a.kind ?? "turning_point",
1559
+ summary: a.summary,
1560
+ whyItMattered: a.whyItMattered,
1561
+ relatedEntities: [],
1562
+ salience: a.salience ?? "medium",
1563
+ });
1564
+ (0, runtime_1.emitNervesEvent)({ component: "repertoire", event: "repertoire.capture_episode", message: `captured episode ${episode.id}`, meta: { id: episode.id } });
1565
+ return JSON.stringify(episode, null, 2);
1566
+ },
1567
+ },
1568
+ {
1569
+ tool: {
1570
+ type: "function",
1571
+ function: {
1572
+ name: "query_presence",
1573
+ description: "Check who's around — my own availability/lane and known peer agents.",
1574
+ parameters: { type: "object", properties: {} },
1575
+ },
1576
+ },
1577
+ handler: () => {
1578
+ const agentRoot = (0, identity_1.getAgentRoot)();
1579
+ const agentName = (0, identity_1.getAgentName)();
1580
+ const self = (0, presence_1.readPresence)(agentRoot, agentName);
1581
+ const peers = (0, presence_1.readPeerPresence)(agentRoot);
1582
+ (0, runtime_1.emitNervesEvent)({ component: "repertoire", event: "repertoire.query_presence", message: `presence: self + ${peers.length} peers`, meta: { peerCount: peers.length } });
1583
+ return JSON.stringify({ self, peers }, null, 2);
1584
+ },
1585
+ },
1586
+ {
1587
+ tool: {
1588
+ type: "function",
1589
+ function: {
1590
+ name: "query_cares",
1591
+ description: "Query things I care about — ongoing concerns, watched situations, projects, people.",
1592
+ parameters: {
1593
+ type: "object",
1594
+ properties: {
1595
+ status: { type: "string", description: "Filter by status: 'active', 'watching', 'resolved', 'dormant', or 'all' (default: active cares only)" },
1596
+ },
1597
+ },
1598
+ },
1599
+ },
1600
+ handler: (a) => {
1601
+ const agentRoot = (0, identity_1.getAgentRoot)();
1602
+ const cares = a.status === "all" ? (0, cares_1.readCares)(agentRoot) : (0, cares_1.readActiveCares)(agentRoot);
1603
+ (0, runtime_1.emitNervesEvent)({ component: "repertoire", event: "repertoire.query_cares", message: `queried ${cares.length} cares`, meta: { count: cares.length } });
1604
+ return JSON.stringify(cares, null, 2);
1605
+ },
1606
+ },
1607
+ {
1608
+ tool: {
1609
+ type: "function",
1610
+ function: {
1611
+ name: "care_manage",
1612
+ description: "Create, update, or resolve a care. Cares are things I watch over — people, projects, missions, system health.",
1613
+ parameters: {
1614
+ type: "object",
1615
+ properties: {
1616
+ action: { type: "string", enum: ["create", "update", "resolve"], description: "What to do" },
1617
+ id: { type: "string", description: "Care ID (required for update/resolve)" },
1618
+ label: { type: "string", description: "Short label for the care" },
1619
+ why: { type: "string", description: "Why this matters" },
1620
+ salience: { type: "string", description: "low, medium, high, or critical" },
1621
+ kind: { type: "string", description: "person, agent, project, mission, or system" },
1622
+ stewardship: { type: "string", description: "mine, shared, or delegated" },
1623
+ },
1624
+ required: ["action"],
1625
+ },
1626
+ },
1627
+ },
1628
+ handler: (a) => {
1629
+ const agentRoot = (0, identity_1.getAgentRoot)();
1630
+ let result;
1631
+ if (a.action === "create") {
1632
+ result = (0, cares_1.createCare)(agentRoot, {
1633
+ label: a.label ?? "untitled",
1634
+ why: a.why ?? "",
1635
+ kind: a.kind ?? "project",
1636
+ status: "active",
1637
+ salience: a.salience ?? "medium",
1638
+ steward: a.stewardship ?? "mine",
1639
+ relatedFriendIds: [],
1640
+ relatedAgentIds: [],
1641
+ relatedObligationIds: [],
1642
+ relatedEpisodeIds: [],
1643
+ currentRisk: null,
1644
+ nextCheckAt: null,
1645
+ });
1646
+ }
1647
+ else if (a.action === "update") {
1648
+ const updates = {};
1649
+ if (a.label)
1650
+ updates.label = a.label;
1651
+ if (a.why)
1652
+ updates.why = a.why;
1653
+ if (a.salience)
1654
+ updates.salience = a.salience;
1655
+ result = (0, cares_1.updateCare)(agentRoot, a.id, updates);
1656
+ }
1657
+ else if (a.action === "resolve") {
1658
+ result = (0, cares_1.resolveCare)(agentRoot, a.id);
1659
+ }
1660
+ (0, runtime_1.emitNervesEvent)({ component: "repertoire", event: "repertoire.care_manage", message: `care ${a.action}`, meta: { action: a.action, id: a.id } });
1661
+ return JSON.stringify(result, null, 2);
1662
+ },
1663
+ },
1664
+ {
1665
+ tool: {
1666
+ type: "function",
1667
+ function: {
1668
+ name: "query_relationships",
1669
+ description: "Query known agent relationships — familiarity, trust, shared missions, interaction history.",
1670
+ parameters: {
1671
+ type: "object",
1672
+ properties: {
1673
+ agentName: { type: "string", description: "Specific agent name to query (omit for all)" },
1674
+ },
1675
+ },
1676
+ },
1677
+ },
1678
+ handler: (a) => {
1679
+ const agentRoot = (0, identity_1.getAgentRoot)();
1680
+ const result = a.agentName
1681
+ ? (0, agent_relationships_1.readRelationship)(agentRoot, a.agentName)
1682
+ : (0, agent_relationships_1.readRelationships)(agentRoot);
1683
+ (0, runtime_1.emitNervesEvent)({ component: "repertoire", event: "repertoire.query_relationships", message: `queried relationships`, meta: { agentName: a.agentName ?? "all" } });
1684
+ return JSON.stringify(result, null, 2);
1685
+ },
1686
+ },
1687
+ {
1688
+ tool: {
1689
+ type: "function",
1690
+ function: {
1691
+ name: "intention_capture",
1692
+ description: "File a lightweight mental note — something I want to do or check later, below the ceremony threshold of tasks or cares. Cheap to create, easy to close.",
1693
+ parameters: {
1694
+ type: "object",
1695
+ properties: {
1696
+ content: { type: "string", description: "What I want to remember to do" },
1697
+ salience: { type: "string", description: "low, medium, or high (default: low)" },
1698
+ nudgeAfter: { type: "string", description: "ISO timestamp — nudge me after this time" },
1699
+ },
1700
+ required: ["content"],
1701
+ },
1702
+ },
1703
+ },
1704
+ handler: (a) => {
1705
+ const agentRoot = (0, identity_1.getAgentRoot)();
1706
+ const intention = (0, intentions_1.captureIntention)(agentRoot, {
1707
+ content: a.content,
1708
+ salience: a.salience ?? "low",
1709
+ source: "tool",
1710
+ ...(a.nudgeAfter ? { nudgeAfter: a.nudgeAfter } : {}),
1711
+ });
1712
+ (0, runtime_1.emitNervesEvent)({ component: "repertoire", event: "repertoire.intention_capture", message: `captured intention ${intention.id}`, meta: { id: intention.id } });
1713
+ return JSON.stringify(intention, null, 2);
1714
+ },
1715
+ },
1716
+ {
1717
+ tool: {
1718
+ type: "function",
1719
+ function: {
1720
+ name: "intention_manage",
1721
+ description: "Resolve or dismiss an intention. Resolve = done. Dismiss = no longer relevant. Both remove it from active list.",
1722
+ parameters: {
1723
+ type: "object",
1724
+ properties: {
1725
+ action: { type: "string", enum: ["resolve", "dismiss"], description: "What to do" },
1726
+ id: { type: "string", description: "Intention ID" },
1727
+ },
1728
+ required: ["action", "id"],
1729
+ },
1730
+ },
1731
+ },
1732
+ handler: (a) => {
1733
+ const agentRoot = (0, identity_1.getAgentRoot)();
1734
+ const result = a.action === "resolve"
1735
+ ? (0, intentions_1.resolveIntention)(agentRoot, a.id)
1736
+ : (0, intentions_1.dismissIntention)(agentRoot, a.id);
1737
+ (0, runtime_1.emitNervesEvent)({ component: "repertoire", event: "repertoire.intention_manage", message: `intention ${a.action}: ${a.id}`, meta: { action: a.action, id: a.id } });
1738
+ return JSON.stringify(result, null, 2);
1739
+ },
1740
+ },
1501
1741
  ...tools_1.codingToolDefinitions,
1502
1742
  ];
1503
1743
  exports.tools = exports.baseToolDefinitions.map((d) => d.tool);
@@ -797,7 +797,7 @@ async function runCliSession(options) {
797
797
  }
798
798
  }
799
799
  else {
800
- // Legacy path: inline runAgent (used by adoption specialist and tests)
800
+ // Legacy path: inline runAgent (used by serpent guide and tests)
801
801
  const prefix = options.getContentPrefix?.();
802
802
  messages.push({ role: "user", content: prefix ? `${prefix}\n\n${input}` : input });
803
803
  const traceId = (0, nerves_1.createTraceId)();
@@ -845,6 +845,8 @@ async function runCliSession(options) {
845
845
  if (inkRef) {
846
846
  // Suppress React "state update on unmounted component" warnings during cleanup.
847
847
  // Ink's useInput hook fires after unmount — this is harmless but noisy.
848
+ // This also covers the SerpentGuide exitOnToolCall path, which aborts the
849
+ // current request and breaks out of the loop into this finally block.
848
850
  // eslint-disable-next-line no-console -- intentional console.warn/error override for cleanup
849
851
  const origWarn = console.warn;
850
852
  const origError = console.error; // eslint-disable-line no-console
@@ -38,6 +38,7 @@ var __importStar = (this && this.__importStar) || (function () {
38
38
  };
39
39
  })();
40
40
  Object.defineProperty(exports, "__esModule", { value: true });
41
+ exports.emitObligationTransitionEpisodes = emitObligationTransitionEpisodes;
41
42
  exports.handleInboundTurn = handleInboundTurn;
42
43
  const runtime_1 = require("../nerves/runtime");
43
44
  const commands_1 = require("./commands");
@@ -58,6 +59,32 @@ const obligations_2 = require("../mind/obligations");
58
59
  const provider_failover_1 = require("../heart/provider-failover");
59
60
  const provider_ping_1 = require("../heart/provider-ping");
60
61
  const auth_flow_1 = require("../heart/daemon/auth-flow");
62
+ const tempo_1 = require("../heart/tempo");
63
+ const temporal_view_1 = require("../heart/temporal-view");
64
+ const wake_packet_1 = require("../heart/wake-packet");
65
+ const presence_1 = require("../heart/presence");
66
+ const episodes_1 = require("../mind/episodes");
67
+ const cares_1 = require("../heart/cares");
68
+ /**
69
+ * Emit episodes for obligation state transitions detected during a turn.
70
+ * Exported for direct testability (avoids v8 coverage merge issues in multi-file test suites).
71
+ */
72
+ function emitObligationTransitionEpisodes(agentRoot, preTurnObligationIds, postTurnObligations, preTurnObligations) {
73
+ const postTurnObligationIds = new Set(postTurnObligations.map((ob) => `${ob.id}:${ob.status}`));
74
+ for (const key of preTurnObligationIds) {
75
+ if (!postTurnObligationIds.has(key)) {
76
+ const [obId] = key.split(":");
77
+ const matchedOb = postTurnObligations.find((ob) => ob.id === obId) ?? preTurnObligations.find((ob) => ob.id === obId);
78
+ (0, episodes_1.emitEpisode)(agentRoot, {
79
+ kind: "obligation_shift",
80
+ summary: `obligation "${matchedOb?.content ?? obId}" status changed`,
81
+ whyItMattered: "obligation state transition detected during turn",
82
+ relatedEntities: [`obligation:${obId}`],
83
+ salience: "medium",
84
+ });
85
+ }
86
+ }
87
+ }
61
88
  function emptyTaskBoard() {
62
89
  return {
63
90
  compact: "",
@@ -430,6 +457,57 @@ async function handleInboundTurn(input) {
430
457
  for (const msg of input.messages) {
431
458
  sessionMessages.push(msg);
432
459
  }
460
+ // Step 4b: Continuity pipeline — derive tempo, build wake packet, snapshot obligations
461
+ let renderedWakePacket;
462
+ const preTurnObligationIds = new Set(pendingObligations.map((ob) => `${ob.id}:${ob.status}`));
463
+ try {
464
+ const agentRoot = (0, identity_1.getAgentRoot)();
465
+ const agentName = (0, identity_1.getAgentName)();
466
+ const recentEpisodes = (0, episodes_1.readRecentEpisodes)(agentRoot, { limit: 20 });
467
+ const activeCares = (0, cares_1.readActiveCares)(agentRoot);
468
+ const tempoState = (0, tempo_1.deriveTempo)({
469
+ activeSessions: sessionActivity.length + 1,
470
+ openObligations: pendingObligations.length,
471
+ recentEpisodeCount: recentEpisodes.length,
472
+ lastActivityAgeMs: sessionActivity.length > 0
473
+ ? Date.now() - new Date(sessionActivity[0].lastActivityAt).getTime()
474
+ : 0,
475
+ hasBlockers: false, // obligations use specific statuses, not "blocked"
476
+ highSalienceEpisodes: recentEpisodes.filter((ep) => ep.salience === "high" || ep.salience === "critical").length,
477
+ activeCareCount: activeCares.length,
478
+ atRiskCareCount: activeCares.filter((c) => c.currentRisk != null).length,
479
+ });
480
+ const temporalView = (0, temporal_view_1.buildTemporalView)(agentRoot, {
481
+ tempo: tempoState.mode,
482
+ preloaded: {
483
+ recentEpisodes,
484
+ activeObligations: pendingObligations,
485
+ activeCares,
486
+ },
487
+ });
488
+ const wakePacket = (0, wake_packet_1.buildWakePacket)(temporalView);
489
+ renderedWakePacket = (0, wake_packet_1.renderWakePacket)(wakePacket);
490
+ if (!renderedWakePacket)
491
+ renderedWakePacket = undefined;
492
+ // Update self-presence
493
+ const presence = (0, presence_1.derivePresence)(agentRoot, agentName, {
494
+ activeSessions: sessionActivity.length + 1,
495
+ openObligations: pendingObligations.length,
496
+ activeBridges: activeBridges.length,
497
+ codingLanes: codingSessions.length,
498
+ currentTempo: tempoState.mode,
499
+ });
500
+ (0, presence_1.writePresence)(agentRoot, agentName, presence);
501
+ }
502
+ catch (continuityError) {
503
+ (0, runtime_1.emitNervesEvent)({
504
+ level: "warn",
505
+ component: "senses",
506
+ event: "senses.continuity_error",
507
+ message: "continuity pipeline failed, continuing without wake packet",
508
+ meta: { error: continuityError instanceof Error ? continuityError.message : String(continuityError) },
509
+ });
510
+ }
433
511
  // Step 5: runAgent
434
512
  const existingToolContext = input.runAgentOptions?.toolContext;
435
513
  const runAgentOptions = {
@@ -437,6 +515,7 @@ async function handleInboundTurn(input) {
437
515
  bridgeContext,
438
516
  activeWorkFrame,
439
517
  delegationDecision,
518
+ wakePacket: renderedWakePacket,
440
519
  pendingMessages: pending.length > 0 ? pending.map((msg) => ({ from: msg.from, content: msg.content })) : undefined,
441
520
  currentSessionKey: currentSession.key,
442
521
  currentObligation,
@@ -501,6 +580,15 @@ async function handleInboundTurn(input) {
501
580
  }
502
581
  /* v8 ignore stop */
503
582
  }
583
+ // Step 5c: Emit episodes for obligation state transitions
584
+ try {
585
+ const agentRoot = (0, identity_1.getAgentRoot)();
586
+ const postTurnObligations = (0, obligations_1.readPendingObligations)(agentRoot);
587
+ emitObligationTransitionEpisodes(agentRoot, preTurnObligationIds, postTurnObligations, pendingObligations);
588
+ }
589
+ catch {
590
+ // Episode emission is non-fatal
591
+ }
504
592
  // Step 6: postTurn
505
593
  const continuingState = {
506
594
  ...(mustResolveBeforeHandoff ? { mustResolveBeforeHandoff: true } : {}),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.184",
3
+ "version": "0.1.0-alpha.186",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",
@@ -9,7 +9,7 @@
9
9
  },
10
10
  "files": [
11
11
  "dist/",
12
- "AdoptionSpecialist.ouro/",
12
+ "SerpentGuide.ouro/",
13
13
  "subagents/",
14
14
  "skills/",
15
15
  "assets/",