@ouro.bot/cli 0.1.0-alpha.430 → 0.1.0-alpha.432
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/changelog.json +201 -184
- package/dist/heart/daemon/cli-defaults.js +26 -4
- package/dist/heart/daemon/cli-exec.js +231 -35
- package/dist/heart/daemon/daemon-runtime-sync.js +8 -8
- package/dist/heart/daemon/startup-tui.js +20 -7
- package/dist/heart/daemon/up-progress.js +15 -3
- package/dist/heart/runtime-capability-check.js +170 -0
- package/dist/heart/versioning/update-checker.js +2 -0
- package/package.json +4 -1
|
@@ -53,6 +53,7 @@ const runtime_1 = require("../../nerves/runtime");
|
|
|
53
53
|
const ouro_path_installer_1 = require("../versioning/ouro-path-installer");
|
|
54
54
|
const ouro_uti_1 = require("../versioning/ouro-uti");
|
|
55
55
|
const ouro_version_manager_1 = require("../versioning/ouro-version-manager");
|
|
56
|
+
const update_checker_1 = require("../versioning/update-checker");
|
|
56
57
|
const skill_management_installer_1 = require("./skill-management-installer");
|
|
57
58
|
const hatch_flow_1 = require("../hatch/hatch-flow");
|
|
58
59
|
const specialist_orchestrator_1 = require("../hatch/specialist-orchestrator");
|
|
@@ -144,6 +145,29 @@ function defaultReadRecentDaemonLogLines(lines = 10) {
|
|
|
144
145
|
}
|
|
145
146
|
return recentLines.slice(-lines).map((line) => (0, log_tailer_1.formatLogLine)(line));
|
|
146
147
|
}
|
|
148
|
+
/* v8 ignore start -- CLI npm registry fetch wrapper: integration code @preserve */
|
|
149
|
+
async function defaultFetchCliRegistryJson(timeoutMs) {
|
|
150
|
+
const controller = new AbortController();
|
|
151
|
+
const timeoutId = setTimeout(() => {
|
|
152
|
+
controller.abort();
|
|
153
|
+
}, timeoutMs);
|
|
154
|
+
try {
|
|
155
|
+
const res = await fetch("https://registry.npmjs.org/@ouro.bot/cli", {
|
|
156
|
+
signal: controller.signal,
|
|
157
|
+
});
|
|
158
|
+
return res.json();
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
162
|
+
throw new Error(`update check timed out after ${Math.max(1, Math.round(timeoutMs / 1000))}s`);
|
|
163
|
+
}
|
|
164
|
+
throw error;
|
|
165
|
+
}
|
|
166
|
+
finally {
|
|
167
|
+
clearTimeout(timeoutId);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/* v8 ignore stop */
|
|
147
171
|
function defaultSleep(ms) {
|
|
148
172
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
149
173
|
}
|
|
@@ -501,6 +525,7 @@ function createDefaultOuroCliDeps(socketPath = socket_client_1.DEFAULT_DAEMON_SO
|
|
|
501
525
|
readRecentDaemonLogLines: defaultReadRecentDaemonLogLines,
|
|
502
526
|
sleep: defaultSleep,
|
|
503
527
|
now: () => Date.now(),
|
|
528
|
+
updateCheckTimeoutMs: update_checker_1.CLI_UPDATE_CHECK_TIMEOUT_MS,
|
|
504
529
|
startupPollIntervalMs: 250,
|
|
505
530
|
startupStabilityWindowMs: 1_500,
|
|
506
531
|
startupTimeoutMs: 10_000,
|
|
@@ -540,10 +565,7 @@ function createDefaultOuroCliDeps(socketPath = socket_client_1.DEFAULT_DAEMON_SO
|
|
|
540
565
|
checkForCliUpdate: async () => {
|
|
541
566
|
const { checkForUpdate } = await Promise.resolve().then(() => __importStar(require("../versioning/update-checker")));
|
|
542
567
|
return checkForUpdate((0, bundle_manifest_1.getPackageVersion)(), {
|
|
543
|
-
fetchRegistryJson:
|
|
544
|
-
const res = await fetch("https://registry.npmjs.org/@ouro.bot/cli");
|
|
545
|
-
return res.json();
|
|
546
|
-
},
|
|
568
|
+
fetchRegistryJson: () => defaultFetchCliRegistryJson(update_checker_1.CLI_UPDATE_CHECK_TIMEOUT_MS),
|
|
547
569
|
distTag: "latest",
|
|
548
570
|
});
|
|
549
571
|
},
|
|
@@ -76,6 +76,7 @@ const provider_state_1 = require("../provider-state");
|
|
|
76
76
|
const machine_identity_1 = require("../machine-identity");
|
|
77
77
|
const provider_models_1 = require("../provider-models");
|
|
78
78
|
const ouro_version_manager_1 = require("../versioning/ouro-version-manager");
|
|
79
|
+
const update_checker_1 = require("../versioning/update-checker");
|
|
79
80
|
const sync_1 = require("../sync");
|
|
80
81
|
const cli_parse_1 = require("./cli-parse");
|
|
81
82
|
const cli_parse_2 = require("./cli-parse");
|
|
@@ -97,12 +98,62 @@ const up_progress_1 = require("./up-progress");
|
|
|
97
98
|
const provider_ping_1 = require("../provider-ping");
|
|
98
99
|
const agent_discovery_1 = require("./agent-discovery");
|
|
99
100
|
const connect_bay_1 = require("./connect-bay");
|
|
101
|
+
const runtime_capability_check_1 = require("../runtime-capability-check");
|
|
100
102
|
// ── ensureDaemonRunning ──
|
|
101
103
|
const DEFAULT_DAEMON_STARTUP_TIMEOUT_MS = 10_000;
|
|
102
104
|
const DEFAULT_DAEMON_STARTUP_POLL_INTERVAL_MS = 500;
|
|
103
105
|
const DEFAULT_DAEMON_STARTUP_STABILITY_WINDOW_MS = 1_500;
|
|
104
106
|
const DEFAULT_DAEMON_STARTUP_RETRY_LIMIT = 1;
|
|
105
107
|
const DEFAULT_DAEMON_STARTUP_LOG_LINES = 10;
|
|
108
|
+
function summarizeCliUpdateCheckStatus(error, timedOut = false) {
|
|
109
|
+
const normalized = error.trim().toLowerCase();
|
|
110
|
+
if (timedOut || normalized.includes("timed out") || normalized.includes("abort")) {
|
|
111
|
+
return "skipped; registry did not answer";
|
|
112
|
+
}
|
|
113
|
+
if (normalized.includes("registry unavailable")) {
|
|
114
|
+
return "skipped; registry unavailable";
|
|
115
|
+
}
|
|
116
|
+
if (normalized.includes("network") ||
|
|
117
|
+
normalized.includes("fetch failed") ||
|
|
118
|
+
normalized.includes("enotfound") ||
|
|
119
|
+
normalized.includes("eai_again") ||
|
|
120
|
+
normalized.includes("econnreset")) {
|
|
121
|
+
return "skipped; registry unavailable";
|
|
122
|
+
}
|
|
123
|
+
return "skipped; update check unavailable";
|
|
124
|
+
}
|
|
125
|
+
async function runCliUpdateCheckWithTimeout(checkForCliUpdate, timeoutMs = update_checker_1.CLI_UPDATE_CHECK_TIMEOUT_MS) {
|
|
126
|
+
return await new Promise((resolve, reject) => {
|
|
127
|
+
let settled = false;
|
|
128
|
+
const timeoutId = setTimeout(() => {
|
|
129
|
+
settled = true;
|
|
130
|
+
resolve({
|
|
131
|
+
timedOut: true,
|
|
132
|
+
result: {
|
|
133
|
+
available: false,
|
|
134
|
+
error: `update check timed out after ${Math.max(1, Math.round(timeoutMs / 1000))}s`,
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
}, timeoutMs);
|
|
138
|
+
/* v8 ignore start -- Promise-settlement wiring is exercised by command tests; v8 misses these callback lines @preserve */
|
|
139
|
+
void checkForCliUpdate()
|
|
140
|
+
.then((result) => {
|
|
141
|
+
if (settled)
|
|
142
|
+
return;
|
|
143
|
+
settled = true;
|
|
144
|
+
clearTimeout(timeoutId);
|
|
145
|
+
resolve({ result, timedOut: false });
|
|
146
|
+
})
|
|
147
|
+
.catch((error) => {
|
|
148
|
+
if (settled)
|
|
149
|
+
return;
|
|
150
|
+
settled = true;
|
|
151
|
+
clearTimeout(timeoutId);
|
|
152
|
+
reject(error);
|
|
153
|
+
});
|
|
154
|
+
/* v8 ignore stop */
|
|
155
|
+
});
|
|
156
|
+
}
|
|
106
157
|
async function checkAgentProviders(deps, agentsOverride, onProgress) {
|
|
107
158
|
const agents = agentsOverride ?? await listCliAgents(deps);
|
|
108
159
|
const bundlesRoot = deps.bundlesRoot ?? (0, identity_1.getAgentBundlesRoot)();
|
|
@@ -386,6 +437,17 @@ function renderCommandBoard(deps, options) {
|
|
|
386
437
|
sections: options.sections,
|
|
387
438
|
}).trimEnd();
|
|
388
439
|
}
|
|
440
|
+
function writeConnectorIntro(deps, options) {
|
|
441
|
+
const text = ttyBoardEnabled(deps)
|
|
442
|
+
? renderCommandBoard(deps, {
|
|
443
|
+
title: options.title,
|
|
444
|
+
subtitle: options.subtitle,
|
|
445
|
+
summary: options.summary,
|
|
446
|
+
sections: options.sections,
|
|
447
|
+
})
|
|
448
|
+
: options.fallbackLines.join("\n");
|
|
449
|
+
deps.writeStdout(text);
|
|
450
|
+
}
|
|
389
451
|
async function promptForNamedAgent(title, subtitle, agents, deps) {
|
|
390
452
|
if (!deps.promptInput)
|
|
391
453
|
throw new Error("agent selection requires interactive input");
|
|
@@ -566,8 +628,8 @@ async function ensureDaemonRunning(deps, options = {}) {
|
|
|
566
628
|
};
|
|
567
629
|
let lastPid = null;
|
|
568
630
|
for (let attempt = 0; attempt <= retryLimit; attempt += 1) {
|
|
569
|
-
deps.reportDaemonStartupPhase?.("
|
|
570
|
-
deps.reportDaemonStartupPhase?.("waiting for
|
|
631
|
+
deps.reportDaemonStartupPhase?.("starting a fresh background service");
|
|
632
|
+
deps.reportDaemonStartupPhase?.("waiting for the new background service to answer");
|
|
571
633
|
deps.cleanupStaleSocket(deps.socketPath);
|
|
572
634
|
const bootStartedAtMs = (deps.now ?? Date.now)();
|
|
573
635
|
const started = await deps.startDaemonProcess(deps.socketPath);
|
|
@@ -605,7 +667,7 @@ async function ensureDaemonRunning(deps, options = {}) {
|
|
|
605
667
|
if (!startupFailure.retryable || attempt >= retryLimit) {
|
|
606
668
|
break;
|
|
607
669
|
}
|
|
608
|
-
deps.reportDaemonStartupPhase?.("
|
|
670
|
+
deps.reportDaemonStartupPhase?.("background service startup went sideways once; trying one more time");
|
|
609
671
|
}
|
|
610
672
|
return {
|
|
611
673
|
alreadyRunning: false,
|
|
@@ -1427,14 +1489,17 @@ const DEFAULT_DAEMON_STATUS_TIMEOUT_MS = 4_000;
|
|
|
1427
1489
|
const DEFAULT_AGENT_RESTART_TIMEOUT_MS = 8_000;
|
|
1428
1490
|
const CONNECT_PROVIDER_CHOICES = ["openai-codex", "anthropic", "minimax", "azure", "github-copilot"];
|
|
1429
1491
|
function hasRuntimeConfigValue(config, key) {
|
|
1492
|
+
return readRuntimeConfigString(config, key) !== null;
|
|
1493
|
+
}
|
|
1494
|
+
function readRuntimeConfigString(config, key) {
|
|
1430
1495
|
const segments = key.split(".");
|
|
1431
1496
|
let cursor = config;
|
|
1432
1497
|
for (const segment of segments) {
|
|
1433
1498
|
if (!cursor || typeof cursor !== "object" || Array.isArray(cursor))
|
|
1434
|
-
return
|
|
1499
|
+
return null;
|
|
1435
1500
|
cursor = cursor[segment];
|
|
1436
1501
|
}
|
|
1437
|
-
return typeof cursor === "string" && cursor.trim().length > 0;
|
|
1502
|
+
return typeof cursor === "string" && cursor.trim().length > 0 ? cursor : null;
|
|
1438
1503
|
}
|
|
1439
1504
|
function runtimeConfigReadStatus(runtime) {
|
|
1440
1505
|
if (runtime.reason === "missing")
|
|
@@ -1514,46 +1579,47 @@ function readRuntimeApplyWorker(payload, agent) {
|
|
|
1514
1579
|
}
|
|
1515
1580
|
async function applyRuntimeChangeToRunningAgent(agent, deps, onProgress) {
|
|
1516
1581
|
try {
|
|
1517
|
-
onProgress?.("checking
|
|
1582
|
+
onProgress?.("checking whether Ouro is already running");
|
|
1518
1583
|
const alive = await deps.checkSocketAlive(deps.socketPath);
|
|
1519
1584
|
if (!alive)
|
|
1520
1585
|
return "daemon is not running; next `ouro up` will load the change";
|
|
1521
|
-
onProgress?.(
|
|
1586
|
+
onProgress?.(`asking Ouro to reload ${agent}`);
|
|
1522
1587
|
const response = await withCliTimeout(DEFAULT_AGENT_RESTART_TIMEOUT_MS, "daemon restart request timed out", () => deps.sendCommand(deps.socketPath, { kind: "agent.restart", agent }));
|
|
1523
1588
|
if (!response.ok)
|
|
1524
1589
|
return `daemon restart skipped: ${response.error ?? response.message ?? "unknown daemon error"}`;
|
|
1525
1590
|
const deadline = cliNowMs(deps) + DEFAULT_RUNTIME_APPLY_TIMEOUT_MS;
|
|
1526
|
-
onProgress?.(`waiting for ${agent} to
|
|
1591
|
+
onProgress?.(`waiting for ${agent} to come back\n- reload request accepted`);
|
|
1527
1592
|
while (cliNowMs(deps) < deadline) {
|
|
1528
1593
|
try {
|
|
1529
1594
|
const statusResponse = await withCliTimeout(DEFAULT_DAEMON_STATUS_TIMEOUT_MS, "daemon status timed out", () => deps.sendCommand(deps.socketPath, { kind: "daemon.status" }));
|
|
1530
1595
|
if (statusResponse.ok) {
|
|
1531
1596
|
const payload = (0, cli_render_1.parseStatusPayload)(statusResponse.data);
|
|
1532
1597
|
if (!payload) {
|
|
1533
|
-
onProgress?.("daemon status did not include structured worker state");
|
|
1598
|
+
onProgress?.("waiting for Ouro to confirm the reload\n- daemon status did not include structured worker state");
|
|
1534
1599
|
return "restart requested; daemon status is unavailable, so verify with `ouro status` if needed";
|
|
1535
1600
|
}
|
|
1536
1601
|
const worker = readRuntimeApplyWorker(payload, agent);
|
|
1537
1602
|
if (!worker) {
|
|
1538
|
-
onProgress?.(`
|
|
1603
|
+
onProgress?.(`waiting for ${agent} to come back\n- ${agent} is not listed by daemon yet`);
|
|
1539
1604
|
}
|
|
1540
1605
|
else if (worker.status === "running") {
|
|
1541
|
-
onProgress?.(`daemon reports ${agent}/${worker.worker} running`);
|
|
1606
|
+
onProgress?.(`waiting for ${agent} to come back\n- daemon reports ${agent}/${worker.worker} running`);
|
|
1542
1607
|
return `restarted ${agent} and the daemon reports it running`;
|
|
1543
1608
|
}
|
|
1544
1609
|
else if (worker.status === "crashed") {
|
|
1610
|
+
onProgress?.(`waiting for ${agent} to come back\n- daemon reports ${agent}/${worker.worker} crashed`);
|
|
1545
1611
|
return `restart requested, but ${agent}/${worker.worker} crashed before reporting running${worker.errorReason ? `: ${worker.errorReason}` : worker.fixHint ? `: ${worker.fixHint}` : ""}`;
|
|
1546
1612
|
}
|
|
1547
1613
|
else {
|
|
1548
|
-
onProgress?.(`
|
|
1614
|
+
onProgress?.(`waiting for ${agent} to come back\n- current worker state: ${worker.status}`);
|
|
1549
1615
|
}
|
|
1550
1616
|
}
|
|
1551
1617
|
else {
|
|
1552
|
-
onProgress?.(`
|
|
1618
|
+
onProgress?.(`waiting for ${agent} to come back\n- latest status check: ${statusResponse.error ?? statusResponse.message ?? "unknown error"}`);
|
|
1553
1619
|
}
|
|
1554
1620
|
}
|
|
1555
1621
|
catch (error) {
|
|
1556
|
-
onProgress?.(`
|
|
1622
|
+
onProgress?.(`waiting for ${agent} to come back\n- latest status check: ${error instanceof Error ? error.message : String(error)}`);
|
|
1557
1623
|
}
|
|
1558
1624
|
if (cliNowMs(deps) >= deadline - DEFAULT_RUNTIME_APPLY_POLL_INTERVAL_MS)
|
|
1559
1625
|
break;
|
|
@@ -1730,12 +1796,44 @@ async function buildConnectMenu(agent, deps, onProgress) {
|
|
|
1730
1796
|
onProgress?.("loading this machine's settings");
|
|
1731
1797
|
const machineRuntime = await (0, runtime_credentials_1.refreshMachineRuntimeCredentialConfig)(agent, currentMachineId(deps), { preserveCachedOnFailure: true });
|
|
1732
1798
|
const { teamsEnabled, blueBubblesEnabled } = readConnectBaySenseFlags(agent, deps);
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1799
|
+
let perplexityStatus;
|
|
1800
|
+
let perplexityDetailLines;
|
|
1801
|
+
const perplexityApiKey = runtimeConfig.ok
|
|
1802
|
+
? readRuntimeConfigString(runtimeConfig.config, "integrations.perplexityApiKey")
|
|
1803
|
+
: null;
|
|
1804
|
+
if (!runtimeConfig.ok) {
|
|
1805
|
+
perplexityStatus = runtimeConfigReadStatus(runtimeConfig);
|
|
1806
|
+
perplexityDetailLines = [];
|
|
1807
|
+
}
|
|
1808
|
+
else if (!perplexityApiKey) {
|
|
1809
|
+
perplexityStatus = "missing";
|
|
1810
|
+
perplexityDetailLines = ["no API key saved yet"];
|
|
1811
|
+
}
|
|
1812
|
+
else {
|
|
1813
|
+
onProgress?.("verifying Perplexity search");
|
|
1814
|
+
const verification = await (0, runtime_capability_check_1.verifyPerplexityCapability)(perplexityApiKey);
|
|
1815
|
+
perplexityStatus = verification.ok ? "ready" : "needs attention";
|
|
1816
|
+
perplexityDetailLines = [verification.ok ? "verified live just now" : `live check failed: ${verification.summary}`];
|
|
1817
|
+
}
|
|
1818
|
+
let embeddingsStatus;
|
|
1819
|
+
let embeddingsDetailLines;
|
|
1820
|
+
const embeddingsApiKey = runtimeConfig.ok
|
|
1821
|
+
? readRuntimeConfigString(runtimeConfig.config, "integrations.openaiEmbeddingsApiKey")
|
|
1822
|
+
: null;
|
|
1823
|
+
if (!runtimeConfig.ok) {
|
|
1824
|
+
embeddingsStatus = runtimeConfigReadStatus(runtimeConfig);
|
|
1825
|
+
embeddingsDetailLines = [];
|
|
1826
|
+
}
|
|
1827
|
+
else if (!embeddingsApiKey) {
|
|
1828
|
+
embeddingsStatus = "missing";
|
|
1829
|
+
embeddingsDetailLines = ["no API key saved yet"];
|
|
1830
|
+
}
|
|
1831
|
+
else {
|
|
1832
|
+
onProgress?.("verifying memory embeddings");
|
|
1833
|
+
const verification = await (0, runtime_capability_check_1.verifyEmbeddingsCapability)(embeddingsApiKey);
|
|
1834
|
+
embeddingsStatus = verification.ok ? "ready" : "needs attention";
|
|
1835
|
+
embeddingsDetailLines = [verification.ok ? "verified live just now" : `live check failed: ${verification.summary}`];
|
|
1836
|
+
}
|
|
1739
1837
|
const teamsStatus = runtimeConfig.ok
|
|
1740
1838
|
? hasRuntimeConfigValue(runtimeConfig.config, "teams.clientId")
|
|
1741
1839
|
&& hasRuntimeConfigValue(runtimeConfig.config, "teams.clientSecret")
|
|
@@ -1768,6 +1866,7 @@ async function buildConnectMenu(agent, deps, onProgress) {
|
|
|
1768
1866
|
section: "Portable",
|
|
1769
1867
|
status: perplexityStatus,
|
|
1770
1868
|
description: "Web search via Perplexity.",
|
|
1869
|
+
detailLines: perplexityDetailLines,
|
|
1771
1870
|
nextAction: (0, connect_bay_1.connectEntryNeedsAttention)({
|
|
1772
1871
|
option: "2",
|
|
1773
1872
|
name: "Perplexity search",
|
|
@@ -1781,6 +1880,7 @@ async function buildConnectMenu(agent, deps, onProgress) {
|
|
|
1781
1880
|
section: "Portable",
|
|
1782
1881
|
status: embeddingsStatus,
|
|
1783
1882
|
description: "Memory retrieval and note search.",
|
|
1883
|
+
detailLines: embeddingsDetailLines,
|
|
1784
1884
|
nextAction: (0, connect_bay_1.connectEntryNeedsAttention)({
|
|
1785
1885
|
option: "3",
|
|
1786
1886
|
name: "Memory embeddings",
|
|
@@ -1828,16 +1928,44 @@ async function executeConnectPerplexity(agent, deps) {
|
|
|
1828
1928
|
throw new Error("SerpentGuide has no persistent runtime credentials. Connect Perplexity on the hatchling agent instead.");
|
|
1829
1929
|
}
|
|
1830
1930
|
const promptSecret = requirePromptSecret(deps, "Perplexity API key entry");
|
|
1831
|
-
deps
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1931
|
+
writeConnectorIntro(deps, {
|
|
1932
|
+
title: "Connect Perplexity",
|
|
1933
|
+
subtitle: `${agent} gets portable web search.`,
|
|
1934
|
+
summary: "Add one hidden API key, verify it live, and make web search travel with this agent.",
|
|
1935
|
+
sections: [
|
|
1936
|
+
{
|
|
1937
|
+
title: "Unlocks",
|
|
1938
|
+
lines: [
|
|
1939
|
+
"Portable web search inside Ouro.",
|
|
1940
|
+
],
|
|
1941
|
+
},
|
|
1942
|
+
{
|
|
1943
|
+
title: "What you need",
|
|
1944
|
+
lines: [
|
|
1945
|
+
"One Perplexity API key.",
|
|
1946
|
+
"It stays hidden while you type.",
|
|
1947
|
+
],
|
|
1948
|
+
},
|
|
1949
|
+
{
|
|
1950
|
+
title: "Where it lives",
|
|
1951
|
+
lines: [
|
|
1952
|
+
`${agent}'s vault runtime/config item.`,
|
|
1953
|
+
"It travels with the agent across machines.",
|
|
1954
|
+
],
|
|
1955
|
+
},
|
|
1956
|
+
],
|
|
1957
|
+
fallbackLines: [
|
|
1958
|
+
`Connect Perplexity for ${agent}`,
|
|
1959
|
+
"The API key stays hidden while you type.",
|
|
1960
|
+
`Ouro stores it in ${agent}'s vault runtime/config item.`,
|
|
1961
|
+
],
|
|
1962
|
+
});
|
|
1836
1963
|
const key = (await promptSecret("Perplexity API key: ")).trim();
|
|
1837
1964
|
if (!key)
|
|
1838
1965
|
throw new Error("Perplexity API key cannot be blank");
|
|
1839
1966
|
const progress = createHumanCommandProgress(deps, "connect perplexity");
|
|
1840
1967
|
let stored;
|
|
1968
|
+
let verification;
|
|
1841
1969
|
let reload;
|
|
1842
1970
|
try {
|
|
1843
1971
|
stored = await runCommandProgressPhase(progress, "saving Perplexity search", () => storeRuntimeConfigKey({
|
|
@@ -1848,6 +1976,23 @@ async function executeConnectPerplexity(agent, deps) {
|
|
|
1848
1976
|
deps,
|
|
1849
1977
|
onProgress: (message) => progress.updateDetail(message),
|
|
1850
1978
|
}), () => "secret stored");
|
|
1979
|
+
progress.startPhase("verifying Perplexity search");
|
|
1980
|
+
verification = await (0, runtime_capability_check_1.verifyPerplexityCapability)(key);
|
|
1981
|
+
if (!verification.ok) {
|
|
1982
|
+
progress.failPhase("verifying Perplexity search", verification.summary);
|
|
1983
|
+
progress.end();
|
|
1984
|
+
const message = [
|
|
1985
|
+
`Perplexity key was saved for ${agent}, but the live check failed.`,
|
|
1986
|
+
`stored: ${stored.itemPath}`,
|
|
1987
|
+
`live check: ${verification.summary}`,
|
|
1988
|
+
"secret was not printed",
|
|
1989
|
+
"",
|
|
1990
|
+
`Next: rerun \`ouro connect perplexity --agent ${agent}\` with a working key.`,
|
|
1991
|
+
].join("\n");
|
|
1992
|
+
deps.writeStdout(message);
|
|
1993
|
+
return message;
|
|
1994
|
+
}
|
|
1995
|
+
progress.completePhase("verifying Perplexity search", verification.summary);
|
|
1851
1996
|
reload = await runCommandProgressPhase(progress, `applying change to running ${agent}`, () => applyRuntimeChangeToRunningAgent(agent, deps, (message) => progress.updateDetail(message)), (result) => result);
|
|
1852
1997
|
}
|
|
1853
1998
|
finally {
|
|
@@ -1870,16 +2015,44 @@ async function executeConnectEmbeddings(agent, deps) {
|
|
|
1870
2015
|
throw new Error("SerpentGuide has no persistent runtime credentials. Connect embeddings on the hatchling agent instead.");
|
|
1871
2016
|
}
|
|
1872
2017
|
const promptSecret = requirePromptSecret(deps, "OpenAI embeddings API key entry");
|
|
1873
|
-
deps
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
2018
|
+
writeConnectorIntro(deps, {
|
|
2019
|
+
title: "Connect Embeddings",
|
|
2020
|
+
subtitle: `${agent} gets portable note and memory search.`,
|
|
2021
|
+
summary: "Add one hidden API key, verify it live, and let semantic memory travel with this agent.",
|
|
2022
|
+
sections: [
|
|
2023
|
+
{
|
|
2024
|
+
title: "Unlocks",
|
|
2025
|
+
lines: [
|
|
2026
|
+
"Portable note search and memory retrieval.",
|
|
2027
|
+
],
|
|
2028
|
+
},
|
|
2029
|
+
{
|
|
2030
|
+
title: "What you need",
|
|
2031
|
+
lines: [
|
|
2032
|
+
"One OpenAI embeddings API key.",
|
|
2033
|
+
"It stays hidden while you type.",
|
|
2034
|
+
],
|
|
2035
|
+
},
|
|
2036
|
+
{
|
|
2037
|
+
title: "Where it lives",
|
|
2038
|
+
lines: [
|
|
2039
|
+
`${agent}'s vault runtime/config item.`,
|
|
2040
|
+
"It travels with the agent across machines.",
|
|
2041
|
+
],
|
|
2042
|
+
},
|
|
2043
|
+
],
|
|
2044
|
+
fallbackLines: [
|
|
2045
|
+
`Connect embeddings for ${agent}`,
|
|
2046
|
+
"The API key stays hidden while you type.",
|
|
2047
|
+
`Ouro stores it in ${agent}'s vault runtime/config item.`,
|
|
2048
|
+
],
|
|
2049
|
+
});
|
|
1878
2050
|
const key = (await promptSecret("OpenAI embeddings API key: ")).trim();
|
|
1879
2051
|
if (!key)
|
|
1880
2052
|
throw new Error("OpenAI embeddings API key cannot be blank");
|
|
1881
2053
|
const progress = createHumanCommandProgress(deps, "connect embeddings");
|
|
1882
2054
|
let stored;
|
|
2055
|
+
let verification;
|
|
1883
2056
|
let reload;
|
|
1884
2057
|
try {
|
|
1885
2058
|
stored = await runCommandProgressPhase(progress, "saving memory embeddings", () => storeRuntimeConfigKey({
|
|
@@ -1890,6 +2063,23 @@ async function executeConnectEmbeddings(agent, deps) {
|
|
|
1890
2063
|
deps,
|
|
1891
2064
|
onProgress: (message) => progress.updateDetail(message),
|
|
1892
2065
|
}), () => "secret stored");
|
|
2066
|
+
progress.startPhase("verifying memory embeddings");
|
|
2067
|
+
verification = await (0, runtime_capability_check_1.verifyEmbeddingsCapability)(key);
|
|
2068
|
+
if (!verification.ok) {
|
|
2069
|
+
progress.failPhase("verifying memory embeddings", verification.summary);
|
|
2070
|
+
progress.end();
|
|
2071
|
+
const message = [
|
|
2072
|
+
`Embeddings key was saved for ${agent}, but the live check failed.`,
|
|
2073
|
+
`stored: ${stored.itemPath}`,
|
|
2074
|
+
`live check: ${verification.summary}`,
|
|
2075
|
+
"secret was not printed",
|
|
2076
|
+
"",
|
|
2077
|
+
`Next: rerun \`ouro connect embeddings --agent ${agent}\` with a working key.`,
|
|
2078
|
+
].join("\n");
|
|
2079
|
+
deps.writeStdout(message);
|
|
2080
|
+
return message;
|
|
2081
|
+
}
|
|
2082
|
+
progress.completePhase("verifying memory embeddings", verification.summary);
|
|
1893
2083
|
reload = await runCommandProgressPhase(progress, `applying change to running ${agent}`, () => applyRuntimeChangeToRunningAgent(agent, deps, (message) => progress.updateDetail(message)), (result) => result);
|
|
1894
2084
|
}
|
|
1895
2085
|
finally {
|
|
@@ -3450,9 +3640,11 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
3450
3640
|
// ── versioned CLI update check ──
|
|
3451
3641
|
if (deps.checkForCliUpdate) {
|
|
3452
3642
|
progress.startPhase("update check");
|
|
3643
|
+
progress.updateDetail("checking npm registry\ncontinuing startup if it stays quiet");
|
|
3453
3644
|
let pendingReExec = false;
|
|
3645
|
+
let updateCheckStatus = "up to date";
|
|
3454
3646
|
try {
|
|
3455
|
-
const updateResult = await deps.checkForCliUpdate
|
|
3647
|
+
const { result: updateResult, timedOut } = await runCliUpdateCheckWithTimeout(deps.checkForCliUpdate, deps.updateCheckTimeoutMs ?? update_checker_1.CLI_UPDATE_CHECK_TIMEOUT_MS);
|
|
3456
3648
|
if (updateResult.available && updateResult.latestVersion) {
|
|
3457
3649
|
/* v8 ignore next -- fallback: getCurrentCliVersion always injected in tests @preserve */
|
|
3458
3650
|
const currentVersion = linkedVersionBeforeUp ?? "unknown";
|
|
@@ -3466,9 +3658,13 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
3466
3658
|
}
|
|
3467
3659
|
pendingReExec = true;
|
|
3468
3660
|
}
|
|
3661
|
+
else if (updateResult.error) {
|
|
3662
|
+
updateCheckStatus = summarizeCliUpdateCheckStatus(updateResult.error, timedOut);
|
|
3663
|
+
}
|
|
3469
3664
|
/* v8 ignore start -- update check error: tested via daemon-cli-update-flow.test.ts @preserve */
|
|
3470
3665
|
}
|
|
3471
3666
|
catch (error) {
|
|
3667
|
+
updateCheckStatus = summarizeCliUpdateCheckStatus(error instanceof Error ? error.message : String(error));
|
|
3472
3668
|
(0, runtime_1.emitNervesEvent)({
|
|
3473
3669
|
level: "warn",
|
|
3474
3670
|
component: "daemon",
|
|
@@ -3483,7 +3679,7 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
3483
3679
|
deps.reExecFromNewVersion(args);
|
|
3484
3680
|
}
|
|
3485
3681
|
else {
|
|
3486
|
-
progress.completePhase("update check",
|
|
3682
|
+
progress.completePhase("update check", updateCheckStatus);
|
|
3487
3683
|
}
|
|
3488
3684
|
}
|
|
3489
3685
|
progress.startPhase("system setup");
|
|
@@ -3888,16 +4084,16 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
3888
4084
|
const sections = [localSection];
|
|
3889
4085
|
if (deps.checkForCliUpdate) {
|
|
3890
4086
|
try {
|
|
3891
|
-
const updateResult = await deps.checkForCliUpdate
|
|
4087
|
+
const { result: updateResult, timedOut } = await runCliUpdateCheckWithTimeout(deps.checkForCliUpdate, deps.updateCheckTimeoutMs ?? update_checker_1.CLI_UPDATE_CHECK_TIMEOUT_MS);
|
|
3892
4088
|
if (updateResult.latestVersion) {
|
|
3893
4089
|
sections.push(`published latest: ${updateResult.latestVersion} (${updateResult.available ? "update available" : "up to date"})`);
|
|
3894
4090
|
}
|
|
3895
4091
|
else if (updateResult.error) {
|
|
3896
|
-
sections.push(`published latest: unavailable (${updateResult.error})`);
|
|
4092
|
+
sections.push(`published latest: unavailable (${summarizeCliUpdateCheckStatus(updateResult.error, timedOut)})`);
|
|
3897
4093
|
}
|
|
3898
4094
|
}
|
|
3899
4095
|
catch (err) {
|
|
3900
|
-
const reason = err instanceof Error ? err.message : String(err);
|
|
4096
|
+
const reason = summarizeCliUpdateCheckStatus(err instanceof Error ? err.message : String(err));
|
|
3901
4097
|
sections.push(`published latest: unavailable (${reason})`);
|
|
3902
4098
|
}
|
|
3903
4099
|
}
|
|
@@ -9,7 +9,7 @@ async function verifyDaemonStarted(deps) {
|
|
|
9
9
|
const maxWaitMs = 10_000;
|
|
10
10
|
const pollIntervalMs = 500;
|
|
11
11
|
const deadline = Date.now() + maxWaitMs;
|
|
12
|
-
deps.onProgress?.("waiting for the
|
|
12
|
+
deps.onProgress?.("waiting for the replacement background service to answer");
|
|
13
13
|
while (Date.now() < deadline) {
|
|
14
14
|
await new Promise((r) => setTimeout(r, pollIntervalMs));
|
|
15
15
|
if (await deps.checkSocketAlive(deps.socketPath))
|
|
@@ -88,7 +88,7 @@ function formatRuntimeDriftPublicSummary(reasons) {
|
|
|
88
88
|
return reasons.map((reason) => reason.label).join(", ");
|
|
89
89
|
}
|
|
90
90
|
async function ensureCurrentDaemonRuntime(deps) {
|
|
91
|
-
deps.onProgress?.("checking
|
|
91
|
+
deps.onProgress?.("checking whether an older background service is already running");
|
|
92
92
|
const localRuntime = normalizeRuntimeIdentity({
|
|
93
93
|
version: deps.localVersion,
|
|
94
94
|
lastUpdated: deps.localLastUpdated,
|
|
@@ -105,7 +105,7 @@ async function ensureCurrentDaemonRuntime(deps) {
|
|
|
105
105
|
const includesVersionDrift = driftReasons.some((entry) => entry.key === "version");
|
|
106
106
|
const publicDriftSummary = formatRuntimeDriftPublicSummary(driftReasons);
|
|
107
107
|
try {
|
|
108
|
-
deps.onProgress?.("stopping the
|
|
108
|
+
deps.onProgress?.("stopping the older background service");
|
|
109
109
|
await deps.stopDaemon();
|
|
110
110
|
}
|
|
111
111
|
catch (error) {
|
|
@@ -113,8 +113,8 @@ async function ensureCurrentDaemonRuntime(deps) {
|
|
|
113
113
|
result = {
|
|
114
114
|
alreadyRunning: true,
|
|
115
115
|
message: includesVersionDrift
|
|
116
|
-
? `daemon already running (${deps.socketPath}; could not replace the
|
|
117
|
-
: `daemon already running (${deps.socketPath}; could not replace the
|
|
116
|
+
? `daemon already running (${deps.socketPath}; could not replace the older background service ${runningVersion} -> ${deps.localVersion}: ${reason})`
|
|
117
|
+
: `daemon already running (${deps.socketPath}; could not replace the older background service after runtime drift ${publicDriftSummary}: ${reason})`,
|
|
118
118
|
};
|
|
119
119
|
(0, runtime_1.emitNervesEvent)({
|
|
120
120
|
level: "warn",
|
|
@@ -141,7 +141,7 @@ async function ensureCurrentDaemonRuntime(deps) {
|
|
|
141
141
|
return result;
|
|
142
142
|
}
|
|
143
143
|
deps.cleanupStaleSocket(deps.socketPath);
|
|
144
|
-
deps.onProgress?.("starting the
|
|
144
|
+
deps.onProgress?.("starting the replacement background service");
|
|
145
145
|
const started = await deps.startDaemonProcess(deps.socketPath);
|
|
146
146
|
const pid = started.pid ?? "unknown";
|
|
147
147
|
const verified = await verifyDaemonStarted(deps);
|
|
@@ -150,8 +150,8 @@ async function ensureCurrentDaemonRuntime(deps) {
|
|
|
150
150
|
result = {
|
|
151
151
|
alreadyRunning: false,
|
|
152
152
|
message: includesVersionDrift
|
|
153
|
-
? `replaced
|
|
154
|
-
: `replaced
|
|
153
|
+
? `replaced an older background service ${runningVersion} -> ${deps.localVersion} (pid ${pid})${suffix}`
|
|
154
|
+
: `replaced an older background service after runtime drift: ${publicDriftSummary} (pid ${pid})${suffix}`,
|
|
155
155
|
verifyStartupStatus: verified,
|
|
156
156
|
startedPid: started.pid ?? null,
|
|
157
157
|
};
|
|
@@ -93,10 +93,11 @@ function renderWaitingForDaemon(elapsed, latestEvent, prevLineCount = 0, options
|
|
|
93
93
|
const spinner = SPINNER_FRAMES[frameIndex];
|
|
94
94
|
const lines = [];
|
|
95
95
|
lines.push(isTTY
|
|
96
|
-
? `${spinner} ${BOLD}
|
|
97
|
-
: `${spinner}
|
|
96
|
+
? `${spinner} ${BOLD}starting background service${RESET} ${DIM}(${elapsedSec}s)${RESET}`
|
|
97
|
+
: `${spinner} starting background service (${elapsedSec}s)`);
|
|
98
98
|
if (latestEvent) {
|
|
99
|
-
|
|
99
|
+
const detail = `latest daemon event: ${latestEvent}`;
|
|
100
|
+
lines.push(isTTY ? ` ${DIM}${detail}${RESET}` : ` ${detail}`);
|
|
100
101
|
}
|
|
101
102
|
return renderStartupLines(lines, prevLineCount, isTTY);
|
|
102
103
|
}
|
|
@@ -182,7 +183,10 @@ async function pollDaemonStartup(deps) {
|
|
|
182
183
|
}
|
|
183
184
|
// Show what the daemon is doing from its log
|
|
184
185
|
const latestEvent = deps.readLatestDaemonEvent?.() ?? null;
|
|
185
|
-
reportProgress(
|
|
186
|
+
reportProgress([
|
|
187
|
+
"waiting for Ouro to answer",
|
|
188
|
+
latestEvent ? `- latest daemon event: ${latestEvent}` : "- background service is still starting",
|
|
189
|
+
].join("\n"));
|
|
186
190
|
if (shouldRender) {
|
|
187
191
|
const output = renderWaitingForDaemon(elapsed, latestEvent, prevLineCount, { isTTY });
|
|
188
192
|
deps.writeRaw(output);
|
|
@@ -222,11 +226,20 @@ async function pollDaemonStartup(deps) {
|
|
|
222
226
|
await deps.sleep(POLL_INTERVAL_MS);
|
|
223
227
|
}
|
|
224
228
|
}
|
|
229
|
+
function formatStartupWorkerLine(payload) {
|
|
230
|
+
const base = `- ${payload.agent}/${payload.worker}: ${payload.status}`;
|
|
231
|
+
if (payload.status === "crashed" && payload.errorReason) {
|
|
232
|
+
return `${base} (${payload.errorReason})`;
|
|
233
|
+
}
|
|
234
|
+
return base;
|
|
235
|
+
}
|
|
225
236
|
function formatStartupProgressDetail(payload) {
|
|
226
237
|
if (payload.workers.length === 0)
|
|
227
|
-
return "
|
|
228
|
-
|
|
229
|
-
|
|
238
|
+
return "Ouro answered";
|
|
239
|
+
return [
|
|
240
|
+
"Ouro answered",
|
|
241
|
+
...payload.workers.map((worker) => formatStartupWorkerLine(worker)),
|
|
242
|
+
].join("\n");
|
|
230
243
|
}
|
|
231
244
|
function colorStatus(status) {
|
|
232
245
|
const statusColor = status === "running" ? GREEN
|
|
@@ -21,6 +21,14 @@ const BOLD = "\x1b[1m";
|
|
|
21
21
|
const DIM = "\x1b[2m";
|
|
22
22
|
const GREEN = "\x1b[38;2;46;204;64m";
|
|
23
23
|
const RED = "\x1b[38;2;255;106;106m";
|
|
24
|
+
function splitDetailLines(detail) {
|
|
25
|
+
if (!detail)
|
|
26
|
+
return [];
|
|
27
|
+
return detail
|
|
28
|
+
.split(/\r?\n/)
|
|
29
|
+
.map((line) => line.trimEnd())
|
|
30
|
+
.filter((line) => line.length > 0);
|
|
31
|
+
}
|
|
24
32
|
// ── UpProgress class ──
|
|
25
33
|
class UpProgress {
|
|
26
34
|
write;
|
|
@@ -99,7 +107,9 @@ class UpProgress {
|
|
|
99
107
|
this.flushRender();
|
|
100
108
|
return;
|
|
101
109
|
}
|
|
102
|
-
|
|
110
|
+
for (const line of splitDetailLines(detail)) {
|
|
111
|
+
this.write(` ${line}\n`);
|
|
112
|
+
}
|
|
103
113
|
}
|
|
104
114
|
/**
|
|
105
115
|
* Mark the current phase as done. In non-TTY mode, immediately writes
|
|
@@ -198,8 +208,10 @@ class UpProgress {
|
|
|
198
208
|
const elapsedSec = (elapsed / 1000).toFixed(1);
|
|
199
209
|
const frameIndex = Math.floor(elapsed / 80) % SPINNER_FRAMES.length;
|
|
200
210
|
const spinner = SPINNER_FRAMES[frameIndex];
|
|
201
|
-
|
|
202
|
-
|
|
211
|
+
lines.push(` ${BOLD}${spinner}${RESET} ${this.currentPhase.label} ${DIM}(${elapsedSec}s)${RESET}`);
|
|
212
|
+
for (const detailLine of splitDetailLines(this.currentPhase.detail)) {
|
|
213
|
+
lines.push(` ${DIM}${detailLine}${RESET}`);
|
|
214
|
+
}
|
|
203
215
|
}
|
|
204
216
|
let output = "";
|
|
205
217
|
if (this.prevLineCount > 0) {
|