@ouro.bot/cli 0.1.0-alpha.24 → 0.1.0-alpha.241

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 (258) 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 +1459 -0
  5. package/dist/arc/attention-types.js +8 -0
  6. package/dist/arc/cares.js +140 -0
  7. package/dist/arc/episodes.js +119 -0
  8. package/dist/arc/intentions.js +133 -0
  9. package/dist/arc/json-store.js +120 -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 +620 -159
  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 +81 -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 +1847 -0
  29. package/dist/heart/daemon/cli-parse.js +759 -0
  30. package/dist/heart/daemon/cli-render.js +399 -0
  31. package/dist/heart/daemon/cli-types.js +8 -0
  32. package/dist/heart/daemon/daemon-cli.js +29 -1177
  33. package/dist/heart/daemon/daemon-entry.js +202 -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 +492 -20
  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 +202 -0
  56. package/dist/heart/daemon/task-scheduler.js +3 -25
  57. package/dist/heart/daemon/thoughts.js +507 -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 +369 -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 +651 -0
  69. package/dist/heart/migrate-config.js +127 -0
  70. package/dist/heart/model-capabilities.js +48 -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 +187 -41
  81. package/dist/heart/providers/azure.js +116 -10
  82. package/dist/heart/providers/github-copilot.js +157 -0
  83. package/dist/heart/providers/minimax.js +33 -3
  84. package/dist/heart/providers/openai-codex.js +51 -16
  85. package/dist/heart/session-activity.js +172 -0
  86. package/dist/heart/session-recall.js +216 -0
  87. package/dist/heart/start-of-turn-packet.js +330 -0
  88. package/dist/heart/streaming.js +36 -27
  89. package/dist/heart/sync.js +217 -0
  90. package/dist/heart/target-resolution.js +127 -0
  91. package/dist/heart/tempo.js +93 -0
  92. package/dist/heart/temporal-view.js +41 -0
  93. package/dist/heart/tool-activity-callbacks.js +36 -0
  94. package/dist/heart/tool-description.js +135 -0
  95. package/dist/heart/tool-loop.js +200 -0
  96. package/dist/heart/turn-context.js +358 -0
  97. package/dist/heart/turn-coordinator.js +28 -0
  98. package/dist/heart/versioning/ouro-bot-global-installer.js +128 -0
  99. package/dist/heart/{daemon → versioning}/ouro-bot-wrapper.js +1 -1
  100. package/dist/heart/{daemon → versioning}/ouro-path-installer.js +78 -35
  101. package/dist/heart/versioning/ouro-version-manager.js +171 -0
  102. package/dist/heart/{daemon → versioning}/staged-restart.js +30 -6
  103. package/dist/heart/{daemon → versioning}/update-checker.js +9 -1
  104. package/dist/heart/{daemon → versioning}/update-hooks.js +11 -0
  105. package/dist/heart/{daemon → versioning}/wrapper-publish-guard.js +41 -3
  106. package/dist/mind/associative-recall.js +126 -66
  107. package/dist/mind/bundle-manifest.js +8 -1
  108. package/dist/mind/context.js +125 -14
  109. package/dist/mind/{memory.js → diary.js} +73 -96
  110. package/dist/mind/embedding-provider.js +60 -0
  111. package/dist/mind/file-state.js +179 -0
  112. package/dist/mind/first-impressions.js +16 -2
  113. package/dist/mind/friends/channel.js +56 -0
  114. package/dist/mind/friends/group-context.js +144 -0
  115. package/dist/mind/friends/resolver.js +37 -0
  116. package/dist/mind/friends/store-file.js +58 -3
  117. package/dist/mind/friends/trust-explanation.js +74 -0
  118. package/dist/mind/friends/types.js +8 -0
  119. package/dist/mind/journal-index.js +161 -0
  120. package/dist/mind/obligation-steering.js +221 -0
  121. package/dist/mind/pending.js +76 -9
  122. package/dist/mind/phrases.js +1 -0
  123. package/dist/mind/prompt.js +762 -109
  124. package/dist/mind/scrutiny.js +173 -0
  125. package/dist/mind/token-estimate.js +8 -12
  126. package/dist/nerves/coverage/audit.js +1 -1
  127. package/dist/nerves/coverage/file-completeness.js +50 -5
  128. package/dist/nerves/coverage/run-artifacts.js +1 -1
  129. package/dist/nerves/event-buffer.js +111 -0
  130. package/dist/nerves/index.js +64 -4
  131. package/dist/nerves/observation.js +20 -0
  132. package/dist/nerves/runtime.js +5 -1
  133. package/dist/outlook-ui/assets/index-Ck8agNeO.js +61 -0
  134. package/dist/outlook-ui/assets/index-LwChZTgL.css +1 -0
  135. package/dist/outlook-ui/index.html +15 -0
  136. package/dist/repertoire/ado-client.js +15 -56
  137. package/dist/repertoire/ado-semantic.js +11 -10
  138. package/dist/repertoire/api-client.js +97 -0
  139. package/dist/repertoire/bitwarden-store.js +319 -0
  140. package/dist/repertoire/bw-installer.js +79 -0
  141. package/dist/repertoire/coding/codex-jsonl.js +64 -0
  142. package/dist/repertoire/coding/context-pack.js +330 -0
  143. package/dist/repertoire/coding/feedback.js +197 -30
  144. package/dist/repertoire/coding/manager.js +149 -2
  145. package/dist/repertoire/coding/spawner.js +55 -9
  146. package/dist/repertoire/coding/tools.js +170 -7
  147. package/dist/repertoire/commerce-errors.js +109 -0
  148. package/dist/repertoire/commerce-self-test.js +156 -0
  149. package/dist/repertoire/credential-access.js +515 -0
  150. package/dist/repertoire/duffel-client.js +185 -0
  151. package/dist/repertoire/github-client.js +14 -55
  152. package/dist/repertoire/graph-client.js +11 -52
  153. package/dist/repertoire/guardrails.js +369 -0
  154. package/dist/repertoire/mcp-client.js +255 -0
  155. package/dist/repertoire/mcp-manager.js +303 -0
  156. package/dist/repertoire/mcp-tools.js +63 -0
  157. package/dist/repertoire/shell-sessions.js +133 -0
  158. package/dist/repertoire/skills.js +14 -23
  159. package/dist/repertoire/stripe-client.js +131 -0
  160. package/dist/repertoire/tasks/board.js +43 -5
  161. package/dist/repertoire/tasks/fix.js +182 -0
  162. package/dist/repertoire/tasks/index.js +28 -10
  163. package/dist/repertoire/tasks/lifecycle.js +2 -2
  164. package/dist/repertoire/tasks/parser.js +3 -2
  165. package/dist/repertoire/tasks/scanner.js +194 -37
  166. package/dist/repertoire/tasks/transitions.js +11 -28
  167. package/dist/repertoire/tools-base.js +43 -771
  168. package/dist/repertoire/tools-bluebubbles.js +94 -0
  169. package/dist/repertoire/tools-bridge.js +141 -0
  170. package/dist/repertoire/tools-config.js +185 -0
  171. package/dist/repertoire/tools-continuity.js +248 -0
  172. package/dist/repertoire/tools-credential.js +182 -0
  173. package/dist/repertoire/tools-files.js +342 -0
  174. package/dist/repertoire/tools-flight.js +224 -0
  175. package/dist/repertoire/tools-flow.js +62 -0
  176. package/dist/repertoire/tools-github.js +1 -7
  177. package/dist/repertoire/tools-memory.js +347 -0
  178. package/dist/repertoire/tools-session.js +739 -0
  179. package/dist/repertoire/tools-shell.js +120 -0
  180. package/dist/repertoire/tools-stripe.js +180 -0
  181. package/dist/repertoire/tools-surface.js +243 -0
  182. package/dist/repertoire/tools-teams.js +12 -62
  183. package/dist/repertoire/tools-travel.js +125 -0
  184. package/dist/repertoire/tools-user-profile.js +144 -0
  185. package/dist/repertoire/tools-vault.js +110 -0
  186. package/dist/repertoire/tools.js +147 -130
  187. package/dist/repertoire/travel-api-client.js +360 -0
  188. package/dist/repertoire/user-profile.js +118 -0
  189. package/dist/repertoire/vault-setup.js +241 -0
  190. package/dist/scripts/claude-code-hook.js +41 -0
  191. package/dist/scripts/claude-code-stop-hook.js +47 -0
  192. package/dist/senses/attention-queue.js +97 -0
  193. package/dist/senses/{bluebubbles-client.js → bluebubbles/client.js} +109 -10
  194. package/dist/senses/bluebubbles/entry.js +13 -0
  195. package/dist/senses/bluebubbles/inbound-log.js +109 -0
  196. package/dist/senses/bluebubbles/index.js +1374 -0
  197. package/dist/senses/{bluebubbles-media.js → bluebubbles/media.js} +119 -24
  198. package/dist/senses/{bluebubbles-model.js → bluebubbles/model.js} +13 -5
  199. package/dist/senses/{bluebubbles-mutation-log.js → bluebubbles/mutation-log.js} +46 -6
  200. package/dist/senses/bluebubbles/runtime-state.js +109 -0
  201. package/dist/senses/bluebubbles/session-cleanup.js +72 -0
  202. package/dist/senses/cli/adapter.js +274 -0
  203. package/dist/senses/cli/ink-app.js +156 -0
  204. package/dist/senses/cli/inline-diff.js +64 -0
  205. package/dist/senses/cli/message-list.js +51 -0
  206. package/dist/senses/cli/ouro-tui.js +358 -0
  207. package/dist/senses/cli/spinner-imperative.js +135 -0
  208. package/dist/senses/cli/spinner.js +101 -0
  209. package/dist/senses/cli/status-line.js +60 -0
  210. package/dist/senses/cli/streaming-markdown.js +526 -0
  211. package/dist/senses/cli/tool-display.js +83 -0
  212. package/dist/senses/cli/tool-render.js +85 -0
  213. package/dist/senses/cli/tui-store.js +198 -0
  214. package/dist/senses/cli/virtual-list.js +35 -0
  215. package/dist/senses/cli-entry.js +1 -1
  216. package/dist/senses/cli-layout.js +187 -0
  217. package/dist/senses/cli.js +510 -246
  218. package/dist/senses/commands.js +65 -1
  219. package/dist/senses/continuity.js +94 -0
  220. package/dist/senses/habit-turn-message.js +108 -0
  221. package/dist/senses/inner-dialog-worker.js +112 -19
  222. package/dist/senses/inner-dialog.js +593 -85
  223. package/dist/senses/pipeline.js +559 -0
  224. package/dist/senses/shared-turn.js +199 -0
  225. package/dist/senses/surface-tool.js +68 -0
  226. package/dist/senses/teams.js +664 -165
  227. package/dist/senses/trust-gate.js +112 -2
  228. package/package.json +14 -7
  229. package/skills/agent-commerce.md +106 -0
  230. package/skills/browser-navigation.md +110 -0
  231. package/skills/commerce-setup-guide.md +116 -0
  232. package/skills/commerce-setup.md +84 -0
  233. package/skills/configure-dev-tools.md +81 -0
  234. package/skills/travel-planning.md +138 -0
  235. package/dist/heart/daemon/subagent-installer.js +0 -134
  236. package/dist/senses/bluebubbles-entry.js +0 -11
  237. package/dist/senses/bluebubbles.js +0 -421
  238. package/dist/senses/debug-activity.js +0 -107
  239. package/subagents/README.md +0 -73
  240. package/subagents/work-doer.md +0 -235
  241. package/subagents/work-merger.md +0 -618
  242. package/subagents/work-planner.md +0 -382
  243. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/basilisk.md +0 -0
  244. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jafar.md +0 -0
  245. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jormungandr.md +0 -0
  246. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/kaa.md +0 -0
  247. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/medusa.md +0 -0
  248. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/monty.md +0 -0
  249. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/nagini.md +0 -0
  250. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/ouroboros.md +0 -0
  251. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/python.md +0 -0
  252. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/quetzalcoatl.md +0 -0
  253. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/sir-hiss.md +0 -0
  254. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-serpent.md +0 -0
  255. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-snake.md +0 -0
  256. /package/dist/heart/{daemon → hatch}/hatch-animation.js +0 -0
  257. /package/dist/heart/{daemon → hatch}/specialist-orchestrator.js +0 -0
  258. /package/dist/heart/{daemon → versioning}/ouro-uti.js +0 -0
