@ouro.bot/cli 0.1.0-alpha.34 → 0.1.0-alpha.340

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 (314) hide show
  1. package/README.md +188 -187
  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 +2031 -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/packets.js +193 -0
  12. package/dist/arc/presence.js +185 -0
  13. package/dist/arc/task-lifecycle.js +65 -0
  14. package/dist/heart/active-work.js +832 -0
  15. package/dist/heart/agent-entry.js +37 -2
  16. package/dist/heart/attachments/image-normalize.js +194 -0
  17. package/dist/heart/attachments/materialize.js +97 -0
  18. package/dist/heart/attachments/originals.js +88 -0
  19. package/dist/heart/attachments/render.js +29 -0
  20. package/dist/heart/attachments/sources/adapter.js +2 -0
  21. package/dist/heart/attachments/sources/bluebubbles.js +156 -0
  22. package/dist/heart/attachments/sources/cli-local-file.js +78 -0
  23. package/dist/heart/attachments/sources/index.js +16 -0
  24. package/dist/heart/attachments/store.js +103 -0
  25. package/dist/heart/attachments/types.js +93 -0
  26. package/dist/heart/auth/auth-flow.js +463 -0
  27. package/dist/heart/bridges/manager.js +358 -0
  28. package/dist/heart/bridges/state-machine.js +135 -0
  29. package/dist/heart/bridges/store.js +123 -0
  30. package/dist/heart/bundle-state.js +168 -0
  31. package/dist/heart/commitments.js +111 -0
  32. package/dist/heart/config-registry.js +304 -0
  33. package/dist/heart/config.js +53 -21
  34. package/dist/heart/core.js +695 -195
  35. package/dist/heart/cross-chat-delivery.js +131 -0
  36. package/dist/heart/daemon/agent-config-check.js +292 -0
  37. package/dist/heart/daemon/agent-discovery.js +79 -3
  38. package/dist/heart/daemon/agent-service.js +360 -0
  39. package/dist/heart/daemon/agentic-repair.js +170 -0
  40. package/dist/heart/daemon/bluebubbles-health-diagnostics.js +122 -0
  41. package/dist/heart/daemon/cadence.js +70 -0
  42. package/dist/heart/daemon/cli-defaults.js +591 -0
  43. package/dist/heart/daemon/cli-exec.js +2297 -0
  44. package/dist/heart/daemon/cli-help.js +306 -0
  45. package/dist/heart/daemon/cli-parse.js +824 -0
  46. package/dist/heart/daemon/cli-render-doctor.js +57 -0
  47. package/dist/heart/daemon/cli-render.js +512 -0
  48. package/dist/heart/daemon/cli-types.js +8 -0
  49. package/dist/heart/daemon/daemon-cli.js +30 -1171
  50. package/dist/heart/daemon/daemon-entry.js +358 -3
  51. package/dist/heart/daemon/daemon-health.js +141 -0
  52. package/dist/heart/daemon/daemon-runtime-sync.js +157 -12
  53. package/dist/heart/daemon/daemon-tombstone.js +236 -0
  54. package/dist/heart/daemon/daemon.js +751 -58
  55. package/dist/heart/daemon/doctor-types.js +8 -0
  56. package/dist/heart/daemon/doctor.js +401 -0
  57. package/dist/heart/daemon/health-monitor.js +79 -1
  58. package/dist/heart/daemon/hooks/agent-config-v2.js +33 -0
  59. package/dist/heart/daemon/hooks/bundle-meta.js +115 -1
  60. package/dist/heart/daemon/http-health-probe.js +80 -0
  61. package/dist/heart/daemon/inner-status.js +89 -0
  62. package/dist/heart/daemon/interactive-repair.js +91 -0
  63. package/dist/heart/daemon/launchd.js +46 -9
  64. package/dist/heart/daemon/log-tailer.js +82 -12
  65. package/dist/heart/daemon/logs-prune.js +105 -0
  66. package/dist/heart/daemon/message-router.js +17 -8
  67. package/dist/heart/daemon/os-cron-deps.js +134 -0
  68. package/dist/heart/daemon/ouro-bot-entry.js +1 -1
  69. package/dist/heart/daemon/process-manager.js +201 -0
  70. package/dist/heart/daemon/provider-discovery.js +105 -0
  71. package/dist/heart/daemon/pulse.js +463 -0
  72. package/dist/heart/daemon/run-hooks.js +2 -0
  73. package/dist/heart/daemon/runtime-logging.js +67 -16
  74. package/dist/heart/daemon/runtime-metadata.js +101 -0
  75. package/dist/heart/daemon/runtime-mode.js +67 -0
  76. package/dist/heart/daemon/safe-mode.js +161 -0
  77. package/dist/heart/daemon/sense-manager.js +72 -3
  78. package/dist/heart/daemon/session-id-resolver.js +131 -0
  79. package/dist/heart/daemon/skill-management-installer.js +94 -0
  80. package/dist/heart/daemon/socket-client.js +307 -0
  81. package/dist/heart/daemon/stale-bundle-prune.js +96 -0
  82. package/dist/heart/daemon/startup-tui.js +237 -0
  83. package/dist/heart/daemon/task-scheduler.js +3 -25
  84. package/dist/heart/daemon/thoughts.js +510 -0
  85. package/dist/heart/daemon/up-progress.js +135 -0
  86. package/dist/heart/delegation.js +62 -0
  87. package/dist/heart/habits/habit-migration.js +181 -0
  88. package/dist/heart/habits/habit-parser.js +140 -0
  89. package/dist/heart/habits/habit-scheduler.js +371 -0
  90. package/dist/heart/{daemon → hatch}/hatch-flow.js +32 -120
  91. package/dist/heart/{daemon → hatch}/hatch-specialist.js +3 -3
  92. package/dist/heart/{daemon → hatch}/specialist-prompt.js +10 -7
  93. package/dist/heart/{daemon → hatch}/specialist-tools.js +49 -3
  94. package/dist/heart/identity.js +154 -59
  95. package/dist/heart/kicks.js +2 -20
  96. package/dist/heart/mcp/mcp-server.js +653 -0
  97. package/dist/heart/migrate-config.js +127 -0
  98. package/dist/heart/model-capabilities.js +59 -0
  99. package/dist/heart/outlook/outlook-http-hooks.js +64 -0
  100. package/dist/heart/outlook/outlook-http-response.js +7 -0
  101. package/dist/heart/outlook/outlook-http-routes.js +232 -0
  102. package/dist/heart/outlook/outlook-http-static.js +99 -0
  103. package/dist/heart/outlook/outlook-http-transport.js +116 -0
  104. package/dist/heart/outlook/outlook-http.js +99 -0
  105. package/dist/heart/outlook/outlook-read.js +28 -0
  106. package/dist/heart/outlook/outlook-types.js +27 -0
  107. package/dist/heart/outlook/outlook-view.js +194 -0
  108. package/dist/heart/outlook/readers/agent-machine.js +355 -0
  109. package/dist/heart/outlook/readers/continuity-readers.js +332 -0
  110. package/dist/heart/outlook/readers/runtime-readers.js +660 -0
  111. package/dist/heart/outlook/readers/sessions.js +231 -0
  112. package/dist/heart/outlook/readers/shared.js +111 -0
  113. package/dist/heart/progress-story.js +42 -0
  114. package/dist/heart/provider-failover.js +135 -0
  115. package/dist/heart/provider-models.js +81 -0
  116. package/dist/heart/provider-ping.js +234 -0
  117. package/dist/heart/providers/anthropic-token.js +163 -0
  118. package/dist/heart/providers/anthropic.js +171 -50
  119. package/dist/heart/providers/azure.js +97 -11
  120. package/dist/heart/providers/error-classification.js +63 -0
  121. package/dist/heart/providers/github-copilot.js +135 -0
  122. package/dist/heart/providers/minimax-vlm.js +189 -0
  123. package/dist/heart/providers/minimax.js +23 -6
  124. package/dist/heart/providers/openai-codex.js +33 -23
  125. package/dist/heart/session-activity.js +190 -0
  126. package/dist/heart/session-events.js +726 -0
  127. package/dist/heart/session-recall.js +162 -0
  128. package/dist/heart/start-of-turn-packet.js +341 -0
  129. package/dist/heart/streaming.js +36 -27
  130. package/dist/heart/sync.js +332 -0
  131. package/dist/heart/target-resolution.js +127 -0
  132. package/dist/heart/tempo.js +93 -0
  133. package/dist/heart/temporal-view.js +41 -0
  134. package/dist/heart/tool-activity-callbacks.js +36 -0
  135. package/dist/heart/tool-description.js +135 -0
  136. package/dist/heart/tool-friction.js +55 -0
  137. package/dist/heart/tool-loop.js +200 -0
  138. package/dist/heart/turn-context.js +358 -0
  139. package/dist/heart/turn-coordinator.js +28 -0
  140. package/dist/heart/{daemon → versioning}/ouro-bot-global-installer.js +1 -1
  141. package/dist/heart/{daemon → versioning}/ouro-bot-wrapper.js +1 -1
  142. package/dist/heart/{daemon → versioning}/ouro-path-installer.js +78 -35
  143. package/dist/heart/versioning/ouro-version-manager.js +295 -0
  144. package/dist/heart/{daemon → versioning}/staged-restart.js +40 -8
  145. package/dist/heart/{daemon → versioning}/update-checker.js +12 -2
  146. package/dist/heart/{daemon → versioning}/update-hooks.js +63 -59
  147. package/dist/mind/associative-recall.js +137 -66
  148. package/dist/mind/bundle-manifest.js +7 -1
  149. package/dist/mind/context.js +89 -93
  150. package/dist/mind/diary-integrity.js +60 -0
  151. package/dist/mind/{memory.js → diary.js} +84 -96
  152. package/dist/mind/embedding-provider.js +60 -0
  153. package/dist/mind/file-state.js +179 -0
  154. package/dist/mind/first-impressions.js +14 -1
  155. package/dist/mind/friends/channel.js +56 -0
  156. package/dist/mind/friends/group-context.js +144 -0
  157. package/dist/mind/friends/resolver.js +37 -0
  158. package/dist/mind/friends/store-file.js +58 -3
  159. package/dist/mind/friends/trust-explanation.js +74 -0
  160. package/dist/mind/friends/types.js +8 -0
  161. package/dist/mind/journal-index.js +161 -0
  162. package/dist/mind/obligation-steering.js +221 -0
  163. package/dist/mind/pending.js +74 -7
  164. package/dist/mind/prompt.js +999 -111
  165. package/dist/mind/provenance-trust.js +26 -0
  166. package/dist/mind/scrutiny.js +173 -0
  167. package/dist/mind/token-estimate.js +8 -12
  168. package/dist/nerves/cli-logging.js +7 -1
  169. package/dist/nerves/coverage/audit.js +1 -1
  170. package/dist/nerves/coverage/file-completeness.js +83 -5
  171. package/dist/nerves/coverage/run-artifacts.js +1 -1
  172. package/dist/nerves/event-buffer.js +111 -0
  173. package/dist/nerves/index.js +224 -4
  174. package/dist/nerves/observation.js +20 -0
  175. package/dist/nerves/redact.js +79 -0
  176. package/dist/nerves/runtime.js +5 -1
  177. package/dist/outlook-ui/assets/index-DC7sZefn.js +61 -0
  178. package/dist/outlook-ui/assets/index-LwChZTgL.css +1 -0
  179. package/dist/outlook-ui/index.html +15 -0
  180. package/dist/repertoire/ado-client.js +15 -56
  181. package/dist/repertoire/ado-semantic.js +11 -10
  182. package/dist/repertoire/api-client.js +97 -0
  183. package/dist/repertoire/bitwarden-store.js +319 -0
  184. package/dist/repertoire/bundle-templates.js +72 -0
  185. package/dist/repertoire/bw-installer.js +79 -0
  186. package/dist/repertoire/coding/codex-jsonl.js +64 -0
  187. package/dist/repertoire/coding/context-pack.js +330 -0
  188. package/dist/repertoire/coding/feedback.js +197 -30
  189. package/dist/repertoire/coding/manager.js +158 -9
  190. package/dist/repertoire/coding/spawner.js +55 -9
  191. package/dist/repertoire/coding/tools.js +170 -7
  192. package/dist/repertoire/commerce-errors.js +109 -0
  193. package/dist/repertoire/commerce-self-test.js +156 -0
  194. package/dist/repertoire/credential-access.js +527 -0
  195. package/dist/repertoire/duffel-client.js +185 -0
  196. package/dist/repertoire/github-client.js +14 -55
  197. package/dist/repertoire/graph-client.js +11 -52
  198. package/dist/repertoire/guardrails.js +375 -0
  199. package/dist/repertoire/mcp-client.js +255 -0
  200. package/dist/repertoire/mcp-manager.js +305 -0
  201. package/dist/repertoire/mcp-tools.js +63 -0
  202. package/dist/repertoire/shell-sessions.js +133 -0
  203. package/dist/repertoire/skills.js +14 -23
  204. package/dist/repertoire/stripe-client.js +131 -0
  205. package/dist/repertoire/tasks/board.js +43 -5
  206. package/dist/repertoire/tasks/fix.js +182 -0
  207. package/dist/repertoire/tasks/index.js +28 -10
  208. package/dist/repertoire/tasks/lifecycle.js +2 -2
  209. package/dist/repertoire/tasks/parser.js +3 -2
  210. package/dist/repertoire/tasks/scanner.js +194 -37
  211. package/dist/repertoire/tasks/transitions.js +16 -79
  212. package/dist/repertoire/tool-results.js +29 -0
  213. package/dist/repertoire/tools-attachments.js +316 -0
  214. package/dist/repertoire/tools-base.js +45 -771
  215. package/dist/repertoire/tools-bluebubbles.js +1 -0
  216. package/dist/repertoire/tools-bridge.js +141 -0
  217. package/dist/repertoire/tools-bundle.js +984 -0
  218. package/dist/repertoire/tools-config.js +185 -0
  219. package/dist/repertoire/tools-continuity.js +248 -0
  220. package/dist/repertoire/tools-credential.js +182 -0
  221. package/dist/repertoire/tools-files.js +342 -0
  222. package/dist/repertoire/tools-flight.js +224 -0
  223. package/dist/repertoire/tools-flow.js +105 -0
  224. package/dist/repertoire/tools-github.js +1 -7
  225. package/dist/repertoire/tools-memory.js +376 -0
  226. package/dist/repertoire/tools-session.js +739 -0
  227. package/dist/repertoire/tools-shell.js +120 -0
  228. package/dist/repertoire/tools-stripe.js +180 -0
  229. package/dist/repertoire/tools-surface.js +243 -0
  230. package/dist/repertoire/tools-teams.js +12 -62
  231. package/dist/repertoire/tools-travel.js +125 -0
  232. package/dist/repertoire/tools-user-profile.js +144 -0
  233. package/dist/repertoire/tools-vault.js +110 -0
  234. package/dist/repertoire/tools.js +144 -138
  235. package/dist/repertoire/travel-api-client.js +360 -0
  236. package/dist/repertoire/user-profile.js +118 -0
  237. package/dist/repertoire/vault-setup.js +241 -0
  238. package/dist/scripts/claude-code-hook.js +41 -0
  239. package/dist/scripts/claude-code-stop-hook.js +47 -0
  240. package/dist/senses/attention-queue.js +116 -0
  241. package/dist/senses/bluebubbles/attachment-cache.js +53 -0
  242. package/dist/senses/bluebubbles/attachment-download.js +137 -0
  243. package/dist/senses/{bluebubbles-client.js → bluebubbles/client.js} +225 -9
  244. package/dist/senses/bluebubbles/entry.js +13 -0
  245. package/dist/senses/bluebubbles/inbound-log.js +113 -0
  246. package/dist/senses/bluebubbles/index.js +1590 -0
  247. package/dist/senses/{bluebubbles-media.js → bluebubbles/media.js} +121 -70
  248. package/dist/senses/{bluebubbles-model.js → bluebubbles/model.js} +43 -12
  249. package/dist/senses/{bluebubbles-mutation-log.js → bluebubbles/mutation-log.js} +46 -6
  250. package/dist/senses/bluebubbles/replay.js +129 -0
  251. package/dist/senses/bluebubbles/runtime-state.js +109 -0
  252. package/dist/senses/{bluebubbles-session-cleanup.js → bluebubbles/session-cleanup.js} +1 -1
  253. package/dist/senses/cli/bracketed-paste.js +82 -0
  254. package/dist/senses/cli/image-paste.js +287 -0
  255. package/dist/senses/cli/image-ref-navigation.js +75 -0
  256. package/dist/senses/cli/ink-app.js +156 -0
  257. package/dist/senses/cli/inline-diff.js +64 -0
  258. package/dist/senses/cli/input-keys.js +174 -0
  259. package/dist/senses/cli/kill-ring.js +86 -0
  260. package/dist/senses/cli/message-list.js +51 -0
  261. package/dist/senses/cli/ouro-tui.js +605 -0
  262. package/dist/senses/cli/spinner-imperative.js +135 -0
  263. package/dist/senses/cli/spinner.js +101 -0
  264. package/dist/senses/cli/status-line.js +60 -0
  265. package/dist/senses/cli/streaming-markdown.js +526 -0
  266. package/dist/senses/cli/tool-display.js +83 -0
  267. package/dist/senses/cli/tool-render.js +85 -0
  268. package/dist/senses/cli/tui-store.js +240 -0
  269. package/dist/senses/cli/virtual-list.js +35 -0
  270. package/dist/senses/cli-entry.js +1 -1
  271. package/dist/senses/cli-layout.js +187 -0
  272. package/dist/senses/cli.js +595 -246
  273. package/dist/senses/commands.js +65 -1
  274. package/dist/senses/continuity.js +94 -0
  275. package/dist/senses/habit-turn-message.js +108 -0
  276. package/dist/senses/inner-dialog-worker.js +112 -19
  277. package/dist/senses/inner-dialog.js +633 -86
  278. package/dist/senses/pipeline.js +567 -0
  279. package/dist/senses/shared-turn.js +199 -0
  280. package/dist/senses/surface-tool.js +68 -0
  281. package/dist/senses/teams.js +665 -160
  282. package/dist/senses/trust-gate.js +112 -2
  283. package/package.json +29 -7
  284. package/skills/agent-commerce.md +106 -0
  285. package/skills/browser-navigation.md +110 -0
  286. package/skills/commerce-setup-guide.md +116 -0
  287. package/skills/commerce-setup.md +84 -0
  288. package/skills/configure-dev-tools.md +81 -0
  289. package/skills/travel-planning.md +138 -0
  290. package/dist/heart/daemon/subagent-installer.js +0 -134
  291. package/dist/senses/bluebubbles-entry.js +0 -11
  292. package/dist/senses/bluebubbles.js +0 -547
  293. package/dist/senses/debug-activity.js +0 -124
  294. package/subagents/README.md +0 -73
  295. package/subagents/work-doer.md +0 -235
  296. package/subagents/work-merger.md +0 -618
  297. package/subagents/work-planner.md +0 -382
  298. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/basilisk.md +0 -0
  299. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jafar.md +0 -0
  300. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jormungandr.md +0 -0
  301. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/kaa.md +0 -0
  302. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/medusa.md +0 -0
  303. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/monty.md +0 -0
  304. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/nagini.md +0 -0
  305. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/ouroboros.md +0 -0
  306. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/python.md +0 -0
  307. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/quetzalcoatl.md +0 -0
  308. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/sir-hiss.md +0 -0
  309. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-serpent.md +0 -0
  310. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-snake.md +0 -0
  311. /package/dist/heart/{daemon → hatch}/hatch-animation.js +0 -0
  312. /package/dist/heart/{daemon → hatch}/specialist-orchestrator.js +0 -0
  313. /package/dist/heart/{daemon → versioning}/ouro-uti.js +0 -0
  314. /package/dist/heart/{daemon → versioning}/wrapper-publish-guard.js +0 -0
