@ouro.bot/cli 0.1.0-alpha.26 → 0.1.0-alpha.261

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 (266) hide show
  1. package/README.md +187 -191
  2. package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/agent.json +3 -2
  3. package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/SOUL.md +1 -1
  4. package/changelog.json +1582 -0
  5. package/dist/arc/attention-types.js +8 -0
  6. package/dist/arc/cares.js +140 -0
  7. package/dist/arc/episodes.js +117 -0
  8. package/dist/arc/intentions.js +133 -0
  9. package/dist/arc/json-store.js +117 -0
  10. package/dist/arc/obligations.js +237 -0
  11. package/dist/arc/presence.js +185 -0
  12. package/dist/heart/active-work.js +832 -0
  13. package/dist/heart/agent-entry.js +37 -2
  14. package/dist/heart/auth/auth-flow.js +447 -0
  15. package/dist/heart/bridges/manager.js +358 -0
  16. package/dist/heart/bridges/state-machine.js +135 -0
  17. package/dist/heart/bridges/store.js +123 -0
  18. package/dist/heart/commitments.js +111 -0
  19. package/dist/heart/config-registry.js +304 -0
  20. package/dist/heart/config.js +63 -30
  21. package/dist/heart/core.js +644 -196
  22. package/dist/heart/cross-chat-delivery.js +131 -0
  23. package/dist/heart/daemon/agent-config-check.js +149 -0
  24. package/dist/heart/daemon/agent-discovery.js +145 -0
  25. package/dist/heart/daemon/agent-service.js +360 -0
  26. package/dist/heart/daemon/cadence.js +70 -0
  27. package/dist/heart/daemon/cli-defaults.js +557 -0
  28. package/dist/heart/daemon/cli-exec.js +1877 -0
  29. package/dist/heart/daemon/cli-parse.js +759 -0
  30. package/dist/heart/daemon/cli-render.js +464 -0
  31. package/dist/heart/daemon/cli-types.js +8 -0
  32. package/dist/heart/daemon/daemon-cli.js +29 -1179
  33. package/dist/heart/daemon/daemon-entry.js +216 -4
  34. package/dist/heart/daemon/daemon-health.js +136 -0
  35. package/dist/heart/daemon/daemon-runtime-sync.js +153 -12
  36. package/dist/heart/daemon/daemon-tombstone.js +123 -0
  37. package/dist/heart/daemon/daemon.js +590 -54
  38. package/dist/heart/daemon/hooks/agent-config-v2.js +33 -0
  39. package/dist/heart/daemon/hooks/bundle-meta.js +115 -1
  40. package/dist/heart/daemon/inner-status.js +89 -0
  41. package/dist/heart/daemon/launchd.js +39 -8
  42. package/dist/heart/daemon/log-tailer.js +4 -3
  43. package/dist/heart/daemon/message-router.js +17 -8
  44. package/dist/heart/daemon/os-cron-deps.js +134 -0
  45. package/dist/heart/daemon/ouro-bot-entry.js +1 -1
  46. package/dist/heart/daemon/process-manager.js +123 -0
  47. package/dist/heart/daemon/run-hooks.js +2 -0
  48. package/dist/heart/daemon/runtime-logging.js +58 -15
  49. package/dist/heart/daemon/runtime-metadata.js +101 -0
  50. package/dist/heart/daemon/runtime-mode.js +67 -0
  51. package/dist/heart/daemon/safe-mode.js +161 -0
  52. package/dist/heart/daemon/sense-manager.js +72 -3
  53. package/dist/heart/daemon/session-id-resolver.js +131 -0
  54. package/dist/heart/daemon/skill-management-installer.js +94 -0
  55. package/dist/heart/daemon/socket-client.js +276 -0
  56. package/dist/heart/daemon/task-scheduler.js +3 -25
  57. package/dist/heart/daemon/thoughts.js +510 -0
  58. package/dist/heart/delegation.js +62 -0
  59. package/dist/heart/habits/habit-migration.js +181 -0
  60. package/dist/heart/habits/habit-parser.js +119 -0
  61. package/dist/heart/habits/habit-scheduler.js +371 -0
  62. package/dist/heart/{daemon → hatch}/hatch-flow.js +30 -120
  63. package/dist/heart/{daemon → hatch}/hatch-specialist.js +3 -3
  64. package/dist/heart/{daemon → hatch}/specialist-prompt.js +10 -7
  65. package/dist/heart/{daemon → hatch}/specialist-tools.js +49 -3
  66. package/dist/heart/identity.js +157 -55
  67. package/dist/heart/kicks.js +2 -20
  68. package/dist/heart/mcp/mcp-server.js +653 -0
  69. package/dist/heart/migrate-config.js +127 -0
  70. package/dist/heart/model-capabilities.js +59 -0
  71. package/dist/heart/outlook/outlook-http.js +439 -0
  72. package/dist/heart/outlook/outlook-read.js +1618 -0
  73. package/dist/heart/outlook/outlook-render.js +1032 -0
  74. package/dist/heart/outlook/outlook-types.js +7 -0
  75. package/dist/heart/outlook/outlook-view.js +194 -0
  76. package/dist/heart/progress-story.js +42 -0
  77. package/dist/heart/provider-failover.js +88 -0
  78. package/dist/heart/provider-ping.js +162 -0
  79. package/dist/heart/providers/anthropic-token.js +163 -0
  80. package/dist/heart/providers/anthropic.js +169 -46
  81. package/dist/heart/providers/azure.js +98 -11
  82. package/dist/heart/providers/error-classification.js +63 -0
  83. package/dist/heart/providers/github-copilot.js +136 -0
  84. package/dist/heart/providers/minimax-vlm.js +189 -0
  85. package/dist/heart/providers/minimax.js +23 -5
  86. package/dist/heart/providers/openai-codex.js +33 -22
  87. package/dist/heart/session-activity.js +172 -0
  88. package/dist/heart/session-recall.js +216 -0
  89. package/dist/heart/start-of-turn-packet.js +330 -0
  90. package/dist/heart/streaming.js +36 -27
  91. package/dist/heart/sync.js +277 -0
  92. package/dist/heart/target-resolution.js +127 -0
  93. package/dist/heart/tempo.js +93 -0
  94. package/dist/heart/temporal-view.js +41 -0
  95. package/dist/heart/tool-activity-callbacks.js +36 -0
  96. package/dist/heart/tool-description.js +135 -0
  97. package/dist/heart/tool-loop.js +200 -0
  98. package/dist/heart/turn-context.js +358 -0
  99. package/dist/heart/turn-coordinator.js +28 -0
  100. package/dist/heart/versioning/ouro-bot-global-installer.js +128 -0
  101. package/dist/heart/{daemon → versioning}/ouro-bot-wrapper.js +1 -1
  102. package/dist/heart/{daemon → versioning}/ouro-path-installer.js +78 -35
  103. package/dist/heart/versioning/ouro-version-manager.js +171 -0
  104. package/dist/heart/{daemon → versioning}/staged-restart.js +30 -6
  105. package/dist/heart/{daemon → versioning}/update-checker.js +12 -2
  106. package/dist/heart/{daemon → versioning}/wrapper-publish-guard.js +41 -3
  107. package/dist/mind/associative-recall.js +126 -66
  108. package/dist/mind/bundle-manifest.js +8 -1
  109. package/dist/mind/context.js +125 -14
  110. package/dist/mind/{memory.js → diary.js} +68 -96
  111. package/dist/mind/embedding-provider.js +60 -0
  112. package/dist/mind/file-state.js +179 -0
  113. package/dist/mind/first-impressions.js +16 -2
  114. package/dist/mind/friends/channel.js +56 -0
  115. package/dist/mind/friends/group-context.js +144 -0
  116. package/dist/mind/friends/resolver.js +37 -0
  117. package/dist/mind/friends/store-file.js +58 -3
  118. package/dist/mind/friends/trust-explanation.js +74 -0
  119. package/dist/mind/friends/types.js +8 -0
  120. package/dist/mind/journal-index.js +161 -0
  121. package/dist/mind/obligation-steering.js +221 -0
  122. package/dist/mind/pending.js +76 -9
  123. package/dist/mind/phrases.js +1 -0
  124. package/dist/mind/prompt.js +765 -109
  125. package/dist/mind/scrutiny.js +173 -0
  126. package/dist/mind/token-estimate.js +8 -12
  127. package/dist/nerves/coverage/audit.js +1 -1
  128. package/dist/nerves/coverage/file-completeness.js +50 -5
  129. package/dist/nerves/coverage/run-artifacts.js +1 -1
  130. package/dist/nerves/event-buffer.js +111 -0
  131. package/dist/nerves/index.js +64 -4
  132. package/dist/nerves/observation.js +20 -0
  133. package/dist/nerves/runtime.js +5 -1
  134. package/dist/outlook-ui/assets/index-Ck8agNeO.js +61 -0
  135. package/dist/outlook-ui/assets/index-LwChZTgL.css +1 -0
  136. package/dist/outlook-ui/index.html +15 -0
  137. package/dist/repertoire/ado-client.js +15 -56
  138. package/dist/repertoire/ado-semantic.js +11 -10
  139. package/dist/repertoire/api-client.js +97 -0
  140. package/dist/repertoire/bitwarden-store.js +319 -0
  141. package/dist/repertoire/bw-installer.js +79 -0
  142. package/dist/repertoire/coding/codex-jsonl.js +64 -0
  143. package/dist/repertoire/coding/context-pack.js +330 -0
  144. package/dist/repertoire/coding/feedback.js +197 -30
  145. package/dist/repertoire/coding/manager.js +149 -2
  146. package/dist/repertoire/coding/spawner.js +55 -9
  147. package/dist/repertoire/coding/tools.js +170 -7
  148. package/dist/repertoire/commerce-errors.js +109 -0
  149. package/dist/repertoire/commerce-self-test.js +156 -0
  150. package/dist/repertoire/credential-access.js +515 -0
  151. package/dist/repertoire/duffel-client.js +185 -0
  152. package/dist/repertoire/github-client.js +14 -55
  153. package/dist/repertoire/graph-client.js +11 -52
  154. package/dist/repertoire/guardrails.js +369 -0
  155. package/dist/repertoire/mcp-client.js +255 -0
  156. package/dist/repertoire/mcp-manager.js +305 -0
  157. package/dist/repertoire/mcp-tools.js +63 -0
  158. package/dist/repertoire/shell-sessions.js +133 -0
  159. package/dist/repertoire/skills.js +14 -23
  160. package/dist/repertoire/stripe-client.js +131 -0
  161. package/dist/repertoire/tasks/board.js +43 -5
  162. package/dist/repertoire/tasks/fix.js +182 -0
  163. package/dist/repertoire/tasks/index.js +28 -10
  164. package/dist/repertoire/tasks/lifecycle.js +2 -2
  165. package/dist/repertoire/tasks/parser.js +3 -2
  166. package/dist/repertoire/tasks/scanner.js +194 -37
  167. package/dist/repertoire/tasks/transitions.js +11 -28
  168. package/dist/repertoire/tools-base.js +43 -771
  169. package/dist/repertoire/tools-bluebubbles.js +201 -0
  170. package/dist/repertoire/tools-bridge.js +141 -0
  171. package/dist/repertoire/tools-config.js +185 -0
  172. package/dist/repertoire/tools-continuity.js +248 -0
  173. package/dist/repertoire/tools-credential.js +182 -0
  174. package/dist/repertoire/tools-files.js +342 -0
  175. package/dist/repertoire/tools-flight.js +224 -0
  176. package/dist/repertoire/tools-flow.js +62 -0
  177. package/dist/repertoire/tools-github.js +1 -7
  178. package/dist/repertoire/tools-memory.js +347 -0
  179. package/dist/repertoire/tools-session.js +739 -0
  180. package/dist/repertoire/tools-shell.js +120 -0
  181. package/dist/repertoire/tools-stripe.js +180 -0
  182. package/dist/repertoire/tools-surface.js +243 -0
  183. package/dist/repertoire/tools-teams.js +12 -62
  184. package/dist/repertoire/tools-travel.js +125 -0
  185. package/dist/repertoire/tools-user-profile.js +144 -0
  186. package/dist/repertoire/tools-vault.js +110 -0
  187. package/dist/repertoire/tools.js +155 -130
  188. package/dist/repertoire/travel-api-client.js +360 -0
  189. package/dist/repertoire/user-profile.js +118 -0
  190. package/dist/repertoire/vault-setup.js +241 -0
  191. package/dist/scripts/claude-code-hook.js +41 -0
  192. package/dist/scripts/claude-code-stop-hook.js +47 -0
  193. package/dist/senses/attention-queue.js +97 -0
  194. package/dist/senses/bluebubbles/attachment-cache.js +53 -0
  195. package/dist/senses/{bluebubbles-client.js → bluebubbles/client.js} +145 -10
  196. package/dist/senses/bluebubbles/entry.js +13 -0
  197. package/dist/senses/bluebubbles/inbound-log.js +110 -0
  198. package/dist/senses/bluebubbles/index.js +1374 -0
  199. package/dist/senses/bluebubbles/media.js +455 -0
  200. package/dist/senses/{bluebubbles-model.js → bluebubbles/model.js} +20 -5
  201. package/dist/senses/{bluebubbles-mutation-log.js → bluebubbles/mutation-log.js} +46 -6
  202. package/dist/senses/bluebubbles/runtime-state.js +109 -0
  203. package/dist/senses/bluebubbles/session-cleanup.js +72 -0
  204. package/dist/senses/cli/bracketed-paste.js +82 -0
  205. package/dist/senses/cli/image-paste.js +211 -0
  206. package/dist/senses/cli/image-ref-navigation.js +75 -0
  207. package/dist/senses/cli/ink-app.js +156 -0
  208. package/dist/senses/cli/inline-diff.js +64 -0
  209. package/dist/senses/cli/input-keys.js +168 -0
  210. package/dist/senses/cli/kill-ring.js +86 -0
  211. package/dist/senses/cli/message-list.js +51 -0
  212. package/dist/senses/cli/ouro-tui.js +581 -0
  213. package/dist/senses/cli/spinner-imperative.js +135 -0
  214. package/dist/senses/cli/spinner.js +101 -0
  215. package/dist/senses/cli/status-line.js +60 -0
  216. package/dist/senses/cli/streaming-markdown.js +526 -0
  217. package/dist/senses/cli/tool-display.js +83 -0
  218. package/dist/senses/cli/tool-render.js +85 -0
  219. package/dist/senses/cli/tui-store.js +240 -0
  220. package/dist/senses/cli/virtual-list.js +35 -0
  221. package/dist/senses/cli-entry.js +1 -1
  222. package/dist/senses/cli-layout.js +187 -0
  223. package/dist/senses/cli.js +569 -246
  224. package/dist/senses/commands.js +65 -1
  225. package/dist/senses/continuity.js +94 -0
  226. package/dist/senses/habit-turn-message.js +108 -0
  227. package/dist/senses/inner-dialog-worker.js +112 -19
  228. package/dist/senses/inner-dialog.js +593 -85
  229. package/dist/senses/pipeline.js +552 -0
  230. package/dist/senses/shared-turn.js +199 -0
  231. package/dist/senses/surface-tool.js +68 -0
  232. package/dist/senses/teams.js +664 -165
  233. package/dist/senses/trust-gate.js +112 -2
  234. package/package.json +14 -7
  235. package/skills/agent-commerce.md +106 -0
  236. package/skills/browser-navigation.md +110 -0
  237. package/skills/commerce-setup-guide.md +116 -0
  238. package/skills/commerce-setup.md +84 -0
  239. package/skills/configure-dev-tools.md +81 -0
  240. package/skills/travel-planning.md +138 -0
  241. package/dist/heart/daemon/subagent-installer.js +0 -134
  242. package/dist/senses/bluebubbles-entry.js +0 -11
  243. package/dist/senses/bluebubbles-media.js +0 -244
  244. package/dist/senses/bluebubbles.js +0 -421
  245. package/dist/senses/debug-activity.js +0 -107
  246. package/subagents/README.md +0 -73
  247. package/subagents/work-doer.md +0 -235
  248. package/subagents/work-merger.md +0 -618
  249. package/subagents/work-planner.md +0 -382
  250. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/basilisk.md +0 -0
  251. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jafar.md +0 -0
  252. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jormungandr.md +0 -0
  253. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/kaa.md +0 -0
  254. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/medusa.md +0 -0
  255. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/monty.md +0 -0
  256. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/nagini.md +0 -0
  257. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/ouroboros.md +0 -0
  258. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/python.md +0 -0
  259. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/quetzalcoatl.md +0 -0
  260. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/sir-hiss.md +0 -0
  261. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-serpent.md +0 -0
  262. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-snake.md +0 -0
  263. /package/dist/heart/{daemon → hatch}/hatch-animation.js +0 -0
  264. /package/dist/heart/{daemon → hatch}/specialist-orchestrator.js +0 -0
  265. /package/dist/heart/{daemon → versioning}/ouro-uti.js +0 -0
  266. /package/dist/heart/{daemon → versioning}/update-hooks.js +0 -0