@@ -0,0 +1,241 @@
1
+ "use strict";
2
+ /**
3
+ * Vault setup module — Bitwarden/Vaultwarden account creation.
4
+ *
5
+ * Implements the Bitwarden registration protocol using Node.js crypto:
6
+ * - PBKDF2-SHA256 for master key derivation
7
+ * - HKDF-SHA256 for key stretching
8
+ * - AES-256-CBC for symmetric key protection
9
+ * - RSA-2048 keypair for asymmetric encryption
10
+ *
11
+ * All crypto follows the Bitwarden security whitepaper:
12
+ * https://bitwarden.com/help/bitwarden-security-white-paper/
13
+ */
14
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
15
+ if (k2 === undefined) k2 = k;
16
+ var desc = Object.getOwnPropertyDescriptor(m, k);
17
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
18
+ desc = { enumerable: true, get: function() { return m[k]; } };
19
+ }
20
+ Object.defineProperty(o, k2, desc);
21
+ }) : (function(o, m, k, k2) {
22
+ if (k2 === undefined) k2 = k;
23
+ o[k2] = m[k];
24
+ }));
25
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
26
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
27
+ }) : function(o, v) {
28
+ o["default"] = v;
29
+ });
30
+ var __importStar = (this && this.__importStar) || (function () {
31
+ var ownKeys = function(o) {
32
+ ownKeys = Object.getOwnPropertyNames || function (o) {
33
+ var ar = [];
34
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
35
+ return ar;
36
+ };
37
+ return ownKeys(o);
38
+ };
39
+ return function (mod) {
40
+ if (mod && mod.__esModule) return mod;
41
+ var result = {};
42
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
43
+ __setModuleDefault(result, mod);
44
+ return result;
45
+ };
46
+ })();
47
+ Object.defineProperty(exports, "__esModule", { value: true });
48
+ exports.deriveMasterKey = deriveMasterKey;
49
+ exports.deriveMasterPasswordHash = deriveMasterPasswordHash;
50
+ exports.deriveStretchedMasterKey = deriveStretchedMasterKey;
51
+ exports.makeProtectedSymmetricKey = makeProtectedSymmetricKey;
52
+ exports.createVaultAccount = createVaultAccount;
53
+ const crypto = __importStar(require("node:crypto"));
54
+ const runtime_1 = require("../nerves/runtime");
55
+ // ---------------------------------------------------------------------------
56
+ // Crypto primitives
57
+ // ---------------------------------------------------------------------------
58
+ /**
59
+ * Derive the master key from password and email using PBKDF2-SHA256.
60
+ * Email is lowercased and used as the salt per Bitwarden spec.
61
+ */
62
+ function deriveMasterKey(password, email, iterations) {
63
+ return new Promise((resolve, reject) => {
64
+ crypto.pbkdf2(password, email.toLowerCase(), iterations, 32, "sha256", (err, key) => {
65
+ /* v8 ignore next -- defensive: pbkdf2 rejects on invalid input @preserve */
66
+ if (err)
67
+ reject(err);
68
+ else
69
+ resolve(key);
70
+ });
71
+ });
72
+ }
73
+ /**
74
+ * Derive the master password hash: PBKDF2-SHA256(masterKey, password, 1 iteration).
75
+ * This hash is sent to the server for authentication — it never sees the master key.
76
+ */
77
+ function deriveMasterPasswordHash(masterKey, password) {
78
+ return new Promise((resolve, reject) => {
79
+ crypto.pbkdf2(masterKey, password, 1, 32, "sha256", (err, hash) => {
80
+ /* v8 ignore next -- defensive: pbkdf2 rejects on invalid input @preserve */
81
+ if (err)
82
+ reject(err);
83
+ else
84
+ resolve(hash.toString("base64"));
85
+ });
86
+ });
87
+ }
88
+ /**
89
+ * Stretch the master key using HKDF-Expand-only (RFC 5869 §2.3) to produce a 64-byte key.
90
+ * First 32 bytes = encryption key, last 32 bytes = MAC key.
91
+ *
92
+ * CRITICAL: Bitwarden uses HKDF-Expand ONLY (no Extract step).
93
+ * Node.js crypto.hkdfSync() does Extract+Expand which produces DIFFERENT output.
94
+ * Reference: https://github.com/bitwarden/sdk-internal/blob/main/crates/bitwarden-crypto/src/util.rs
95
+ * Bitwarden calls Hkdf::<Sha256>::from_prk(masterKey).expand(info, output) — Expand only.
96
+ */
97
+ function deriveStretchedMasterKey(masterKey) {
98
+ const encKey = hkdfExpandOnly(masterKey, "enc", 32);
99
+ const macKey = hkdfExpandOnly(masterKey, "mac", 32);
100
+ return Buffer.concat([encKey, macKey]);
101
+ }
102
+ /**
103
+ * HKDF-Expand only (RFC 5869 §2.3) — no Extract step.
104
+ * Matches Bitwarden's Hkdf::from_prk(prk).expand(info).
105
+ */
106
+ function hkdfExpandOnly(prk, info, length) {
107
+ const hashLen = 32; // SHA-256
108
+ const n = Math.ceil(length / hashLen);
109
+ let okm = Buffer.alloc(0);
110
+ let t = Buffer.alloc(0);
111
+ for (let i = 1; i <= n; i++) {
112
+ t = crypto.createHmac("sha256", prk)
113
+ .update(Buffer.concat([t, Buffer.from(info, "utf8"), Buffer.from([i])]))
114
+ .digest();
115
+ okm = Buffer.concat([okm, t]);
116
+ }
117
+ return okm.subarray(0, length);
118
+ }
119
+ /**
120
+ * Encrypt data with AES-256-CBC and HMAC-SHA256 MAC.
121
+ * Returns a Bitwarden "type 2" cipherstring: "2.<iv>|<ct>|<mac>"
122
+ */
123
+ function encryptWithStretchedKey(data, stretchedKey) {
124
+ const encKey = stretchedKey.subarray(0, 32);
125
+ const macKey = stretchedKey.subarray(32, 64);
126
+ const iv = crypto.randomBytes(16);
127
+ const cipher = crypto.createCipheriv("aes-256-cbc", encKey, iv);
128
+ const ct = Buffer.concat([cipher.update(data), cipher.final()]);
129
+ // MAC covers iv + ct
130
+ const mac = crypto.createHmac("sha256", macKey)
131
+ .update(iv)
132
+ .update(ct)
133
+ .digest();
134
+ return `2.${iv.toString("base64")}|${ct.toString("base64")}|${mac.toString("base64")}`;
135
+ }
136
+ /**
137
+ * Generate a 64-byte symmetric key, encrypt it with the stretched master key.
138
+ * Returns the "protected symmetric key" cipherstring.
139
+ */
140
+ function makeProtectedSymmetricKey(stretchedMasterKey) {
141
+ const symKey = crypto.randomBytes(64);
142
+ return encryptWithStretchedKey(symKey, stretchedMasterKey);
143
+ }
144
+ /**
145
+ * Generate an RSA-2048 keypair.
146
+ * Returns { publicKey: base64-DER, privateKeyDer: Buffer }.
147
+ */
148
+ function generateRsaKeypair() {
149
+ const { publicKey, privateKey } = crypto.generateKeyPairSync("rsa", {
150
+ modulusLength: 2048,
151
+ publicKeyEncoding: { type: "spki", format: "der" },
152
+ privateKeyEncoding: { type: "pkcs8", format: "der" },
153
+ });
154
+ return {
155
+ publicKeyB64: publicKey.toString("base64"),
156
+ privateKeyDer: privateKey,
157
+ };
158
+ }
159
+ // ---------------------------------------------------------------------------
160
+ // Registration
161
+ // ---------------------------------------------------------------------------
162
+ const KDF_PBKDF2 = 0;
163
+ const KDF_ITERATIONS = 600000;
164
+ /**
165
+ * Create a Bitwarden account on the configured Vaultwarden server.
166
+ * Uses the Bitwarden registration API with standard KDF implementation.
167
+ */
168
+ async function createVaultAccount(agentName, serverUrl, email, masterPassword) {
169
+ (0, runtime_1.emitNervesEvent)({
170
+ event: "repertoire.vault_setup_start",
171
+ component: "repertoire",
172
+ message: `creating vault account for ${agentName}`,
173
+ meta: { agentName, serverUrl, email },
174
+ });
175
+ try {
176
+ // Step 1: Derive keys
177
+ const masterKey = await deriveMasterKey(masterPassword, email, KDF_ITERATIONS);
178
+ const masterPasswordHash = await deriveMasterPasswordHash(masterKey, masterPassword);
179
+ const stretchedKey = deriveStretchedMasterKey(masterKey);
180
+ // Step 2: Generate symmetric key (64 bytes = 32 enc + 32 mac), encrypt with stretched key
181
+ const symKey = crypto.randomBytes(64);
182
+ const protectedSymKey = encryptWithStretchedKey(symKey, stretchedKey);
183
+ // Step 3: Generate RSA keypair, encrypt private key with the symmetric key
184
+ const { publicKeyB64, privateKeyDer } = generateRsaKeypair();
185
+ const encryptedPrivateKey = encryptWithStretchedKey(privateKeyDer, symKey);
186
+ // Step 4: POST registration
187
+ const res = await fetch(`${serverUrl}/api/accounts/register`, {
188
+ method: "POST",
189
+ headers: { "Content-Type": "application/json" },
190
+ body: JSON.stringify({
191
+ name: agentName,
192
+ email,
193
+ masterPasswordHash,
194
+ masterPasswordHint: null,
195
+ key: protectedSymKey,
196
+ kdf: KDF_PBKDF2,
197
+ kdfIterations: KDF_ITERATIONS,
198
+ keys: {
199
+ publicKey: publicKeyB64,
200
+ encryptedPrivateKey,
201
+ },
202
+ }),
203
+ });
204
+ if (!res.ok) {
205
+ let errorDetail;
206
+ try {
207
+ const body = await res.json();
208
+ errorDetail = body.message ?? `HTTP ${res.status} ${res.statusText}`;
209
+ }
210
+ catch {
211
+ errorDetail = `HTTP ${res.status} ${res.statusText}`;
212
+ }
213
+ (0, runtime_1.emitNervesEvent)({
214
+ level: "error",
215
+ event: "repertoire.vault_setup_error",
216
+ component: "repertoire",
217
+ message: `vault registration failed: ${errorDetail}`,
218
+ meta: { agentName, serverUrl, email, reason: errorDetail },
219
+ });
220
+ return { success: false, email, serverUrl, error: errorDetail };
221
+ }
222
+ (0, runtime_1.emitNervesEvent)({
223
+ event: "repertoire.vault_setup_end",
224
+ component: "repertoire",
225
+ message: `vault account created for ${agentName}`,
226
+ meta: { agentName, serverUrl, email },
227
+ });
228
+ return { success: true, email, serverUrl };
229
+ }
230
+ catch (err) {
231
+ const reason = err instanceof Error ? err.message : String(err);
232
+ (0, runtime_1.emitNervesEvent)({
233
+ level: "error",
234
+ event: "repertoire.vault_setup_error",
235
+ component: "repertoire",
236
+ message: `vault setup failed: ${reason}`,
237
+ meta: { agentName, serverUrl, email, reason },
238
+ });
239
+ return { success: false, email, serverUrl, error: reason };
240
+ }
241
+ }
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ // Claude Code lifecycle hook handler.
3
+ // Receives events from Claude Code's hooks system (SessionStart, Stop, PostToolUse)
4
+ // and forwards them to the Ouroboros daemon for agent awareness.
5
+ //
6
+ // This module exports handleHookEvent for testability.
7
+ // The actual hook scripts (scripts/claude-code-hook.js) read stdin and call this.
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.handleHookEvent = handleHookEvent;
10
+ const socket_client_1 = require("../heart/daemon/socket-client");
11
+ const runtime_1 = require("../nerves/runtime");
12
+ /**
13
+ * Handle a Claude Code lifecycle hook event.
14
+ * Sends the event to the daemon and always exits 0 (hooks must not block the IDE).
15
+ */
16
+ async function handleHookEvent(hookEvent) {
17
+ (0, runtime_1.emitNervesEvent)({
18
+ component: "daemon",
19
+ event: "daemon.hook_event_received",
20
+ message: "claude code hook event received",
21
+ meta: { hookEvent: hookEvent.event, sessionId: hookEvent.sessionId },
22
+ });
23
+ try {
24
+ await (0, socket_client_1.sendDaemonCommand)(socket_client_1.DEFAULT_DAEMON_SOCKET_PATH, {
25
+ kind: "hook.event",
26
+ event: hookEvent.event,
27
+ sessionId: hookEvent.sessionId,
28
+ toolName: hookEvent.toolName,
29
+ });
30
+ }
31
+ catch {
32
+ // Daemon unavailable — silently ignore. Hooks must not block.
33
+ (0, runtime_1.emitNervesEvent)({
34
+ component: "daemon",
35
+ event: "daemon.hook_event_daemon_unavailable",
36
+ message: "daemon unavailable for hook event",
37
+ meta: { hookEvent: hookEvent.event },
38
+ });
39
+ }
40
+ return { exitCode: 0 };
41
+ }
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ // Claude Code stop hook — checks the agent's pending queue and returns
3
+ // any messages as additionalContext for injection into the next turn.
4
+ //
5
+ // This is how the agent can proactively communicate back to the dev tool user:
6
+ // the agent surfaces a message to the pending queue, and the stop hook picks
7
+ // it up and injects it as context in the Claude Code session.
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.handleStopHook = handleStopHook;
10
+ const pending_1 = require("../mind/pending");
11
+ const runtime_1 = require("../nerves/runtime");
12
+ /**
13
+ * Check the pending queue for messages from the agent to this dev tool session.
14
+ * Returns accumulated message text as additionalContext.
15
+ */
16
+ async function handleStopHook(input) {
17
+ (0, runtime_1.emitNervesEvent)({
18
+ component: "daemon",
19
+ event: "daemon.stop_hook_check_start",
20
+ message: "checking pending queue for stop hook",
21
+ meta: { agentName: input.agentName, friendId: input.friendId, sessionId: input.sessionId },
22
+ });
23
+ try {
24
+ const pendingDir = (0, pending_1.getPendingDir)(input.agentName, input.friendId, "mcp", input.sessionId);
25
+ const pending = (0, pending_1.drainPending)(pendingDir);
26
+ if (pending.length === 0) {
27
+ return { additionalContext: "" };
28
+ }
29
+ const text = pending.map((m) => m.content).join("\n\n---\n\n");
30
+ (0, runtime_1.emitNervesEvent)({
31
+ component: "daemon",
32
+ event: "daemon.stop_hook_check_end",
33
+ message: "pending messages found for stop hook",
34
+ meta: { agentName: input.agentName, count: pending.length },
35
+ });
36
+ return { additionalContext: text };
37
+ }
38
+ catch {
39
+ (0, runtime_1.emitNervesEvent)({
40
+ component: "daemon",
41
+ event: "daemon.stop_hook_check_error",
42
+ message: "error checking pending queue in stop hook",
43
+ meta: { agentName: input.agentName },
44
+ });
45
+ return { additionalContext: "" };
46
+ }
47
+ }
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildAttentionQueue = buildAttentionQueue;
4
+ exports.dequeueAttentionItem = dequeueAttentionItem;
5
+ exports.attentionQueueEmpty = attentionQueueEmpty;
6
+ exports.buildAttentionQueueSummary = buildAttentionQueueSummary;
7
+ const runtime_1 = require("../nerves/runtime");
8
+ // ── Queue construction ───────────────────────────────────────────
9
+ function generateItemId() {
10
+ return Math.random().toString(36).slice(2, 10);
11
+ }
12
+ function originKey(friendId, channel, key) {
13
+ return `${friendId}/${channel}/${key}`;
14
+ }
15
+ function buildAttentionQueue(input) {
16
+ const { drainedPending, outstandingObligations, friendNameResolver } = input;
17
+ const seen = new Set();
18
+ const items = [];
19
+ // Source 1: drained pending messages with delegatedFrom (current-turn delegations)
20
+ for (const msg of drainedPending) {
21
+ if (!msg.delegatedFrom)
22
+ continue;
23
+ const { friendId, channel, key, bridgeId } = msg.delegatedFrom;
24
+ const oKey = originKey(friendId, channel, key);
25
+ seen.add(oKey);
26
+ const resolvedName = friendNameResolver(friendId);
27
+ items.push({
28
+ id: msg.obligationId ?? generateItemId(),
29
+ friendId,
30
+ friendName: resolvedName ?? friendId,
31
+ channel,
32
+ key,
33
+ ...(bridgeId ? { bridgeId } : {}),
34
+ delegatedContent: msg.content,
35
+ ...(msg.obligationId ? { obligationId: msg.obligationId } : {}),
36
+ source: "drained",
37
+ timestamp: msg.timestamp,
38
+ });
39
+ }
40
+ // Source 2: outstanding obligations (crash recovery)
41
+ for (const obligation of outstandingObligations) {
42
+ const { friendId, channel, key, bridgeId } = obligation.origin;
43
+ const oKey = originKey(friendId, channel, key);
44
+ if (seen.has(oKey))
45
+ continue; // deduplicate: prefer drained version
46
+ seen.add(oKey);
47
+ const resolvedName = friendNameResolver(friendId);
48
+ items.push({
49
+ id: obligation.id,
50
+ friendId,
51
+ friendName: resolvedName ?? friendId,
52
+ channel,
53
+ key,
54
+ ...(bridgeId ? { bridgeId } : {}),
55
+ delegatedContent: obligation.delegatedContent,
56
+ obligationId: obligation.id,
57
+ source: "obligation-recovery",
58
+ timestamp: obligation.createdAt,
59
+ });
60
+ }
61
+ // Sort FIFO (oldest first)
62
+ items.sort((a, b) => a.timestamp - b.timestamp);
63
+ (0, runtime_1.emitNervesEvent)({
64
+ event: "senses.attention_queue_built",
65
+ component: "senses",
66
+ message: `attention queue built with ${items.length} item(s)`,
67
+ meta: {
68
+ drainedCount: items.filter((i) => i.source === "drained").length,
69
+ recoveredCount: items.filter((i) => i.source === "obligation-recovery").length,
70
+ },
71
+ });
72
+ return items;
73
+ }
74
+ // ── Queue operations ─────────────────────────────────────────────
75
+ function dequeueAttentionItem(queue, id) {
76
+ const index = queue.findIndex((item) => item.id === id);
77
+ if (index === -1)
78
+ return null;
79
+ return queue.splice(index, 1)[0];
80
+ }
81
+ function attentionQueueEmpty(queue) {
82
+ return queue.length === 0;
83
+ }
84
+ // ── Queue visibility ─────────────────────────────────────────────
85
+ const CONTENT_PREVIEW_MAX = 80;
86
+ function buildAttentionQueueSummary(queue) {
87
+ if (queue.length === 0)
88
+ return "";
89
+ const lines = ["you're holding:"];
90
+ for (const item of queue) {
91
+ const preview = item.delegatedContent.length > CONTENT_PREVIEW_MAX
92
+ ? `${item.delegatedContent.slice(0, CONTENT_PREVIEW_MAX - 3)}...`
93
+ : item.delegatedContent;
94
+ lines.push(`- [${item.id}] ${item.friendName} asked: "${preview}"`);
95
+ }
96
+ return lines.join("\n");
97
+ }
@@ -2,11 +2,11 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createBlueBubblesClient = createBlueBubblesClient;
4
4
  const node_crypto_1 = require("node:crypto");