@@ -0,0 +1,231 @@
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.readSessionInventory = readSessionInventory;
37
+ exports.readSessionTranscript = readSessionTranscript;
38
+ const path = __importStar(require("path"));
39
+ const runtime_1 = require("../../../nerves/runtime");
40
+ const identity_1 = require("../../identity");
41
+ const session_events_1 = require("../../session-events");
42
+ const shared_1 = require("./shared");
43
+ /* v8 ignore start — session envelope parsing utilities */
44
+ function parseSessionUsage(raw) {
45
+ if (!raw || typeof raw !== "object")
46
+ return null;
47
+ const record = raw;
48
+ const inputTokens = typeof record.input_tokens === "number" ? record.input_tokens : 0;
49
+ const outputTokens = typeof record.output_tokens === "number" ? record.output_tokens : 0;
50
+ const reasoningTokens = typeof record.reasoning_tokens === "number" ? record.reasoning_tokens : 0;
51
+ const totalTokens = typeof record.total_tokens === "number" ? record.total_tokens : 0;
52
+ if (inputTokens === 0 && outputTokens === 0 && totalTokens === 0)
53
+ return null;
54
+ return { input_tokens: inputTokens, output_tokens: outputTokens, reasoning_tokens: reasoningTokens, total_tokens: totalTokens };
55
+ }
56
+ function parseSessionContinuity(raw) {
57
+ if (!raw)
58
+ return null;
59
+ if (typeof raw !== "object")
60
+ return null;
61
+ const record = raw;
62
+ const continuity = {
63
+ mustResolveBeforeHandoff: record.mustResolveBeforeHandoff === true,
64
+ lastFriendActivityAt: typeof record.lastFriendActivityAt === "string" ? record.lastFriendActivityAt : null,
65
+ };
66
+ if (!continuity.mustResolveBeforeHandoff && continuity.lastFriendActivityAt === null)
67
+ return null;
68
+ return continuity;
69
+ }
70
+ function extractContent(event) {
71
+ if (!event)
72
+ return null;
73
+ const text = (0, session_events_1.extractEventText)(event);
74
+ return text.length > 0 ? text : null;
75
+ }
76
+ function extractToolCallNames(event) {
77
+ if (!event)
78
+ return [];
79
+ return event.toolCalls
80
+ .map((call) => call.function.name)
81
+ .filter((name) => typeof name === "string" && name.length > 0);
82
+ }
83
+ /* v8 ignore stop */
84
+ function estimateTokenCount(messages) {
85
+ let charCount = 0;
86
+ for (const msg of messages) {
87
+ const content = extractContent(msg);
88
+ if (content)
89
+ charCount += content.length;
90
+ if (msg.toolCalls.length > 0)
91
+ charCount += JSON.stringify(msg.toolCalls).length;
92
+ }
93
+ return Math.ceil(charCount / 4);
94
+ }
95
+ /* v8 ignore start — filesystem traversal with defensive isDirectory checks */
96
+ function resolveAllSessionPaths(sessionsDir) {
97
+ const results = [];
98
+ if (!(0, shared_1.safeIsDirectory)(sessionsDir))
99
+ return results;
100
+ for (const friendId of (0, shared_1.safeReaddir)(sessionsDir)) {
101
+ const friendDir = path.join(sessionsDir, friendId);
102
+ if (!(0, shared_1.safeIsDirectory)(friendDir))
103
+ continue;
104
+ for (const channel of (0, shared_1.safeReaddir)(friendDir)) {
105
+ const channelDir = path.join(friendDir, channel);
106
+ if (!(0, shared_1.safeIsDirectory)(channelDir))
107
+ continue;
108
+ for (const file of (0, shared_1.safeReaddir)(channelDir)) {
109
+ if (!file.endsWith(".json"))
110
+ continue;
111
+ const key = file.slice(0, -5);
112
+ results.push({
113
+ friendId,
114
+ channel,
115
+ key,
116
+ sessionPath: path.join(channelDir, file),
117
+ });
118
+ }
119
+ }
120
+ }
121
+ return results;
122
+ }
123
+ /* v8 ignore stop */
124
+ /* v8 ignore start — defensive parsing */
125
+ function readSessionInventory(agentName, options = {}) {
126
+ const bundlesRoot = options.bundlesRoot ?? (0, identity_1.getAgentBundlesRoot)();
127
+ const now = options.now?.() ?? new Date();
128
+ const agentRoot = path.join(bundlesRoot, `${agentName}.ouro`);
129
+ const sessionsDir = path.join(agentRoot, "state", "sessions");
130
+ const friendsDir = path.join(agentRoot, "friends");
131
+ const allSessions = resolveAllSessionPaths(sessionsDir);
132
+ const items = [];
133
+ for (const { friendId, channel, key, sessionPath } of allSessions) {
134
+ if (friendId === "self" && channel === "inner")
135
+ continue;
136
+ const envelope = (0, shared_1.readSessionEnvelope)(sessionPath);
137
+ const events = envelope?.events ?? [];
138
+ const chronology = (0, session_events_1.deriveSessionChronology)(events);
139
+ const lastUsage = parseSessionUsage(envelope?.lastUsage);
140
+ const continuity = parseSessionContinuity(envelope?.state);
141
+ const hasObservedEventTiming = events.some((event) => event.time.authoredAt !== null || event.time.observedAt !== null);
142
+ const lastActivityAt = hasObservedEventTiming
143
+ ? (chronology.lastActivityAt ?? continuity?.lastFriendActivityAt ?? (0, shared_1.safeFileMtime)(sessionPath) ?? now.toISOString())
144
+ : (continuity?.lastFriendActivityAt ?? (0, shared_1.safeFileMtime)(sessionPath) ?? now.toISOString());
145
+ const activitySource = hasObservedEventTiming && chronology.lastActivityAt
146
+ ? "event-timeline"
147
+ : continuity?.lastFriendActivityAt
148
+ ? "friend-facing"
149
+ : "mtime-fallback";
150
+ const userMessages = events.filter((m) => m.role === "user");
151
+ const assistantMessages = events.filter((m) => m.role === "assistant");
152
+ const lastUser = userMessages.length > 0 ? userMessages[userMessages.length - 1] : null;
153
+ const lastAssistant = assistantMessages.length > 0 ? assistantMessages[assistantMessages.length - 1] : null;
154
+ const latestToolCallNames = [];
155
+ for (let i = events.length - 1; i >= 0; i--) {
156
+ const names = extractToolCallNames(events[i]);
157
+ if (names.length > 0) {
158
+ latestToolCallNames.push(...names);
159
+ break;
160
+ }
161
+ }
162
+ const friendName = (0, shared_1.resolveFriendName)(friendsDir, friendId);
163
+ const lastMsg = events.length > 0 ? events[events.length - 1] : null;
164
+ const mustResolve = continuity?.mustResolveBeforeHandoff === true;
165
+ let replyState = "idle";
166
+ if (mustResolve) {
167
+ replyState = "on-hold";
168
+ }
169
+ else if (lastMsg?.role === "user") {
170
+ replyState = "needs-reply";
171
+ }
172
+ else if (events.length > 0) {
173
+ replyState = "monitoring";
174
+ }
175
+ items.push({
176
+ friendId,
177
+ friendName,
178
+ channel,
179
+ key,
180
+ sessionPath,
181
+ lastActivityAt,
182
+ activitySource,
183
+ replyState,
184
+ messageCount: events.length,
185
+ lastUsage,
186
+ continuity,
187
+ latestUserExcerpt: (0, shared_1.truncateExcerpt)(extractContent(lastUser)),
188
+ latestAssistantExcerpt: (0, shared_1.truncateExcerpt)(extractContent(lastAssistant)),
189
+ latestToolCallNames,
190
+ estimatedTokens: events.length > 0 ? estimateTokenCount(events) : null,
191
+ });
192
+ }
193
+ items.sort((a, b) => b.lastActivityAt.localeCompare(a.lastActivityAt));
194
+ const ageThreshold = now.getTime() - shared_1.STALE_THRESHOLD_MS;
195
+ const activeCount = items.filter((item) => Date.parse(item.lastActivityAt) >= ageThreshold).length;
196
+ (0, runtime_1.emitNervesEvent)({
197
+ component: "heart",
198
+ event: "heart.outlook_sessions_read",
199
+ message: "reading outlook session inventory",
200
+ meta: { agentName, totalCount: items.length, activeCount },
201
+ });
202
+ return {
203
+ totalCount: items.length,
204
+ activeCount,
205
+ staleCount: items.length - activeCount,
206
+ items,
207
+ };
208
+ }
209
+ function readSessionTranscript(agentName, friendId, channel, key, options = {}) {
210
+ const bundlesRoot = options.bundlesRoot ?? (0, identity_1.getAgentBundlesRoot)();
211
+ const agentRoot = path.join(bundlesRoot, `${agentName}.ouro`);
212
+ const sessionPath = path.join(agentRoot, "state", "sessions", friendId, channel, `${key}.json`);
213
+ const envelope = (0, shared_1.readSessionEnvelope)(sessionPath);
214
+ if (!envelope)
215
+ return null;
216
+ const rawMessages = envelope.events;
217
+ const friendsDir = path.join(agentRoot, "friends");
218
+ const friendName = (0, shared_1.resolveFriendName)(friendsDir, friendId);
219
+ const messages = rawMessages;
220
+ return {
221
+ friendId,
222
+ friendName,
223
+ channel,
224
+ key,
225
+ sessionPath,
226
+ messageCount: messages.length,
227
+ lastUsage: parseSessionUsage(envelope.lastUsage),
228
+ continuity: parseSessionContinuity(envelope.state),
229
+ messages,
230
+ };
231
+ }
@@ -0,0 +1,111 @@
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.STALE_THRESHOLD_MS = exports.BLOCKED_CODING_STATUSES = exports.ACTIVE_CODING_STATUSES = void 0;
37
+ exports.issue = issue;
38
+ exports.safeReaddir = safeReaddir;
39
+ exports.safeIsDirectory = safeIsDirectory;
40
+ exports.resolveFriendName = resolveFriendName;
41
+ exports.safeFileMtime = safeFileMtime;
42
+ exports.truncateExcerpt = truncateExcerpt;
43
+ exports.readSessionEnvelope = readSessionEnvelope;
44
+ const fs = __importStar(require("fs"));
45
+ const path = __importStar(require("path"));
46
+ const runtime_1 = require("../../../nerves/runtime");
47
+ const session_events_1 = require("../../session-events");
48
+ exports.ACTIVE_CODING_STATUSES = new Set(["spawning", "running", "waiting_input", "stalled"]);
49
+ exports.BLOCKED_CODING_STATUSES = new Set(["waiting_input", "stalled"]);
50
+ exports.STALE_THRESHOLD_MS = 24 * 60 * 60 * 1000;
51
+ function issue(code, detail) {
52
+ return { code, detail };
53
+ }
54
+ function safeReaddir(dir) {
55
+ try {
56
+ return fs.readdirSync(dir);
57
+ }
58
+ catch {
59
+ return [];
60
+ }
61
+ }
62
+ function safeIsDirectory(filePath) {
63
+ try {
64
+ return fs.statSync(filePath).isDirectory();
65
+ /* v8 ignore start */
66
+ }
67
+ catch {
68
+ return false;
69
+ }
70
+ /* v8 ignore stop */
71
+ }
72
+ /* v8 ignore start — defensive friend name resolution */
73
+ function resolveFriendName(friendsDir, friendId) {
74
+ try {
75
+ const raw = fs.readFileSync(path.join(friendsDir, `${friendId}.json`), "utf-8");
76
+ const parsed = JSON.parse(raw);
77
+ return typeof parsed.name === "string" ? parsed.name : friendId;
78
+ }
79
+ catch {
80
+ return friendId;
81
+ }
82
+ }
83
+ /* v8 ignore stop */
84
+ /* v8 ignore start — utility helpers with defensive branches */
85
+ function safeFileMtime(filePath) {
86
+ try {
87
+ return fs.statSync(filePath).mtime.toISOString();
88
+ }
89
+ catch {
90
+ return null;
91
+ }
92
+ }
93
+ function truncateExcerpt(content, maxLength = 200) {
94
+ if (!content)
95
+ return null;
96
+ if (content.length <= maxLength)
97
+ return content;
98
+ const truncated = content.slice(0, maxLength);
99
+ const lastSpace = truncated.lastIndexOf(" ");
100
+ return (lastSpace > maxLength * 0.6 ? truncated.slice(0, lastSpace) : truncated) + "…";
101
+ }
102
+ /* v8 ignore stop */
103
+ function readSessionEnvelope(sessionPath) {
104
+ (0, runtime_1.emitNervesEvent)({
105
+ component: "heart",
106
+ event: "heart.outlook_session_envelope_read",
107
+ message: "reading outlook session envelope",
108
+ meta: { sessionPath },
109
+ });
110
+ return (0, session_events_1.loadSessionEnvelopeFile)(sessionPath);
111
+ }
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildProgressStory = buildProgressStory;
4
+ exports.renderProgressStory = renderProgressStory;
5
+ const runtime_1 = require("../nerves/runtime");
6
+ function labelForScope(scope) {
7
+ return scope === "inner-delegation" ? "inner work" : "shared work";
8
+ }
9
+ function compactDetail(text) {
10
+ if (typeof text !== "string")
11
+ return null;
12
+ const trimmed = text.trim();
13
+ return trimmed.length > 0 ? trimmed : null;
14
+ }
15
+ function buildProgressStory(input) {
16
+ const detailLines = [
17
+ compactDetail(input.objective),
18
+ compactDetail(input.outcomeText),
19
+ compactDetail(input.bridgeId ? `bridge: ${input.bridgeId}` : null),
20
+ compactDetail(input.taskName ? `task: ${input.taskName}` : null),
21
+ ].filter((line) => Boolean(line));
22
+ const story = {
23
+ statusLine: `${labelForScope(input.scope)}: ${input.phase}`,
24
+ detailLines,
25
+ };
26
+ (0, runtime_1.emitNervesEvent)({
27
+ component: "engine",
28
+ event: "engine.progress_story_build",
29
+ message: "built shared progress story",
30
+ meta: {
31
+ scope: input.scope,
32
+ phase: input.phase,
33
+ detailLines: detailLines.length,
34
+ hasBridge: Boolean(input.bridgeId),
35
+ hasTask: Boolean(input.taskName),
36
+ },
37
+ });
38
+ return story;
39
+ }
40
+ function renderProgressStory(story) {
41
+ return [story.statusLine, ...story.detailLines].join("\n");
42
+ }
@@ -0,0 +1,135 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildFailoverContext = buildFailoverContext;
4
+ exports.handleFailoverReply = handleFailoverReply;
5
+ const provider_models_1 = require("./provider-models");
6
+ const runtime_1 = require("../nerves/runtime");
7
+ const CLASSIFICATION_LABELS = {
8
+ "auth-failure": "authentication failed",
9
+ "usage-limit": "hit its usage limit",
10
+ "rate-limit": "is being rate limited",
11
+ "server-error": "is experiencing an outage",
12
+ "network-error": "is unreachable (network error)",
13
+ "unknown": "encountered an error",
14
+ };
15
+ function formatProviderWithModel(provider, model) {
16
+ if (!model)
17
+ return provider;
18
+ if ((0, provider_models_1.getProviderModelMismatchMessage)(provider, model)) {
19
+ return `${provider} [configured model: ${model}]`;
20
+ }
21
+ return `${provider} (${model})`;
22
+ }
23
+ function formatErrorDetail(errorMessage, errorSummary) {
24
+ const detail = errorMessage.replace(/\s+/g, " ").trim();
25
+ if (!detail || detail === errorSummary)
26
+ return "";
27
+ return detail.length > 300 ? `${detail.slice(0, 297)}...` : detail;
28
+ }
29
+ function formatFailingProviderLine(provider, classification, agentName) {
30
+ const authCommand = `ouro auth --agent ${agentName} --provider ${provider}`;
31
+ switch (classification) {
32
+ case "auth-failure":
33
+ return ` - ${provider}: credentials need to be refreshed. Run \`${authCommand}\`.`;
34
+ case "network-error":
35
+ return ` - ${provider}: could not be reached. Check network/provider availability; if credentials may be stale, run \`${authCommand}\`.`;
36
+ case "server-error":
37
+ return ` - ${provider}: provider outage or server error. Retry later; if it keeps failing, run \`${authCommand}\`.`;
38
+ case "rate-limit":
39
+ return ` - ${provider}: rate limited. Wait and retry, or switch to a ready provider below.`;
40
+ case "usage-limit":
41
+ return ` - ${provider}: usage limit hit. Wait for quota reset, raise quota, or switch to a ready provider below.`;
42
+ case "unknown":
43
+ return ` - ${provider}: could not be reached. Run \`${authCommand}\` if credentials may be stale.`;
44
+ }
45
+ }
46
+ function buildFailoverContext(errorMessage, classification, currentProvider, currentModel, agentName, inventory, providerModels) {
47
+ const label = CLASSIFICATION_LABELS[classification];
48
+ const providerWithModel = formatProviderWithModel(currentProvider, currentModel);
49
+ const errorSummary = `${providerWithModel} ${label}`;
50
+ const errorDetail = formatErrorDetail(errorMessage, errorSummary);
51
+ const modelMismatch = (0, provider_models_1.getProviderModelMismatchMessage)(currentProvider, currentModel);
52
+ const workingProviders = [];
53
+ const unconfiguredProviders = [];
54
+ const failingProviders = [];
55
+ for (const [provider, result] of Object.entries(inventory)) {
56
+ if (result.ok) {
57
+ workingProviders.push(provider);
58
+ }
59
+ else if (result.classification === "auth-failure" && result.message === "no credentials configured") {
60
+ unconfiguredProviders.push(provider);
61
+ }
62
+ else {
63
+ // Configured but ping failed (expired token, provider also down, etc.)
64
+ failingProviders.push({ provider, classification: result.classification });
65
+ }
66
+ }
67
+ const lines = [`${errorSummary}.`];
68
+ if (errorDetail) {
69
+ lines.push(`provider detail: ${errorDetail}`);
70
+ }
71
+ if (classification === "auth-failure") {
72
+ lines.push("");
73
+ lines.push("To keep using the current provider:");
74
+ lines.push(` 1. Run \`ouro auth --agent ${agentName} --provider ${currentProvider}\``);
75
+ }
76
+ if (modelMismatch) {
77
+ const defaultModel = (0, provider_models_1.getDefaultModelForProvider)(currentProvider);
78
+ lines.push("");
79
+ lines.push("Config warning:");
80
+ lines.push(` - ${modelMismatch}`);
81
+ lines.push(" - Repair the configured model with:");
82
+ lines.push(` \`ouro config model --agent ${agentName} --facing human ${defaultModel}\``);
83
+ lines.push(` \`ouro config model --agent ${agentName} --facing agent ${defaultModel}\``);
84
+ }
85
+ if (workingProviders.length > 0) {
86
+ lines.push("");
87
+ lines.push("Ready providers:");
88
+ for (const provider of workingProviders) {
89
+ const model = (0, provider_models_1.resolveModelForProviderDisplay)(provider, providerModels[provider]);
90
+ lines.push(` - ${provider} (${model}): reply "switch to ${provider}"`);
91
+ }
92
+ }
93
+ if (failingProviders.length > 0) {
94
+ lines.push("");
95
+ lines.push("Configured but unavailable:");
96
+ for (const { provider, classification } of failingProviders) {
97
+ lines.push(formatFailingProviderLine(provider, classification, agentName));
98
+ }
99
+ }
100
+ if (unconfiguredProviders.length > 0) {
101
+ lines.push("");
102
+ lines.push("Not configured:");
103
+ for (const provider of unconfiguredProviders) {
104
+ lines.push(` - ${provider}: run \`ouro auth --agent ${agentName} --provider ${provider}\``);
105
+ }
106
+ }
107
+ if (workingProviders.length === 0 && unconfiguredProviders.length === 0 && failingProviders.length === 0) {
108
+ lines.push("");
109
+ lines.push(`No other providers are available. Run \`ouro auth --agent ${agentName}\` in terminal to configure one.`);
110
+ }
111
+ (0, runtime_1.emitNervesEvent)({
112
+ component: "engine",
113
+ event: "engine.failover_context_built",
114
+ message: "built provider failover context",
115
+ meta: { currentProvider, classification, workingProviders, unconfiguredProviders },
116
+ });
117
+ return {
118
+ errorSummary,
119
+ classification,
120
+ currentProvider,
121
+ agentName,
122
+ workingProviders,
123
+ unconfiguredProviders,
124
+ userMessage: lines.join("\n"),
125
+ };
126
+ }
127
+ function handleFailoverReply(reply, context) {
128
+ const lower = reply.toLowerCase().trim();
129
+ for (const provider of context.workingProviders) {
130
+ if (lower.includes(`switch to ${provider}`) || lower === provider) {
131
+ return { action: "switch", provider };
132
+ }
133
+ }
134
+ return { action: "dismiss" };
135
+ }
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_PROVIDER_MODELS = void 0;
4
+ exports.getProviderDisplayName = getProviderDisplayName;
5
+ exports.getDefaultModelForProvider = getDefaultModelForProvider;
6
+ exports.isModelClearlyIncompatibleWithProvider = isModelClearlyIncompatibleWithProvider;
7
+ exports.resolveModelForProviderSelection = resolveModelForProviderSelection;
8
+ exports.resolveModelForProviderDisplay = resolveModelForProviderDisplay;
9
+ exports.getProviderModelMismatchMessage = getProviderModelMismatchMessage;
10
+ const runtime_1 = require("../nerves/runtime");
11
+ exports.DEFAULT_PROVIDER_MODELS = {
12
+ anthropic: "claude-opus-4-6",
13
+ azure: "gpt-4o-mini",
14
+ minimax: "MiniMax-M2.7",
15
+ "openai-codex": "gpt-5.4",
16
+ "github-copilot": "claude-sonnet-4.6",
17
+ };
18
+ const PROVIDER_NAMES = {
19
+ anthropic: "Anthropic",
20
+ azure: "Azure OpenAI",
21
+ minimax: "MiniMax",
22
+ "openai-codex": "OpenAI Codex",
23
+ "github-copilot": "GitHub Copilot",
24
+ };
25
+ function normalized(model) {
26
+ return model.trim().toLowerCase();
27
+ }
28
+ function getProviderDisplayName(provider) {
29
+ return PROVIDER_NAMES[provider];
30
+ }
31
+ function getDefaultModelForProvider(provider) {
32
+ return exports.DEFAULT_PROVIDER_MODELS[provider];
33
+ }
34
+ function isModelClearlyIncompatibleWithProvider(provider, model) {
35
+ const value = normalized(model);
36
+ if (!value)
37
+ return true;
38
+ switch (provider) {
39
+ case "anthropic":
40
+ return !value.startsWith("claude-");
41
+ case "minimax":
42
+ return !value.startsWith("minimax");
43
+ case "openai-codex":
44
+ return value.startsWith("claude-") || value.startsWith("minimax");
45
+ case "azure":
46
+ return value.startsWith("claude-") || value.startsWith("minimax");
47
+ case "github-copilot":
48
+ return false;
49
+ }
50
+ }
51
+ function resolveModelForProviderSelection(provider, currentModel) {
52
+ const trimmed = currentModel.trim();
53
+ if (trimmed && !isModelClearlyIncompatibleWithProvider(provider, trimmed)) {
54
+ return { model: trimmed, preserved: true };
55
+ }
56
+ const model = getDefaultModelForProvider(provider);
57
+ (0, runtime_1.emitNervesEvent)({
58
+ component: "config/identity",
59
+ event: "config_identity.provider_model_defaulted",
60
+ message: "defaulted provider model during provider selection",
61
+ meta: { provider, previousModel: currentModel, model },
62
+ });
63
+ return { model, preserved: false };
64
+ }
65
+ function resolveModelForProviderDisplay(provider, modelHint) {
66
+ const hint = modelHint?.trim() ?? "";
67
+ if (hint && !isModelClearlyIncompatibleWithProvider(provider, hint))
68
+ return hint;
69
+ return getDefaultModelForProvider(provider);
70
+ }
71
+ function getProviderModelMismatchMessage(provider, model) {
72
+ const trimmed = model.trim();
73
+ if (!isModelClearlyIncompatibleWithProvider(provider, trimmed))
74
+ return null;
75
+ const providerName = getProviderDisplayName(provider);
76
+ const defaultModel = getDefaultModelForProvider(provider);
77
+ if (!trimmed) {
78
+ return `${providerName} has no model set. Suggested model: ${defaultModel}.`;
79
+ }
80
+ return `${providerName} is currently paired with ${trimmed}, which does not look like a model for ${providerName}. Suggested model: ${defaultModel}.`;
81
+ }