@ouro.bot/cli 0.1.0-alpha.409 → 0.1.0-alpha.410
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 +11 -4
- package/changelog.json +12 -0
- package/dist/heart/config.js +14 -5
- package/dist/heart/daemon/bluebubbles-health-diagnostics.js +1 -1
- package/dist/heart/daemon/cli-exec.js +334 -43
- package/dist/heart/daemon/cli-help.js +22 -5
- package/dist/heart/daemon/cli-parse.js +37 -4
- package/dist/heart/daemon/cli-render.js +1 -0
- package/dist/heart/daemon/doctor.js +12 -4
- package/dist/heart/daemon/process-manager.js +13 -0
- package/dist/heart/daemon/sense-manager.js +35 -11
- package/dist/heart/hatch/specialist-prompt.js +2 -2
- package/dist/heart/hatch/specialist-tools.js +6 -4
- package/dist/heart/runtime-credentials.js +96 -17
- package/dist/heart/sense-truth.js +3 -0
- package/dist/heart/turn-context.js +1 -1
- package/dist/mind/prompt.js +3 -2
- package/dist/repertoire/vault-unlock.js +30 -0
- package/dist/senses/bluebubbles/entry.js +4 -1
- package/package.json +1 -1
|
@@ -163,6 +163,13 @@ exports.COMMAND_REGISTRY = {
|
|
|
163
163
|
example: "ouro auth --agent ouroboros",
|
|
164
164
|
subcommands: ["verify", "switch"],
|
|
165
165
|
},
|
|
166
|
+
connect: {
|
|
167
|
+
category: "Auth",
|
|
168
|
+
description: "Connect integrations and local senses such as Perplexity search and BlueBubbles iMessage",
|
|
169
|
+
usage: "ouro connect [perplexity|bluebubbles] --agent <name>",
|
|
170
|
+
example: "ouro connect perplexity --agent ouroboros",
|
|
171
|
+
subcommands: ["perplexity", "bluebubbles"],
|
|
172
|
+
},
|
|
166
173
|
use: {
|
|
167
174
|
category: "Auth",
|
|
168
175
|
description: "Choose this machine's provider/model lane for an agent",
|
|
@@ -265,6 +272,16 @@ const SUBCOMMAND_HELP = {
|
|
|
265
272
|
usage: "ouro auth switch --agent <name> --provider <provider> [--facing human|agent]",
|
|
266
273
|
example: "ouro auth switch --agent ouroboros --provider minimax",
|
|
267
274
|
},
|
|
275
|
+
"connect perplexity": {
|
|
276
|
+
description: "Connect Perplexity search for this agent",
|
|
277
|
+
usage: "ouro connect perplexity --agent <name>",
|
|
278
|
+
example: "ouro connect perplexity --agent ouroboros",
|
|
279
|
+
},
|
|
280
|
+
"connect bluebubbles": {
|
|
281
|
+
description: "Attach BlueBubbles iMessage to this machine only",
|
|
282
|
+
usage: "ouro connect bluebubbles --agent <name>",
|
|
283
|
+
example: "ouro connect bluebubbles --agent ouroboros",
|
|
284
|
+
},
|
|
268
285
|
"provider refresh": {
|
|
269
286
|
description: "Reload this agent's provider credentials from its vault into daemon memory",
|
|
270
287
|
usage: "ouro provider refresh --agent <name>",
|
|
@@ -296,14 +313,14 @@ const SUBCOMMAND_HELP = {
|
|
|
296
313
|
example: "ouro vault status --agent ouroboros",
|
|
297
314
|
},
|
|
298
315
|
"vault config set": {
|
|
299
|
-
description: "Write runtime configuration into the agent credential vault",
|
|
300
|
-
usage: "ouro vault config set --agent <name> --key <path> [--value <value>]",
|
|
301
|
-
example: "ouro vault config set --agent ouroboros --key
|
|
316
|
+
description: "Write runtime configuration into the agent credential vault without printing values",
|
|
317
|
+
usage: "ouro vault config set --agent <name> --key <path> [--value <value>] [--scope agent|machine]",
|
|
318
|
+
example: "ouro vault config set --agent ouroboros --key teams.clientSecret",
|
|
302
319
|
},
|
|
303
320
|
"vault config status": {
|
|
304
321
|
description: "List runtime configuration keys stored in the agent credential vault",
|
|
305
|
-
usage: "ouro vault config status --agent <name>",
|
|
306
|
-
example: "ouro vault config status --agent ouroboros",
|
|
322
|
+
usage: "ouro vault config status --agent <name> [--scope agent|machine|all]",
|
|
323
|
+
example: "ouro vault config status --agent ouroboros --scope all",
|
|
307
324
|
},
|
|
308
325
|
};
|
|
309
326
|
// ── Levenshtein distance ──
|
|
@@ -83,6 +83,7 @@ function usage() {
|
|
|
83
83
|
" ouro config model --agent <name> <model-name>",
|
|
84
84
|
" ouro config models --agent <name>",
|
|
85
85
|
" ouro auth --agent <name> [--provider <provider>]",
|
|
86
|
+
" ouro connect [perplexity|bluebubbles] --agent <name>",
|
|
86
87
|
" ouro auth verify --agent <name> [--provider <provider>]",
|
|
87
88
|
" ouro auth switch --agent <name> --provider <provider>",
|
|
88
89
|
" ouro vault create --agent <name> --email <email> [--server <url>] [--store <store>]",
|
|
@@ -90,8 +91,8 @@ function usage() {
|
|
|
90
91
|
" ouro vault recover --agent <name> --from <json> [--from <json>] [--email <email>] [--server <url>] [--store <store>]",
|
|
91
92
|
" ouro vault unlock --agent <name> [--store auto|macos-keychain|windows-dpapi|linux-secret-service|plaintext-file]",
|
|
92
93
|
" ouro vault status --agent <name> [--store auto|macos-keychain|windows-dpapi|linux-secret-service|plaintext-file]",
|
|
93
|
-
" ouro vault config set --agent <name> --key <path> [--value <value>]",
|
|
94
|
-
" ouro vault config status --agent <name>",
|
|
94
|
+
" ouro vault config set --agent <name> --key <path> [--value <value>] [--scope agent|machine]",
|
|
95
|
+
" ouro vault config status --agent <name> [--scope agent|machine|all]",
|
|
95
96
|
" ouro chat <agent>",
|
|
96
97
|
" ouro msg --to <agent> [--session <id>] [--task <ref>] <message>",
|
|
97
98
|
" ouro poke <agent> --task <task-id>",
|
|
@@ -544,6 +545,7 @@ function parseVaultConfigCommand(args) {
|
|
|
544
545
|
const { agent, rest } = extractAgentFlag(args.slice(1));
|
|
545
546
|
let key;
|
|
546
547
|
let value;
|
|
548
|
+
let scope;
|
|
547
549
|
for (let i = 0; i < rest.length; i += 1) {
|
|
548
550
|
const token = rest[i];
|
|
549
551
|
if (token === "--key") {
|
|
@@ -556,6 +558,15 @@ function parseVaultConfigCommand(args) {
|
|
|
556
558
|
i += 1;
|
|
557
559
|
continue;
|
|
558
560
|
}
|
|
561
|
+
if (token === "--scope") {
|
|
562
|
+
const raw = rest[i + 1];
|
|
563
|
+
if (raw !== "agent" && raw !== "machine" && raw !== "all") {
|
|
564
|
+
throw new Error("vault config --scope must be agent, machine, or all");
|
|
565
|
+
}
|
|
566
|
+
scope = raw;
|
|
567
|
+
i += 1;
|
|
568
|
+
continue;
|
|
569
|
+
}
|
|
559
570
|
throw new Error("Usage: ouro vault config set --agent <name> --key <path> [--value <value>] OR ouro vault config status --agent <name>");
|
|
560
571
|
}
|
|
561
572
|
if (!agent || (sub !== "set" && sub !== "status")) {
|
|
@@ -565,12 +576,32 @@ function parseVaultConfigCommand(args) {
|
|
|
565
576
|
if (key || value) {
|
|
566
577
|
throw new Error("Usage: ouro vault config status --agent <name>");
|
|
567
578
|
}
|
|
568
|
-
return { kind: "vault.config.status", agent };
|
|
579
|
+
return { kind: "vault.config.status", agent, ...(scope ? { scope } : {}) };
|
|
569
580
|
}
|
|
581
|
+
if (scope === "all")
|
|
582
|
+
throw new Error("vault config --scope all is only valid for status");
|
|
570
583
|
if (!key) {
|
|
571
584
|
throw new Error("Usage: ouro vault config set --agent <name> --key <path> [--value <value>]");
|
|
572
585
|
}
|
|
573
|
-
return { kind: "vault.config.set", agent, key, ...(value !== undefined ? { value } : {}) };
|
|
586
|
+
return { kind: "vault.config.set", agent, key, ...(value !== undefined ? { value } : {}), ...(scope ? { scope } : {}) };
|
|
587
|
+
}
|
|
588
|
+
function normalizeConnectTarget(value) {
|
|
589
|
+
if (!value)
|
|
590
|
+
return undefined;
|
|
591
|
+
if (value === "perplexity" || value === "perplexity-search")
|
|
592
|
+
return "perplexity";
|
|
593
|
+
if (value === "bluebubbles" || value === "imessage" || value === "messages")
|
|
594
|
+
return "bluebubbles";
|
|
595
|
+
throw new Error("Usage: ouro connect [perplexity|bluebubbles] --agent <name>");
|
|
596
|
+
}
|
|
597
|
+
function parseConnectCommand(args) {
|
|
598
|
+
const { agent, rest } = extractAgentFlag(args);
|
|
599
|
+
if (!agent)
|
|
600
|
+
throw new Error("Usage: ouro connect --agent <name> [perplexity|bluebubbles]");
|
|
601
|
+
if (rest.length > 1)
|
|
602
|
+
throw new Error("Usage: ouro connect [perplexity|bluebubbles] --agent <name>");
|
|
603
|
+
const target = normalizeConnectTarget(rest[0]);
|
|
604
|
+
return { kind: "connect", agent, ...(target ? { target } : {}) };
|
|
574
605
|
}
|
|
575
606
|
function parseProviderUseCommand(args) {
|
|
576
607
|
const { agent, rest: afterAgent } = extractAgentFlag(args);
|
|
@@ -1053,6 +1084,8 @@ function parseOuroCommand(args) {
|
|
|
1053
1084
|
return parseHatchCommand(args.slice(1));
|
|
1054
1085
|
if (head === "auth")
|
|
1055
1086
|
return parseAuthCommand(args.slice(1));
|
|
1087
|
+
if (head === "connect")
|
|
1088
|
+
return parseConnectCommand(args.slice(1));
|
|
1056
1089
|
if (head === "vault")
|
|
1057
1090
|
return parseVaultCommand(args.slice(1));
|
|
1058
1091
|
if (head === "task")
|
|
@@ -19,6 +19,7 @@ const runtime_1 = require("../../nerves/runtime");
|
|
|
19
19
|
const bluebubbles_health_diagnostics_1 = require("./bluebubbles-health-diagnostics");
|
|
20
20
|
const ouro_path_installer_1 = require("../versioning/ouro-path-installer");
|
|
21
21
|
const runtime_credentials_1 = require("../runtime-credentials");
|
|
22
|
+
const machine_identity_1 = require("../machine-identity");
|
|
22
23
|
const DEFAULT_BLUEBUBBLES_REQUEST_TIMEOUT_MS = 30_000;
|
|
23
24
|
// ── Category checkers ──
|
|
24
25
|
function checkCliPath(deps) {
|
|
@@ -201,14 +202,21 @@ async function checkSenses(deps) {
|
|
|
201
202
|
});
|
|
202
203
|
}
|
|
203
204
|
if (sense === "bluebubbles" && senseObj.enabled === true) {
|
|
204
|
-
const
|
|
205
|
+
const machineId = (0, machine_identity_1.loadOrCreateMachineIdentity)({ homeDir: deps.homedir }).machineId;
|
|
206
|
+
const runtimeConfig = await (0, runtime_credentials_1.refreshMachineRuntimeCredentialConfig)(agentName, machineId, { preserveCachedOnFailure: true });
|
|
205
207
|
if (!runtimeConfig.ok) {
|
|
208
|
+
if (runtimeConfig.reason === "missing") {
|
|
209
|
+
checks.push({
|
|
210
|
+
label: `${agentDir} bluebubbles config`,
|
|
211
|
+
status: "pass",
|
|
212
|
+
detail: "not attached on this machine",
|
|
213
|
+
});
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
206
216
|
checks.push({
|
|
207
217
|
label: `${agentDir} bluebubbles config`,
|
|
208
218
|
status: "fail",
|
|
209
|
-
detail: runtimeConfig.
|
|
210
|
-
? "missing vault runtime/config"
|
|
211
|
-
: `vault runtime/config unavailable: ${runtimeConfig.error}`,
|
|
219
|
+
detail: `machine runtime config unavailable: ${runtimeConfig.error}`,
|
|
212
220
|
});
|
|
213
221
|
continue;
|
|
214
222
|
}
|
|
@@ -158,6 +158,19 @@ class DaemonProcessManager {
|
|
|
158
158
|
state.snapshot.status = "starting";
|
|
159
159
|
if (this.configCheckFn) {
|
|
160
160
|
const result = await this.configCheckFn(agent);
|
|
161
|
+
if (result.skip) {
|
|
162
|
+
state.snapshot.status = "stopped";
|
|
163
|
+
state.snapshot.errorReason = null;
|
|
164
|
+
state.snapshot.fixHint = null;
|
|
165
|
+
(0, runtime_1.emitNervesEvent)({
|
|
166
|
+
component: "daemon",
|
|
167
|
+
event: "daemon.agent_config_skipped",
|
|
168
|
+
message: result.error ?? "agent start skipped by config check",
|
|
169
|
+
meta: { agent, fix: result.fix ?? null },
|
|
170
|
+
});
|
|
171
|
+
this.notifySnapshotChange(state.snapshot);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
161
174
|
if (!result.ok) {
|
|
162
175
|
state.snapshot.status = "crashed";
|
|
163
176
|
// Surface the error and fix to the snapshot so sibling agents can
|
|
@@ -41,6 +41,7 @@ const runtime_1 = require("../../nerves/runtime");
|
|
|
41
41
|
const identity_1 = require("../identity");
|
|
42
42
|
const runtime_credentials_1 = require("../runtime-credentials");
|
|
43
43
|
const sense_truth_1 = require("../sense-truth");
|
|
44
|
+
const machine_identity_1 = require("../machine-identity");
|
|
44
45
|
const process_manager_1 = require("./process-manager");
|
|
45
46
|
const DEFAULT_TEAMS_PORT = 3978;
|
|
46
47
|
const DEFAULT_BLUEBUBBLES_PORT = 18790;
|
|
@@ -106,11 +107,12 @@ function compactRuntimeConfigError(agent, error) {
|
|
|
106
107
|
function runtimeConfigUnavailableDetail(agent, runtimeConfig) {
|
|
107
108
|
if (runtimeConfig.ok)
|
|
108
109
|
return "";
|
|
110
|
+
const itemName = /^vault:[^:]+:(.+)$/.exec(runtimeConfig.itemPath)?.[1] ?? "runtime/config";
|
|
109
111
|
if (runtimeConfig.reason === "missing")
|
|
110
|
-
return `missing vault
|
|
111
|
-
return `vault
|
|
112
|
+
return `missing vault ${itemName} (${agent})`;
|
|
113
|
+
return `vault ${itemName} unavailable (${compactRuntimeConfigError(agent, runtimeConfig.error)})`;
|
|
112
114
|
}
|
|
113
|
-
function senseFactsFromRuntimeConfig(agent, senses, runtimeConfig) {
|
|
115
|
+
function senseFactsFromRuntimeConfig(agent, senses, runtimeConfig, machineRuntimeConfig = (0, runtime_credentials_1.readMachineRuntimeCredentialConfig)(agent)) {
|
|
114
116
|
const base = {
|
|
115
117
|
cli: { configured: true, detail: "local interactive terminal" },
|
|
116
118
|
teams: { configured: false, detail: "not enabled in agent.json" },
|
|
@@ -120,8 +122,9 @@ function senseFactsFromRuntimeConfig(agent, senses, runtimeConfig) {
|
|
|
120
122
|
const unavailableDetail = runtimeConfigUnavailableDetail(agent, runtimeConfig);
|
|
121
123
|
const teams = payload.teams;
|
|
122
124
|
const teamsChannel = payload.teamsChannel;
|
|
123
|
-
const
|
|
124
|
-
const
|
|
125
|
+
const machinePayload = machineRuntimeConfig.ok ? machineRuntimeConfig.config : {};
|
|
126
|
+
const bluebubbles = machinePayload.bluebubbles;
|
|
127
|
+
const bluebubblesChannel = machinePayload.bluebubblesChannel;
|
|
125
128
|
if (senses.teams.enabled) {
|
|
126
129
|
const missing = [];
|
|
127
130
|
if (!textField(teams, "clientId"))
|
|
@@ -137,7 +140,9 @@ function senseFactsFromRuntimeConfig(agent, senses, runtimeConfig) {
|
|
|
137
140
|
}
|
|
138
141
|
: {
|
|
139
142
|
configured: false,
|
|
140
|
-
detail: runtimeConfig.ok
|
|
143
|
+
detail: runtimeConfig.ok
|
|
144
|
+
? `missing ${missing.join("/")}`
|
|
145
|
+
: unavailableDetail,
|
|
141
146
|
};
|
|
142
147
|
}
|
|
143
148
|
if (senses.bluebubbles.enabled) {
|
|
@@ -153,7 +158,12 @@ function senseFactsFromRuntimeConfig(agent, senses, runtimeConfig) {
|
|
|
153
158
|
}
|
|
154
159
|
: {
|
|
155
160
|
configured: false,
|
|
156
|
-
|
|
161
|
+
optional: !machineRuntimeConfig.ok && machineRuntimeConfig.reason === "missing",
|
|
162
|
+
detail: !machineRuntimeConfig.ok && machineRuntimeConfig.reason === "missing"
|
|
163
|
+
? "not attached on this machine"
|
|
164
|
+
: machineRuntimeConfig.ok
|
|
165
|
+
? `missing ${missing.join("/")}`
|
|
166
|
+
: runtimeConfigUnavailableDetail(agent, machineRuntimeConfig),
|
|
157
167
|
};
|
|
158
168
|
}
|
|
159
169
|
return base;
|
|
@@ -162,7 +172,10 @@ function senseRepairHint(agent, sense) {
|
|
|
162
172
|
if (sense === "teams") {
|
|
163
173
|
return `Run 'ouro vault config set --agent ${agent} --key teams.clientId', teams.clientSecret, and teams.tenantId; then run 'ouro up' again.`;
|
|
164
174
|
}
|
|
165
|
-
return `Run 'ouro
|
|
175
|
+
return `Run 'ouro connect bluebubbles --agent ${agent}' to attach BlueBubbles on this machine; then run 'ouro up' again.`;
|
|
176
|
+
}
|
|
177
|
+
function currentMachineId() {
|
|
178
|
+
return (0, machine_identity_1.loadOrCreateMachineIdentity)({ homeDir: os.homedir() }).machineId;
|
|
166
179
|
}
|
|
167
180
|
function parseSenseSnapshotName(name) {
|
|
168
181
|
const parts = name.split(":");
|
|
@@ -240,7 +253,7 @@ class DaemonSenseManager {
|
|
|
240
253
|
this.bundlesRoot = bundlesRoot;
|
|
241
254
|
this.contexts = new Map(options.agents.map((agent) => {
|
|
242
255
|
const senses = readAgentSenses(path.join(bundlesRoot, `${agent}.ouro`, "agent.json"));
|
|
243
|
-
const facts = senseFactsFromRuntimeConfig(agent, senses, (0, runtime_credentials_1.readRuntimeCredentialConfig)(agent));
|
|
256
|
+
const facts = senseFactsFromRuntimeConfig(agent, senses, (0, runtime_credentials_1.readRuntimeCredentialConfig)(agent), (0, runtime_credentials_1.readMachineRuntimeCredentialConfig)(agent));
|
|
244
257
|
return [agent, { senses, facts }];
|
|
245
258
|
}));
|
|
246
259
|
const managedSenseAgents = [...this.contexts.entries()].flatMap(([agent, context]) => {
|
|
@@ -264,10 +277,20 @@ class DaemonSenseManager {
|
|
|
264
277
|
if (!context)
|
|
265
278
|
return { ok: true };
|
|
266
279
|
const refreshed = await (0, runtime_credentials_1.refreshRuntimeCredentialConfig)(parsed.agent, { preserveCachedOnFailure: true });
|
|
267
|
-
|
|
280
|
+
const machineRefreshed = parsed.sense === "bluebubbles"
|
|
281
|
+
? await (0, runtime_credentials_1.refreshMachineRuntimeCredentialConfig)(parsed.agent, currentMachineId(), { preserveCachedOnFailure: true })
|
|
282
|
+
: (0, runtime_credentials_1.readMachineRuntimeCredentialConfig)(parsed.agent);
|
|
283
|
+
context.facts = senseFactsFromRuntimeConfig(parsed.agent, context.senses, refreshed, machineRefreshed);
|
|
268
284
|
const fact = context.facts[parsed.sense];
|
|
269
285
|
if (fact.configured)
|
|
270
286
|
return { ok: true };
|
|
287
|
+
if (fact.optional) {
|
|
288
|
+
return {
|
|
289
|
+
ok: false,
|
|
290
|
+
skip: true,
|
|
291
|
+
error: `${parsed.sense} is enabled for ${parsed.agent} but not attached on this machine`,
|
|
292
|
+
};
|
|
293
|
+
}
|
|
271
294
|
return {
|
|
272
295
|
ok: false,
|
|
273
296
|
error: `${parsed.sense} is enabled for ${parsed.agent} but runtime credentials are not ready: ${fact.detail}`,
|
|
@@ -309,7 +332,7 @@ class DaemonSenseManager {
|
|
|
309
332
|
runtime.set(parsed.agent, current);
|
|
310
333
|
}
|
|
311
334
|
const rows = [...this.contexts.entries()].flatMap(([agent, context]) => {
|
|
312
|
-
context.facts = senseFactsFromRuntimeConfig(agent, context.senses, (0, runtime_credentials_1.readRuntimeCredentialConfig)(agent));
|
|
335
|
+
context.facts = senseFactsFromRuntimeConfig(agent, context.senses, (0, runtime_credentials_1.readRuntimeCredentialConfig)(agent), (0, runtime_credentials_1.readMachineRuntimeCredentialConfig)(agent));
|
|
313
336
|
const blueBubblesRuntimeFacts = readBlueBubblesRuntimeFacts(agent, this.bundlesRoot, runtime.get(agent)?.bluebubbles);
|
|
314
337
|
const runtimeInfo = {
|
|
315
338
|
cli: { configured: true },
|
|
@@ -319,6 +342,7 @@ class DaemonSenseManager {
|
|
|
319
342
|
},
|
|
320
343
|
bluebubbles: {
|
|
321
344
|
configured: context.facts.bluebubbles.configured,
|
|
345
|
+
optional: context.facts.bluebubbles.optional,
|
|
322
346
|
...blueBubblesRuntimeFacts,
|
|
323
347
|
},
|
|
324
348
|
};
|
|
@@ -92,8 +92,8 @@ function buildSpecialistSystemPrompt(soulText, identityText, existingBundles, co
|
|
|
92
92
|
"- `read_file`: Read a file from disk. Useful for reviewing existing agent bundles or migration sources.",
|
|
93
93
|
"- `list_directory`: List directory contents. Useful for exploring existing agent bundles.",
|
|
94
94
|
"- I also have the normal local harness tools when useful here, including `shell`, `ouro task create`, `ouro reminder create`, note tools, coding tools, and repo helpers.",
|
|
95
|
-
"- `complete_adoption`: Finalize the bundle. Validates, asks the harness to collect the hatchling vault unlock secret through
|
|
96
|
-
"- The complete_adoption tool triggers
|
|
95
|
+
"- `complete_adoption`: Finalize the bundle. Validates, asks the harness to collect and confirm the hatchling vault unlock secret through hidden terminal prompts, scaffolds structural dirs, moves to ~/AgentBundles/, writes secrets, plays hatch animation. I call this with `name` (PascalCase) and `handoff_message` (warm message for the human).",
|
|
96
|
+
"- The complete_adoption tool triggers hidden terminal prompts for the hatchling vault unlock secret. I must never ask the human to type the vault unlock secret into chat, and I must never include it in tool arguments.",
|
|
97
97
|
"- `settle`: End the conversation with a final message. I call this after complete_adoption succeeds.",
|
|
98
98
|
"",
|
|
99
99
|
"I must call `settle` when I am done to end the session cleanly.",
|
|
@@ -174,14 +174,16 @@ async function execCompleteAdoption(args, deps) {
|
|
|
174
174
|
const vault = (0, identity_1.resolveVaultConfig)(name);
|
|
175
175
|
let vaultUnlockSecret;
|
|
176
176
|
try {
|
|
177
|
-
vaultUnlockSecret =
|
|
177
|
+
vaultUnlockSecret = await (0, vault_unlock_1.promptConfirmedVaultUnlockSecret)({
|
|
178
|
+
promptSecret: deps.promptSecret,
|
|
179
|
+
question: `Choose Ouro vault unlock secret for ${vault.email}: `,
|
|
180
|
+
confirmQuestion: `Confirm Ouro vault unlock secret for ${vault.email}: `,
|
|
181
|
+
emptyError: "hatchling vault creation requires an unlock secret. Re-run `ouro hatch` in an interactive terminal and enter a human-chosen unlock secret.",
|
|
182
|
+
});
|
|
178
183
|
}
|
|
179
184
|
catch (error) {
|
|
180
185
|
return `error: failed to read hatchling vault unlock secret: ${error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error)}`;
|
|
181
186
|
}
|
|
182
|
-
if (!vaultUnlockSecret) {
|
|
183
|
-
return "error: hatchling vault creation requires an unlock secret. Re-run `ouro hatch` in an interactive terminal and enter a human-chosen unlock secret.";
|
|
184
|
-
}
|
|
185
187
|
// Scaffold structural dirs into tempDir
|
|
186
188
|
scaffoldBundle(deps.tempDir);
|
|
187
189
|
// Move tempDir -> final bundle location
|
|
@@ -33,18 +33,25 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.RUNTIME_CONFIG_ITEM_NAME = void 0;
|
|
36
|
+
exports.MACHINE_RUNTIME_CONFIG_ITEM_PREFIX = exports.RUNTIME_CONFIG_ITEM_NAME = void 0;
|
|
37
|
+
exports.machineRuntimeConfigItemName = machineRuntimeConfigItemName;
|
|
37
38
|
exports.readRuntimeCredentialConfig = readRuntimeCredentialConfig;
|
|
39
|
+
exports.readMachineRuntimeCredentialConfig = readMachineRuntimeCredentialConfig;
|
|
38
40
|
exports.cacheRuntimeCredentialConfig = cacheRuntimeCredentialConfig;
|
|
41
|
+
exports.cacheMachineRuntimeCredentialConfig = cacheMachineRuntimeCredentialConfig;
|
|
39
42
|
exports.refreshRuntimeCredentialConfig = refreshRuntimeCredentialConfig;
|
|
43
|
+
exports.refreshMachineRuntimeCredentialConfig = refreshMachineRuntimeCredentialConfig;
|
|
40
44
|
exports.upsertRuntimeCredentialConfig = upsertRuntimeCredentialConfig;
|
|
45
|
+
exports.upsertMachineRuntimeCredentialConfig = upsertMachineRuntimeCredentialConfig;
|
|
41
46
|
exports.resetRuntimeCredentialConfigCache = resetRuntimeCredentialConfigCache;
|
|
42
47
|
const crypto = __importStar(require("node:crypto"));
|
|
43
48
|
const runtime_1 = require("../nerves/runtime");
|
|
44
49
|
const credential_access_1 = require("../repertoire/credential-access");
|
|
45
50
|
const identity_1 = require("./identity");
|
|
46
51
|
exports.RUNTIME_CONFIG_ITEM_NAME = "runtime/config";
|
|
52
|
+
exports.MACHINE_RUNTIME_CONFIG_ITEM_PREFIX = "runtime/machines";
|
|
47
53
|
let cachedRuntimeConfigs = new Map();
|
|
54
|
+
let cachedMachineRuntimeConfigs = new Map();
|
|
48
55
|
function isRecord(value) {
|
|
49
56
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
50
57
|
}
|
|
@@ -56,8 +63,14 @@ function stableJson(value) {
|
|
|
56
63
|
}
|
|
57
64
|
return JSON.stringify(value);
|
|
58
65
|
}
|
|
59
|
-
function runtimeConfigVaultPath(agentName) {
|
|
60
|
-
return `vault:${agentName}:${
|
|
66
|
+
function runtimeConfigVaultPath(agentName, itemName = exports.RUNTIME_CONFIG_ITEM_NAME) {
|
|
67
|
+
return `vault:${agentName}:${itemName}`;
|
|
68
|
+
}
|
|
69
|
+
function machineRuntimeConfigItemName(machineId) {
|
|
70
|
+
const normalized = machineId.trim();
|
|
71
|
+
if (!normalized)
|
|
72
|
+
throw new Error("machineId must be non-empty");
|
|
73
|
+
return `${exports.MACHINE_RUNTIME_CONFIG_ITEM_PREFIX}/${normalized}/config`;
|
|
61
74
|
}
|
|
62
75
|
function runtimeConfigRevision(payload) {
|
|
63
76
|
return `runtime_${crypto
|
|
@@ -89,21 +102,45 @@ function resultFromPayload(agentName, payload) {
|
|
|
89
102
|
updatedAt: payload.updatedAt,
|
|
90
103
|
};
|
|
91
104
|
}
|
|
92
|
-
function
|
|
105
|
+
function resultFromPayloadForItem(agentName, itemName, payload) {
|
|
106
|
+
return {
|
|
107
|
+
ok: true,
|
|
108
|
+
itemPath: runtimeConfigVaultPath(agentName, itemName),
|
|
109
|
+
config: { ...payload.config },
|
|
110
|
+
revision: runtimeConfigRevision(payload),
|
|
111
|
+
updatedAt: payload.updatedAt,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
function missingRuntimeConfig(agentName, itemName = exports.RUNTIME_CONFIG_ITEM_NAME) {
|
|
93
115
|
return {
|
|
94
116
|
ok: false,
|
|
95
117
|
reason: "missing",
|
|
96
|
-
itemPath: runtimeConfigVaultPath(agentName),
|
|
97
|
-
error: `no runtime credentials stored at ${runtimeConfigVaultPath(agentName)}`,
|
|
118
|
+
itemPath: runtimeConfigVaultPath(agentName, itemName),
|
|
119
|
+
error: `no runtime credentials stored at ${runtimeConfigVaultPath(agentName, itemName)}`,
|
|
98
120
|
};
|
|
99
121
|
}
|
|
100
122
|
function cacheResult(agentName, result) {
|
|
101
123
|
cachedRuntimeConfigs.set(agentName, result);
|
|
102
124
|
return result;
|
|
103
125
|
}
|
|
126
|
+
function cacheMachineResult(agentName, result) {
|
|
127
|
+
cachedMachineRuntimeConfigs.set(agentName, result);
|
|
128
|
+
return result;
|
|
129
|
+
}
|
|
130
|
+
function missingMachineRuntimeConfig(agentName) {
|
|
131
|
+
return {
|
|
132
|
+
ok: false,
|
|
133
|
+
reason: "missing",
|
|
134
|
+
itemPath: `vault:${agentName}:${exports.MACHINE_RUNTIME_CONFIG_ITEM_PREFIX}/<this-machine>/config`,
|
|
135
|
+
error: `no machine runtime credentials loaded for ${agentName}`,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
104
138
|
function readRuntimeCredentialConfig(agentName = (0, identity_1.getAgentName)()) {
|
|
105
139
|
return cachedRuntimeConfigs.get(agentName) ?? missingRuntimeConfig(agentName);
|
|
106
140
|
}
|
|
141
|
+
function readMachineRuntimeCredentialConfig(agentName = (0, identity_1.getAgentName)()) {
|
|
142
|
+
return cachedMachineRuntimeConfigs.get(agentName) ?? missingMachineRuntimeConfig(agentName);
|
|
143
|
+
}
|
|
107
144
|
function cacheRuntimeCredentialConfig(agentName, config, now = new Date()) {
|
|
108
145
|
const payload = {
|
|
109
146
|
schemaVersion: 1,
|
|
@@ -113,24 +150,35 @@ function cacheRuntimeCredentialConfig(agentName, config, now = new Date()) {
|
|
|
113
150
|
};
|
|
114
151
|
return cacheResult(agentName, resultFromPayload(agentName, payload));
|
|
115
152
|
}
|
|
116
|
-
|
|
153
|
+
function cacheMachineRuntimeCredentialConfig(agentName, config, now = new Date(), machineId = "<this-machine>") {
|
|
154
|
+
const payload = {
|
|
155
|
+
schemaVersion: 1,
|
|
156
|
+
kind: "runtime-config",
|
|
157
|
+
updatedAt: now.toISOString(),
|
|
158
|
+
config: { ...config },
|
|
159
|
+
};
|
|
160
|
+
return cacheMachineResult(agentName, resultFromPayloadForItem(agentName, machineRuntimeConfigItemName(machineId), payload));
|
|
161
|
+
}
|
|
162
|
+
async function refreshRuntimeCredentialConfigItem(agentName, itemName, cache, options = {}) {
|
|
117
163
|
try {
|
|
118
164
|
const store = (0, credential_access_1.getCredentialStore)(agentName);
|
|
119
|
-
const raw = await store.getRawSecret(
|
|
165
|
+
const raw = await store.getRawSecret(itemName, "password");
|
|
120
166
|
const payload = validateRuntimeCredentialPayload(JSON.parse(raw));
|
|
121
|
-
const result =
|
|
167
|
+
const result = resultFromPayloadForItem(agentName, itemName, payload);
|
|
122
168
|
(0, runtime_1.emitNervesEvent)({
|
|
123
169
|
component: "config/identity",
|
|
124
170
|
event: "config.runtime_credentials_loaded",
|
|
125
171
|
message: "loaded runtime credentials from vault",
|
|
126
172
|
meta: { agentName, itemPath: result.itemPath, revision: result.revision },
|
|
127
173
|
});
|
|
128
|
-
return
|
|
174
|
+
return cache(agentName, result);
|
|
129
175
|
}
|
|
130
176
|
catch (error) {
|
|
131
177
|
const message = error instanceof Error ? error.message : String(error);
|
|
132
|
-
const
|
|
133
|
-
|
|
178
|
+
const existing = cache === cacheMachineResult
|
|
179
|
+
? cachedMachineRuntimeConfigs.get(agentName)
|
|
180
|
+
: cachedRuntimeConfigs.get(agentName);
|
|
181
|
+
const reason = message.includes(`no credential found for domain "${itemName}"`)
|
|
134
182
|
? "missing"
|
|
135
183
|
: message.includes("runtime credential payload")
|
|
136
184
|
? "invalid"
|
|
@@ -138,8 +186,8 @@ async function refreshRuntimeCredentialConfig(agentName, options = {}) {
|
|
|
138
186
|
const result = {
|
|
139
187
|
ok: false,
|
|
140
188
|
reason,
|
|
141
|
-
itemPath: runtimeConfigVaultPath(agentName),
|
|
142
|
-
error: reason === "missing" ? `no runtime credentials stored at ${runtimeConfigVaultPath(agentName)}` : message,
|
|
189
|
+
itemPath: runtimeConfigVaultPath(agentName, itemName),
|
|
190
|
+
error: reason === "missing" ? `no runtime credentials stored at ${runtimeConfigVaultPath(agentName, itemName)}` : message,
|
|
143
191
|
};
|
|
144
192
|
(0, runtime_1.emitNervesEvent)({
|
|
145
193
|
level: reason === "missing" ? "warn" : "error",
|
|
@@ -148,11 +196,17 @@ async function refreshRuntimeCredentialConfig(agentName, options = {}) {
|
|
|
148
196
|
message: "runtime credentials unavailable",
|
|
149
197
|
meta: { agentName, reason, itemPath: result.itemPath },
|
|
150
198
|
});
|
|
151
|
-
if (options.preserveCachedOnFailure &&
|
|
152
|
-
return
|
|
153
|
-
return
|
|
199
|
+
if (options.preserveCachedOnFailure && existing?.ok)
|
|
200
|
+
return existing;
|
|
201
|
+
return cache(agentName, result);
|
|
154
202
|
}
|
|
155
203
|
}
|
|
204
|
+
async function refreshRuntimeCredentialConfig(agentName, options = {}) {
|
|
205
|
+
return refreshRuntimeCredentialConfigItem(agentName, exports.RUNTIME_CONFIG_ITEM_NAME, cacheResult, options);
|
|
206
|
+
}
|
|
207
|
+
async function refreshMachineRuntimeCredentialConfig(agentName, machineId, options = {}) {
|
|
208
|
+
return refreshRuntimeCredentialConfigItem(agentName, machineRuntimeConfigItemName(machineId), cacheMachineResult, options);
|
|
209
|
+
}
|
|
156
210
|
async function upsertRuntimeCredentialConfig(agentName, config, now = new Date()) {
|
|
157
211
|
const payload = {
|
|
158
212
|
schemaVersion: 1,
|
|
@@ -176,6 +230,31 @@ async function upsertRuntimeCredentialConfig(agentName, config, now = new Date()
|
|
|
176
230
|
cacheResult(agentName, result);
|
|
177
231
|
return result;
|
|
178
232
|
}
|
|
233
|
+
async function upsertMachineRuntimeCredentialConfig(agentName, machineId, config, now = new Date()) {
|
|
234
|
+
const payload = {
|
|
235
|
+
schemaVersion: 1,
|
|
236
|
+
kind: "runtime-config",
|
|
237
|
+
updatedAt: now.toISOString(),
|
|
238
|
+
config: { ...config },
|
|
239
|
+
};
|
|
240
|
+
const itemName = machineRuntimeConfigItemName(machineId);
|
|
241
|
+
const store = (0, credential_access_1.getCredentialStore)(agentName);
|
|
242
|
+
await store.store(itemName, {
|
|
243
|
+
username: itemName,
|
|
244
|
+
password: JSON.stringify(payload),
|
|
245
|
+
notes: "Ouro machine-local runtime credentials for senses attached to one machine. Portable runtime credentials live in runtime/config.",
|
|
246
|
+
});
|
|
247
|
+
const result = resultFromPayloadForItem(agentName, itemName, payload);
|
|
248
|
+
(0, runtime_1.emitNervesEvent)({
|
|
249
|
+
component: "config/identity",
|
|
250
|
+
event: "config.runtime_credentials_upserted",
|
|
251
|
+
message: "upserted machine runtime credential config in vault",
|
|
252
|
+
meta: { agentName, itemPath: result.itemPath, revision: result.revision },
|
|
253
|
+
});
|
|
254
|
+
cacheMachineResult(agentName, result);
|
|
255
|
+
return result;
|
|
256
|
+
}
|
|
179
257
|
function resetRuntimeCredentialConfigCache() {
|
|
180
258
|
cachedRuntimeConfigs = new Map();
|
|
259
|
+
cachedMachineRuntimeConfigs = new Map();
|
|
181
260
|
}
|
|
@@ -28,6 +28,9 @@ function resolveStatus(enabled, daemonManaged, runtimeInfo) {
|
|
|
28
28
|
if (runtimeInfo?.runtime === "running") {
|
|
29
29
|
return "running";
|
|
30
30
|
}
|
|
31
|
+
if (runtimeInfo?.configured === false && runtimeInfo.optional) {
|
|
32
|
+
return "not_attached";
|
|
33
|
+
}
|
|
31
34
|
if (runtimeInfo?.configured === false) {
|
|
32
35
|
return "needs_config";
|
|
33
36
|
}
|
|
@@ -162,7 +162,7 @@ function readSenseStatusLines() {
|
|
|
162
162
|
},
|
|
163
163
|
{
|
|
164
164
|
label: "BlueBubbles",
|
|
165
|
-
status: !senses.bluebubbles.enabled ? "disabled" : configured.bluebubbles ? "ready" : "
|
|
165
|
+
status: !senses.bluebubbles.enabled ? "disabled" : configured.bluebubbles ? "ready" : "not_attached",
|
|
166
166
|
},
|
|
167
167
|
];
|
|
168
168
|
return rows.map((row) => `- ${row.label}: ${row.status}`);
|
package/dist/mind/prompt.js
CHANGED
|
@@ -433,7 +433,7 @@ function localSenseStatusLines() {
|
|
|
433
433
|
},
|
|
434
434
|
{
|
|
435
435
|
label: "BlueBubbles",
|
|
436
|
-
status: !senses.bluebubbles.enabled ? "disabled" : configured.bluebubbles ? "ready" : "
|
|
436
|
+
status: !senses.bluebubbles.enabled ? "disabled" : configured.bluebubbles ? "ready" : "not_attached",
|
|
437
437
|
},
|
|
438
438
|
];
|
|
439
439
|
_senseStatusLinesCache = rows.map((row) => `- ${row.label}: ${row.status}`);
|
|
@@ -446,12 +446,13 @@ function senseRuntimeGuidance(channel, preReadStatusLines) {
|
|
|
446
446
|
lines.push("- interactive = available when opened by the user instead of kept running by the daemon");
|
|
447
447
|
lines.push("- disabled = turned off in agent.json");
|
|
448
448
|
lines.push("- needs_config = enabled but missing required vault runtime/config values");
|
|
449
|
+
lines.push("- not_attached = enabled globally but no local-machine attachment is configured here");
|
|
449
450
|
lines.push("- ready = enabled and configured; `ouro up` should bring it online");
|
|
450
451
|
lines.push("- running = enabled and currently active");
|
|
451
452
|
lines.push("- error = enabled but unhealthy");
|
|
452
453
|
lines.push("If asked how to enable another sense, I explain the relevant agent.json senses entry and required agent-vault runtime/config fields instead of guessing.");
|
|
453
454
|
lines.push("teams setup truth: enable `senses.teams.enabled`, then store `teams.clientId`, `teams.clientSecret`, and `teams.tenantId` in the agent vault runtime/config item.");
|
|
454
|
-
lines.push("bluebubbles setup truth:
|
|
455
|
+
lines.push("bluebubbles setup truth: run `ouro connect bluebubbles --agent <agent>`; it stores this machine's BlueBubbles URL/password/listener config in the agent vault machine runtime item.");
|
|
455
456
|
if (channel === "cli") {
|
|
456
457
|
lines.push("cli is interactive: it is available when the user opens it, not something `ouro up` daemonizes.");
|
|
457
458
|
}
|
|
@@ -37,6 +37,7 @@ exports.vaultUnlockReplaceRecoverFix = vaultUnlockReplaceRecoverFix;
|
|
|
37
37
|
exports.credentialVaultNotConfiguredError = credentialVaultNotConfiguredError;
|
|
38
38
|
exports.isCredentialVaultNotConfiguredError = isCredentialVaultNotConfiguredError;
|
|
39
39
|
exports.vaultCreateRecoverFix = vaultCreateRecoverFix;
|
|
40
|
+
exports.promptConfirmedVaultUnlockSecret = promptConfirmedVaultUnlockSecret;
|
|
40
41
|
exports.resolveVaultUnlockStore = resolveVaultUnlockStore;
|
|
41
42
|
exports.readVaultUnlockSecret = readVaultUnlockSecret;
|
|
42
43
|
exports.storeVaultUnlockSecret = storeVaultUnlockSecret;
|
|
@@ -122,6 +123,35 @@ function vaultCreateRecoverFix(agentName, nextStep = "Then run 'ouro up' again."
|
|
|
122
123
|
nextStep,
|
|
123
124
|
].join(" ");
|
|
124
125
|
}
|
|
126
|
+
function vaultUnlockSecretStrengthIssues(secret) {
|
|
127
|
+
const issues = [];
|
|
128
|
+
if (secret.length < 8)
|
|
129
|
+
issues.push("at least 8 characters");
|
|
130
|
+
if (!/[a-z]/.test(secret))
|
|
131
|
+
issues.push("a lowercase letter");
|
|
132
|
+
if (!/[A-Z]/.test(secret))
|
|
133
|
+
issues.push("an uppercase letter");
|
|
134
|
+
if (!/[0-9]/.test(secret))
|
|
135
|
+
issues.push("a number");
|
|
136
|
+
if (!/[^A-Za-z0-9]/.test(secret))
|
|
137
|
+
issues.push("a special character");
|
|
138
|
+
return issues;
|
|
139
|
+
}
|
|
140
|
+
async function promptConfirmedVaultUnlockSecret(input) {
|
|
141
|
+
const secret = (await input.promptSecret(input.question)).trim();
|
|
142
|
+
if (!secret) {
|
|
143
|
+
throw new Error(input.emptyError);
|
|
144
|
+
}
|
|
145
|
+
const issues = vaultUnlockSecretStrengthIssues(secret);
|
|
146
|
+
if (issues.length > 0) {
|
|
147
|
+
throw new Error(`vault unlock secret is too weak: add ${issues.join(", ")}. Use at least 8 characters with uppercase and lowercase letters, one number, and one special character.`);
|
|
148
|
+
}
|
|
149
|
+
const confirmation = (await input.promptSecret(input.confirmQuestion)).trim();
|
|
150
|
+
if (secret !== confirmation) {
|
|
151
|
+
throw new Error("vault unlock secrets did not match. Re-run the command and enter the same secret twice.");
|
|
152
|
+
}
|
|
153
|
+
return secret;
|
|
154
|
+
}
|
|
125
155
|
function lostUnlockSecretGuidance(config) {
|
|
126
156
|
if (!config.agentName) {
|
|
127
157
|
return "If nobody saved that unlock secret, run `ouro vault replace --agent <agent>` to create a new empty vault and re-enter credentials. If you do have a local JSON credential export, run `ouro vault recover --agent <agent> --from <json>` to import it.";
|