@ouro.bot/cli 0.1.0-alpha.559 → 0.1.0-alpha.560
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 +5 -2
- package/changelog.json +8 -0
- package/dist/heart/config-registry.js +18 -0
- package/dist/heart/core.js +1 -1
- package/dist/heart/daemon/cli-exec.js +59 -3
- package/dist/heart/daemon/cli-parse.js +5 -3
- package/dist/heart/daemon/runtime-logging.js +1 -1
- package/dist/heart/daemon/sense-manager.js +42 -6
- package/dist/heart/identity.js +4 -1
- package/dist/heart/sense-truth.js +2 -0
- package/dist/heart/turn-context.js +8 -0
- package/dist/mailbox-ui/assets/{index-Cm51CY9W.js → index-B-461hes.js} +1 -1
- package/dist/mailbox-ui/index.html +1 -1
- package/dist/mind/friends/channel.js +9 -0
- package/dist/mind/prompt.js +16 -0
- package/dist/senses/voice/elevenlabs.js +125 -0
- package/dist/senses/voice/index.js +22 -0
- package/dist/senses/voice/transcript.js +70 -0
- package/dist/senses/voice/turn.js +85 -0
- package/dist/senses/voice/types.js +2 -0
- package/dist/senses/voice/whisper.js +133 -0
- package/dist/senses/voice-entry.js +80 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -23,6 +23,7 @@ Current first-class senses:
|
|
|
23
23
|
- `teams`
|
|
24
24
|
- `bluebubbles`
|
|
25
25
|
- `mail`
|
|
26
|
+
- `voice`
|
|
26
27
|
|
|
27
28
|
(MCP is a bridge for developer tools — a separate channel, not a sense. See `src/heart/mcp/` for the implementation.)
|
|
28
29
|
|
|
@@ -47,7 +48,7 @@ The shared harness lives in `src/`:
|
|
|
47
48
|
- `src/repertoire/`
|
|
48
49
|
Tool registry (split into category modules: files, shell, notes, bridge, session, continuity, flow, surface, config, and sense-specific tools), coding orchestration, task tools, shared API client, and integration clients (Graph, ADO, GitHub).
|
|
49
50
|
- `src/senses/`
|
|
50
|
-
CLI (with TUI in senses/cli/), Teams, BlueBubbles (in senses/bluebubbles/), Mail (in senses/mail.ts), activity transport, inner-dialog orchestration, and contextual heartbeat. The MCP bridge is at `src/heart/mcp/`, not here.
|
|
51
|
+
CLI (with TUI in senses/cli/), Teams, BlueBubbles (in senses/bluebubbles/), Mail (in senses/mail.ts), Voice (in senses/voice/), activity transport, inner-dialog orchestration, and contextual heartbeat. The MCP bridge is at `src/heart/mcp/`, not here.
|
|
51
52
|
- `src/nerves/`
|
|
52
53
|
Structured runtime logging and coverage-audit infrastructure.
|
|
53
54
|
- `src/__tests__/`
|
|
@@ -95,7 +96,7 @@ Task docs do not live in this repo anymore. Planning and doing docs live in the
|
|
|
95
96
|
|
|
96
97
|
## Runtime Truths
|
|
97
98
|
|
|
98
|
-
- `agent.json` is the source of truth for identity, phrase pools, context settings, enabled senses, vault coordinates, and provider+model selection. It has two provider lanes: `outward` for CLI, Teams, and
|
|
99
|
+
- `agent.json` is the source of truth for identity, phrase pools, context settings, enabled senses, vault coordinates, and provider+model selection. It has two provider lanes: `outward` for CLI, Teams, BlueBubbles, Mail, and Voice turns, and `inner` for inner dialogue.
|
|
99
100
|
- Legacy `humanFacing`/`agentFacing` provider fields are read only as compatibility aliases for `outward`/`inner`; they are not a second config surface.
|
|
100
101
|
- Each agent has one credential vault for provider, runtime, sense, integration, travel, and tool credentials. There is no machine-wide credential pool.
|
|
101
102
|
- Vault unlock material is local machine state. Prefer macOS Keychain, Windows DPAPI, or Linux Secret Service; plaintext fallback is allowed only by explicit human choice.
|
|
@@ -104,6 +105,7 @@ Task docs do not live in this repo anymore. Planning and doing docs live in the
|
|
|
104
105
|
- Human TTY commands share one CLI surface family: bare `ouro` opens the home deck, `ouro up` uses the boot checklist, `ouro connect`/`ouro auth verify`/`ouro repair` agree on provider and vault truth, and `ouro help`/`ouro whoami`/`ouro versions`/`ouro hatch` render through the same Ouro-branded wizard/guide language instead of raw transcript walls. Orientation commands such as root `ouro connect` may use shorter live probes, while startup and verification commands own durable readiness updates.
|
|
105
106
|
- Human-facing CLI commands that can wait on browser auth, vault IO, daemon startup, daemon restart, provider checks, or connector setup use a shared progress checklist. If a cursor may blink for more than a few seconds, the command should print or animate the current step instead of going quiet.
|
|
106
107
|
- CLI commands that mutate bundle config, such as vault setup or `ouro connect bluebubbles`, run bundle sync after the change when `sync.enabled` is true and report a compact `bundle sync:` line.
|
|
108
|
+
- Voice is transcript-first: voice sessions use the ordinary `state/sessions/<friend>/voice/<key>.json` session path and appear in Ouro Mailbox as text transcripts. ElevenLabs API credentials live in portable `runtime/config` at `integrations.elevenLabsApiKey`; Whisper.cpp CLI/model paths live in the machine runtime item at `voice.whisperCliPath` and `voice.whisperModelPath`.
|
|
107
109
|
- The daemon discovers bundles dynamically from `~/AgentBundles`.
|
|
108
110
|
- `ouro status` reports version, last-updated time, discovered agents, senses, and workers.
|
|
109
111
|
- `bundle-meta.json` tracks the runtime version that last touched a bundle.
|
|
@@ -191,6 +193,7 @@ ouro connect perplexity --agent <name>
|
|
|
191
193
|
ouro connect embeddings --agent <name>
|
|
192
194
|
ouro connect teams --agent <name>
|
|
193
195
|
ouro connect bluebubbles --agent <name>
|
|
196
|
+
ouro connect voice --agent <name>
|
|
194
197
|
ouro auth --agent <name>
|
|
195
198
|
ouro auth --agent <name> --provider <provider>
|
|
196
199
|
ouro auth verify --agent <name> [--provider <provider>]
|
package/changelog.json
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
|
|
3
3
|
"versions": [
|
|
4
|
+
{
|
|
5
|
+
"version": "0.1.0-alpha.560",
|
|
6
|
+
"changes": [
|
|
7
|
+
"Voice is now a first-class disabled-by-default sense and channel, with daemon inventory, connect-menu, prompt status, session transcript, and Mailbox UI surfaces.",
|
|
8
|
+
"Voice foundation modules now define Whisper.cpp STT, ElevenLabs streaming TTS, canonical transcript metadata, and loopback voice turns through ordinary `voice` sessions.",
|
|
9
|
+
"The daemon can launch a managed voice entrypoint, and docs now spell out voice runtime credential ownership plus the next meeting/Riverside joining milestone."
|
|
10
|
+
]
|
|
11
|
+
},
|
|
4
12
|
{
|
|
5
13
|
"version": "0.1.0-alpha.559",
|
|
6
14
|
"changes": [
|
|
@@ -182,6 +182,24 @@ const registryData = [
|
|
|
182
182
|
topics: ["senses", "bluebubbles", "imessage", "channels", "interface"],
|
|
183
183
|
validate: validateObject({ enabled: validateBoolean }),
|
|
184
184
|
},
|
|
185
|
+
{
|
|
186
|
+
path: "senses.mail",
|
|
187
|
+
tier: "self",
|
|
188
|
+
description: "Mail sense configuration. Controls whether the agent-owned Mailroom mailbox is enabled.",
|
|
189
|
+
default: { enabled: false },
|
|
190
|
+
effects: "Enables or disables the agent mail sense. Runtime credentials still live in the agent vault.",
|
|
191
|
+
topics: ["senses", "mail", "mailroom", "channels", "interface"],
|
|
192
|
+
validate: validateObject({ enabled: validateBoolean }),
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
path: "senses.voice",
|
|
196
|
+
tier: "self",
|
|
197
|
+
description: "Voice sense configuration. Controls whether conversational audio sessions are enabled.",
|
|
198
|
+
default: { enabled: false },
|
|
199
|
+
effects: "Enables or disables voice sessions. STT/TTS credentials and local audio attachments are configured separately.",
|
|
200
|
+
topics: ["senses", "voice", "audio", "speech", "channels", "interface"],
|
|
201
|
+
validate: validateObject({ enabled: validateBoolean }),
|
|
202
|
+
},
|
|
185
203
|
{
|
|
186
204
|
path: "sync.enabled",
|
|
187
205
|
tier: "self",
|
package/dist/heart/core.js
CHANGED
|
@@ -207,7 +207,7 @@ function hasFreshPendingWork(options) {
|
|
|
207
207
|
* where mid-turn delivery is meaningful. Inner dialog has `ponder`. MCP returns
|
|
208
208
|
* synchronously. Mail is batch. Anything else (unknown channel) treats as non-chat. */
|
|
209
209
|
function isChatStyleChannel(channel) {
|
|
210
|
-
return channel === "cli" || channel === "teams" || channel === "bluebubbles";
|
|
210
|
+
return channel === "cli" || channel === "teams" || channel === "bluebubbles" || channel === "voice";
|
|
211
211
|
}
|
|
212
212
|
// Sole-call tools must be the only tool call in a turn. When they appear
|
|
213
213
|
// alongside other tools, the sole-call tool is rejected with this message.
|
|
@@ -2484,11 +2484,12 @@ function enableAgentSense(agent, sense, deps) {
|
|
|
2484
2484
|
teams: senses.teams ?? { enabled: false },
|
|
2485
2485
|
bluebubbles: senses.bluebubbles ?? { enabled: false },
|
|
2486
2486
|
mail: senses.mail ?? { enabled: false },
|
|
2487
|
+
voice: senses.voice ?? { enabled: false },
|
|
2487
2488
|
[sense]: { ...existing, enabled: true },
|
|
2488
2489
|
};
|
|
2489
2490
|
fs.writeFileSync(configPath, `${JSON.stringify(raw, null, 2)}\n`, "utf-8");
|
|
2490
2491
|
}
|
|
2491
|
-
const CONNECT_MENU_PROMPT = "Choose [1-
|
|
2492
|
+
const CONNECT_MENU_PROMPT = "Choose [1-7] or type a name: ";
|
|
2492
2493
|
function connectMenuIsTTY(deps) {
|
|
2493
2494
|
return deps.isTTY ?? process.stdout.isTTY === true;
|
|
2494
2495
|
}
|
|
@@ -2499,6 +2500,7 @@ function readConnectBaySenseFlags(agent, deps) {
|
|
|
2499
2500
|
teamsEnabled: parsed.senses?.teams?.enabled === true,
|
|
2500
2501
|
blueBubblesEnabled: parsed.senses?.bluebubbles?.enabled === true,
|
|
2501
2502
|
mailEnabled: parsed.senses?.mail?.enabled === true,
|
|
2503
|
+
voiceEnabled: parsed.senses?.voice?.enabled === true,
|
|
2502
2504
|
};
|
|
2503
2505
|
}
|
|
2504
2506
|
async function buildConnectMenu(agent, deps, onProgress) {
|
|
@@ -2528,13 +2530,16 @@ async function buildConnectMenu(agent, deps, onProgress) {
|
|
|
2528
2530
|
const runtimeConfig = await (0, runtime_credentials_1.refreshRuntimeCredentialConfig)(agent, { preserveCachedOnFailure: true });
|
|
2529
2531
|
onProgress?.("loading this machine's settings");
|
|
2530
2532
|
const machineRuntime = await (0, runtime_credentials_1.refreshMachineRuntimeCredentialConfig)(agent, currentMachineId(deps), { preserveCachedOnFailure: true });
|
|
2531
|
-
const { teamsEnabled, blueBubblesEnabled, mailEnabled } = readConnectBaySenseFlags(agent, deps);
|
|
2533
|
+
const { teamsEnabled, blueBubblesEnabled, mailEnabled, voiceEnabled } = readConnectBaySenseFlags(agent, deps);
|
|
2532
2534
|
const perplexityApiKey = runtimeConfig.ok
|
|
2533
2535
|
? readRuntimeConfigString(runtimeConfig.config, "integrations.perplexityApiKey")
|
|
2534
2536
|
: null;
|
|
2535
2537
|
const embeddingsApiKey = runtimeConfig.ok
|
|
2536
2538
|
? readRuntimeConfigString(runtimeConfig.config, "integrations.openaiEmbeddingsApiKey")
|
|
2537
2539
|
: null;
|
|
2540
|
+
const elevenLabsApiKey = runtimeConfig.ok
|
|
2541
|
+
? readRuntimeConfigString(runtimeConfig.config, "integrations.elevenLabsApiKey")
|
|
2542
|
+
: null;
|
|
2538
2543
|
const shouldVerifyPerplexity = runtimeConfig.ok && !!perplexityApiKey;
|
|
2539
2544
|
const shouldVerifyEmbeddings = runtimeConfig.ok && !!embeddingsApiKey;
|
|
2540
2545
|
let perplexityVerification;
|
|
@@ -2605,6 +2610,16 @@ async function buildConnectMenu(agent, deps, onProgress) {
|
|
|
2605
2610
|
? "attached"
|
|
2606
2611
|
: "not attached"
|
|
2607
2612
|
: machineRuntimeReadStatus(machineRuntime);
|
|
2613
|
+
const voiceStatus = runtimeConfig.ok
|
|
2614
|
+
? machineRuntime.ok
|
|
2615
|
+
? elevenLabsApiKey
|
|
2616
|
+
&& hasRuntimeConfigValue(machineRuntime.config, "voice.whisperCliPath")
|
|
2617
|
+
&& hasRuntimeConfigValue(machineRuntime.config, "voice.whisperModelPath")
|
|
2618
|
+
&& voiceEnabled
|
|
2619
|
+
? "attached"
|
|
2620
|
+
: "not attached"
|
|
2621
|
+
: machineRuntimeReadStatus(machineRuntime)
|
|
2622
|
+
: runtimeConfigReadStatus(runtimeConfig);
|
|
2608
2623
|
const mailroomConfig = runtimeConfig.ok && runtimeConfig.config.mailroom && typeof runtimeConfig.config.mailroom === "object" && !Array.isArray(runtimeConfig.config.mailroom)
|
|
2609
2624
|
? runtimeConfig.config.mailroom
|
|
2610
2625
|
: {};
|
|
@@ -2695,6 +2710,26 @@ async function buildConnectMenu(agent, deps, onProgress) {
|
|
|
2695
2710
|
status: mailStatus,
|
|
2696
2711
|
}) ? `ouro connect mail --agent ${agent}` : undefined,
|
|
2697
2712
|
},
|
|
2713
|
+
{
|
|
2714
|
+
option: "7",
|
|
2715
|
+
name: "Voice",
|
|
2716
|
+
section: "This machine",
|
|
2717
|
+
status: voiceStatus,
|
|
2718
|
+
description: "Conversational audio via local Whisper.cpp STT and ElevenLabs TTS.",
|
|
2719
|
+
detailLines: runtimeConfig.ok && machineRuntime.ok
|
|
2720
|
+
? [
|
|
2721
|
+
elevenLabsApiKey ? "ElevenLabs API key saved in portable runtime config" : "missing integrations.elevenLabsApiKey",
|
|
2722
|
+
hasRuntimeConfigValue(machineRuntime.config, "voice.whisperCliPath") ? "Whisper.cpp CLI path saved for this machine" : "missing voice.whisperCliPath",
|
|
2723
|
+
hasRuntimeConfigValue(machineRuntime.config, "voice.whisperModelPath") ? "Whisper.cpp model path saved for this machine" : "missing voice.whisperModelPath",
|
|
2724
|
+
]
|
|
2725
|
+
: [],
|
|
2726
|
+
nextAction: (0, connect_bay_1.connectEntryNeedsAttention)({
|
|
2727
|
+
option: "7",
|
|
2728
|
+
name: "Voice",
|
|
2729
|
+
section: "This machine",
|
|
2730
|
+
status: voiceStatus,
|
|
2731
|
+
}) ? `ouro connect voice --agent ${agent}` : undefined,
|
|
2732
|
+
},
|
|
2698
2733
|
];
|
|
2699
2734
|
const isTTY = connectMenuIsTTY(deps);
|
|
2700
2735
|
return (0, connect_bay_1.renderConnectBay)(entries, {
|
|
@@ -4228,8 +4263,24 @@ function connectMenuTarget(answer) {
|
|
|
4228
4263
|
return "bluebubbles";
|
|
4229
4264
|
if (normalized === "6" || normalized === "mail" || normalized === "email" || normalized === "mailroom")
|
|
4230
4265
|
return "mail";
|
|
4266
|
+
if (normalized === "7" || normalized === "voice" || normalized === "audio" || normalized === "speech")
|
|
4267
|
+
return "voice";
|
|
4231
4268
|
return "cancel";
|
|
4232
4269
|
}
|
|
4270
|
+
async function executeConnectVoice(agent, deps) {
|
|
4271
|
+
const message = [
|
|
4272
|
+
`Voice foundation for ${agent}`,
|
|
4273
|
+
"Configure the portable ElevenLabs API key with:",
|
|
4274
|
+
` ouro vault config set --agent ${agent} --key integrations.elevenLabsApiKey`,
|
|
4275
|
+
"Configure this machine's Whisper.cpp attachment with:",
|
|
4276
|
+
` ouro vault config set --agent ${agent} --scope machine --key voice.whisperCliPath`,
|
|
4277
|
+
` ouro vault config set --agent ${agent} --scope machine --key voice.whisperModelPath`,
|
|
4278
|
+
"Then enable agent.json: senses.voice.enabled = true and restart with `ouro up`.",
|
|
4279
|
+
"Meeting-link joining and browser/system audio routing are tracked as the next milestone.",
|
|
4280
|
+
].join("\n");
|
|
4281
|
+
deps.writeStdout(message);
|
|
4282
|
+
return message;
|
|
4283
|
+
}
|
|
4233
4284
|
async function executeConnect(command, deps) {
|
|
4234
4285
|
if (command.target === "providers")
|
|
4235
4286
|
return executeConnectProviders(command.agent, deps);
|
|
@@ -4243,6 +4294,8 @@ async function executeConnect(command, deps) {
|
|
|
4243
4294
|
return executeConnectBlueBubbles(command.agent, deps);
|
|
4244
4295
|
if (command.target === "mail")
|
|
4245
4296
|
return executeConnectMail(command.agent, deps, command);
|
|
4297
|
+
if (command.target === "voice")
|
|
4298
|
+
return executeConnectVoice(command.agent, deps);
|
|
4246
4299
|
const progress = createHumanCommandProgress(deps, "connect");
|
|
4247
4300
|
let menu;
|
|
4248
4301
|
try {
|
|
@@ -4254,7 +4307,7 @@ async function executeConnect(command, deps) {
|
|
|
4254
4307
|
const promptInput = deps.promptInput;
|
|
4255
4308
|
if (!promptInput) {
|
|
4256
4309
|
const message = [
|
|
4257
|
-
menu.replace(/\nChoose \[1-
|
|
4310
|
+
menu.replace(/\nChoose \[1-7\] or type a name: $/, ""),
|
|
4258
4311
|
"",
|
|
4259
4312
|
`Run: ouro connect providers --agent ${command.agent}`,
|
|
4260
4313
|
`Run: ouro connect perplexity --agent ${command.agent}`,
|
|
@@ -4262,6 +4315,7 @@ async function executeConnect(command, deps) {
|
|
|
4262
4315
|
`Run: ouro connect teams --agent ${command.agent}`,
|
|
4263
4316
|
`Run: ouro connect bluebubbles --agent ${command.agent}`,
|
|
4264
4317
|
`Run: ouro connect mail --agent ${command.agent}`,
|
|
4318
|
+
`Run: ouro connect voice --agent ${command.agent}`,
|
|
4265
4319
|
].join("\n");
|
|
4266
4320
|
deps.writeStdout(message);
|
|
4267
4321
|
return message;
|
|
@@ -4279,6 +4333,8 @@ async function executeConnect(command, deps) {
|
|
|
4279
4333
|
return executeConnectBlueBubbles(command.agent, deps);
|
|
4280
4334
|
if (answer === "mail")
|
|
4281
4335
|
return executeConnectMail(command.agent, deps);
|
|
4336
|
+
if (answer === "voice")
|
|
4337
|
+
return executeConnectVoice(command.agent, deps);
|
|
4282
4338
|
const message = "connect cancelled.";
|
|
4283
4339
|
deps.writeStdout(message);
|
|
4284
4340
|
return message;
|
|
@@ -85,7 +85,7 @@ function usage() {
|
|
|
85
85
|
" ouro config models [--agent <name>]",
|
|
86
86
|
" ouro auth [--agent <name>] [--provider <provider>]",
|
|
87
87
|
" ouro account ensure [--agent <name>] [--owner-email <email> --source <label>|--no-delegated-source] [--rotate-missing-mail-keys]",
|
|
88
|
-
" ouro connect [providers|perplexity|embeddings|teams|bluebubbles|mail] [--agent <name>] [--owner-email <email> --source <label>|--no-delegated-source] [--rotate-missing-mail-keys]",
|
|
88
|
+
" ouro connect [providers|perplexity|embeddings|teams|bluebubbles|mail|voice] [--agent <name>] [--owner-email <email> --source <label>|--no-delegated-source] [--rotate-missing-mail-keys]",
|
|
89
89
|
" ouro mail import-mbox --file <path> [--owner-email <email>] [--source <label>] [--agent <name>] [--foreground]",
|
|
90
90
|
" ouro mail backfill-indexes [--agent <name>] [--foreground]",
|
|
91
91
|
" ouro auth verify [--agent <name>] [--provider <provider>]",
|
|
@@ -811,7 +811,9 @@ function normalizeConnectTarget(value) {
|
|
|
811
811
|
return "bluebubbles";
|
|
812
812
|
if (value === "mail" || value === "email" || value === "mailroom")
|
|
813
813
|
return "mail";
|
|
814
|
-
|
|
814
|
+
if (value === "voice" || value === "audio" || value === "speech")
|
|
815
|
+
return "voice";
|
|
816
|
+
throw new Error("Usage: ouro connect [providers|perplexity|embeddings|teams|bluebubbles|mail|voice] [--agent <name>]");
|
|
815
817
|
}
|
|
816
818
|
function extractMailSourceFlags(args, usageText) {
|
|
817
819
|
const rest = [];
|
|
@@ -864,7 +866,7 @@ function extractMailSourceFlags(args, usageText) {
|
|
|
864
866
|
};
|
|
865
867
|
}
|
|
866
868
|
function parseConnectCommand(args) {
|
|
867
|
-
const usageText = "Usage: ouro connect [providers|perplexity|embeddings|teams|bluebubbles|mail] [--agent <name>] [--owner-email <email> --source <label>|--no-delegated-source] [--rotate-missing-mail-keys]";
|
|
869
|
+
const usageText = "Usage: ouro connect [providers|perplexity|embeddings|teams|bluebubbles|mail|voice] [--agent <name>] [--owner-email <email> --source <label>|--no-delegated-source] [--rotate-missing-mail-keys]";
|
|
868
870
|
const { agent, rest: afterAgent } = extractAgentFlag(args);
|
|
869
871
|
const mailFlags = extractMailSourceFlags(afterAgent, usageText);
|
|
870
872
|
if (mailFlags.rest.length > 1)
|
|
@@ -50,7 +50,7 @@ function defaultLoggingForProcess(processName) {
|
|
|
50
50
|
sinks: ["ndjson"],
|
|
51
51
|
};
|
|
52
52
|
}
|
|
53
|
-
if (processName === "bluebubbles" || processName === "mail") {
|
|
53
|
+
if (processName === "bluebubbles" || processName === "mail" || processName === "voice") {
|
|
54
54
|
return {
|
|
55
55
|
level: "warn",
|
|
56
56
|
sinks: ["terminal", "ndjson"],
|
|
@@ -55,6 +55,7 @@ function defaultSenses() {
|
|
|
55
55
|
teams: { ...identity_1.DEFAULT_AGENT_SENSES.teams },
|
|
56
56
|
bluebubbles: { ...identity_1.DEFAULT_AGENT_SENSES.bluebubbles },
|
|
57
57
|
mail: { ...identity_1.DEFAULT_AGENT_SENSES.mail },
|
|
58
|
+
voice: { ...identity_1.DEFAULT_AGENT_SENSES.voice },
|
|
58
59
|
};
|
|
59
60
|
}
|
|
60
61
|
function readAgentSenses(agentJsonPath) {
|
|
@@ -80,7 +81,7 @@ function readAgentSenses(agentJsonPath) {
|
|
|
80
81
|
if (!rawSenses || typeof rawSenses !== "object" || Array.isArray(rawSenses)) {
|
|
81
82
|
return defaults;
|
|
82
83
|
}
|
|
83
|
-
for (const sense of ["cli", "teams", "bluebubbles", "mail"]) {
|
|
84
|
+
for (const sense of ["cli", "teams", "bluebubbles", "mail", "voice"]) {
|
|
84
85
|
const rawSense = rawSenses[sense];
|
|
85
86
|
if (!rawSense || typeof rawSense !== "object" || Array.isArray(rawSense)) {
|
|
86
87
|
continue;
|
|
@@ -121,6 +122,7 @@ function senseFactsFromRuntimeConfig(agent, senses, runtimeConfig, machineRuntim
|
|
|
121
122
|
teams: { configured: false, detail: "not enabled in agent.json" },
|
|
122
123
|
bluebubbles: { configured: false, detail: "not enabled in agent.json" },
|
|
123
124
|
mail: { configured: false, detail: "not enabled in agent.json" },
|
|
125
|
+
voice: { configured: false, detail: "not enabled in agent.json" },
|
|
124
126
|
};
|
|
125
127
|
const payload = runtimeConfig.ok ? runtimeConfig.config : {};
|
|
126
128
|
const unavailableDetail = runtimeConfigUnavailableDetail(agent, runtimeConfig);
|
|
@@ -130,6 +132,8 @@ function senseFactsFromRuntimeConfig(agent, senses, runtimeConfig, machineRuntim
|
|
|
130
132
|
const bluebubbles = machinePayload.bluebubbles;
|
|
131
133
|
const bluebubblesChannel = machinePayload.bluebubblesChannel;
|
|
132
134
|
const mailroom = payload.mailroom;
|
|
135
|
+
const integrations = payload.integrations;
|
|
136
|
+
const voice = machinePayload.voice;
|
|
133
137
|
if (senses.teams.enabled) {
|
|
134
138
|
const missing = [];
|
|
135
139
|
if (!textField(teams, "clientId"))
|
|
@@ -189,6 +193,28 @@ function senseFactsFromRuntimeConfig(agent, senses, runtimeConfig, machineRuntim
|
|
|
189
193
|
: unavailableDetail,
|
|
190
194
|
};
|
|
191
195
|
}
|
|
196
|
+
if (senses.voice.enabled) {
|
|
197
|
+
const missing = [];
|
|
198
|
+
if (!textField(integrations, "elevenLabsApiKey"))
|
|
199
|
+
missing.push("integrations.elevenLabsApiKey");
|
|
200
|
+
if (!textField(voice, "whisperCliPath"))
|
|
201
|
+
missing.push("voice.whisperCliPath");
|
|
202
|
+
if (!textField(voice, "whisperModelPath"))
|
|
203
|
+
missing.push("voice.whisperModelPath");
|
|
204
|
+
base.voice = missing.length === 0
|
|
205
|
+
? { configured: true, detail: "local Whisper.cpp STT + ElevenLabs TTS" }
|
|
206
|
+
: {
|
|
207
|
+
configured: false,
|
|
208
|
+
optional: !machineRuntimeConfig.ok && machineRuntimeConfig.reason === "missing",
|
|
209
|
+
detail: !machineRuntimeConfig.ok && machineRuntimeConfig.reason === "missing"
|
|
210
|
+
? "not attached on this machine"
|
|
211
|
+
: runtimeConfig.ok && machineRuntimeConfig.ok
|
|
212
|
+
? `missing ${missing.join("/")}`
|
|
213
|
+
: !runtimeConfig.ok
|
|
214
|
+
? unavailableDetail
|
|
215
|
+
: runtimeConfigUnavailableDetail(agent, machineRuntimeConfig),
|
|
216
|
+
};
|
|
217
|
+
}
|
|
192
218
|
return base;
|
|
193
219
|
}
|
|
194
220
|
function senseRepairHint(agent, sense) {
|
|
@@ -198,6 +224,9 @@ function senseRepairHint(agent, sense) {
|
|
|
198
224
|
if (sense === "mail") {
|
|
199
225
|
return `Agent-runnable: provision Mailroom access with 'ouro connect mail --agent ${agent}', then restart with 'ouro up'.`;
|
|
200
226
|
}
|
|
227
|
+
if (sense === "voice") {
|
|
228
|
+
return `Agent-runnable: run 'ouro connect voice --agent ${agent}' for config guidance, save ElevenLabs and local Whisper.cpp settings, then run 'ouro up' again.`;
|
|
229
|
+
}
|
|
201
230
|
return `Run 'ouro connect bluebubbles --agent ${agent}' to attach BlueBubbles on this machine; then run 'ouro up' again.`;
|
|
202
231
|
}
|
|
203
232
|
function currentMachineId() {
|
|
@@ -208,7 +237,7 @@ function parseSenseSnapshotName(name) {
|
|
|
208
237
|
if (parts.length !== 2)
|
|
209
238
|
return null;
|
|
210
239
|
const [agent, sense] = parts;
|
|
211
|
-
if (sense !== "teams" && sense !== "bluebubbles" && sense !== "mail")
|
|
240
|
+
if (sense !== "teams" && sense !== "bluebubbles" && sense !== "mail" && sense !== "voice")
|
|
212
241
|
return null;
|
|
213
242
|
return { agent, sense };
|
|
214
243
|
}
|
|
@@ -222,12 +251,14 @@ function managedSenseEntry(sense) {
|
|
|
222
251
|
return "senses/teams-entry.js";
|
|
223
252
|
if (sense === "bluebubbles")
|
|
224
253
|
return "senses/bluebubbles/entry.js";
|
|
254
|
+
if (sense === "voice")
|
|
255
|
+
return "senses/voice-entry.js";
|
|
225
256
|
return "senses/mail-entry.js";
|
|
226
257
|
}
|
|
227
258
|
function runtimeCredentialBootstrapFor(agent, sense) {
|
|
228
259
|
const runtime = (0, runtime_credentials_1.readRuntimeCredentialConfig)(agent);
|
|
229
|
-
const machineId = sense === "bluebubbles" ? currentMachineId() : undefined;
|
|
230
|
-
const machine = sense === "bluebubbles" ? (0, runtime_credentials_1.readMachineRuntimeCredentialConfig)(agent) : null;
|
|
260
|
+
const machineId = sense === "bluebubbles" || sense === "voice" ? currentMachineId() : undefined;
|
|
261
|
+
const machine = sense === "bluebubbles" || sense === "voice" ? (0, runtime_credentials_1.readMachineRuntimeCredentialConfig)(agent) : null;
|
|
231
262
|
const providerPool = (0, provider_credentials_1.readProviderCredentialPool)(agent);
|
|
232
263
|
const providerCredentialRecords = providerPool.ok
|
|
233
264
|
? Object.values(providerPool.pool.providers).filter((record) => !!record)
|
|
@@ -394,7 +425,7 @@ class DaemonSenseManager {
|
|
|
394
425
|
return [agent, { senses, facts }];
|
|
395
426
|
}));
|
|
396
427
|
const managedSenseAgents = [...this.contexts.entries()].flatMap(([agent, context]) => {
|
|
397
|
-
return ["teams", "bluebubbles", "mail"]
|
|
428
|
+
return ["teams", "bluebubbles", "mail", "voice"]
|
|
398
429
|
.filter((sense) => context.senses[sense].enabled)
|
|
399
430
|
.map((sense) => ({
|
|
400
431
|
name: `${agent}:${sense}`,
|
|
@@ -453,7 +484,7 @@ class DaemonSenseManager {
|
|
|
453
484
|
async refreshSenseConfigAndRetry(name, parsed) {
|
|
454
485
|
try {
|
|
455
486
|
const refreshed = await (0, runtime_credentials_1.refreshRuntimeCredentialConfig)(parsed.agent, { preserveCachedOnFailure: true });
|
|
456
|
-
const machineRefreshed = parsed.sense === "bluebubbles"
|
|
487
|
+
const machineRefreshed = parsed.sense === "bluebubbles" || parsed.sense === "voice"
|
|
457
488
|
? await (0, runtime_credentials_1.refreshMachineRuntimeCredentialConfig)(parsed.agent, currentMachineId(), { preserveCachedOnFailure: true })
|
|
458
489
|
: (0, runtime_credentials_1.readMachineRuntimeCredentialConfig)(parsed.agent);
|
|
459
490
|
const context = this.contexts.get(parsed.agent);
|
|
@@ -572,6 +603,11 @@ class DaemonSenseManager {
|
|
|
572
603
|
configured: context.facts.mail.configured,
|
|
573
604
|
...(runtime.get(agent)?.mail ?? {}),
|
|
574
605
|
},
|
|
606
|
+
voice: {
|
|
607
|
+
configured: context.facts.voice.configured,
|
|
608
|
+
optional: context.facts.voice.optional,
|
|
609
|
+
...(runtime.get(agent)?.voice ?? {}),
|
|
610
|
+
},
|
|
575
611
|
};
|
|
576
612
|
const inventory = (0, sense_truth_1.getSenseInventory)({ senses: context.senses }, runtimeInfo);
|
|
577
613
|
return inventory.map((entry) => ({
|
package/dist/heart/identity.js
CHANGED
|
@@ -136,6 +136,7 @@ exports.DEFAULT_AGENT_SENSES = {
|
|
|
136
136
|
teams: { enabled: false },
|
|
137
137
|
bluebubbles: { enabled: false },
|
|
138
138
|
mail: { enabled: false },
|
|
139
|
+
voice: { enabled: false },
|
|
139
140
|
};
|
|
140
141
|
function normalizeSenses(value, configFile) {
|
|
141
142
|
const defaults = {
|
|
@@ -143,6 +144,7 @@ function normalizeSenses(value, configFile) {
|
|
|
143
144
|
teams: { ...exports.DEFAULT_AGENT_SENSES.teams },
|
|
144
145
|
bluebubbles: { ...exports.DEFAULT_AGENT_SENSES.bluebubbles },
|
|
145
146
|
mail: { ...exports.DEFAULT_AGENT_SENSES.mail },
|
|
147
|
+
voice: { ...exports.DEFAULT_AGENT_SENSES.voice },
|
|
146
148
|
};
|
|
147
149
|
if (value === undefined) {
|
|
148
150
|
return defaults;
|
|
@@ -158,7 +160,7 @@ function normalizeSenses(value, configFile) {
|
|
|
158
160
|
throw new Error(`agent.json at ${configFile} must include senses as an object when present.`);
|
|
159
161
|
}
|
|
160
162
|
const raw = value;
|
|
161
|
-
const senseNames = ["cli", "teams", "bluebubbles", "mail"];
|
|
163
|
+
const senseNames = ["cli", "teams", "bluebubbles", "mail", "voice"];
|
|
162
164
|
for (const senseName of senseNames) {
|
|
163
165
|
const rawSense = raw[senseName];
|
|
164
166
|
if (rawSense === undefined) {
|
|
@@ -201,6 +203,7 @@ function buildDefaultAgentTemplate(_agentName) {
|
|
|
201
203
|
teams: { ...exports.DEFAULT_AGENT_SENSES.teams },
|
|
202
204
|
bluebubbles: { ...exports.DEFAULT_AGENT_SENSES.bluebubbles },
|
|
203
205
|
mail: { ...exports.DEFAULT_AGENT_SENSES.mail },
|
|
206
|
+
voice: { ...exports.DEFAULT_AGENT_SENSES.voice },
|
|
204
207
|
},
|
|
205
208
|
phrases: {
|
|
206
209
|
thinking: [...exports.DEFAULT_AGENT_PHRASES.thinking],
|
|
@@ -8,6 +8,7 @@ const SENSES = [
|
|
|
8
8
|
{ sense: "teams", label: "Teams", daemonManaged: true },
|
|
9
9
|
{ sense: "bluebubbles", label: "BlueBubbles", daemonManaged: true },
|
|
10
10
|
{ sense: "mail", label: "Mail", daemonManaged: true },
|
|
11
|
+
{ sense: "voice", label: "Voice", daemonManaged: true },
|
|
11
12
|
];
|
|
12
13
|
function configuredSenses(senses) {
|
|
13
14
|
const configured = senses ?? {};
|
|
@@ -17,6 +18,7 @@ function configuredSenses(senses) {
|
|
|
17
18
|
teams: configured.teams ?? { ...identity_1.DEFAULT_AGENT_SENSES.teams },
|
|
18
19
|
bluebubbles: configured.bluebubbles ?? { ...identity_1.DEFAULT_AGENT_SENSES.bluebubbles },
|
|
19
20
|
mail: configured.mail ?? { ...identity_1.DEFAULT_AGENT_SENSES.mail },
|
|
21
|
+
voice: configured.voice ?? { ...identity_1.DEFAULT_AGENT_SENSES.voice },
|
|
20
22
|
};
|
|
21
23
|
}
|
|
22
24
|
function resolveStatus(enabled, daemonManaged, runtimeInfo) {
|
|
@@ -153,6 +153,7 @@ function readSenseStatusLines() {
|
|
|
153
153
|
teams: configuredSenses.teams ?? { enabled: false },
|
|
154
154
|
bluebubbles: configuredSenses.bluebubbles ?? { enabled: false },
|
|
155
155
|
mail: configuredSenses.mail ?? { enabled: false },
|
|
156
|
+
voice: configuredSenses.voice ?? { enabled: false },
|
|
156
157
|
};
|
|
157
158
|
const payload = (0, config_1.loadConfig)();
|
|
158
159
|
const agentName = (0, identity_1.getAgentName)();
|
|
@@ -163,12 +164,15 @@ function readSenseStatusLines() {
|
|
|
163
164
|
const teams = recordOrUndefined(runtimePayload.teams) ?? recordOrUndefined(payload.teams);
|
|
164
165
|
const bluebubbles = recordOrUndefined(machinePayload.bluebubbles) ?? recordOrUndefined(payload.bluebubbles);
|
|
165
166
|
const mailroom = recordOrUndefined(runtimePayload.mailroom) ?? recordOrUndefined(payload.mailroom);
|
|
167
|
+
const voice = recordOrUndefined(machinePayload.voice) ?? recordOrUndefined(payload.voice);
|
|
168
|
+
const integrations = recordOrUndefined(runtimePayload.integrations) ?? recordOrUndefined(payload.integrations);
|
|
166
169
|
const privateKeys = mailroom?.privateKeys;
|
|
167
170
|
const configured = {
|
|
168
171
|
cli: true,
|
|
169
172
|
teams: hasTextField(teams, "clientId") && hasTextField(teams, "clientSecret") && hasTextField(teams, "tenantId"),
|
|
170
173
|
bluebubbles: hasTextField(bluebubbles, "serverUrl") && hasTextField(bluebubbles, "password"),
|
|
171
174
|
mail: hasTextField(mailroom, "mailboxAddress") && !!privateKeys && typeof privateKeys === "object" && !Array.isArray(privateKeys),
|
|
175
|
+
voice: hasTextField(integrations, "elevenLabsApiKey") && hasTextField(voice, "whisperCliPath") && hasTextField(voice, "whisperModelPath"),
|
|
172
176
|
};
|
|
173
177
|
const rows = [
|
|
174
178
|
{ label: "CLI", status: "interactive" },
|
|
@@ -184,6 +188,10 @@ function readSenseStatusLines() {
|
|
|
184
188
|
label: "Mail",
|
|
185
189
|
status: !senses.mail.enabled ? "disabled" : configured.mail ? "ready" : "needs_config",
|
|
186
190
|
},
|
|
191
|
+
{
|
|
192
|
+
label: "Voice",
|
|
193
|
+
status: !senses.voice.enabled ? "disabled" : configured.voice ? "ready" : "needs_config",
|
|
194
|
+
},
|
|
187
195
|
];
|
|
188
196
|
return rows.map((row) => `- ${row.label}: ${row.status}`);
|
|
189
197
|
}
|