@ouro.bot/cli 0.1.0-alpha.22 → 0.1.0-alpha.221
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +187 -191
- package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/agent.json +3 -2
- package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/SOUL.md +1 -1
- package/changelog.json +1360 -2
- package/dist/arc/attention-types.js +8 -0
- package/dist/arc/cares.js +140 -0
- package/dist/arc/episodes.js +119 -0
- package/dist/arc/intentions.js +133 -0
- package/dist/arc/json-store.js +120 -0
- package/dist/arc/obligations.js +237 -0
- package/dist/arc/presence.js +185 -0
- package/dist/heart/active-work.js +832 -0
- package/dist/heart/agent-entry.js +37 -2
- package/dist/heart/auth/auth-flow.js +444 -0
- package/dist/heart/bridges/manager.js +358 -0
- package/dist/heart/bridges/state-machine.js +135 -0
- package/dist/heart/bridges/store.js +123 -0
- package/dist/heart/commitments.js +111 -0
- package/dist/heart/config-registry.js +305 -0
- package/dist/heart/config.js +59 -30
- package/dist/heart/core.js +601 -145
- package/dist/heart/cross-chat-delivery.js +131 -0
- package/dist/heart/daemon/agent-config-check.js +149 -0
- package/dist/heart/daemon/agent-discovery.js +81 -0
- package/dist/heart/daemon/agent-service.js +360 -0
- package/dist/heart/daemon/cadence.js +70 -0
- package/dist/heart/daemon/cli-defaults.js +557 -0
- package/dist/heart/daemon/cli-exec.js +1847 -0
- package/dist/heart/daemon/cli-parse.js +759 -0
- package/dist/heart/daemon/cli-render.js +316 -0
- package/dist/heart/daemon/cli-types.js +8 -0
- package/dist/heart/daemon/daemon-cli.js +29 -1163
- package/dist/heart/daemon/daemon-entry.js +202 -4
- package/dist/heart/daemon/daemon-health.js +136 -0
- package/dist/heart/daemon/daemon-runtime-sync.js +153 -12
- package/dist/heart/daemon/daemon-tombstone.js +123 -0
- package/dist/heart/daemon/daemon.js +487 -20
- package/dist/heart/daemon/hooks/agent-config-v2.js +33 -0
- package/dist/heart/daemon/hooks/bundle-meta.js +115 -1
- package/dist/heart/daemon/inner-status.js +89 -0
- package/dist/heart/daemon/launchd.js +39 -8
- package/dist/heart/daemon/log-tailer.js +4 -3
- package/dist/heart/daemon/message-router.js +17 -8
- package/dist/heart/daemon/os-cron-deps.js +134 -0
- package/dist/heart/daemon/ouro-bot-entry.js +1 -1
- package/dist/heart/daemon/process-manager.js +123 -0
- package/dist/heart/daemon/run-hooks.js +2 -0
- package/dist/heart/daemon/runtime-logging.js +58 -15
- package/dist/heart/daemon/runtime-metadata.js +101 -0
- package/dist/heart/daemon/runtime-mode.js +67 -0
- package/dist/heart/daemon/safe-mode.js +161 -0
- package/dist/heart/daemon/sense-manager.js +72 -3
- package/dist/heart/daemon/session-id-resolver.js +131 -0
- package/dist/heart/daemon/skill-management-installer.js +94 -0
- package/dist/heart/daemon/socket-client.js +202 -0
- package/dist/heart/daemon/task-scheduler.js +3 -25
- package/dist/heart/daemon/thoughts.js +507 -0
- package/dist/heart/delegation.js +62 -0
- package/dist/heart/habits/habit-migration.js +181 -0
- package/dist/heart/habits/habit-parser.js +119 -0
- package/dist/heart/habits/habit-scheduler.js +369 -0
- package/dist/heart/{daemon → hatch}/hatch-flow.js +29 -119
- package/dist/heart/{daemon → hatch}/hatch-specialist.js +3 -3
- package/dist/heart/{daemon → hatch}/specialist-prompt.js +10 -7
- package/dist/heart/{daemon → hatch}/specialist-tools.js +49 -3
- package/dist/heart/identity.js +144 -55
- package/dist/heart/kicks.js +2 -20
- package/dist/heart/mcp/mcp-server.js +608 -0
- package/dist/heart/migrate-config.js +127 -0
- package/dist/heart/model-capabilities.js +48 -0
- package/dist/heart/outlook/outlook-http.js +439 -0
- package/dist/heart/outlook/outlook-read.js +1618 -0
- package/dist/heart/outlook/outlook-render.js +1032 -0
- package/dist/heart/outlook/outlook-types.js +7 -0
- package/dist/heart/outlook/outlook-view.js +194 -0
- package/dist/heart/progress-story.js +42 -0
- package/dist/heart/provider-failover.js +88 -0
- package/dist/heart/provider-ping.js +162 -0
- package/dist/heart/providers/anthropic-token.js +163 -0
- package/dist/heart/providers/anthropic.js +187 -41
- package/dist/heart/providers/azure.js +116 -10
- package/dist/heart/providers/github-copilot.js +157 -0
- package/dist/heart/providers/minimax.js +33 -3
- package/dist/heart/providers/openai-codex.js +51 -16
- package/dist/heart/session-activity.js +172 -0
- package/dist/heart/session-recall.js +216 -0
- package/dist/heart/start-of-turn-packet.js +330 -0
- package/dist/heart/streaming.js +36 -27
- package/dist/heart/sync.js +217 -0
- package/dist/heart/target-resolution.js +127 -0
- package/dist/heart/tempo.js +93 -0
- package/dist/heart/temporal-view.js +41 -0
- package/dist/heart/tool-activity-callbacks.js +36 -0
- package/dist/heart/tool-description.js +135 -0
- package/dist/heart/tool-loop.js +200 -0
- package/dist/heart/turn-context.js +358 -0
- package/dist/heart/turn-coordinator.js +28 -0
- package/dist/heart/versioning/ouro-bot-global-installer.js +128 -0
- package/dist/heart/{daemon → versioning}/ouro-bot-wrapper.js +1 -1
- package/dist/heart/{daemon → versioning}/ouro-path-installer.js +78 -35
- package/dist/heart/versioning/ouro-version-manager.js +171 -0
- package/dist/heart/{daemon → versioning}/staged-restart.js +30 -6
- package/dist/heart/{daemon → versioning}/update-checker.js +9 -1
- package/dist/heart/{daemon → versioning}/update-hooks.js +20 -2
- package/dist/heart/{daemon → versioning}/wrapper-publish-guard.js +41 -3
- package/dist/mind/associative-recall.js +126 -66
- package/dist/mind/bundle-manifest.js +8 -1
- package/dist/mind/context.js +125 -14
- package/dist/mind/{memory.js → diary.js} +73 -96
- package/dist/mind/embedding-provider.js +60 -0
- package/dist/mind/file-state.js +179 -0
- package/dist/mind/first-impressions.js +16 -2
- package/dist/mind/friends/channel.js +56 -0
- package/dist/mind/friends/group-context.js +144 -0
- package/dist/mind/friends/store-file.js +36 -0
- package/dist/mind/friends/trust-explanation.js +74 -0
- package/dist/mind/friends/types.js +8 -0
- package/dist/mind/journal-index.js +161 -0
- package/dist/mind/obligation-steering.js +221 -0
- package/dist/mind/pending.js +76 -9
- package/dist/mind/phrases.js +1 -0
- package/dist/mind/prompt.js +779 -109
- package/dist/mind/scrutiny.js +173 -0
- package/dist/mind/token-estimate.js +8 -12
- package/dist/nerves/coverage/audit.js +1 -1
- package/dist/nerves/coverage/file-completeness.js +47 -5
- package/dist/nerves/coverage/run-artifacts.js +1 -1
- package/dist/nerves/event-buffer.js +111 -0
- package/dist/nerves/index.js +64 -4
- package/dist/nerves/observation.js +20 -0
- package/dist/nerves/runtime.js +5 -1
- package/dist/outlook-ui/assets/index-Ck8agNeO.js +61 -0
- package/dist/outlook-ui/assets/index-LwChZTgL.css +1 -0
- package/dist/outlook-ui/index.html +15 -0
- package/dist/repertoire/ado-client.js +15 -56
- package/dist/repertoire/ado-semantic.js +11 -5
- package/dist/repertoire/api-client.js +100 -0
- package/dist/repertoire/bitwarden-client.js +290 -0
- package/dist/repertoire/coding/codex-jsonl.js +64 -0
- package/dist/repertoire/coding/context-pack.js +330 -0
- package/dist/repertoire/coding/feedback.js +197 -30
- package/dist/repertoire/coding/manager.js +149 -2
- package/dist/repertoire/coding/spawner.js +55 -9
- package/dist/repertoire/coding/tools.js +170 -7
- package/dist/repertoire/github-client.js +14 -55
- package/dist/repertoire/graph-client.js +11 -52
- package/dist/repertoire/guardrails.js +342 -0
- package/dist/repertoire/mcp-client.js +254 -0
- package/dist/repertoire/mcp-manager.js +251 -0
- package/dist/repertoire/shell-sessions.js +133 -0
- package/dist/repertoire/skills.js +14 -23
- package/dist/repertoire/tasks/board.js +43 -5
- package/dist/repertoire/tasks/fix.js +182 -0
- package/dist/repertoire/tasks/index.js +28 -10
- package/dist/repertoire/tasks/lifecycle.js +2 -2
- package/dist/repertoire/tasks/parser.js +3 -2
- package/dist/repertoire/tasks/scanner.js +194 -37
- package/dist/repertoire/tasks/transitions.js +11 -28
- package/dist/repertoire/tools-base.js +35 -771
- package/dist/repertoire/tools-bluebubbles.js +94 -0
- package/dist/repertoire/tools-bridge.js +141 -0
- package/dist/repertoire/tools-config.js +266 -0
- package/dist/repertoire/tools-continuity.js +248 -0
- package/dist/repertoire/tools-files.js +342 -0
- package/dist/repertoire/tools-flow.js +62 -0
- package/dist/repertoire/tools-github.js +1 -6
- package/dist/repertoire/tools-memory.js +347 -0
- package/dist/repertoire/tools-session.js +721 -0
- package/dist/repertoire/tools-shell.js +120 -0
- package/dist/repertoire/tools-surface.js +242 -0
- package/dist/repertoire/tools-teams.js +12 -59
- package/dist/repertoire/tools-travel.js +125 -0
- package/dist/repertoire/tools-vault.js +185 -0
- package/dist/repertoire/tools.js +132 -124
- package/dist/repertoire/travel-api-client.js +224 -0
- package/dist/scripts/claude-code-hook.js +41 -0
- package/dist/scripts/claude-code-stop-hook.js +47 -0
- package/dist/senses/attention-queue.js +97 -0
- package/dist/senses/{bluebubbles-client.js → bluebubbles/client.js} +109 -10
- package/dist/senses/bluebubbles/entry.js +13 -0
- package/dist/senses/bluebubbles/inbound-log.js +109 -0
- package/dist/senses/bluebubbles/index.js +1339 -0
- package/dist/senses/{bluebubbles-media.js → bluebubbles/media.js} +119 -24
- package/dist/senses/{bluebubbles-model.js → bluebubbles/model.js} +13 -5
- package/dist/senses/{bluebubbles-mutation-log.js → bluebubbles/mutation-log.js} +46 -6
- package/dist/senses/bluebubbles/runtime-state.js +109 -0
- package/dist/senses/bluebubbles/session-cleanup.js +72 -0
- package/dist/senses/cli/adapter.js +274 -0
- package/dist/senses/cli/ink-app.js +156 -0
- package/dist/senses/cli/inline-diff.js +64 -0
- package/dist/senses/cli/message-list.js +51 -0
- package/dist/senses/cli/ouro-tui.js +358 -0
- package/dist/senses/cli/spinner-imperative.js +135 -0
- package/dist/senses/cli/spinner.js +101 -0
- package/dist/senses/cli/status-line.js +60 -0
- package/dist/senses/cli/streaming-markdown.js +526 -0
- package/dist/senses/cli/tool-display.js +83 -0
- package/dist/senses/cli/tool-render.js +85 -0
- package/dist/senses/cli/tui-store.js +198 -0
- package/dist/senses/cli/virtual-list.js +35 -0
- package/dist/senses/cli-entry.js +1 -1
- package/dist/senses/cli-layout.js +187 -0
- package/dist/senses/cli.js +503 -244
- package/dist/senses/commands.js +65 -1
- package/dist/senses/continuity.js +94 -0
- package/dist/senses/habit-turn-message.js +108 -0
- package/dist/senses/inner-dialog-worker.js +112 -19
- package/dist/senses/inner-dialog.js +591 -84
- package/dist/senses/pipeline.js +559 -0
- package/dist/senses/shared-turn.js +196 -0
- package/dist/senses/surface-tool.js +68 -0
- package/dist/senses/teams.js +667 -120
- package/dist/senses/trust-gate.js +112 -2
- package/package.json +12 -6
- package/skills/browser-navigation.md +110 -0
- package/skills/configure-dev-tools.md +81 -0
- package/skills/travel-planning.md +143 -0
- package/dist/heart/daemon/subagent-installer.js +0 -134
- package/dist/senses/bluebubbles-entry.js +0 -11
- package/dist/senses/bluebubbles.js +0 -421
- package/dist/senses/debug-activity.js +0 -107
- package/subagents/README.md +0 -73
- package/subagents/work-doer.md +0 -235
- package/subagents/work-merger.md +0 -618
- package/subagents/work-planner.md +0 -382
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/basilisk.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jafar.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jormungandr.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/kaa.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/medusa.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/monty.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/nagini.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/ouroboros.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/python.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/quetzalcoatl.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/sir-hiss.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-serpent.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-snake.md +0 -0
- /package/dist/heart/{daemon → hatch}/hatch-animation.js +0 -0
- /package/dist/heart/{daemon → hatch}/specialist-orchestrator.js +0 -0
- /package/dist/heart/{daemon → versioning}/ouro-uti.js +0 -0
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Travel API client module.
|
|
4
|
+
*
|
|
5
|
+
* Provides HTTP clients for:
|
|
6
|
+
* - OpenWeatherMap (query-param auth via vault)
|
|
7
|
+
* - US State Dept Travel Advisories (no auth)
|
|
8
|
+
* - Nominatim Geocoding (no auth, requires User-Agent)
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.TRAVEL_ADVISORY_RSS_URL = void 0;
|
|
12
|
+
exports.setWeatherVaultConfig = setWeatherVaultConfig;
|
|
13
|
+
exports.getWeather = getWeather;
|
|
14
|
+
exports.getWeatherByCity = getWeatherByCity;
|
|
15
|
+
exports.parseAdvisoryLevel = parseAdvisoryLevel;
|
|
16
|
+
exports.parseCountryName = parseCountryName;
|
|
17
|
+
exports.parseAdvisoryText = parseAdvisoryText;
|
|
18
|
+
exports.getTravelAdvisory = getTravelAdvisory;
|
|
19
|
+
exports.geocode = geocode;
|
|
20
|
+
exports.reverseGeocode = reverseGeocode;
|
|
21
|
+
exports.searchPOI = searchPOI;
|
|
22
|
+
const runtime_1 = require("../nerves/runtime");
|
|
23
|
+
const bitwarden_client_1 = require("./bitwarden-client");
|
|
24
|
+
// --- OpenWeatherMap ---
|
|
25
|
+
/**
|
|
26
|
+
* Vault item ID and field name for OpenWeatherMap API key.
|
|
27
|
+
* Override via setWeatherVaultConfig() for custom vault layouts.
|
|
28
|
+
*/
|
|
29
|
+
let weatherVaultItemId = "openweathermap-api";
|
|
30
|
+
let weatherVaultField = "apiKey";
|
|
31
|
+
function setWeatherVaultConfig(itemId, field) {
|
|
32
|
+
weatherVaultItemId = itemId;
|
|
33
|
+
if (field)
|
|
34
|
+
weatherVaultField = field;
|
|
35
|
+
}
|
|
36
|
+
async function getWeatherApiKey() {
|
|
37
|
+
const client = (0, bitwarden_client_1.getBitwardenClient)();
|
|
38
|
+
return client.getRawSecret(weatherVaultItemId, weatherVaultField);
|
|
39
|
+
}
|
|
40
|
+
async function getWeather(lat, lon) {
|
|
41
|
+
return withNervesEvents("getWeather", { lat, lon }, async () => {
|
|
42
|
+
const apiKey = await getWeatherApiKey();
|
|
43
|
+
const url = `https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lon}&appid=${apiKey}&units=metric`;
|
|
44
|
+
const res = await fetch(url);
|
|
45
|
+
if (!res.ok) {
|
|
46
|
+
throw new Error(`OpenWeatherMap API error: ${res.status} ${res.statusText}`);
|
|
47
|
+
}
|
|
48
|
+
const data = await res.json();
|
|
49
|
+
return parseWeatherResponse(data);
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
async function getWeatherByCity(city) {
|
|
53
|
+
return withNervesEvents("getWeatherByCity", { city }, async () => {
|
|
54
|
+
const apiKey = await getWeatherApiKey();
|
|
55
|
+
const url = `https://api.openweathermap.org/data/2.5/weather?q=${encodeURIComponent(city)}&appid=${apiKey}&units=metric`;
|
|
56
|
+
const res = await fetch(url);
|
|
57
|
+
if (!res.ok) {
|
|
58
|
+
throw new Error(`OpenWeatherMap API error: ${res.status} ${res.statusText}`);
|
|
59
|
+
}
|
|
60
|
+
const data = await res.json();
|
|
61
|
+
return parseWeatherResponse(data);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
function parseWeatherResponse(data) {
|
|
65
|
+
return {
|
|
66
|
+
temperature: data.main.temp,
|
|
67
|
+
feelsLike: data.main.feels_like,
|
|
68
|
+
description: data.weather?.[0]?.description ?? "unknown",
|
|
69
|
+
humidity: data.main.humidity,
|
|
70
|
+
windSpeed: data.wind.speed,
|
|
71
|
+
city: data.name,
|
|
72
|
+
country: data.sys.country,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
// --- US State Dept Travel Advisories ---
|
|
76
|
+
/**
|
|
77
|
+
* Official US State Department travel advisories RSS feed.
|
|
78
|
+
* This is the canonical public feed maintained by travel.state.gov.
|
|
79
|
+
* Returns XML/RSS with per-country advisory items.
|
|
80
|
+
*/
|
|
81
|
+
exports.TRAVEL_ADVISORY_RSS_URL = "https://travel.state.gov/_res/rss/TAsTWs.xml";
|
|
82
|
+
/**
|
|
83
|
+
* Parse advisory level (1-4) from an RSS item title like
|
|
84
|
+
* "Afghanistan - Level 4: Do Not Travel"
|
|
85
|
+
*/
|
|
86
|
+
function parseAdvisoryLevel(title) {
|
|
87
|
+
const match = title.match(/Level\s+(\d)/);
|
|
88
|
+
return match ? parseInt(match[1], 10) : 0;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Parse country name from an RSS item title like
|
|
92
|
+
* "Afghanistan - Level 4: Do Not Travel"
|
|
93
|
+
*/
|
|
94
|
+
function parseCountryName(title) {
|
|
95
|
+
const dashIdx = title.indexOf(" - ");
|
|
96
|
+
return dashIdx > 0 ? title.slice(0, dashIdx).trim() : title.trim();
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Extract advisory text from title (the part after "Level N: ").
|
|
100
|
+
*/
|
|
101
|
+
function parseAdvisoryText(title) {
|
|
102
|
+
const match = title.match(/Level\s+\d:\s*(.+)/);
|
|
103
|
+
return match ? match[1].trim() : title.trim();
|
|
104
|
+
}
|
|
105
|
+
async function getTravelAdvisory(countryCode) {
|
|
106
|
+
return withNervesEvents("getTravelAdvisory", { countryCode }, async () => {
|
|
107
|
+
const res = await fetch(exports.TRAVEL_ADVISORY_RSS_URL, {
|
|
108
|
+
headers: {
|
|
109
|
+
Accept: "application/xml, text/xml",
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
if (!res.ok) {
|
|
113
|
+
throw new Error(`Travel advisory API error: ${res.status} ${res.statusText}`);
|
|
114
|
+
}
|
|
115
|
+
const xml = await res.text();
|
|
116
|
+
// Parse RSS items via regex (no XML parser dependency needed).
|
|
117
|
+
// Each <item> contains <title>, <pubDate>, and <category domain="Country-Tag">.
|
|
118
|
+
const itemPattern = /<item>([\s\S]*?)<\/item>/g;
|
|
119
|
+
let match;
|
|
120
|
+
while ((match = itemPattern.exec(xml)) !== null) {
|
|
121
|
+
const block = match[1];
|
|
122
|
+
// Extract country tag from <category domain="Country-Tag">XX</category>
|
|
123
|
+
const tagMatch = block.match(/<category\s+domain="Country-Tag">(.*?)<\/category>/);
|
|
124
|
+
const tag = tagMatch?.[1]?.trim().toUpperCase();
|
|
125
|
+
// The RSS feed uses FIPS country codes (e.g. "AF" for Afghanistan),
|
|
126
|
+
// which sometimes differ from ISO 3166. Try exact match first.
|
|
127
|
+
if (tag !== countryCode.toUpperCase())
|
|
128
|
+
continue;
|
|
129
|
+
const titleMatch = block.match(/<title>(.*?)<\/title>/);
|
|
130
|
+
const title = titleMatch?.[1] ?? "";
|
|
131
|
+
const pubDateMatch = block.match(/<pubDate>(.*?)<\/pubDate>/);
|
|
132
|
+
const pubDate = pubDateMatch?.[1]?.trim() ?? "";
|
|
133
|
+
return {
|
|
134
|
+
countryCode,
|
|
135
|
+
countryName: parseCountryName(title),
|
|
136
|
+
advisoryLevel: parseAdvisoryLevel(title),
|
|
137
|
+
advisoryText: parseAdvisoryText(title),
|
|
138
|
+
lastUpdated: pubDate,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
throw new Error(`No travel advisory found for country code "${countryCode}"`);
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
// --- Nominatim Geocoding ---
|
|
145
|
+
const NOMINATIM_BASE = "https://nominatim.openstreetmap.org";
|
|
146
|
+
const NOMINATIM_HEADERS = {
|
|
147
|
+
"User-Agent": "Ouroboros/1.0 (https://github.com/ouroborosbot/ouroboros)",
|
|
148
|
+
Accept: "application/json",
|
|
149
|
+
};
|
|
150
|
+
async function geocode(query) {
|
|
151
|
+
return withNervesEvents("geocode", { query }, async () => {
|
|
152
|
+
const url = `${NOMINATIM_BASE}/search?q=${encodeURIComponent(query)}&format=json`;
|
|
153
|
+
const res = await fetch(url, { headers: NOMINATIM_HEADERS });
|
|
154
|
+
if (!res.ok) {
|
|
155
|
+
throw new Error(`Nominatim geocode error: ${res.status} ${res.statusText}`);
|
|
156
|
+
}
|
|
157
|
+
const data = await res.json();
|
|
158
|
+
return data.map(parseGeoLocation);
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
async function reverseGeocode(lat, lon) {
|
|
162
|
+
return withNervesEvents("reverseGeocode", { lat, lon }, async () => {
|
|
163
|
+
const url = `${NOMINATIM_BASE}/reverse?lat=${lat}&lon=${lon}&format=json`;
|
|
164
|
+
const res = await fetch(url, { headers: NOMINATIM_HEADERS });
|
|
165
|
+
if (!res.ok) {
|
|
166
|
+
throw new Error(`Nominatim reverse geocode error: ${res.status} ${res.statusText}`);
|
|
167
|
+
}
|
|
168
|
+
const data = await res.json();
|
|
169
|
+
return parseGeoLocation(data);
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
async function searchPOI(query, lat, lon, radiusKm) {
|
|
173
|
+
return withNervesEvents("searchPOI", { query, lat, lon, radiusKm }, async () => {
|
|
174
|
+
const radius = radiusKm ?? 10;
|
|
175
|
+
// Nominatim uses viewbox for bounded search (approximate degrees from km)
|
|
176
|
+
const degOffset = radius / 111; // ~111 km per degree
|
|
177
|
+
const viewbox = `${lon - degOffset},${lat + degOffset},${lon + degOffset},${lat - degOffset}`;
|
|
178
|
+
const url = `${NOMINATIM_BASE}/search?q=${encodeURIComponent(query)}&format=json&viewbox=${viewbox}&bounded=1`;
|
|
179
|
+
const res = await fetch(url, { headers: NOMINATIM_HEADERS });
|
|
180
|
+
if (!res.ok) {
|
|
181
|
+
throw new Error(`Nominatim POI search error: ${res.status} ${res.statusText}`);
|
|
182
|
+
}
|
|
183
|
+
const data = await res.json();
|
|
184
|
+
return data.map(parseGeoLocation);
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
function parseGeoLocation(item) {
|
|
188
|
+
return {
|
|
189
|
+
lat: parseFloat(item.lat),
|
|
190
|
+
lon: parseFloat(item.lon),
|
|
191
|
+
displayName: item.display_name,
|
|
192
|
+
type: item.type ?? "unknown",
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
// --- Shared nerves event wrapper ---
|
|
196
|
+
async function withNervesEvents(operation, meta, fn) {
|
|
197
|
+
(0, runtime_1.emitNervesEvent)({
|
|
198
|
+
event: "client.request_start",
|
|
199
|
+
component: "clients",
|
|
200
|
+
message: `travel API ${operation} starting`,
|
|
201
|
+
meta: { operation, ...meta },
|
|
202
|
+
});
|
|
203
|
+
try {
|
|
204
|
+
const result = await fn();
|
|
205
|
+
(0, runtime_1.emitNervesEvent)({
|
|
206
|
+
event: "client.request_end",
|
|
207
|
+
component: "clients",
|
|
208
|
+
message: `travel API ${operation} complete`,
|
|
209
|
+
meta: { operation, ...meta },
|
|
210
|
+
});
|
|
211
|
+
return result;
|
|
212
|
+
}
|
|
213
|
+
catch (err) {
|
|
214
|
+
(0, runtime_1.emitNervesEvent)({
|
|
215
|
+
level: "error",
|
|
216
|
+
event: "client.error",
|
|
217
|
+
component: "clients",
|
|
218
|
+
message: `travel API ${operation} failed`,
|
|
219
|
+
/* v8 ignore next -- defensive: callers throw Error instances @preserve */
|
|
220
|
+
meta: { operation, reason: err instanceof Error ? err.message : String(err), ...meta },
|
|
221
|
+
});
|
|
222
|
+
throw err;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
@@ -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("
|
|
6
|
-
const identity_1 = require("
|
|
7
|
-
const runtime_1 = require("
|
|
8
|
-
const
|
|
9
|
-
const
|
|
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
|
-
|
|
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,
|
|
426
|
+
const normalized = (0, model_1.normalizeBlueBubblesEvent)({
|
|
386
427
|
type: event.eventType,
|
|
387
428
|
data,
|
|
388
429
|
});
|
|
389
|
-
|
|
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,
|
|
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)();
|