5
- const config_1 = require("../heart/config");
6
- const identity_1 = require("../heart/identity");
7
- const runtime_1 = require("../nerves/runtime");
8
- const bluebubbles_model_1 = require("./bluebubbles-model");
9
- const bluebubbles_media_1 = require("./bluebubbles-media");
5
+ const config_1 = require("../../heart/config");
6
+ const identity_1 = require("../../heart/identity");
7
+ const runtime_1 = require("../../nerves/runtime");
8
+ const model_1 = require("./model");
9
+ const media_1 = require("./media");
10
10
  function buildBlueBubblesApiUrl(baseUrl, endpoint, password) {
11
11
  const root = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
12
12
  const url = new URL(endpoint.replace(/^\//, ""), root);
@@ -170,6 +170,12 @@ function applyRepairNotice(event, notice) {
170
170
  repairNotice: notice,
171
171
  };
172
172
  }
173
+ function hasRecoverableMessageContent(event) {
174
+ return event.kind === "message"
175
+ && (event.textForAgent.trim().length > 0
176
+ || event.attachments.length > 0
177
+ || event.hasPayloadData);
178
+ }
173
179
  function hydrateTextForAgent(event, rawData) {
174
180
  if (event.kind !== "message") {
175
181
  return { ...event, requiresRepair: false };
@@ -196,7 +202,8 @@ function extractRepairData(payload) {
196
202
  return asRecord(record?.data) ?? record;
197
203
  }
198
204
  function providerSupportsAudioInput(provider) {
199
- return provider === "azure" || provider === "openai-codex";
205
+ void provider;
206
+ return false;
200
207
  }
201
208
  async function resolveChatGuid(chat, config, channelConfig) {
202
209
  return chat.chatGuid
@@ -322,6 +329,40 @@ function createBlueBubblesClient(config = (0, config_1.getBlueBubblesConfig)(),
322
329
  throw new Error(`BlueBubbles read failed (${response.status}): ${errorText || "unknown"}`);
323
330
  }
324
331
  },
332
+ async checkHealth() {
333
+ const url = buildBlueBubblesApiUrl(config.serverUrl, "/api/v1/message/count", config.password);
334
+ (0, runtime_1.emitNervesEvent)({
335
+ component: "senses",
336
+ event: "senses.bluebubbles_healthcheck_start",
337
+ message: "probing bluebubbles upstream health",
338
+ meta: { serverUrl: config.serverUrl },
339
+ });
340
+ const response = await fetch(url, {
341
+ method: "GET",
342
+ signal: AbortSignal.timeout(channelConfig.requestTimeoutMs),
343
+ });
344
+ if (!response.ok) {
345
+ const errorText = await response.text().catch(() => "");
346
+ (0, runtime_1.emitNervesEvent)({
347
+ level: "warn",
348
+ component: "senses",
349
+ event: "senses.bluebubbles_healthcheck_error",
350
+ message: "bluebubbles upstream health probe failed",
351
+ meta: {
352
+ serverUrl: config.serverUrl,
353
+ status: response.status,
354
+ reason: errorText || "unknown",
355
+ },
356
+ });
357
+ throw new Error(`BlueBubbles upstream health check failed (${response.status}): ${errorText || "unknown"}`);
358
+ }
359
+ (0, runtime_1.emitNervesEvent)({
360
+ component: "senses",
361
+ event: "senses.bluebubbles_healthcheck_end",
362
+ message: "bluebubbles upstream health probe succeeded",
363
+ meta: { serverUrl: config.serverUrl },
364
+ });
365
+ },
325
366
  async repairEvent(event) {
326
367
  if (!event.requiresRepair) {
327
368
  (0, runtime_1.emitNervesEvent)({
@@ -382,16 +423,25 @@ function createBlueBubblesClient(config = (0, config_1.getBlueBubblesConfig)(),
382
423
  });
383
424
  return repaired;
384
425
  }
385
- const normalized = (0, bluebubbles_model_1.normalizeBlueBubblesEvent)({
426
+ const normalized = (0, model_1.normalizeBlueBubblesEvent)({
386
427
  type: event.eventType,
387
428
  data,
388
429
  });
389
- let hydrated = hydrateTextForAgent(normalized, data);
430
+ const recoveredMessage = event.kind === "mutation"
431
+ && !event.shouldNotifyAgent
432
+ ? (0, model_1.normalizeBlueBubblesEvent)({
433
+ type: "new-message",
434
+ data,
435
+ })
436
+ : null;
437
+ let hydrated = recoveredMessage && hasRecoverableMessageContent(recoveredMessage)
438
+ ? hydrateTextForAgent(recoveredMessage, data)
439
+ : hydrateTextForAgent(normalized, data);
390
440
  if (hydrated.kind === "message" &&
391
441
  hydrated.balloonBundleId !== "com.apple.messages.URLBalloonProvider" &&
392
442
  hydrated.attachments.length > 0) {
393
- const media = await (0, bluebubbles_media_1.hydrateBlueBubblesAttachments)(hydrated.attachments, config, channelConfig, {
394
- preferAudioInput: providerSupportsAudioInput((0, identity_1.loadAgentConfig)().provider),
443
+ const media = await (0, media_1.hydrateBlueBubblesAttachments)(hydrated.attachments, config, channelConfig, {
444
+ preferAudioInput: providerSupportsAudioInput((0, identity_1.loadAgentConfig)().humanFacing.provider),
395
445
  });
396
446
  const transcriptSuffix = media.transcriptAdditions.map((entry) => `[${entry}]`).join("\n");
397
447
  const noticeSuffix = media.notices.map((entry) => `[${entry}]`).join("\n");
@@ -410,6 +460,7 @@ function createBlueBubblesClient(config = (0, config_1.getBlueBubblesConfig)(),
410
460
  kind: hydrated.kind,
411
461
  messageGuid: hydrated.messageGuid,
412
462
  repairedFrom: event.kind,
463
+ promotedFromMutation: event.kind === "mutation" && hydrated.kind === "message",
413
464
  },
414
465
  });
415
466
  return hydrated;
@@ -429,5 +480,53 @@ function createBlueBubblesClient(config = (0, config_1.getBlueBubblesConfig)(),
429
480
  return applyRepairNotice(event, `BlueBubbles repair failed: ${reason}`);
430
481
  }
431
482
  },
483
+ async getMessageText(messageGuid) {
484
+ const url = buildRepairUrl(config.serverUrl, messageGuid, config.password);
485
+ try {
486
+ const response = await fetch(url, {
487
+ method: "GET",
488
+ signal: AbortSignal.timeout(channelConfig.requestTimeoutMs),
489
+ });
490
+ if (!response.ok) {
491
+ (0, runtime_1.emitNervesEvent)({
492
+ level: "warn",
493
+ component: "senses",
494
+ event: "senses.bluebubbles_get_message_text_error",
495
+ message: "failed to fetch message text",
496
+ meta: { messageGuid, status: response.status },
497
+ });
498
+ return null;
499
+ }
500
+ const payload = await parseJsonBody(response);
501
+ const data = extractRepairData(payload);
502
+ if (!data || typeof data.text !== "string") {
503
+ (0, runtime_1.emitNervesEvent)({
504
+ level: "warn",
505
+ component: "senses",
506
+ event: "senses.bluebubbles_get_message_text_error",
507
+ message: "message payload missing text field",
508
+ meta: { messageGuid, hasData: !!data, textType: data ? typeof data.text : "n/a" },
509
+ });
510
+ return null;
511
+ }
512
+ (0, runtime_1.emitNervesEvent)({
513
+ component: "senses",
514
+ event: "senses.bluebubbles_get_message_text",
515
+ message: "fetched message text by guid",
516
+ meta: { messageGuid },
517
+ });
518
+ return data.text.trim() || null;
519
+ }
520
+ catch (error) {
521
+ (0, runtime_1.emitNervesEvent)({
522
+ level: "warn",
523
+ component: "senses",
524
+ event: "senses.bluebubbles_get_message_text_error",
525
+ message: "exception fetching message text",
526
+ meta: { messageGuid, reason: error instanceof Error ? error.message : String(error) },
527
+ });
528
+ return null;
529
+ }
530
+ },
432
531
  };
433
532
  }
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ // Thin entrypoint for `npm run bluebubbles` / `node dist/senses/bluebubbles/entry.js --agent <name>`.
3
+ // Separated from index.ts so the BlueBubbles adapter stays testable.
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ if (!process.argv.includes("--agent")) {
6
+ // eslint-disable-next-line no-console -- pre-boot guard: --agent check before imports
7
+ console.error("Missing required --agent <name> argument.\nUsage: node dist/senses/bluebubbles/entry.js --agent ouroboros");
8
+ process.exit(1);
9
+ }
10
+ const index_1 = require("./index");
11
+ const runtime_logging_1 = require("../../heart/daemon/runtime-logging");
12
+ (0, runtime_logging_1.configureDaemonRuntimeLogger)("bluebubbles");
13
+ (0, index_1.startBlueBubblesApp)();