@@ -0,0 +1,181 @@
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.migrateHabitsFromTaskSystem = migrateHabitsFromTaskSystem;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const runtime_1 = require("../../nerves/runtime");
40
+ const habit_parser_1 = require("./habit-parser");
41
+ const parser_1 = require("../../repertoire/tasks/parser");
42
+ /** Fields that belong to the task system and should be stripped from migrated habits. */
43
+ const TASK_ONLY_FIELDS = new Set([
44
+ "type",
45
+ "category",
46
+ "requester",
47
+ "validator",
48
+ "scheduledAt",
49
+ "updated",
50
+ "depends_on",
51
+ "parent_task",
52
+ "artifacts",
53
+ ]);
54
+ /** Regex matching the YYYY-MM-DD-HHMM- timestamp prefix in task filenames. */
55
+ const TIMESTAMP_PREFIX = /^\d{4}-\d{2}-\d{2}-\d{4}-/;
56
+ /** Map old task statuses to habit statuses. */
57
+ function mapStatus(taskStatus) {
58
+ if (taskStatus === "processing")
59
+ return "active";
60
+ if (taskStatus === "paused")
61
+ return "paused";
62
+ if (taskStatus === "done")
63
+ return null; // skip done habits
64
+ return "active"; // default to active for unknown statuses
65
+ }
66
+ /** Strip timestamp prefix from filename to get the slug name. */
67
+ function stripTimestampPrefix(filename) {
68
+ return filename.replace(TIMESTAMP_PREFIX, "");
69
+ }
70
+ /**
71
+ * Migrate habit files from the old `tasks/habits/` location to the new `habits/` bundle root.
72
+ * - Strips timestamp prefix from filenames
73
+ * - Maps task statuses to habit statuses
74
+ * - Strips task-only frontmatter fields
75
+ * - Preserves body text
76
+ * - Skips done habits, README files, and already-migrated habits
77
+ * - No-op if `tasks/habits/` does not exist
78
+ */
79
+ function migrateHabitsFromTaskSystem(bundleRoot) {
80
+ const oldHabitsDir = path.join(bundleRoot, "tasks", "habits");
81
+ if (!fs.existsSync(oldHabitsDir)) {
82
+ return;
83
+ }
84
+ let files;
85
+ try {
86
+ files = fs.readdirSync(oldHabitsDir);
87
+ }
88
+ catch {
89
+ /* v8 ignore next -- race condition: dir removed between existsSync and readdirSync @preserve */
90
+ return;
91
+ }
92
+ const mdFiles = files.filter((f) => f.endsWith(".md") && f !== "README.md");
93
+ if (mdFiles.length === 0)
94
+ return;
95
+ const newHabitsDir = path.join(bundleRoot, "habits");
96
+ fs.mkdirSync(newHabitsDir, { recursive: true });
97
+ let migratedCount = 0;
98
+ for (const file of mdFiles) {
99
+ const slugName = stripTimestampPrefix(file);
100
+ const targetPath = path.join(newHabitsDir, slugName);
101
+ // Skip if already migrated
102
+ if (fs.existsSync(targetPath)) {
103
+ (0, runtime_1.emitNervesEvent)({
104
+ component: "daemon",
105
+ event: "daemon.habit_migration_skip",
106
+ message: "habit already exists at target, skipping",
107
+ meta: { file, targetPath },
108
+ });
109
+ continue;
110
+ }
111
+ const sourcePath = path.join(oldHabitsDir, file);
112
+ let content;
113
+ try {
114
+ content = fs.readFileSync(sourcePath, "utf-8");
115
+ }
116
+ catch {
117
+ continue;
118
+ }
119
+ // Parse frontmatter and body
120
+ const lines = content.split(/\r?\n/);
121
+ if (lines[0]?.trim() !== "---")
122
+ continue;
123
+ const closing = lines.findIndex((line, index) => index > 0 && line.trim() === "---");
124
+ if (closing === -1)
125
+ continue;
126
+ const rawFrontmatter = lines.slice(1, closing).join("\n");
127
+ const body = lines.slice(closing + 1).join("\n").trim();
128
+ const frontmatter = (0, parser_1.parseFrontmatter)(rawFrontmatter);
129
+ // Check status — skip done habits
130
+ const rawStatus = typeof frontmatter.status === "string" ? frontmatter.status : "processing";
131
+ const habitStatus = mapStatus(rawStatus);
132
+ if (habitStatus === null) {
133
+ (0, runtime_1.emitNervesEvent)({
134
+ component: "daemon",
135
+ event: "daemon.habit_migration_skip",
136
+ message: "skipping done habit",
137
+ meta: { file, status: rawStatus },
138
+ });
139
+ continue;
140
+ }
141
+ // Build new frontmatter, stripping task-only fields
142
+ const newFrontmatter = {};
143
+ if (typeof frontmatter.title === "string")
144
+ newFrontmatter.title = frontmatter.title;
145
+ if (typeof frontmatter.cadence === "string")
146
+ newFrontmatter.cadence = frontmatter.cadence;
147
+ newFrontmatter.status = habitStatus;
148
+ newFrontmatter.lastRun = typeof frontmatter.lastRun === "string" && frontmatter.lastRun !== "null"
149
+ ? frontmatter.lastRun
150
+ : "null";
151
+ newFrontmatter.created = typeof frontmatter.created === "string" ? frontmatter.created : "null";
152
+ // Add any other non-task fields from original
153
+ for (const [key, value] of Object.entries(frontmatter)) {
154
+ if (TASK_ONLY_FIELDS.has(key))
155
+ continue;
156
+ if (key in newFrontmatter)
157
+ continue;
158
+ /* v8 ignore next -- dead code: status is caught by `key in newFrontmatter` above since newFrontmatter.status is always set @preserve */
159
+ if (key === "status")
160
+ continue; // already mapped
161
+ newFrontmatter[key] = value;
162
+ }
163
+ const rendered = (0, habit_parser_1.renderHabitFile)(newFrontmatter, body);
164
+ fs.writeFileSync(targetPath, rendered, "utf-8");
165
+ migratedCount++;
166
+ (0, runtime_1.emitNervesEvent)({
167
+ component: "daemon",
168
+ event: "daemon.habit_migrated",
169
+ message: "migrated habit from task system",
170
+ meta: { from: sourcePath, to: targetPath },
171
+ });
172
+ }
173
+ if (migratedCount > 0) {
174
+ (0, runtime_1.emitNervesEvent)({
175
+ component: "daemon",
176
+ event: "daemon.habit_migration_complete",
177
+ message: "habit migration complete",
178
+ meta: { bundleRoot, count: migratedCount },
179
+ });
180
+ }
181
+ }
@@ -0,0 +1,119 @@
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.parseHabitFile = parseHabitFile;
37
+ exports.renderHabitFile = renderHabitFile;
38
+ const path = __importStar(require("path"));
39
+ const parser_1 = require("../../repertoire/tasks/parser");
40
+ const runtime_1 = require("../../nerves/runtime");
41
+ function isHabitStatus(value) {
42
+ return value === "active" || value === "paused";
43
+ }
44
+ function extractFrontmatterAndBody(content) {
45
+ const lines = content.split(/\r?\n/);
46
+ if (lines[0]?.trim() !== "---") {
47
+ return null;
48
+ }
49
+ const closing = lines.findIndex((line, index) => index > 0 && line.trim() === "---");
50
+ if (closing === -1) {
51
+ return null;
52
+ }
53
+ const rawFrontmatter = lines.slice(1, closing).join("\n");
54
+ const body = lines.slice(closing + 1).join("\n").trim();
55
+ return { frontmatter: (0, parser_1.parseFrontmatter)(rawFrontmatter), body };
56
+ }
57
+ function parseHabitFile(content, filePath) {
58
+ (0, runtime_1.emitNervesEvent)({
59
+ event: "daemon.habit_parse",
60
+ component: "daemon",
61
+ message: "parsing habit file",
62
+ meta: { filePath },
63
+ });
64
+ const stem = path.basename(filePath, ".md");
65
+ const parsed = extractFrontmatterAndBody(content);
66
+ if (!parsed) {
67
+ return {
68
+ name: stem,
69
+ title: stem,
70
+ cadence: null,
71
+ status: "active",
72
+ lastRun: null,
73
+ created: null,
74
+ body: content.trim(),
75
+ };
76
+ }
77
+ const { frontmatter, body } = parsed;
78
+ const rawTitle = frontmatter.title;
79
+ const title = typeof rawTitle === "string" && rawTitle.length > 0 ? rawTitle : stem;
80
+ const rawCadence = frontmatter.cadence;
81
+ const cadence = typeof rawCadence === "string" && rawCadence.length > 0 ? rawCadence : null;
82
+ const rawStatus = frontmatter.status;
83
+ const status = typeof rawStatus === "string" && isHabitStatus(rawStatus) ? rawStatus : "active";
84
+ const rawLastRun = frontmatter.lastRun;
85
+ const lastRun = typeof rawLastRun === "string" && rawLastRun.length > 0 ? rawLastRun : null;
86
+ const rawCreated = frontmatter.created;
87
+ const created = typeof rawCreated === "string" && rawCreated.length > 0 ? rawCreated : null;
88
+ return {
89
+ name: stem,
90
+ title,
91
+ cadence,
92
+ status,
93
+ lastRun,
94
+ created,
95
+ body,
96
+ };
97
+ }
98
+ function formatFrontmatterValue(value) {
99
+ if (value === null || value === undefined)
100
+ return "null";
101
+ return String(value);
102
+ }
103
+ function renderHabitFile(frontmatter, body) {
104
+ (0, runtime_1.emitNervesEvent)({
105
+ event: "daemon.habit_render",
106
+ component: "daemon",
107
+ message: "rendering habit file",
108
+ meta: {},
109
+ });
110
+ const lines = ["---"];
111
+ for (const key of Object.keys(frontmatter)) {
112
+ lines.push(`${key}: ${formatFrontmatterValue(frontmatter[key])}`);
113
+ }
114
+ lines.push("---");
115
+ lines.push("");
116
+ lines.push(body.trim());
117
+ lines.push("");
118
+ return lines.join("\n");
119
+ }
@@ -0,0 +1,371 @@
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.HabitScheduler = void 0;
37
+ const path = __importStar(require("path"));
38
+ const runtime_1 = require("../../nerves/runtime");
39
+ const habit_parser_1 = require("./habit-parser");
40
+ const cadence_1 = require("../daemon/cadence");
41
+ const WATCH_DEBOUNCE_MS = 200;
42
+ class HabitScheduler {
43
+ agent;
44
+ habitsDir;
45
+ osCronManager;
46
+ onHabitFire;
47
+ deps;
48
+ execForVerify;
49
+ platform;
50
+ watcher = null;
51
+ debounceTimer = null;
52
+ parseErrors = [];
53
+ timerFallbacks = new Map();
54
+ degradedHabitNames = new Map();
55
+ periodicTimer = null;
56
+ constructor(options) {
57
+ this.agent = options.agent;
58
+ this.habitsDir = options.habitsDir;
59
+ this.osCronManager = options.osCronManager;
60
+ this.onHabitFire = options.onHabitFire;
61
+ this.deps = options.deps;
62
+ this.execForVerify = options.execForVerify;
63
+ this.platform = options.platform ?? process.platform;
64
+ }
65
+ start() {
66
+ (0, runtime_1.emitNervesEvent)({
67
+ component: "daemon",
68
+ event: "daemon.habit_scheduler_start",
69
+ message: "habit scheduler starting",
70
+ meta: { agent: this.agent, habitsDir: this.habitsDir },
71
+ });
72
+ const habits = this.scanHabits();
73
+ const jobs = this.buildJobs(habits);
74
+ this.osCronManager.sync(jobs);
75
+ this.verifyCronAndCreateFallbacks(jobs);
76
+ this.fireOverdueHabits(habits);
77
+ }
78
+ reconcile() {
79
+ (0, runtime_1.emitNervesEvent)({
80
+ component: "daemon",
81
+ event: "daemon.habit_scheduler_reconcile",
82
+ message: "habit scheduler reconciling",
83
+ meta: { agent: this.agent },
84
+ });
85
+ // Clear ALL existing timers FIRST to prevent overlap window
86
+ this.clearAllTimerFallbacks();
87
+ const habits = this.scanHabits();
88
+ const jobs = this.buildJobs(habits);
89
+ this.osCronManager.sync(jobs);
90
+ this.verifyCronAndCreateFallbacks(jobs);
91
+ this.fireOverdueHabits(habits);
92
+ }
93
+ fireOverdueHabits(habits) {
94
+ for (const habit of habits) {
95
+ if (habit.status !== "active")
96
+ continue;
97
+ if (!habit.cadence)
98
+ continue;
99
+ const cadenceMs = (0, cadence_1.parseCadenceToMs)(habit.cadence);
100
+ if (cadenceMs === null)
101
+ continue;
102
+ const nowMs = this.deps.now();
103
+ if (habit.lastRun === null) {
104
+ (0, runtime_1.emitNervesEvent)({
105
+ component: "daemon",
106
+ event: "daemon.habit_fire",
107
+ message: "firing overdue habit (never run)",
108
+ meta: { habitName: habit.name, agent: this.agent },
109
+ });
110
+ this.onHabitFire(habit.name);
111
+ continue;
112
+ }
113
+ const lastRunMs = new Date(habit.lastRun).getTime();
114
+ const elapsed = nowMs - lastRunMs;
115
+ if (elapsed >= cadenceMs) {
116
+ (0, runtime_1.emitNervesEvent)({
117
+ component: "daemon",
118
+ event: "daemon.habit_fire",
119
+ message: "firing overdue habit",
120
+ meta: { habitName: habit.name, agent: this.agent, elapsedMs: elapsed },
121
+ });
122
+ this.onHabitFire(habit.name);
123
+ }
124
+ }
125
+ }
126
+ stop() {
127
+ // `_end` (not `_stop`) to pair with `daemon.habit_scheduler_start`
128
+ // under the nerves audit start/end pairing rule.
129
+ (0, runtime_1.emitNervesEvent)({
130
+ component: "daemon",
131
+ event: "daemon.habit_scheduler_end",
132
+ message: "habit scheduler stopping",
133
+ meta: { agent: this.agent },
134
+ });
135
+ this.stopPeriodicReconciliation();
136
+ this.clearAllTimerFallbacks();
137
+ this.osCronManager.removeAll();
138
+ }
139
+ listOverdueHabits() {
140
+ const habits = this.scanHabits();
141
+ const nowMs = this.deps.now();
142
+ const overdue = [];
143
+ for (const habit of habits) {
144
+ if (habit.status !== "active")
145
+ continue;
146
+ if (!habit.cadence)
147
+ continue;
148
+ const cadenceMs = (0, cadence_1.parseCadenceToMs)(habit.cadence);
149
+ if (cadenceMs === null)
150
+ continue;
151
+ if (habit.lastRun === null) {
152
+ overdue.push({ name: habit.name, elapsedMs: Infinity });
153
+ continue;
154
+ }
155
+ const lastRunMs = new Date(habit.lastRun).getTime();
156
+ const elapsed = nowMs - lastRunMs;
157
+ if (elapsed >= cadenceMs) {
158
+ overdue.push({ name: habit.name, elapsedMs: elapsed });
159
+ }
160
+ }
161
+ return overdue;
162
+ }
163
+ getParseErrors() {
164
+ return [...this.parseErrors];
165
+ }
166
+ getHabitFile(name) {
167
+ const filePath = path.join(this.habitsDir, `${name}.md`);
168
+ try {
169
+ const content = this.deps.readFile(filePath, "utf-8");
170
+ return (0, habit_parser_1.parseHabitFile)(content, filePath);
171
+ }
172
+ catch {
173
+ return null;
174
+ }
175
+ }
176
+ watchForChanges() {
177
+ const watchFn = this.deps.watch;
178
+ if (!watchFn)
179
+ return;
180
+ // Ensure habits directory exists before watching — agents may not have one yet
181
+ try {
182
+ this.watcher = watchFn(this.habitsDir, (_event, _filename) => {
183
+ if (this.debounceTimer !== null) {
184
+ clearTimeout(this.debounceTimer);
185
+ }
186
+ this.debounceTimer = setTimeout(() => {
187
+ this.debounceTimer = null;
188
+ this.reconcile();
189
+ }, WATCH_DEBOUNCE_MS);
190
+ });
191
+ /* v8 ignore start — ENOENT catch requires real missing directory @preserve */
192
+ }
193
+ catch {
194
+ // habits directory may not exist for all agents — skip watching silently
195
+ }
196
+ /* v8 ignore stop */
197
+ }
198
+ stopWatching() {
199
+ if (this.debounceTimer !== null) {
200
+ clearTimeout(this.debounceTimer);
201
+ this.debounceTimer = null;
202
+ }
203
+ if (this.watcher !== null) {
204
+ this.watcher.close();
205
+ this.watcher = null;
206
+ }
207
+ }
208
+ getDegradedHabits() {
209
+ const result = [];
210
+ for (const [name, reason] of this.degradedHabitNames) {
211
+ result.push({ name, reason });
212
+ }
213
+ return result;
214
+ }
215
+ static DEFAULT_PERIODIC_INTERVAL_MS = 300_000; // 5 minutes
216
+ static INITIAL_RECONCILIATION_DELAY_MS = 30_000; // 30 seconds
217
+ startPeriodicReconciliation(intervalMs) {
218
+ const interval = intervalMs ?? HabitScheduler.DEFAULT_PERIODIC_INTERVAL_MS;
219
+ // First reconciliation after a short delay (30s)
220
+ this.periodicTimer = setTimeout(() => {
221
+ this.reconcile();
222
+ this.scheduleNextReconciliation(interval);
223
+ }, HabitScheduler.INITIAL_RECONCILIATION_DELAY_MS);
224
+ }
225
+ stopPeriodicReconciliation() {
226
+ if (this.periodicTimer !== null) {
227
+ clearTimeout(this.periodicTimer);
228
+ this.periodicTimer = null;
229
+ }
230
+ }
231
+ scheduleNextReconciliation(intervalMs) {
232
+ this.periodicTimer = setTimeout(() => {
233
+ this.reconcile();
234
+ this.scheduleNextReconciliation(intervalMs);
235
+ }, intervalMs);
236
+ }
237
+ verifyCronAndCreateFallbacks(jobs) {
238
+ if (!this.execForVerify)
239
+ return;
240
+ const verifiedLabels = this.verifyCronEntries();
241
+ for (const job of jobs) {
242
+ const label = `bot.ouro.${job.agent}.${job.taskId}`;
243
+ const isVerified = this.platform === "darwin"
244
+ ? verifiedLabels.has(label)
245
+ : verifiedLabels.has(job.taskId);
246
+ if (!isVerified) {
247
+ (0, runtime_1.emitNervesEvent)({
248
+ component: "daemon",
249
+ event: "daemon.habit_cron_verification_failed",
250
+ message: `cron verification failed for habit: ${job.taskId}`,
251
+ meta: { habitName: job.taskId, agent: job.agent, label },
252
+ });
253
+ // Parse cadence from the original habit file for timer interval
254
+ const habitFile = this.getHabitFile(job.taskId);
255
+ const ms = habitFile?.cadence ? (0, cadence_1.parseCadenceToMs)(habitFile.cadence) : null;
256
+ if (ms !== null) {
257
+ this.createTimerFallback(job.taskId, ms);
258
+ }
259
+ this.degradedHabitNames.set(job.taskId, "cron registration failed — using timer fallback");
260
+ }
261
+ }
262
+ }
263
+ verifyCronEntries() {
264
+ const verified = new Set();
265
+ try {
266
+ if (this.platform === "darwin") {
267
+ const output = this.execForVerify("launchctl list");
268
+ const lines = output.split("\n");
269
+ for (const line of lines) {
270
+ const match = line.match(/bot\.ouro\.\S+\.\S+/);
271
+ if (match) {
272
+ verified.add(match[0]);
273
+ }
274
+ }
275
+ }
276
+ else {
277
+ const output = this.execForVerify("crontab -l");
278
+ const lines = output.split("\n");
279
+ for (const line of lines) {
280
+ const match = line.match(/ouro poke \S+ --habit (\S+)/);
281
+ if (match) {
282
+ verified.add(match[1]);
283
+ }
284
+ }
285
+ }
286
+ }
287
+ catch {
288
+ // Verification command failed — return empty set (all habits unverified)
289
+ }
290
+ return verified;
291
+ }
292
+ createTimerFallback(habitName, cadenceMs) {
293
+ const schedule = () => {
294
+ const timer = setTimeout(() => {
295
+ this.onHabitFire(habitName);
296
+ schedule();
297
+ }, cadenceMs);
298
+ this.timerFallbacks.set(habitName, timer);
299
+ };
300
+ schedule();
301
+ }
302
+ clearAllTimerFallbacks() {
303
+ for (const timer of this.timerFallbacks.values()) {
304
+ clearTimeout(timer);
305
+ }
306
+ this.timerFallbacks.clear();
307
+ this.degradedHabitNames.clear();
308
+ }
309
+ scanHabits() {
310
+ let files;
311
+ try {
312
+ files = this.deps.readdir(this.habitsDir);
313
+ }
314
+ catch {
315
+ this.parseErrors = [];
316
+ return [];
317
+ }
318
+ const habits = [];
319
+ const errors = [];
320
+ for (const file of files) {
321
+ if (!file.endsWith(".md"))
322
+ continue;
323
+ const filePath = path.join(this.habitsDir, file);
324
+ try {
325
+ const content = this.deps.readFile(filePath, "utf-8");
326
+ const habit = (0, habit_parser_1.parseHabitFile)(content, filePath);
327
+ habits.push(habit);
328
+ }
329
+ catch (error) {
330
+ const errorMessage = error instanceof Error ? error.message : String(error);
331
+ errors.push({ file, error: errorMessage });
332
+ (0, runtime_1.emitNervesEvent)({
333
+ level: "error",
334
+ component: "daemon",
335
+ event: "daemon.habit_parse_error",
336
+ message: "failed to parse habit file",
337
+ meta: {
338
+ file,
339
+ error: errorMessage,
340
+ agent: this.agent,
341
+ },
342
+ });
343
+ }
344
+ }
345
+ this.parseErrors = errors;
346
+ return habits;
347
+ }
348
+ buildJobs(habits) {
349
+ const jobs = [];
350
+ for (const habit of habits) {
351
+ if (habit.status !== "active")
352
+ continue;
353
+ if (!habit.cadence)
354
+ continue;
355
+ const cronSchedule = (0, cadence_1.parseCadenceToCron)(habit.cadence);
356
+ if (cronSchedule === null)
357
+ continue;
358
+ jobs.push({
359
+ id: `${this.agent}:${habit.name}:cadence`,
360
+ agent: this.agent,
361
+ taskId: habit.name,
362
+ schedule: cronSchedule,
363
+ lastRun: habit.lastRun,
364
+ command: `${this.deps.ouroPath} poke ${this.agent} --habit ${habit.name}`,
365
+ taskPath: path.join(this.habitsDir, `${habit.name}.md`),
366
+ });
367
+ }
368
+ return jobs;
369
+ }
370
+ }
371
+ exports.HabitScheduler = HabitScheduler;